From bda7f78dc5471984885bbbcc9f535412142dfe84 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Wed, 20 Nov 2013 23:12:35 -0500 Subject: [PATCH 001/129] Re-commit of patches already on trac. --- src/doc/en/reference/geometry/index.rst | 17 +- src/sage/geometry/all.py | 4 + .../geometry/hyperbolic_space/__init__.py | 1 + src/sage/geometry/hyperbolic_space/all.py | 7 + .../hyperbolic_space/hyperbolic_bdry_point.py | 231 ++++ .../hyperbolic_space/hyperbolic_constants.py | 4 + .../hyperbolic_space/hyperbolic_factory.py | 365 +++++ .../hyperbolic_space/hyperbolic_geodesic.py | 1180 +++++++++++++++++ .../hyperbolic_space/hyperbolic_interface.py | 459 +++++++ .../hyperbolic_space/hyperbolic_isometry.py | 705 ++++++++++ .../hyperbolic_space/hyperbolic_methods.py | 912 +++++++++++++ .../hyperbolic_space/hyperbolic_model.py | 1030 ++++++++++++++ .../hyperbolic_space/hyperbolic_point.py | 656 +++++++++ .../hyperbolic_space/model_factory.py | 66 + 14 files changed, 5636 insertions(+), 1 deletion(-) create mode 100644 src/sage/geometry/hyperbolic_space/__init__.py create mode 100644 src/sage/geometry/hyperbolic_space/all.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_constants.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_factory.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_interface.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_methods.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_model.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_point.py create mode 100644 src/sage/geometry/hyperbolic_space/model_factory.py diff --git a/src/doc/en/reference/geometry/index.rst b/src/doc/en/reference/geometry/index.rst index c076a8ec766..82c02541196 100644 --- a/src/doc/en/reference/geometry/index.rst +++ b/src/doc/en/reference/geometry/index.rst @@ -1,5 +1,8 @@ +Geometry +======== + Combinatorial Geometry -====================== +---------------------- Sage includes classes for convex rational polyhedral cones and fans, Groebner fans, lattice and reflexive polytopes (with integral coordinates), and generic @@ -37,5 +40,17 @@ polytopes and polyhedra (with rational or numerical coordinates). sage/geometry/triangulation/base sage/geometry/triangulation/element +Hyperbolic Geometry +------------------- +.. toctree:: + :maxdepth: 1 + + sage/geometry/hyperbolic_space/hyperbolic_point + sage/geometry/hyperbolic_space/hyperbolic_bdry_point + sage/geometry/hyperbolic_space/hyperbolic_isometry + sage/geometry/hyperbolic_space/hyperbolic_geodesic + sage/geometry/hyperbolic_space/hyperbolic_model + sage/geometry/hyperbolic_space/hyperbolic_interface + sage/geometry/hyperbolic_space/hyperbolic_methods .. include:: ../footer.txt diff --git a/src/sage/geometry/all.py b/src/sage/geometry/all.py index bb324207205..222abd7046c 100644 --- a/src/sage/geometry/all.py +++ b/src/sage/geometry/all.py @@ -19,3 +19,7 @@ import toric_plotter + +from hyperbolic_space.all import * + + diff --git a/src/sage/geometry/hyperbolic_space/__init__.py b/src/sage/geometry/hyperbolic_space/__init__.py new file mode 100644 index 00000000000..c9fecacd721 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/__init__.py @@ -0,0 +1 @@ +import all diff --git a/src/sage/geometry/hyperbolic_space/all.py b/src/sage/geometry/hyperbolic_space/all.py new file mode 100644 index 00000000000..66d065f53fc --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/all.py @@ -0,0 +1,7 @@ +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'UHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'PD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'KM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'HM') + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py new file mode 100644 index 00000000000..9ab5bdfe589 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py @@ -0,0 +1,231 @@ +""" +Ideal Boundary Points in Hyperbolic Space + +This module implements the abstract base class for ideal points in +hyperbolic space of arbitrary dimension. It also contains the +implementations for specific models of hyperbolic geometry. + +Note that not all models of hyperbolic space are bounded, meaning that +the ideal boundary is not the topological boundary of the set underlying +tho model. For example, the unit disk model is bounded with boundary +given by the unit sphere. The hyperboloid model is not bounded. + +AUTHORS: + +- Greg Laun (2013): initial version + +EXAMPLES: + +We can construct boundary points in the upper half plane model, +abbreviated UHP for convenience:: + + sage: UHP.point(3) + Boundary point in UHP 3. + +Points on the boundary are infinitely far from interior points:: + + sage: UHP.point(3).dist(UHP.point(I)) + +Infinity +""" + + +#*********************************************************************** +# Copyright (C) 2013 Greg Laun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** + +from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint +from sage.symbolic.pynac import I +from sage.rings.infinity import infinity +from sage.misc.lazy_import import lazy_import + +lazy_import('sage.plot.point', 'point') +lazy_import('sage.rings.all', 'RR') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') + + + +class HyperbolicBdryPoint(HyperbolicPoint): + r""" + Abstract base class for points on the ideal boundary of hyperbolic + space. This class should never be instantiated. + + INPUT: + + - The coordinates of a hyperbolic boundary point in the appropriate model. + + OUTPUT: + + - A hyperbolic boundary point. + + EXAMPLES: + + Note that the coordinate does not differentiate the different models:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * + sage: p = HyperbolicBdryPointUHP(1); p + Boundary point in UHP 1. + + sage: q = HyperbolicBdryPointPD(1); q + Boundary point in PD 1. + + sage: p == q + False + + sage: bool(p.coordinates() == q.coordinates()) + True + + It is an error to specify a an interior point of hyperbolic space:: + + sage: HyperbolicBdryPointUHP(0.2 + 0.3*I) + Traceback (most recent call last): + ... + ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model. + """ + def __init__(self, coordinates, **graphics_options): + r""" + See ``HyperbolicPoint`` for full documentation. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * + sage: HyperbolicBdryPointUHP(1) + Boundary point in UHP 1. + """ + if not self.model().bounded: + raise NotImplementedError( + "{0} is not a bounded model; boundary" + "points not implemented.".format(self.model_name())) + elif self.model().bdry_point_in_model(coordinates): + self._coordinates = coordinates + else: + raise ValueError( + "{0} is not a valid".format(coordinates) + " boundary point" + " in the {0} model.".format(self.model_name())) + self._graphics_options = graphics_options + + def __repr__(self): + r""" + Return a string representation of ``self``. + + OUTPUT: + + - string. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * + sage: HyperbolicBdryPointUHP(infinity).__repr__() + 'Boundary point in UHP +Infinity.' + + sage: HyperbolicBdryPointPD(-1).__repr__() + 'Boundary point in PD -1.' + + sage: HyperbolicBdryPointKM((0, -1)).__repr__() + 'Boundary point in KM (0, -1).' + """ + return "Boundary point in {0} {1}.".format(self.model_name(), + self.coordinates()) + + +class HyperbolicBdryPointUHP (HyperbolicBdryPoint): + r""" + Create a boundary point for the UHP model. + + INPUT: + + - The coordinates of a hyperbolic boundary point in the upper half plane. + + OUTPUT: + + - A hyperbolic boundary point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointUHP + sage: q = HyperbolicBdryPointUHP(1); q + Boundary point in UHP 1. + """ + HFactory = HyperbolicFactoryUHP + HMethods = HyperbolicMethodsUHP + + def show(self, boundary = True, **options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * + sage: HyperbolicBdryPointUHP(0).show() + sage: HyperbolicBdryPointUHP(infinity).show() + Traceback (most recent call last): + ... + NotImplementedError: Can't draw the point infinity. + """ + opts = dict([('axes', False),('aspect_ratio',1)]) + opts.update(self.graphics_options()) + opts.update(options) + from sage.misc.functional import numerical_approx + p = self.coordinates() + if p == infinity: + raise NotImplementedError("Can't draw the point infinity.") + p = numerical_approx(p) + pic = point((p, 0), **opts) + if boundary: + bd_pic = self.HFactory.get_background_graphic(bd_min = p - 1, + bd_max = p + 1) + pic = bd_pic + pic + return pic + + +class HyperbolicBdryPointPD (HyperbolicBdryPoint): + r""" + Create a boundary point for the PD model. + + INPUT: + + - The coordinates of a hyperbolic boundary point in the Poincare disk. + + OUTPUT: + + - A hyperbolic boundary point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointPD + sage: q = HyperbolicBdryPointPD(1); q + Boundary point in PD 1. + """ + HFactory = HyperbolicFactoryPD + HMethods = HyperbolicMethodsUHP + + +class HyperbolicBdryPointKM (HyperbolicBdryPoint): + r""" + Create a boundary point for the KM model. + + INPUT: + + - The coordinates of a hyperbolic boundary point in the Klein disk. + + OUTPUT: + + - A hyperbolic boundary point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointKM + sage: q = HyperbolicBdryPointKM((1,0)); q + Boundary point in KM (1, 0). + """ + HFactory = HyperbolicFactoryKM + HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py b/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py new file mode 100644 index 00000000000..955ed3535b8 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py @@ -0,0 +1,4 @@ +from sage.matrix.constructor import matrix + +EPSILON = 10**-9 +LORENTZ_GRAM = matrix(3,[1,0,0,0,1,0,0,0,-1]) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py new file mode 100644 index 00000000000..6cdbc295ca7 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py @@ -0,0 +1,365 @@ +r""" +AUTHORS: +- Greg Laun (2013): initial version + + +#*********************************************************************** +# Copyright (C) 2013 Greg Laun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** +""" +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.lazy_import import lazy_import +lazy_import('sage.functions.other','sqrt') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModel') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPoint') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPoint') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometry') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicUHP') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicPD') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicKM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicHM') + +class HyperbolicAbstractFactory (UniqueRepresentation): + HModel = HyperbolicModel + HPoint = HyperbolicPoint + HBdryPoint = HyperbolicBdryPoint + HIsometry = HyperbolicIsometry + HGeodesic = HyperbolicGeodesic + + @classmethod + def get_model(cls): + r""" + Return the current model as a class rather than a string. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicFactoryUHP.get_model() + + + sage: HyperbolicFactoryPD.get_model() + + + sage: HyperbolicFactoryKM.get_model() + + + sage: HyperbolicFactoryHM.get_model() + + + """ + return cls.HModel + + @classmethod + def get_interior_point(cls, coordinates, **graphics_options): + r""" + Return a point in the appropriate model given the coordinates. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicFactoryUHP.get_interior_point(2 + 3*I) + Point in UHP 3*I + 2. + + sage: HyperbolicFactoryPD.get_interior_point(0) + Point in PD 0. + + sage: HyperbolicFactoryKM.get_interior_point((0,0)) + Point in KM (0, 0). + + sage: HyperbolicFactoryHM.get_interior_point((0,0,1)) + Point in HM (0, 0, 1). + + sage: p = HyperbolicFactoryUHP.get_interior_point(I, color="red"); p.graphics_options() + {'color': 'red'} + """ + return cls.HPoint(coordinates, **graphics_options) + + @classmethod + def get_bdry_point(cls, coordinates, **graphics_options): + r""" + Return a boundary point in the appropriate model given the + coordinates. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicFactoryUHP.get_bdry_point(12) + Boundary point in UHP 12. + + sage: HyperbolicFactoryUHP.get_bdry_point(infinity) + Boundary point in UHP +Infinity. + + sage: HyperbolicFactoryPD.get_bdry_point(I) + Boundary point in PD I. + + sage: HyperbolicFactoryKM.get_bdry_point((0,-1)) + Boundary point in KM (0, -1). + + Note that not every model is bounded:: + + sage: HyperbolicFactoryHM.get_bdry_point((0,1,-1)) + Traceback (most recent call last): + ... + NotImplementedError: HM is not a bounded model; boundarypoints not implemented. + """ + return cls.HBdryPoint(coordinates, **graphics_options) + + @classmethod + def get_point(cls, coordinates, **graphics_options): + r""" + Automatically determine the type of point to return given either + (1) the coordinates of a point in the interior or ideal boundary + of hyperbolic space or (2) a HyperbolicPoint or + HyperbolicBdryPoint object. + + INPUT: + + - a point in hyperbolic space or on the ideal boundary. + + OUTPUT: + + - A HyperbolicPoint or HyperbolicBdryPoint. + + EXAMPLES:: + + We can create an interior point via the coordinates:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: p = HyperbolicFactoryUHP.get_point(2*I); p + Point in UHP 2*I. + + Or we can create a boundary point via the coordinates:: + + sage: q = HyperbolicFactoryUHP.get_point(23); q + Boundary point in UHP 23. + + Or we can create both types of points by passing the + HyperbolicPoint or HyperbolicBdryPoint object:: + + sage: HyperbolicFactoryUHP.get_point(p) + Point in UHP 2*I. + + sage: HyperbolicFactoryUHP.get_point(q) + Boundary point in UHP 23. + + sage: HyperbolicFactoryUHP.get_point(12 - I) + Traceback (most recent call last): + ... + ValueError: -I + 12 is neither an interior nor boundary point in the UHP model. + """ + from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint + if isinstance(coordinates, HyperbolicPoint): + coordinates.update_graphics(True, **graphics_options) + return coordinates #both Point and BdryPoint + elif cls.HModel.point_in_model(coordinates): + return cls.HPoint(coordinates, **graphics_options) + elif cls.HModel.bdry_point_in_model(coordinates): + return cls.HBdryPoint(coordinates, **graphics_options) + else: + e_1 = "{0} is neither an interior nor boundary".format(coordinates) + e_2 = " point in the {0} model.".format(cls.get_model().short_name) + raise ValueError(e_1 + e_2) + + @classmethod + def get_geodesic(cls, start, end, **graphics_options): + r""" + Return a geodesic in the appropriate model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicFactoryUHP.get_geodesic(I, 2*I) + Geodesic in UHP from I to 2*I. + + sage: HyperbolicFactoryPD.get_geodesic(0, I/2) + Geodesic in PD from 0 to 1/2*I. + + sage: HyperbolicFactoryKM.get_geodesic((1/2, 1/2), (0,0)) + Geodesic in KM from (1/2, 1/2) to (0, 0). + + sage: HyperbolicFactoryHM.get_geodesic((0,0,1), (1,0, sqrt(2))) + Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)). + """ + return cls.HGeodesic(start, end, **graphics_options) + + @classmethod + def get_isometry(cls, A): + r""" + Return an isometry in the appropriate model given the matrix. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicFactoryUHP.get_isometry(identity_matrix(2)) + Isometry in UHP + [1 0] + [0 1]. + + sage: HyperbolicFactoryPD.get_isometry(identity_matrix(2)) + Isometry in PD + [1 0] + [0 1]. + + sage: HyperbolicFactoryKM.get_isometry(identity_matrix(3)) + Isometry in KM + [1 0 0] + [0 1 0] + [0 0 1]. + + sage: HyperbolicFactoryHM.get_isometry(identity_matrix(3)) + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1]. + """ + return cls.HIsometry(A) + + @classmethod + def get_background_graphic(cls, **bdry_options): + r""" + Return a graphic object that makes the model easier to + visualize. For bounded models, such as the upper half space and + the unit ball models, the background object is the ideal + boundary. For the hyperboloid model, the background object is + the hyperboloid itself. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryPD.get_background_graphic() + """ + return None + +class HyperbolicFactoryUHP (HyperbolicAbstractFactory, UniqueRepresentation): + HModel = HyperbolicModelUHP + HPoint = HyperbolicPointUHP + HBdryPoint = HyperbolicBdryPointUHP + HIsometry = HyperbolicIsometryUHP + HGeodesic = HyperbolicGeodesicUHP + + @classmethod + def get_background_graphic(cls, **bdry_options): + r""" + Return a graphic object that makes the model easier to visualize. + For bounded models, such as the upper half space and the unit + ball models, the background object is the ideal boundary. For + the hyperboloid model, the background object is the hyperboloid + itself. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryUHP.get_background_graphic() + """ + from sage.plot.line import line + bd_min = bdry_options.get('bd_min', -5) + bd_max = bdry_options.get('bd_max', 5) + return line(((bd_min, 0), (bd_max, 0)), color='black') + +class HyperbolicFactoryPD (HyperbolicAbstractFactory, UniqueRepresentation): + from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD + HModel = HyperbolicModelPD + HPoint = HyperbolicPointPD + HBdryPoint = HyperbolicBdryPointPD + HIsometry = HyperbolicIsometryPD + HGeodesic = HyperbolicGeodesicPD + + @classmethod + def get_background_graphic(cls, **bdry_options): + r""" + Return a graphic object that makes the model easier to visualize. + For bounded models, such as the upper half space and the unit + ball models, the background object is the ideal boundary. For + the hyperboloid model, the background object is the hyperboloid + itself. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryPD.get_background_graphic() + """ + from sage.plot.circle import circle + return circle((0,0),1, axes=False, color = 'black') + + +class HyperbolicFactoryKM (HyperbolicAbstractFactory, UniqueRepresentation): + from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelKM + HModel = HyperbolicModelKM + HPoint = HyperbolicPointKM + HBdryPoint = HyperbolicBdryPointKM + HIsometry = HyperbolicIsometryKM + HGeodesic = HyperbolicGeodesicKM + + @classmethod + def get_background_graphic(cls, **bdry_options): + r""" + Return a graphic object that makes the model easier to visualize. + For bounded models, such as the upper half space and the unit + ball models, the background object is the ideal boundary. For + the hyperboloid model, the background object is the hyperboloid + itself. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryKM.get_background_graphic() + """ + from sage.plot.circle import circle + return circle((0,0),1, axes=False, color = 'black') + +class HyperbolicFactoryHM (HyperbolicAbstractFactory, UniqueRepresentation): + HModel = HyperbolicModelHM + HPoint = HyperbolicPointHM + HBdryPoint = HyperbolicBdryPointHM + HIsometry = HyperbolicIsometryHM + HGeodesic = HyperbolicGeodesicHM + + @classmethod + def get_background_graphic(cls, **bdry_options): + r""" + Return a graphic object that makes the model easier to visualize. + For bounded models, such as the upper half space and the unit + ball models, the background object is the ideal boundary. For + the hyperboloid model, the background object is the hyperboloid + itself. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryPD.get_background_graphic() + """ + hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1) + z_height = bdry_options.get('z_height', 7.0) + x_max = sqrt((z_height**2 - 1)/2.0) + from sage.plot.plot3d.all import plot3d + from sage.all import var + (x,y) = var('x,y') + return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max),\ + (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py new file mode 100644 index 00000000000..248ba12642d --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -0,0 +1,1180 @@ +r""" +Hyperbolic Geodesics + +This module implements the abstract base class for geodesics in +hyperbolic space of arbitrary dimension. It also contains the +implementations for specific models of hyperbolic geometry. + +AUTHORS: + +- Greg Laun (2013): initial version + +EXAMPLES: + +We can construct geodesics in the upper half plane model, abbreviated +UHP for convenience:: + + sage: UHP.geodesic(2, 3) + Geodesic in UHP from 2 to 3. + sage: g = UHP.geodesic(I, 3 + I) + sage: g.length() + arccosh(11/2) + +Geodesics are oriented, which means that two geodesics with the same +graph will only be equal if their starting and ending points are +the same:: + + sage: UHP.geodesic(1,2) == UHP.geodesic(2,1) + False +""" + + +#*********************************************************************** +# Copyright (C) 2013 Greg Laun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** + +from sage.structure.sage_object import SageObject +from sage.symbolic.pynac import I +from sage.misc.lazy_import import lazy_import +from sage.misc.lazy_attribute import lazy_attribute +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.rings.infinity import infinity + + +lazy_import('sage.rings.all', 'CC') +lazy_import('sage.functions.other', 'real') +lazy_import('sage.functions.other', 'imag') +lazy_import('sage.all', 'var') +lazy_import('sage.plot.plot', 'parametric_plot') +lazy_import('sage.plot.plot3d.all', 'parametric_plot3d') +lazy_import('sage.functions.trig', 'cos') +lazy_import('sage.functions.trig', 'sin') +lazy_import('sage.plot.line', 'line') +lazy_import('sage.rings.all', 'RR') +lazy_import('sage.modules.free_module_element', 'vector') +lazy_import('sage.functions.other','sqrt') +lazy_import('sage.functions.hyperbolic', 'cosh') +lazy_import('sage.functions.hyperbolic', 'sinh') +lazy_import('sage.functions.hyperbolic', 'arcsinh') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') + +class HyperbolicGeodesic(SageObject): + r""" + Abstract base class for oriented geodesics that are not necessarily + complete. + + INPUT: + + - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic. + + - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + + OUTPUT: + + A hyperbolic geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) + sage: g = HyperbolicGeodesicUHP(I, 2 + I) + """ + HFactory = HyperbolicAbstractFactory + HMethods = HyperbolicAbstractMethods + + ##################### + # "Private" Methods # + ##################### + + + def __init__(self, start, end, **graphics_options): + r""" + See `HyperbolicGeodesic` for full documentation. + + EXAMPLES :: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(I, 2 + I) + Geodesic in UHP from I to I + 2. + """ + self._model = self.HFactory.get_model() + self._start = self.HFactory.get_point(start) + self._end = self.HFactory.get_point(end) + self._graphics_options = graphics_options + + @lazy_attribute + def _cached_start(self): + r""" + The representation of the start point used for calculations. + For example, if the current model uses the HyperbolicMethodsUHP + class, then _cached_start will hold the upper half plane + representation of self.start(). + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicPD(0, 1/2)._cached_start + I + """ + return self.model().point_to_model(self.start().coordinates(), + self.HMethods.model_name()) + + @lazy_attribute + def _cached_end(self): + r""" + The representation of the end point used for calculations. For + example, if the current model uses the HyperbolicMethodsUHP + class, then _cached_end will hold the upper half plane + representation of self.end(). + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicPD(0, 1/2)._cached_end + 3/5*I + 4/5 + """ + return self.model().point_to_model(self.end().coordinates(), + self.HMethods.model_name()) + + @lazy_attribute + def _cached_endpoints(self): + r""" + The representation of the end point used for calculations. For + example, if the current model uses the HyperbolicMethodsUHP + class, then _cached_end will hold the upper half plane + representation of self.end(). + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: A = HyperbolicGeodesicPD(0, 1/2) + sage: A._cached_endpoints + [I, 3/5*I + 4/5] + """ + return [self._cached_start, self._cached_end] + + @lazy_attribute + def _complete(self): + r""" + Return whether the geodesic is complete. This is used for + geodesics in non-bounded models. For thse models, + self.complete() simply sets _complete to True. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(1, -12)._complete + True + sage: HyperbolicGeodesicUHP(I, 2 + I)._complete + False + sage: g = HyperbolicGeodesicHM((0,0,1), (0,1, sqrt(2))) + sage: g._complete + False + sage: g.complete()._complete + True + """ + if self.model().bounded: + return (self.model().bdry_point_in_model(self.start().coordinates()) and + self.model().bdry_point_in_model(self.end().coordinates())) + else: + return False #All non-bounded geodesics start life incomplete. + + def __repr__(self): + r""" + Return a string representation of ``self``. + + OUTPUT: + + - string. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(3 + 4*I, I).__repr__() + 'Geodesic in UHP from 4*I + 3 to I.' + + sage: HyperbolicGeodesicPD(1/2 + I/2, 0).__repr__() + 'Geodesic in PD from 1/2*I + 1/2 to 0.' + + sage: HyperbolicGeodesicKM((1/2, 1/2), (0, 0)).__repr__() + 'Geodesic in KM from (1/2, 1/2) to (0, 0).' + + sage: HyperbolicGeodesicHM((0,0,1), (0, 1, sqrt(Integer(2)))).__repr__() + 'Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)).' + """ + return "Geodesic in {0} from {1} to {2}.".format( \ + self.model_name(), self.start().coordinates(), \ + self.end().coordinates()) + + def __eq__(self, other): + r""" + Return rue if self is equal to other as an oriented geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g1 = HyperbolicGeodesicUHP(I, 2*I) + sage: g2 = HyperbolicGeodesicUHP(2*I,I) + sage: g1 == g2 + False + sage: g1 == g1 + True + """ + return (self.model_name() == other.model_name() and + self.start() == other.start() + and self.end() == other.end()) + +####################### +# Setters and Getters # +####################### + + def start(self): + r""" + Return the starting point of the geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(I, 3*I) + sage: g.start() + Point in UHP I. + """ + return self._start + + def end(self): + r""" + Return the starting point of the geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(I, 3*I) + sage: g.end() + Point in UHP 3*I. + """ + return self._end + + def endpoints(self): + r""" + Return a list containing the start and endpoints. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(I, 3*I) + sage: g.endpoints() + [Point in UHP I., Point in UHP 3*I.] + """ + return [self.start(), self.end()] + + @classmethod + def model(cls): + r""" + Return the model to which the HyperbolicPoint belongs. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(I, 2*I).model() + + + sage: HyperbolicGeodesicPD(0, I/2).model() + + + sage: HyperbolicGeodesicKM((0, 0), (0, 1/2)).model() + + + sage: HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))).model() + + """ + return cls.HFactory.get_model() + + @classmethod + def model_name(cls): + r""" + Return the short name of the hyperbolic model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(I, 2*I).model_name() + 'UHP' + + sage: HyperbolicGeodesicPD(0, I/2).model_name() + 'PD' + + sage: HyperbolicGeodesicKM((0, 0), (0, 1/2)).model_name() + 'KM' + + sage: HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))).model_name() + 'HM' + """ + return cls.model().short_name + + def to_model(self, model_name): + r""" + Convert the current object to image in another model. + + INPUT: + + - ``model_name`` -- a string representing the image model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(I, 2*I).to_model('PD') + Geodesic in PD from 0 to 1/3*I. + """ + from sage.geometry.hyperbolic_space.model_factory import ModelFactory + factory = ModelFactory.find_factory(model_name) + if not factory.get_model().bounded and self.is_complete(): + g = self.uncomplete() + return g.to_model(model_name).complete() + start = self.model().point_to_model(self.start().coordinates(), + model_name) + end = self.model().point_to_model(self.end().coordinates(), model_name) + g = factory.get_geodesic(start, end) + if not self.model().bounded and factory.get_model().bounded \ + and self.is_complete(): + # Converting from a non-bounded model to a bounded model + return g.complete() + return g + + def graphics_options(self): + r""" + Return the graphics options of the current point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: p = UHP.point(2 + I, color="red") + sage: p.graphics_options() + {'color': 'red'} + """ + return self._graphics_options + + def update_graphics(self, update = False, **options): + r""" + Update the graphics options of a HyperbolicPoint. If update is + True, the original option are updated rather than overwritten. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(I, 2*I); g.graphics_options() + {} + + sage: g.update_graphics(color = "red"); g.graphics_options() + {'color': 'red'} + + sage: g.update_graphics(color = "blue"); g.graphics_options() + {'color': 'blue'} + + sage: g.update_graphics(True, size = 20); g.graphics_options() + {'color': 'blue', 'size': 20} + + """ + if not update: + self._graphics_options = {} + self._graphics_options.update(**options) + +################### +# Boolean Methods # +################### + + def is_complete(self): + r""" + Return True if ``self`` is a complete geodesic (that is, both + endpoints are on the ideal boundary) and false otherwise. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(I, 2*I).is_complete() + False + + sage: HyperbolicGeodesicUHP(0, I).is_complete() + False + + sage: HyperbolicGeodesicUHP(0, infinity).is_complete() + True + """ + return self._complete + + def is_asymptotically_parallel(self, other): + r""" + Return True if ``self`` and ``other`` are asymptotically + parallel, False otherwise. + + INPUT: + + - ``other`` -- a hyperbolic geodesic. + + OUTPUT: + + - boolean. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: h = HyperbolicGeodesicUHP(-2,4) + sage: g.is_asymptotically_parallel(h) + True + + Ultraparallel geodesics are not asymptotically parallel:: + + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: h = HyperbolicGeodesicUHP(-1,4) + sage: g.is_asymptotically_parallel(h) + False + + No hyperbolic geodesic is asymptotically parallel to itself:: + + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: g.is_asymptotically_parallel(g) + False + """ + p1, p2 = self.complete().endpoints() + q1, q2 = other.complete().endpoints() + return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1,q2])) and + self.model() == other.model()) + + def is_ultra_parallel(self,other): + r""" + Return True if ``self`` and ``other`` are asymptotically + parallel, False otherwise. + + INPUT: + + - ``other`` -- a hyperbolic geodesic. + + OUTPUT: + + - boolean. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(0,1) + sage: h = HyperbolicGeodesicUHP(-3,3) + sage: g.is_ultra_parallel(h) + True + + :: + + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: h = HyperbolicGeodesicUHP(-2,6) + sage: g.is_ultra_parallel(h) + False + + :: + + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: g.is_ultra_parallel(g) + False + """ + [R_self, R_other]= [k.reflection_in() for k in [self,other]] + return (R_self*R_other).classification() == 'hyperbolic' + + def is_parallel(self,other): + r""" + Return True if the two given hyperbolic + geodesics are either ultraparallel or asymptotically parallel, + False otherwise + + INPUT: + + Two hyperbolic geodesics in any model + + OUTPUT: + + True if the given geodesics are either ultraparallel or + asymptotically parallel, False if not. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: h = HyperbolicGeodesicUHP(5,12) + sage: g.is_parallel(h) + True + + :: + + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: h = HyperbolicGeodesicUHP(-2,4) + sage: g.is_parallel(h) + True + + No hyperbolic geodesic is either ultraparallel or + asymptotically parallel to itself:: + + sage: g = HyperbolicGeodesicUHP(-2,5) + sage: g.is_parallel(g) + False + """ + [R_self, R_other]= [k.reflection_in() for k in [self,other]] + return (R_self*R_other).classification() in ['parabolic', 'hyperbolic'] + +################################### +# Methods implemented in HMethods # +################################### + + def ideal_endpoints(self): + r""" + Return the ideal endpoints in bounded models. Raise a + NotImplementedError in models that are not bounded. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(1 + I, 1 + 3*I).ideal_endpoints() + [Boundary point in UHP 1., Boundary point in UHP +Infinity.] + + sage: HyperbolicGeodesicPD(0, I/2).ideal_endpoints() + [Boundary point in PD -I., Boundary point in PD I.] + + sage: HyperbolicGeodesicKM((0,0), (0, 1/2)).ideal_endpoints() + [Boundary point in KM (0, -1)., Boundary point in KM (0, 1).] + + sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).ideal_endpoints() + Traceback (most recent call last): + ... + NotImplementedError: Boundary points are not implemented in the HM model. + + """ + if not self.model().bounded: + raise NotImplementedError("Boundary points are not implemented in the "\ + + "{0} model.".format(self.model_name())) + if self.is_complete(): + return self.endpoints() + ends = self.HMethods.boundary_points(*self._cached_endpoints) + ends = [self.HMethods.model().point_to_model(k, self.model_name()) for + k in ends] + return [self.HFactory.get_bdry_point(k) for k in ends] + + def complete(self): + r""" + Return the ideal endpoints in bounded models. Raise a + NotImplementedError in models that are not bounded. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(1 + I, 1 + 3*I).complete() + Geodesic in UHP from 1 to +Infinity. + + sage: HyperbolicGeodesicPD(0, I/2).complete() + Geodesic in PD from -I to I. + + sage: HyperbolicGeodesicKM((0,0), (0, 1/2)).complete() + Geodesic in KM from (0, -1) to (0, 1). + + sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).complete() + Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)). + + sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).complete().is_complete() + True + """ + if self.model().bounded: + return self.HFactory.get_geodesic(*self.ideal_endpoints()) + else: + from copy import copy + g = copy(self) + g._complete = True + return g + + def uncomplete(self): + r""" + Return a geodesic segment whose completion is the same as that + of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(I, 2 + 3*I) + sage: g.uncomplete() + Geodesic in UHP from I to 3*I + 2. + + sage: g.uncomplete().complete() == g.complete() + True + + sage: h = HyperbolicGeodesicUHP(2, 3) + sage: h.uncomplete().complete() + Geodesic in UHP from 2 to 3. + """ + if not self.is_complete(): + return self + ends = self.HMethods.uncomplete(*self._cached_endpoints) + ends = [self.HMethods.model().point_to_model(k, self.model_name()) for + k in ends] + return self.HFactory.get_geodesic(*ends) + + def reflection_in(self): + r""" + Return the involution fixing ``self``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(2,4).reflection_in() + Isometry in UHP + [ 3 -8] + [ 1 -3]. + + sage: HyperbolicGeodesicPD(0, I).reflection_in() + Isometry in PD + [ 0 I] + [-I 0]. + + sage: HyperbolicGeodesicKM((0,0), (0,1)).reflection_in() + Isometry in KM + [-1 0 0] + [ 0 1 0] + [ 0 0 1]. + + sage: A = HyperbolicGeodesicHM((0,0,1), (1,0, n(sqrt(2)))).reflection_in() + sage: B = diagonal_matrix([1, -1, 1]) + sage: bool((B - A.matrix()).norm() < 10**-9) + True + """ + A = self.HMethods.reflection_in(*self._cached_endpoints) + A = self.HMethods.model().isometry_to_model(A, self.model_name()) + return self.HFactory.get_isometry(A) + + def common_perpendicular(self, other, **graphics_options): + r""" + Return the unique hyperbolic geodesic perpendicular to two given + geodesics, if such a geodesic exists. If none exists, raise a + ValueError. + + INPUT: + + - ``other`` -- a hyperbolic geodesic in the same model as ``self``. + + OUTPUT: + + - a hyperbolic geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(2,3) + sage: h = HyperbolicGeodesicUHP(4,5) + sage: g.common_perpendicular(h) + Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2. + + It is an error to ask for the common perpendicular of two + intersecting geodesics:: + + sage: g = HyperbolicGeodesicUHP(2,4) + sage: h = HyperbolicGeodesicUHP(3, infinity) + sage: g.common_perpendicular(h) + Traceback (most recent call last): + ... + ValueError: Geodesics intersect. No common perpendicular exists. + """ + if not self.is_parallel(other): + raise ValueError('Geodesics intersect. ' \ + 'No common perpendicular exists.') + perp_ends = self.HMethods.common_perpendicular( + *(self._cached_endpoints + other._cached_endpoints)) + M = self.HMethods.model() + perp_ends = [M.point_to_model(k, self.model_name()) for + k in perp_ends] + return self.HFactory.get_geodesic(*perp_ends, **graphics_options) + + def intersection(self, other, **graphics_options): + r""" + Return the point of intersection of two geodesics (if such a + point exists). + + The option `as_complete` determines whether we test for the + completed geodesics to intersect, or just the segments. + + INPUT: + + - ``other`` -- a hyperbolic geodesic in the same model as ``self``. + + OUTPUT: + + - a hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(3,5) + sage: h = HyperbolicGeodesicUHP(4,7) + sage: g.intersection(h) + Point in UHP 2/3*sqrt(-2) + 13/3. + + If the given geodesics do not intersect, raise an error:: + + sage: g = HyperbolicGeodesicUHP(4,5) + sage: h = HyperbolicGeodesicUHP(5,7) + sage: g.intersection(h) + Traceback (most recent call last): + ... + ValueError: Geodesics don't intersect. + + If the given geodesics are identical, return that geodesic:: + + sage: g = HyperbolicGeodesicUHP(4+I,18*I) + sage: h = HyperbolicGeodesicUHP(4+I,18*I) + sage: g.intersection(h) + Geodesic in UHP from I + 4 to 18*I. + """ + if self == other: + return self + elif self.is_parallel(other): + raise ValueError("Geodesics don't intersect.") + inters = self.HMethods.intersection(*(self._cached_endpoints + + other._cached_endpoints)) + if len(inters) == 2: + return self + elif len(inters) == 1: + inters = self.HMethods.model().point_to_model(inters[0], + self.model_name()) + return self.HFactory.get_point(inters, **graphics_options) + else: + raise ValueError("Can't calculate the intersection of" + "{1} and {2}".format(self, other)) + + + def perpendicular_bisector(self, **graphics_options): + r""" + Return the perpendicular bisector of ``self`` if ``self`` has + finite length. Here distance is hyperbolic distance. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP.random_element() + sage: h = g.perpendicular_bisector() + sage: bool(h.intersection(g).coordinates() - g.midpoint().coordinates() < 10**-9) + True + + Complete geodesics cannot be bisected:: + + sage: g = HyperbolicGeodesicUHP(0, 1) + sage: g.perpendicular_bisector() + Traceback (most recent call last): + ... + ValueError: Perpendicular bisector is not defined for complete geodesics. + """ + if self.is_complete(): + raise ValueError("Perpendicular bisector is not defined for " \ + "complete geodesics.") + bisect_ends = self.HMethods.perpendicular_bisector( + *self._cached_endpoints) + M = self.HMethods.model() + bisect_ends = [M.point_to_model(k, self.model_name()) for + k in bisect_ends] + return self.HFactory.get_geodesic(*bisect_ends, **graphics_options) + + @classmethod + def random_element(cls, **kwargs): + r""" + Return a random hyperbolic geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: h = HyperbolicGeodesicUHP.random_element() + sage: bool((h.endpoints()[0].coordinates()).imag() >= 0) + True + """ + # Some kwargs are for parametrizing the random geodesic + # others are for graphics options. Is there a better way to do this? + g_ends = [cls.HMethods.random_point(**kwargs) for k in range(2)] + g_ends = [cls.HMethods.model().point_to_model(k, cls.model_name())\ + for k in g_ends] + return cls.HFactory.get_geodesic(*g_ends, **kwargs) + + def midpoint(self, **graphics_options): + r""" + Return the (hyperbolic) midpoint of a hyperbolic line segment. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP.random_element() + sage: m = g.midpoint() + sage: end1, end2 = g.endpoints() + sage: bool(abs(m.dist(end1) - m.dist(end2)) < 10**-9) + True + + Complete geodesics have no midpoint:: + + sage: HyperbolicGeodesicUHP(0,2).midpoint() + Traceback (most recent call last): + ... + ValueError: Midpoint not defined for complete geodesics. + """ + if self.is_complete(): + raise ValueError("Midpoint not defined for complete geodesics.") + mid = self.HMethods.midpoint(*self._cached_endpoints, + **graphics_options) + mid = self.HMethods.model().point_to_model(mid, self.model_name()) + return self.HFactory.get_point(mid) + + def dist(self, other): + r""" + Return the hyperbolic distance from a given hyperbolic geodesic + to another geodesic or point. + + INPUT: + + - ``other`` -- a hyperbolic geodesic or hyperbolic point in the same model. + + OUTPUT: + + - the hyperbolic distance. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(2, 4.0) + sage: h = HyperbolicGeodesicUHP(5, 7.0) + sage: bool(abs(g.dist(h).n() - 1.92484730023841) < 10**-9) + True + + If the second object is a geodesic ultraparallel to the first, + or if it is a point on the boundary that is not one of the + first object's endpoints, then return +infinity:: + + sage: g = HyperbolicGeodesicUHP(2, 2+I) + sage: p = UHP.point(5) + sage: g.dist(p) + +Infinity + """ + if isinstance(other, HyperbolicGeodesic): + if not self.is_parallel(other): + return 0 + + elif self.is_ultra_parallel(other): + perp = self.common_perpendicular(other) + # Find where self and other intersect the common perp... + p = self.intersection(perp) + q = other.intersection(perp) + # and return their distance + return p.dist(q) + return self.HMethods.geod_dist_from_point(self._cached_start, + self._cached_end, + other._cached_coordinates) + + def angle(self, other): + r""" + Return the angle between any two given geodesics if they + intersect. + + -``other`` -- a hyperbolic geodesic in the same model as + ``self``. + + OUTPUT: + + - The angle in radians between the two given geodesics. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(2, 4) + sage: h = HyperbolicGeodesicUHP(3, 3+I) + sage: g.angle(h) + 1/2*pi + + It is an error to ask for the angle of two geodesics that do not + intersect:: + + sage: g = HyperbolicGeodesicUHP(2, 4) + sage: h = HyperbolicGeodesicUHP(5, 7) + sage: g.angle(h) + Traceback (most recent call last): + ... + ValueError: Geodesics do not intersect. + + If the geodesics are identical, return angle 0:: + + sage: g = HyperbolicGeodesicUHP(2, 4) + sage: g.angle(g) + 0 + """ + if self.is_parallel(other): + raise ValueError("Geodesics do not intersect.") + if not (self.is_complete() and other.is_complete()): + try: + # Make sure the segments intersect. + self.intersection (other) + except ValueError: + print("Warning: Geodesic segments do not intersect. " + "The angle between them is not defined. \n" + "Returning the angle between their completions.") + return self.complete().angle(other.complete()) + return self.HMethods.angle(*(self._cached_endpoints + + other._cached_endpoints)) + + def length(self): + r""" + Return the Hyperbolic length of the hyperbolic line segment. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicUHP(2 + I, 3 + I/2) + sage: g.length() + arccosh(9/4) + """ + end1, end2 = self.endpoints() + return end1.dist(end2) + + +class HyperbolicGeodesicUHP (HyperbolicGeodesic): + r""" + Create a geodesic in the upper half plane model. + + INPUT: + + - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; + + - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + + + OUTPUT: + + A hyperbolic geodesic. + + EXAMPLES:: + + sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) + sage: g = HyperbolicGeodesicUHP(I, 2 + I) + """ + HFactory = HyperbolicFactoryUHP + HMethods = HyperbolicMethodsUHP + + def show(self, boundary = True, **options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicUHP(0, 1).show() + """ + opts = dict([('axes', False),('aspect_ratio',1)]) + opts.update(self.graphics_options()) + opts.update(options) + end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] + bd_1, bd_2 = [CC(k.coordinates()) for k in self.ideal_endpoints()] + if (abs(real(end_1) - real(end_2)) < EPSILON) \ + or CC(infinity) in [end_1, end_2]: #on same vertical line + # If one of the endpoints is infinity, we replace it with a + # large finite point + if end_1 == CC(infinity): + end_1 = (real(end_2) ,(imag(end_2) + 10)) + end_2 = (real(end_2), imag(end_2)) + elif end_2 == CC(infinity): + end_2 = (real(end_1), (imag(end_1) + 10)) + end_1 = (real(end_1),imag(end_1)) + pic = line((end_1,end_2), **opts) + if boundary: + cent = min(bd_1,bd_2) + bd_dict = {'bd_min': cent - 3, 'bd_max': cent + 3} + bd_pic = self.HFactory.get_background_graphic(**bd_dict) + pic = bd_pic + pic + return pic + else: + center = (bd_1 + bd_2)/2 # Circle center + radius = abs(bd_1 - bd_2)/2 + theta1 = CC(end_1 - center).arg() + theta2 = CC(end_2 - center).arg() + if abs(theta1 - theta2) < EPSILON: + theta2 += pi + [theta1, theta2] = sorted([theta1,theta2]) + x = var('x') + pic= parametric_plot((radius*cos(x) + real(center),radius*sin(x) + + imag(center)), (x, theta1, theta2), **opts) + if boundary: + # We want to draw a segment of the real line. The + # computations below compute the projection of the + # geodesic to the real line, and then draw a little + # to the left and right of the projection. + shadow_1, shadow_2 = [real(k) for k in [end_1,end_2]] + midpoint = (shadow_1 + shadow_2)/2 + length = abs(shadow_1 - shadow_2) + bd_dict = {'bd_min': midpoint - length, 'bd_max': midpoint + + length} + bd_pic = self.HFactory.get_background_graphic(**bd_dict) + pic = bd_pic + pic + return pic + + +class HyperbolicGeodesicPD (HyperbolicGeodesic): + r""" + Create a geodesic in the Poincare disk model. + + INPUT: + + - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; + + - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + + OUTPUT: + + A hyperbolic geodesic. + + EXAMPLES:: + + sage: g = HyperbolicGeodesicPD(PD.point(I), PD.point(I/2)) + sage: g = HyperbolicGeodesicPD(I, I/2) + """ + HFactory = HyperbolicFactoryPD + HMethods = HyperbolicMethodsUHP + + def show(self, boundary = True, **options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicPD(0, 1).show() + """ + opts = dict([('axes', False),('aspect_ratio',1)]) + opts.update(self.graphics_options()) + opts.update(options) + end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] + bd_1, bd_2 = [CC(k.coordinates()) for k in self.ideal_endpoints()] + # Check to see if it's a line + if bool (real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))**2 < EPSILON: + pic = line([(real(bd_1),imag(bd_1)),(real(bd_2),imag(bd_2))], + **opts) + else: + # If we are here, we know it's not a line + # So we compute the center and radius of the circle + center = (1/(real(bd_1)*imag(bd_2)-real(bd_2)*imag(bd_1))* + ((imag(bd_2)-imag(bd_1)) + (real(bd_1)-real(bd_2))*I)) + radius = RR(abs(bd_1 - center)) # abs is Euclidean distance + # Now we calculate the angles for the parametric plot + theta1 = CC(end_1- center).arg() + theta2 = CC(end_2 - center).arg() + if theta1 < 0: + theta1 = theta1 + 2*3.14159265358979 + if theta2 < 0: + theta2 = theta2 + 2*3.14159265358979 + [theta1, theta2] = sorted([theta1,theta2]) + x = var('x') + # We use the parameterization (-cos(x), sin(x)) because + # arg returns an argument between -pi and pi. + pic = parametric_plot((radius*cos(x) + real(center), + radius*sin(x) + imag(center)), + (x, theta1, theta2), **opts) + if boundary: + bd_pic = self.HFactory.get_background_graphic() + pic = bd_pic + pic + return pic + + +class HyperbolicGeodesicKM (HyperbolicGeodesic): + r""" + Create a geodesic in the Klein disk model. + + INPUT: + + - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; + + - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + + OUTPUT: + + A hyperbolic geodesic. + + EXAMPLES:: + + sage: g = HyperbolicGeodesicKM(KM.point(I), KM.point(I/2)) + sage: g = HyperbolicGeodesicKM(I, I/2) + """ + HFactory = HyperbolicFactoryKM + HMethods = HyperbolicMethodsUHP + + def show(self, boundary = True, **options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HyperbolicGeodesicKM((0,0), (1,0)).show() + """ + from sage.plot.line import line + opts = dict ([('axes', False), ('aspect_ratio', 1)]) + opts.update(self.graphics_options()) + end_1,end_2 = [k.coordinates() for k in self.endpoints()] + pic = line([end_1,end_2], **opts) + if boundary: + bd_pic = self.HFactory.get_background_graphic() + pic = bd_pic + pic + return pic + + +class HyperbolicGeodesicHM (HyperbolicGeodesic): + r""" + Create a geodesic in the hyperboloid model. + + INPUT: + + - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; + + - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + + OUTPUT: + + A hyperbolic geodesic. + + EXAMPLES:: + + sage: g = HyperbolicGeodesicHM(HM.point((1,0,0)), HM.point((0,1,sqrt(2)))) + sage: g = HyperbolicGeodesicHM((1, 0, 0), (0, 1, sqrt(2))) + """ + HFactory = HyperbolicFactoryHM + HMethods = HyperbolicMethodsUHP + + def show(self, show_hyperboloid = True, **graphics_options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicHM.random_element() + sage: g.show() + """ + from sage.all import var + (x,y,z) = var('x,y,z') + opts = self.graphics_options() + opts.update(graphics_options) + v1,u2 = [vector(k.coordinates()) for k in self.endpoints()] + # Lorentzian Gram Shmidt. The original vectors will be + # u1, u2 and the orthogonal ones will be v1, v2. Except + # v1 = u1, and I don't want to declare another variable, + # hence the odd naming convention above. + # We need the Lorentz dot product of v1 and u2. + v1_ldot_u2 = u2[0]*v1[0] + u2[1]*v1[1] - u2[2]*v1[2] + v2 = u2 + v1_ldot_u2*v1 + v2_norm = sqrt(v2[0]**2 + v2[1]**2 - v2[2]**2) + v2 = v2/v2_norm + v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2] + # Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1 + # That is, v1 is unit timelike and v2 is unit spacelike. + # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike. + hyperbola = cosh(x)*v1 + sinh(x)*v2 + endtime = arcsinh(v2_ldot_u2) + pic = parametric_plot3d(hyperbola,(x,0, endtime),**graphics_options) + if show_hyperboloid: + bd_pic = self.HFactory.get_background_graphic() + pic= bd_pic + pic + return pic diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py new file mode 100644 index 00000000000..d45d0338fc6 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -0,0 +1,459 @@ +r""" +Interface to Hyperbolic Models + +This module provides a convenient interface for interacting with models +of hyperbolic space as well as their points, geodesics, and isometries. + +The primary point of this module is to allow the code that implements +hyperbolic space to be sufficiently decoupled while still providing a +convenient user experience. + +The interfaces are by default given abbreviated names. For example, +UHP (upper half plane model), PD (Poincare disk model), KM (Klein disk +model), and HM (hyperboloid model). + +AUTHORS: + +- Greg Laun (2013): Initial version. + +EXAMPLES:: + + sage: UHP.point(2 + I) + Point in UHP I + 2. + + sage: PD.point(1/2 + I/2) + Point in PD 1/2*I + 1/2. + +""" + +#*********************************************************************** +# +# Copyright (C) 2013 Greg Laun +# +# +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.lazy_import import lazy_import +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModel') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelKM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactory') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPoint') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointKM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointKM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicKM') + + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometry') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryKM') + + +class HyperbolicUserInterface(UniqueRepresentation): + r""" + Abstract base class for Hyperbolic Interfaces. These provide a user + interface for interacting with models of hyperbolic geometry without + having the interface dictate the class structure. + """ + HModel = HyperbolicModel + HFactory = HyperbolicFactory + HPoint = HyperbolicPoint + HIsometry = HyperbolicIsometry + HGeodesic = HyperbolicGeodesic + + @classmethod + def model_name(cls): + r""" + Return the full name of the hyperbolic model. + + EXAMPLES:: + + sage: UHP.model_name() + 'Upper Half Plane Model' + sage: PD.model_name() + 'Poincare Disk Model' + sage: KM.model_name() + 'Klein Disk Model' + sage: HM.model_name() + 'Hyperboloid Model' + """ + return cls.HModel.name + + @classmethod + def short_name(cls): + r""" + Return the short name of the hyperbolic model. + + EXAMPLES:: + + sage: UHP.short_name() + 'UHP' + sage: PD.short_name() + 'PD' + sage: HM.short_name() + 'HM' + sage: KM.short_name() + 'KM' + """ + return cls.HModel.short_name + + @classmethod + def is_bounded(cls): + r""" + Return ``True`` if the model is bounded and ``False`` otherwise. + + EXAMPLES:: + + sage: UHP.is_bounded() + True + sage: PD.is_bounded() + True + sage: KM.is_bounded() + True + sage: HM.is_bounded() + False + """ + return cls.HModel.bounded + + @classmethod + def point(cls, p, **kwargs): + r""" + Return a HyperbolicPoint object in the current model with + coordinates ``p``. + + EXAMPLES:: + + sage: UHP.point(0) + Boundary point in UHP 0. + + sage: PD.point(I/2) + Point in PD 1/2*I. + + sage: KM.point((0,1)) + Boundary point in KM (0, 1). + + sage: HM.point((0,0,1)) + Point in HM (0, 0, 1). + """ + return cls.HFactory.get_point(p, **kwargs) + + @classmethod + def point_in_model(cls, p): + r""" + Return True if ``p`` gives the coordinates of a point in the + interior of hyperbolic space in the model. + + EXAMPLES:: + + sage: UHP.point_in_model(I) + True + sage: UHP.point_in_model(0) # Not interior point. + False + sage: HM.point_in_model((0,1, sqrt(2))) + True + """ + return cls.HModel.point_in_model(p) + + @classmethod + def bdry_point_in_model(cls, p): + r""" + Return True if ``p`` gives the coordinates of a point on the + ideal boundary of hyperbolic space in the current model. + + EXAMPLES:: + + sage: UHP.bdry_point_in_model(0) + True + sage: UHP.bdry_point_in_model(I) # Not boundary point + False + """ + return cls.HModel.bdry_point_in_model(p) + + @classmethod + def isometry_in_model(cls, A): + r""" + Return True if the matrix ``A`` acts isometrically on hyperbolic + space in the current model. + + EXAMPLES:: + + sage: A = matrix(2,[10,0,0,10]) # det(A) is not 1 + sage: UHP.isometry_in_model(A) # But A acts isometrically + True + """ + return cls.HModel.isometry_in_model(A) + + @classmethod + def geodesic(cls, start, end, **kwargs): + r""" + Return an oriented HyperbolicGeodesic object in the current + model that starts at ``start`` and ends at ``end``. + + EXAMPLES:: + + sage: UHP.geodesic(1, 0) + Geodesic in UHP from 1 to 0. + + sage: PD.geodesic(1, 0) + Geodesic in PD from 1 to 0. + + sage: KM.geodesic((0,1/2), (1/2, 0)) + Geodesic in KM from (0, 1/2) to (1/2, 0). + + sage: HM.geodesic((0,0,1), (0,1, sqrt(2))) + Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)). + """ + return cls.HFactory.get_geodesic(start, end, **kwargs) + + @classmethod + def isometry(cls, A): + r""" + Return an HyperbolicIsometry object in the current model that + coincides with (in the case of linear isometry groups) or lifts + to (in the case of projective isometry groups) the matrix ``A``. + + EXAMPLES:: + + sage: UHP.isometry(identity_matrix(2)) + Isometry in UHP + [1 0] + [0 1]. + + sage: PD.isometry(identity_matrix(2)) + Isometry in PD + [1 0] + [0 1]. + + sage: KM.isometry(identity_matrix(3)) + Isometry in KM + [1 0 0] + [0 1 0] + [0 0 1]. + + sage: HM.isometry(identity_matrix(3)) + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1]. + """ + return cls.HFactory.get_isometry(A) + + @classmethod + def random_point(cls, **kwargs): + r""" + Return a random point in the current model. + + EXAMPLES:: + + sage: p = UHP.random_point() + """ + return cls.HPoint.random_element(**kwargs) + + @classmethod + def random_geodesic(cls, **kwargs): + r""" + Return a random geodesic in the current model. + + EXAMPLES:: + + sage: p = UHP.random_geodesic() + """ + return cls.HGeodesic.random_element(**kwargs) + + @classmethod + def random_isometry(cls, **kwargs): + r""" + Return a random isometry in the current model. + + EXAMPLES:: + + sage: p = UHP.random_isometry() + """ + return cls.HIsometry.random_element(**kwargs) + + @classmethod + def isometry_from_fixed_points(cls, p1, p2): + r""" + Given two ideal boundary points ``p1`` and ``p2``, return an + isometry of hyperbolic type that fixes ``p1`` and ``p2``. + + INPUT: + + - ``p1``, ``p2`` -- points in the ideal boundary of hyperbolic space either as coordinates or as HyperbolicPoints. + + OUTPUT: + + - A HyperbolicIsometry in the current model whose classification is hyperbolic that fixes ``p1`` and ``p2``. + + EXAMPLES:: + + sage: UHP.isometry_from_fixed_points(0, 4) + Isometry in UHP + [ -1 0] + [-1/5 -1/5]. + sage: UHP.isometry_from_fixed_points(UHP.point(0), UHP.point(4)) + Isometry in UHP + [ -1 0] + [-1/5 -1/5]. + """ + return cls.HIsometry.isometry_from_fixed_points(cls.point(p1), + cls.point(p2)) + @classmethod + def dist(cls, a, b): + r""" + Return the hyperbolic distance between points ``a`` and ``b``. + + INPUT: + + - ``a`` -- a hyperbolic point. + - ``b`` -- a hyperbolic point. + + EXAMPLES:: + + sage: UHP.dist(UHP.point(I), UHP.point(2*I)) + arccosh(5/4) + sage: UHP.dist(I, 2*I) + arccosh(5/4) + """ + try: + return a.dist(b) + except(AttributeError): + return cls.point(a).dist(cls.point(b)) + + @classmethod + def point_to_model(cls, p, model): + r""" + Return the image of ``p`` in the model ``model``. + + INPUT: + + - ``p`` -- a point in the current model of hyperbolic space either as coordinates or as a HyperbolicPoint. + + - ``model`` -- the name of an implemented model of hyperbolic space of the same dimension. + + EXAMPLES:: + + sage: UHP.point_to_model(I, 'PD') + 0 + sage: PD.point_to_model(I, 'UHP') + +Infinity + sage: UHP.point_to_model(UHP.point(I), 'HM') + (0, 0, 1) + + """ + if isinstance(p, HyperbolicPoint): + p = p.coordinates() + return cls.HModel.point_to_model(p, model) + + @classmethod + def isometry_to_model(cls, A, model): + r""" + Return the image of ``A`` in the isometry group of the model + given by ``model``. + + INPUT: + + - ``A`` -- a matrix acting isometrically on the current model. + - ``model`` -- the name of an implemented model of hyperbolic space of the same dimension. + + EXAMPLES:: + + sage: I2 = identity_matrix(2) + sage: UHP.isometry_to_model(I2, 'HM') + [1 0 0] + [0 1 0] + [0 0 1] + sage: UHP.isometry_to_model(UHP.isometry(I2), 'PD') + [1 0] + [0 1] + """ + if isinstance(A, HyperbolicIsometry): + A = A.matrix() + return cls.HModel.isometry_to_model(A, model) + + +class UHP(HyperbolicUserInterface): + r""" + Hyperbolic interface for the UHP model. + + EXAMPLES:: + sage: UHP.point(I) + Point in UHP I. + """ + HModel = HyperbolicModelUHP + HFactory = HyperbolicFactoryUHP + HPoint = HyperbolicPointUHP + HIsometry = HyperbolicIsometryUHP + HGeodesic = HyperbolicGeodesicUHP + + +class PD(HyperbolicUserInterface): + r""" + Hyperbolic interface for the PD model. + + EXAMPLES:: + sage: PD.point(I) + Point in PD I. + """ + HModel = HyperbolicModelPD + HFactory = HyperbolicFactoryPD + HPoint = HyperbolicPointPD + HIsometry = HyperbolicIsometryPD + HGeodesic = HyperbolicGeodesicPD + + +class KM(HyperbolicUserInterface): + r""" + Hyperbolic interface for the KM model. + + EXAMPLES:: + sage: KM.point((0,0)) + Point in KM (0, 0). + """ + HModel = HyperbolicModelKM + HFactory = HyperbolicFactoryKM + HPoint = HyperbolicPointKM + HIsometry = HyperbolicIsometryKM + HGeodesic = HyperbolicGeodesicKM + + +class HM(HyperbolicUserInterface): + r""" + Hyperbolic interface for the HM model. + + EXAMPLES:: + sage: HM.point((0,0,1)) + Point in HM (0, 0, 1). + """ + HModel = HyperbolicModelHM + HFactory = HyperbolicFactoryHM + HPoint = HyperbolicPointHM + HIsometry = HyperbolicIsometryHM + HGeodesic = HyperbolicGeodesicHM diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py new file mode 100644 index 00000000000..e17102a41e3 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -0,0 +1,705 @@ +r""" +Hyperbolic Isometries + +This module implements the abstract base class for isometries of +hyperbolic space of arbitrary dimension. It also contains the +implementations for specific models of hyperbolic geometry. + +The isometry groups of all implemented models are either matrix Lie +groups or are doubly covered by matrix Lie groups. As such, the +isometry constructor takes a matrix as input. However, since the +isometries themselves may not be matrices, quantities like the trace +and determinant are not directly accessible from this class. + +AUTHORS: + +- Greg Laun (2013): initial version + +EXAMPLES: + +We can construct isometries in the upper half plane model, abbreviated +UHP for convenience:: + + sage: UHP.isometry(matrix(2,[1,2,3,4])) + Isometry in UHP + [1 2] + [3 4]. + sage: A = UHP.isometry(matrix(2,[0,1,1,0])) + sage: A.inverse() + Isometry in UHP + [0 1] + [1 0]. +""" + +#*********************************************************************** +# Copyright (C) 2013 Greg Laun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** + +from sage.structure.sage_object import SageObject +from sage.misc.lazy_import import lazy_import +from sage.misc.lazy_attribute import lazy_attribute +lazy_import('sage.modules.free_module_element', 'vector') +from sage.rings.infinity import infinity +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.misc.latex import latex +from sage.rings.all import CC +from sage.functions.other import real, imag + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') + +class HyperbolicIsometry(SageObject): + r""" + Abstract base class for hyperbolic isometries. This class should + never be instantiated. + + INPUT: + + - A matrix representing a hyperbolic isometry in the appropriate model. + + OUTPUT: + + - A hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(identity_matrix(2)) + sage: B = HyperbolicIsometryHM(identity_matrix(3)) + """ + HFactory = HyperbolicAbstractFactory + HMethods = HyperbolicAbstractMethods + + ##################### + # "Private" Methods # + ##################### + + def __init__(self, A): + r""" + See `HyperbolicIsometry` for full documentation. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: HyperbolicIsometryUHP(matrix(2, [0,1,-1,0])) + Isometry in UHP + [ 0 1] + [-1 0]. + """ + self._model = self.HFactory.get_model() + self._model.isometry_test(A) + self._matrix = A + + @lazy_attribute + def _cached_matrix(self): + r""" + The representation of the current isometry used for + calculations. For example, if the current model uses the + HyperbolicMethodsUHP class, then _cached_matrix will hold the + SL(2,`\Bold{R}`) representation of self.matrix(). + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryHM(identity_matrix(3)) + sage: A._cached_matrix + [1 0] + [0 1] + """ + return self.model().isometry_to_model(self.matrix(), + self.HMethods.model().short_name) + + def __repr__(self): + r""" + Return a string representation of ``self``. + + OUTPUT: + + - a string. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: HyperbolicIsometryUHP(identity_matrix(2)).__repr__() + 'Isometry in UHP\n[1 0]\n[0 1].' + """ + return "Isometry in {0}\n{1}.".format(self.model_name(), self.matrix()) + + def _latex_(self): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = UHP.isometry(identity_matrix(2)) + sage: B = HM.isometry(identity_matrix(3)) + sage: latex(A) + \pm \left(\begin{array}{rr} + 1 & 0 \\ + 0 & 1 + \end{array}\right) + + sage: latex(B) + \left(\begin{array}{rrr} + 1 & 0 & 0 \\ + 0 & 1 & 0 \\ + 0 & 0 & 1 + \end{array}\right) + """ + if self.model().isometry_group_is_projective: + return "\pm " + latex(self.matrix()) + else: + return latex(self.matrix()) + + def __eq__(self, other): + r""" + Return True if the isometries are the same and False otherwise. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(identity_matrix(2)) + sage: B = HyperbolicIsometryUHP(-identity_matrix(2)) + sage: A == B + True + """ + pos_matrix = bool(abs(self.matrix() - other.matrix()) < EPSILON) + neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) + if self._model.isometry_group_is_projective: + return self.model() == other.model() and (pos_matrix or neg_matrix) + else: + return self.model_name() == other.model_name() and pos_matrix + + def __pow__ (self, n): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(matrix(2,[3,1,2,1])) + sage: A**3 + Isometry in UHP + [41 15] + [30 11]. + """ + return type (self) (self.matrix()**n) + + def __mul__ (self, other): + r""" + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) + sage: B = HyperbolicIsometryUHP(Matrix(2,[3,1,1,2])) + sage: B*A + Isometry in UHP + [16 8] + [ 7 6]. + sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) + sage: p = UHP.point(2 + I) + sage: A*p + Point in UHP 8/17*I + 53/17. + + sage: g = UHP.geodesic(2 + I,4 + I) + sage: A*g + Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. + + sage: A = diagonal_matrix([1, -1, 1]) + sage: A = HyperbolicIsometryHM(A) + sage: A.orientation_preserving() + False + sage: p = HM.point((0, 1, sqrt(2))) + sage: A*p + Point in HM (0, -1, sqrt(2)). + + """ + from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic + from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint + if self.model_name() != other.model_name(): + raise TypeError("{0} and {1} are not in the same" + "model".format(self, other)) + if isinstance(other, HyperbolicIsometry): + return type (self) (self.matrix()*other.matrix()) + elif isinstance(other, HyperbolicPoint): + return self.HFactory.get_point(self.model().isometry_act_on_point( + self.matrix(), other.coordinates())) + elif isinstance(other, HyperbolicGeodesic): + return self.HFactory.get_geodesic(self*other.start(), self*other.end()) + else: + NotImplementedError("Multiplication is not defined between a " + "hyperbolic isometry and {0}".format(other)) + + # def __call__ (self, other): + # r""" + # EXAMPLES:: + + # sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + # sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) + # sage: p = UHP.point(2 + I) + # sage: A(p) + # Point in UHP 8/17*I + 53/17. + + # sage: g = UHP.geodesic(2 + I,4 + I) + # sage: A (g) + # Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. + + # sage: A = diagonal_matrix([1, -1, 1]) + # sage: A = HyperbolicIsometryHM(A) + # sage: A.orientation_preserving() + # False + # sage: p = HM.point((0, 1, sqrt(2))) + # sage: A(p) + # Point in HM (0, -1, sqrt(2)). + # """ + # from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic + # if self.model() != other.model(): + # raise TypeError("{0} is not in the {1} model.".format(other, self.model_name())) + # if isinstance(other, HyperbolicGeodesic): + # return self.HFactory.get_geodesic(self(other.start()), self(other.end())) + # return self.HFactory.get_point(self.model().isometry_act_on_point( + # self.matrix(), other.coordinates())) + +####################### +# Setters and Getters # +####################### + + def matrix(self): + r""" + Return the matrix of the isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: HyperbolicIsometryUHP(-identity_matrix(2)).matrix() + [-1 0] + [ 0 -1] + """ + return self._matrix + + def inverse(self): + r""" + Return the inverse of the isometry ``self``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(matrix(2,[4,1,3,2])) + sage: B = A.inverse() + sage: A*B == HyperbolicIsometryUHP(identity_matrix(2)) + True + """ + return self.HFactory.get_isometry(self.matrix().inverse()) + + @classmethod + def model(this): + r""" + Return the model to which the HyperbolicIsometry belongs. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: HyperbolicIsometryUHP(identity_matrix(2)).model() + + + sage: HyperbolicIsometryPD(identity_matrix(2)).model() + + + sage: HyperbolicIsometryKM(identity_matrix(3)).model() + + + sage: HyperbolicIsometryHM(identity_matrix(3)).model() + + """ + return this.HFactory.get_model() + + @classmethod + def model_name(this): + r""" + Return the short name of the hyperbolic model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: HyperbolicIsometryUHP(identity_matrix(2)).model_name() + 'UHP' + + sage: HyperbolicIsometryPD(identity_matrix(2)).model_name() + 'PD' + + sage: HyperbolicIsometryKM(identity_matrix(3)).model_name() + 'KM' + + sage: HyperbolicIsometryHM(identity_matrix(3)).model_name() + 'HM' + """ + return this.model().short_name + + def to_model(self, model_name): + r""" + Convert the current object to image in another model. + + INPUT: + + - ``model_name`` -- a string representing the image model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: HyperbolicIsometryUHP(identity_matrix(2)).to_model('HM') + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1]. + """ + from sage.geometry.hyperbolic_space.model_factory import ModelFactory + factory = ModelFactory.find_factory(model_name) + matrix = self.model().isometry_to_model(self.matrix(), model_name) + return factory.get_isometry(matrix) + +################### +# Boolean Methods # +################### + + def orientation_preserving(self): + r""" + Return `True` if self is orientation preserving and `False` + otherwise. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(identity_matrix(2)) + sage: A.orientation_preserving() + True + sage: B = HyperbolicIsometryUHP(matrix(2,[0,1,1,0])) + sage: B.orientation_preserving() + False + """ + return bool(self.matrix().det() > 0) + +################################### +# Methods implemented in HMethods # +################################### + + def classification(self): + r""" + Classify the hyperbolic isometry as elliptic, parabolic, + hyperbolic or a reflection. + + A hyperbolic isometry fixes two points on the boundary of + hyperbolic space, a parabolic isometry fixes one point on the + boundary of hyperbolic space, and an elliptic isometry fixes no + points. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) + sage: H.classification() + 'hyperbolic' + + sage: P = HyperbolicIsometryUHP(matrix(2,[1,1,0,1])) + sage: P.classification() + 'parabolic' + + sage: E = HyperbolicIsometryUHP(matrix(2,[-1,0,0,1])) + sage: E.classification() + 'reflection' + """ + return self.HMethods.classification(self._cached_matrix) + + def translation_length(self): + r""" + For hyperbolic elements, return the translation length. + Otherwise, raise a ValueError. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) + sage: H.translation_length() + 2*arccosh(5/4) + + :: + + sage: f_1 = UHP.point(-1) + sage: f_2 = UHP.point(1) + sage: H = HyperbolicIsometryUHP.isometry_from_fixed_points(f_1, f_2) + sage: p = UHP.point(exp(i*7*pi/8)) + sage: bool((p.dist(H*p) - H.translation_length()) < 10**-9) + True + """ + return self.HMethods.translation_length(self._cached_matrix) + + def axis(self, **graphics_options): + r""" + For a hyperbolic isometry, return the axis of the + transformation. Otherwise raise a ValueError. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) + sage: H.axis() + Geodesic in UHP from 0 to +Infinity. + + It is an error to call this function on an isometry that is + not hyperbolic:: + + sage: P = HyperbolicIsometryUHP(matrix(2,[1,4,0,1])) + sage: P.axis() + Traceback (most recent call last): + ... + ValueError: The isometry is not hyperbolic. Axis is undefined. + """ + if self.classification() != 'hyperbolic': + raise ValueError("The isometry is not hyperbolic. Axis is" + " undefined.") + return self.HFactory.get_geodesic(*self.fixed_point_set()) + + def fixed_point_set(self, **graphics_options): + r""" + Return the a list containing the fixed point set of orientation- + preserving isometries. + + OUTPUT: + + - a list of hyperbolic points. + + EXAMPLES: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: H = HyperbolicIsometryUHP(matrix(2, [-2/3,-1/3,-1/3,-2/3])) + sage: (p1,p2) = H.fixed_point_set() + sage: H*p1 == p1 + True + sage: H*p2 == p2 + True + sage: A = HyperbolicIsometryUHP(matrix(2,[0,1,1,0])) + sage: A.orientation_preserving() + False + sage: A.fixed_point_set() + [Boundary point in UHP 1., Boundary point in UHP -1.] + + + :: + + sage: B = HyperbolicIsometryUHP(identity_matrix(2)) + sage: B.fixed_point_set() + Traceback (most recent call last): + ... + ValueError: The identity transformation fixes the entire hyperbolic plane. + """ + pts = self.HMethods.fixed_point_set(self._cached_matrix) + pts = [self.HMethods.model().point_to_model(k, self.model_name()) for\ + k in pts] + return [self.HFactory.get_point(k, **graphics_options) for k in pts] + + def fixed_geodesic(self, **graphics_options): + r""" + If ``self`` is a reflection in a geodesic, return that geodesic. + + EXAMPLES:: + + sage: A = UHP.isometry(matrix(2, [0, 1, 1, 0])) + sage: A.fixed_geodesic() + Geodesic in UHP from 1 to -1. + """ + fps = self.HMethods.fixed_point_set(self._cached_matrix) + if len(fps) < 2: + raise ValueError("Isometries of type" + " {0}".format(self.classification()) + + " don't fix geodesics.") + from sage.geometry.hyperbolic_space.model_factory import ModelFactory + fact = ModelFactory.find_factory(self.HMethods.model_name()) + geod = fact.get_geodesic(fps[0], fps[1]) + return geod.to_model(self.model_name()) + + def repelling_fixed_point(self, **graphics_options): + r""" + For a hyperbolic isometry, return the attracting fixed point. + Otherwise raise a ValueError. + + OUTPUT: + + - a hyperbolic point. + + EXAMPLES: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) + sage: A.repelling_fixed_point() + Boundary point in UHP 0. + """ + fp = self.HMethods.repelling_fixed_point(self._cached_matrix) + fp = self.HMethods.model().point_to_model(fp, self.model_name()) + return self.HFactory.get_point(fp) + + def attracting_fixed_point(self, **graphics_options): + r""" + For a hyperbolic isometry, return the attracting fixed point. + Otherwise raise a ValueError. + + OUTPUT: + + - a hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) + sage: A.attracting_fixed_point() + Boundary point in UHP +Infinity. + """ + fp = self.HMethods.attracting_fixed_point(self._cached_matrix) + fp = self.HMethods.model().point_to_model(fp, self.model_name()) + return self.HFactory.get_point(fp) + + @classmethod + def isometry_from_fixed_points(cls, repel, attract): + r""" + Given two fixed points ``repel`` and ``attract`` as hyperbolic + points return a hyperbolic isometry with ``repel`` as repelling + fixed point and ``attract`` as attracting fixed point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: p, q = [UHP.point(k) for k in [2 + I, 3 + I]] + sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) + Traceback (most recent call last): + ... + ValueError: Fixed points of hyperbolic elements must be ideal. + + sage: p, q = [UHP.point(k) for k in [2, 0]] + sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) + Isometry in UHP + [ -1 0] + [-1/3 -1/3]. + """ + try: + A = cls.HMethods.isometry_from_fixed_points(repel._cached_coordinates, + attract._cached_coordinates) + A = cls.HMethods.model().isometry_to_model(A, cls.model_name()) + return cls.HFactory.get_isometry(A) + except(AttributeError): + repel = cls.HFactory.get_point(repel) + attract = cls.HFactory.get_point(attract) + return cls.isometry_from_fixed_points(repel, attract) + + @classmethod + def random_element(cls, preserve_orientation = True, **kwargs): + r""" + Return a random isometry in the Upper Half Plane model. + + INPUT: + + - ``preserve_orientation`` -- if ``True`` return an orientation- preserving isometry. + + OUTPUT: + + - a hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP.random_element() + sage: B = HyperbolicIsometryUHP.random_element(preserve_orientation=False) + sage: B.orientation_preserving() + False + """ + A = cls.HMethods.random_isometry(preserve_orientation, **kwargs) + A = cls.HMethods.model().isometry_to_model(A, cls.model_name()) + return cls.HFactory.get_isometry(A) + + +class HyperbolicIsometryUHP (HyperbolicIsometry): + r""" + Create a hyperbolic isometry in the UHP model. + + INPUT: + + - A matrix in `GL(2,\RR)`. + + OUTPUT: + + - A hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryUHP + sage: A = HyperbolicIsometryUHP(identity_matrix(2)) + """ + + HFactory = HyperbolicFactoryUHP + HMethods = HyperbolicMethodsUHP + +class HyperbolicIsometryPD (HyperbolicIsometry): + r""" + Create a hyperbolic isometry in the PD model. + + INPUT: + + - A matrix in `PU(1,1)`. + + OUTPUT: + + - A hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD + sage: A = HyperbolicIsometryPD(identity_matrix(2)) + """ + HFactory = HyperbolicFactoryPD + HMethods = HyperbolicMethodsUHP + +class HyperbolicIsometryKM (HyperbolicIsometry): + r""" + Create a hyperbolic isometry in the KM model. + + INPUT: + + - A matrix in `SO(2,1)`. + + OUTPUT: + + - A hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryKM + sage: A = HyperbolicIsometryKM(identity_matrix(3)) + """ + HFactory = HyperbolicFactoryKM + HMethods = HyperbolicMethodsUHP + +class HyperbolicIsometryHM (HyperbolicIsometry): + r""" + Create a hyperbolic isometry in the HM model. + + INPUT: + + - A matrix in `SO(2,1)`. + + OUTPUT: + + - A hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryHM + sage: A = HyperbolicIsometryHM(identity_matrix(3)) + """ + HFactory = HyperbolicFactoryHM + HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py new file mode 100644 index 00000000000..beaa6df2fec --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -0,0 +1,912 @@ +r""" +Hyperbolic Methods + +This module should not be used directly by users. It is provided for +developers of sage. + +This module implements computational methods for some models of +hyperbolic space. The methods may operate on points, geodesics, or +isometries of hyperbolic space. However, instead of taking +HyperbolicPoint, HyperbolicGeodesic, or HyperbolicIsometry objects as +input, they instead take the coordinates of points, the endpoints of +geodesics, or matrices. Similarly, they output coordinates or matrices +rather than Hyperbolic objects. + +The methods are factored out of the HyperbolicPoint, HyperbolicGeodesic, +and HyperbolicIsometry classes to allow the implementation of additional +models of hyperbolic space with minimal work. For example, to implement +a model of 2-dimensional hyperbolic space, as long as one provides an +isometry of that model with the upper half plane, one can use the upper +half plane methods to do computations. This prevents, for example, +having to work out an efficient algorithm for computing the midpoint of +a geodesic in every model. Isometries are implemented in the +HyperbolicModel module, and it is primarily in that model that new code +must be added to implement a new model of hyperbolic space. + +In practice, all of the current models of 2 dimensional hyperbolic space +use the upper half plane model for their computations. This can lead to +some problems, such as long coordinate strings for symbolic points. For +example, the vector (1, 0, sqrt(2)) defines a point in the hyperboloid +model. Performing mapping this point to the upper half plane and +performing computations there may return with vector whose components +are unsimplified strings have several sqrt(2)'s. Presently, this +drawback is outweighed by the rapidity with which new models can be +implemented. + +AUTHORS: + +- Greg Laun (2013): Refactoring, rewrites, all docstrings. +- Rania Amer (2011): some UHP and PD methods. +- Jean-Philippe Burelle (2011): some UHP and PD methods. +- Zach Groton (2011): some UHP and PD methods. +- Greg Laun (2011): some UHP and PD methods. +- Jeremy Lent (2011): some UHP and PD methods. +- Leila Vaden (2011): some UHP and PD methods. +- Derrick Wigglesworth (2011): some UHP and PD methods. +- Bill Goldman (2011): many UHP and PD methods, implemented in Mathematica. +""" + +#*********************************************************************** +# +# Copyright (C) 2013 Greg Laun +# +# +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.lazy_import import lazy_import +from sage.symbolic.pynac import I +from sage.functions.all import exp, cos, sin, arccosh, arccos, sqrt, sign +from sage.functions.all import imag, real +from sage.matrix.all import matrix +from sage.rings.all import Integer, RR, RDF, infinity +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModel +from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP +from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + + +class HyperbolicAbstractMethods(UniqueRepresentation): + r""" + The abstract base class for hyperbolic methods. Primarily serving + as a list of methods that must be implemented. + """ + HModel = HyperbolicModel + + @classmethod + def model(cls): + r""" + Return the class of the underlying hyperbolic model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.model() + + """ + return cls.HModel + + @classmethod + def model_name(cls): + r""" + Return the short name of the underlying hyperbolic model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.model_name() + 'UHP' + """ + return cls.HModel.short_name + + +class HyperbolicMethodsUHP (HyperbolicAbstractMethods): + r""" + Hyperbolic methods for the UHP model of hyperbolic space. + """ + HModel = HyperbolicModelUHP + +################# +# Point Methods # +################# + + @classmethod + def point_dist(cls, p1, p2): + r""" + Compute the distance between two points in the Upper Half Plane + using the hyperbolic metric. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) + 1.38629436111989 + """ + cls.model().point_test(p1) + cls.model().point_test(p2) + num = (real(p2) - real(p1))**2 + (imag(p2) - imag(p1))**2 + denom = 2*imag(p1)*imag(p2) + if denom == 0: + return infinity + else: + return arccosh(1 + num/denom) + + @classmethod + def symmetry_in(cls, p): + r""" + Return the involutary isometry fixing the given point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.symmetry_in(3 + 2*I) + [ 3/2 -13/2] + [ 1/2 -3/2] + """ + cls.model().point_test(p) + x,y = real(p), imag(p) + if y > 0: + return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) + + @classmethod + def random_point(cls, **kwargs): + r""" + Return a random point in the upper half + plane. The points are uniformly distributed over the rectangle + `[-10,10]x[-10i,10i]`. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: p = HyperbolicMethodsUHP.random_point() + sage: bool((p.imag()) > 0) + True + """ + # TODO use **kwargs to allow these to be set + real_min = -10 + real_max = 10 + imag_min = 0 + imag_max = 10 + return RR.random_element(min = real_min ,max=real_max) +\ + I*RR.random_element(min = imag_min,max = imag_max) + +#################### +# Geodesic Methods # +#################### + + @classmethod + def boundary_points(cls, p1, p2): + r""" + Given two points ``p1`` and ``p2`` in the hyperbolic plane, + determine the endpoints of the complete hyperbolic geodesic + through ``p1`` and ``p2``. + + INPUT: + + - ``p1``, ``p2`` -- points in the hyperbolic plane. + + OUTPUT: + + - a list of boundary points. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.boundary_points(I, 2*I) + [0, +Infinity] + sage: HyperbolicMethodsUHP.boundary_points(1 + I, 2 + 4*I) + [-sqrt(65) + 9, sqrt(65) + 9] + """ + if p1 == p2: + raise ValueError(str(p1) + " and " + str(p2) + + " are not distinct.") + [x1, x2] = [real(k) for k in [p1,p2]] + [y1,y2] = [imag(k) for k in [p1,p2]] + # infinity is the first endpoint, so the other ideal endpoint + # is just the real part of the second coordinate + if p1 == infinity: + return [p1, x2] + # Same idea as above + elif p2 == infinity: + return [x1, p2] + # We could also have a vertical line with two interior points + elif x1 == x2: + return [x1, infinity] + # Otherwise, we have a semicircular arc in the UHP + else: + c = ((x1+x2)*(x2-x1)+(y1+y2)*(y2-y1))/(2*(x2-x1)) + r = sqrt((c - x1)**2 + y1**2) + return [c-r, c + r] + + @classmethod + def reflection_in(cls, start, end): + r""" + Return the matrix of the involution fixing the geodesic through + ``start`` and ``end``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.reflection_in(0, 1) + [ 1 0] + [ 2 -1] + sage: HyperbolicMethodsUHP.reflection_in(I, 2*I) + [ 1 0] + [ 0 -1] + """ + x,y = [real(k) for k in cls.boundary_points(start, end)] + if x == infinity: + M = matrix(2,[1,-2*y,0,-1]) + elif y == infinity: + M = matrix(2,[1,-2*x,0,-1]) + else: + M = matrix(2,[(x+y)/(y-x),-2*x*y/(y-x),2/(y-x),-(x+y)/(y-x)]) + return M + + @classmethod + def common_perpendicular(cls, start_1, end_1, start_2, end_2): + r""" + Return the unique hyperbolic geodesic perpendicular to two given + geodesics, if such a geodesic exists. If none exists, raise a + ValueError. + + INPUT: + + - ``other`` -- a hyperbolic geodesic in current model. + + OUTPUT: + + - a hyperbolic geodesic. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.common_perpendicular(2, 3, 4, 5) + [1/2*sqrt(3) + 7/2, -1/2*sqrt(3) + 7/2] + + It is an error to ask for the common perpendicular of two + intersecting geodesics:: + + sage: HyperbolicMethodsUHP.common_perpendicular(2, 4, 3, infinity) + Traceback (most recent call last): + ... + ValueError: Geodesics intersect. No common perpendicular exists. + """ + A = cls.reflection_in(start_1, end_1) + B = cls.reflection_in(start_2, end_2) + C = A*B + if cls.classification(C) != 'hyperbolic': + raise ValueError("Geodesics intersect. " + + "No common perpendicular exists.") + return cls.fixed_point_set(C) + + @classmethod + def intersection(cls, start_1, end_1, start_2, end_2): + r""" + Return the point of intersection of two complete geodesics + (if such a point exists). + + INPUT: + + - ``other`` -- a hyperbolic geodesic in the current model. + + OUTPUT: + + - a hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.intersection(3, 5, 4, 7) + [2/3*sqrt(-2) + 13/3] + + If the given geodesics do not intersect, the function raises an + error:: + + sage: HyperbolicMethodsUHP.intersection(4, 5, 5, 7) + Traceback (most recent call last): + ... + ValueError: Geodesics don't intersect. + + If the given geodesics are identical, return that + geodesic:: + + sage: HyperbolicMethodsUHP.intersection(4 + I, 18*I, 4 + I, 18*I) + [-1/8*sqrt(114985) - 307/8, 1/8*sqrt(114985) - 307/8] + """ + start_1, end_1 = sorted(cls.boundary_points(start_1, end_1)) + start_2, end_2 = sorted(cls.boundary_points(start_2, end_2)) + if start_1 == start_2 and end_1 == end_2: # Unoriented geods are same + return [start_1, end_1] + if start_1 == start_2: + return start_1 + elif end_1 == end_2: + return end_1 + A = cls.reflection_in(start_1, end_1) + B = cls.reflection_in(start_2, end_2) + C = A*B + if cls.classification(C) in ['hyperbolic', 'parabolic']: + raise ValueError("Geodesics don't intersect.") + return cls.fixed_point_set(C) + + @classmethod + def perpendicular_bisector(cls, start, end): + r""" + Return the perpendicular bisector of the hyperbolic geodesic + with endpoints start and end if that geodesic has finite length. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] + sage: g = [a, b] + sage: h = HyperbolicMethodsUHP.perpendicular_bisector(*g) + sage: bool(HyperbolicMethodsUHP.intersection(*(g + h))[0] - HyperbolicMethodsUHP.midpoint(*g) < 10**-9) + True + + Complete geodesics cannot be bisected:: + + sage: HyperbolicMethodsUHP.perpendicular_bisector(0, 1) + Traceback (most recent call last): + ... + ZeroDivisionError: input matrix must be nonsingular + """ + from sage.symbolic.constants import pi + from sage.functions.log import exp + start, end = sorted((start, end)) + d = cls.point_dist(start, end)/2 + end_1, end_2 = cls.boundary_points(start, end) + T = (matrix(2,[exp(d/2),0,0,exp(-d/2)])* + matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)])) + S= cls._to_std_geod(end_1, start, end_2) + H = S.inverse()*T*S + return [_mobius_transform(H ,k) for k in [end_1, end_2]] + + @classmethod + def midpoint(cls, start, end): + r""" + Return the (hyperbolic) midpoint of a hyperbolic line segment. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] + sage: g = [a, b] + sage: m = HyperbolicMethodsUHP.midpoint(*g) + sage: d1 =HyperbolicMethodsUHP.point_dist(m, g[0]) + sage: d2 = HyperbolicMethodsUHP.point_dist(m, g[1]) + sage: bool(abs(d1 - d2) < 10**-9) + True + + Complete geodesics have no midpoint:: + + sage: HyperbolicMethodsUHP.midpoint(0, 2) + Traceback (most recent call last): + ... + ZeroDivisionError: input matrix must be nonsingular + """ + half_dist = cls.point_dist(start, end)/2 + end_1,end_2 = cls.boundary_points(start, end) + S = cls._to_std_geod(end_1, start , end_2) + T = matrix(2,[exp(half_dist), 0, 0, 1]) + M = S.inverse()*T*S + if ((real(start - end) < EPSILON) or + (abs(real(start - end)) < EPSILON and + imag(start - end) < EPSILON)): + end_p = start + else: + end_p = end + end_p = _mobius_transform (M, end_p) + return end_p + + @classmethod + def geod_dist_from_point(cls, start, end, p): + r""" + Return the hyperbolic distance from a given hyperbolic geodesic + and a hyperbolic point. + + INPUT: + + - ``other`` -- a hyperbolic point in the same model. + + OUTPUT: + + - the hyperbolic distance. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) + arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) + + If p is a boundary point, the distance is infinity:: + + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) + +Infinity + """ + # Here is the trick for computing distance to a geodesic: + # find an isometry mapping the geodesic to the geodesic between + # 0 and infinity (so corresponding to the line imag(z) = 0. + # then any complex number is r exp(i*theta) in polar coordinates. + # the mutual perpendicular between this point and imag(z) = 0 + # intersects imag(z) = 0 at ri. So we calculate the distance + # between r exp(i*theta) and ri after we transform the original + # point. + (bd_1, bd_2) = cls.boundary_points(start, end) + if bd_1 + bd_2 != infinity: + # Not a straight line + # Map the endpoints to 0 and infinity and the midpoint to 1. + T = cls._crossratio_matrix(bd_1, (bd_1 + bd_2)/2, bd_2) + else: + # Is a straight line + # Map the endpoints to 0 and infinity and another endpoint + # to 1 + T = cls._crossratio_matrix(bd_1, bd_1 + 1, bd_2) + x = _mobius_transform(T, p) + return cls.point_dist(x, abs(x)*I) + + @classmethod + def uncomplete(cls, start, end): + r""" + Return starting and ending points of a geodesic whose completion + is the the geodesic starting at ``start`` and ending at ``end``. + + INPUT: + + - ``start`` -- a real number or infinity. + - ``end`` -- a real number or infinity + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.uncomplete(0,1) + [3/10*I + 1/10, 3/10*I + 9/10] + """ + if start + end == infinity: + p = min(real(start), real(end)) + I + else: + center = (real(start) + real(end))/Integer(2) + radius = abs(real(end) - center) + p = center + radius*I + A = cls._to_std_geod(start, p, end).inverse() + p1 = _mobius_transform(A, I/Integer(3)) + p2 = _mobius_transform(A, 3*I) + return [p1, p2] + + @classmethod + def angle(cls, start_1, end_1, start_2, end_2): + r""" + Return the angle between any two given completed geodesics if + they intersect. + + INPUT: + + -``other`` -- a hyperbolic geodesic in the UHP model. + + OUTPUT: + + - The angle in radians between the two given geodesics + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: numerical_approx(HyperbolicMethodsUHP.angle(2, 4, 3, 3 + I)) + 1.57079632679490 + + If the geodesics are identical, return angle 0:: + + sage: HyperbolicMethodsUHP.angle(2, 4, 2, 4) + 0 + """ + (p_1,p_2) = sorted(cls.boundary_points(start_1, end_1)) + (q_1,q_2) = sorted(cls.boundary_points(start_2, end_2)) + # if the geodesics are equal, the angle between them is 0 + if (abs(p_1 - q_1) < EPSILON \ + and abs(p_2 - q_2) < EPSILON): + return 0 + elif p_2 != infinity: # geodesic not a straight line + # So we send it to the geodesic with endpoints [0, oo] + T = cls._crossratio_matrix(p_1, (p_1+p_2)/2, p_2) + else: + # geodesic is a straight line, so we send it to the geodesic + # with endpoints [0,oo] + T = cls._crossratio_matrix(p_1, p_1 +1, p_2) + # b_1 and b_2 are the endpoints of the image of other + b_1, b_2 = [_mobius_transform(T, k) for k in [q_1, q_2]] + # If other is now a straight line... + if (b_1 == infinity or b_2 == infinity): + # then since they intersect, they are equal + return 0 + else: + return real(arccos((b_1+b_2)/abs(b_2-b_1))) + + +#################### +# Isometry Methods # +#################### + + @classmethod + def classification(cls, M): + r""" + Classify the hyperbolic isometry as elliptic, parabolic, or + hyperbolic. + + A hyperbolic isometry fixes two points on the boundary of + hyperbolic space, a parabolic isometry fixes one point on the + boundary of hyperbolic space, and an elliptic isometry fixes + no points. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.classification(identity_matrix(2)) + 'identity' + + sage: HyperbolicMethodsUHP.classification(4*identity_matrix(2)) + 'identity' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[2,0,0,1/2])) + 'hyperbolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2, [0, 3, -1/3, 6])) + 'hyperbolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[1,1,0,1])) + 'parabolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[-1,0,0,1])) + 'reflection' + """ + A = M.n() + A = A/(abs(A.det()).sqrt()) + tau = abs(A.trace()) + a = A.list() + if A.det() > 0: + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < + EPSILON) + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < + EPSILON) + if tf: + return 'identity' + if tau - 2 < -EPSILON: + return 'elliptic' + elif tau -2 > -EPSILON and tau - 2 < EPSILON: + return 'parabolic' + elif tau - 2 > EPSILON: + return 'hyperbolic' + else: + raise ValueError("Something went wrong with classification!" + + "Trace is " + str(A.trace())) + else: #The isometry reverses orientation. + if tau < EPSILON: + return 'reflection' + else: + return 'orientation-reversing hyperbolic' + + @classmethod + def translation_length(cls, M): + r""" + For hyperbolic elements, return the translation length. + Otherwise, raise a ValueError. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.translation_length(matrix(2,[2,0,0,1/2])) + 2*arccosh(5/4) + + :: + + sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) + sage: p = exp(i*7*pi/8) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: Hp = _mobius_transform(H, p) + sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) + True + """ + d = sqrt(M.det()**2) + tau = sqrt((M/sqrt(d)).trace()**2) + if cls.classification(M) in ['hyperbolic', + 'oriention-reversing hyperbolic']: + return 2*arccosh(tau/2) + raise TypeError("Translation length is only defined for hyperbolic" + " transformations.") + + @classmethod + def isometry_from_fixed_points(cls, repel, attract): + r""" + Given two fixed points ``repel`` and ``attract`` as complex + numbers return a hyperbolic isometry with ``repel`` as repelling + fixed point and ``attract`` as attracting fixed point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2 + I, 3 + I) + Traceback (most recent call last): + ... + ValueError: Fixed points of hyperbolic elements must be ideal. + + sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2, 0) + [ -1 0] + [-1/3 -1/3] + """ + if imag(repel) + imag(attract) > EPSILON: + raise ValueError("Fixed points of hyperbolic elements must be" + " ideal.") + repel = real(repel) + attract = real(attract) + if repel == infinity: + A = cls._mobius_sending([infinity, attract, attract + 1], + [infinity, attract, attract + 2]) + elif attract == infinity: + A = cls._mobius_sending([repel, infinity, repel + 1], + [repel, infinity, repel + 2]) + else: + A = cls._mobius_sending([repel, attract, infinity], + [repel, attract, max(repel, attract) + 1]) + return A + + + @classmethod + def fixed_point_set(cls, M): + r""" + Return the a list containing the fixed point set of + orientation-preserving isometries. + + OUTPUT: + + - a list of hyperbolic points. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) + sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: bool(_mobius_transform(H, p1) == p1) + True + sage: bool(_mobius_transform(H, p2) == p2) + True + + sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) + Traceback (most recent call last): + ... + ValueError: The identity transformation fixes the entire hyperbolic plane. + """ + d = sqrt(M.det()**2) + M = M/sqrt(d) + tau = M.trace()**2 + M_cls = cls.classification(M) + if M_cls == 'identity': + raise ValueError("The identity transformation fixes the entire " + "hyperbolic plane.") + if M_cls == 'parabolic': + if abs(M[1,0]) < EPSILON: + return [infinity] + else: + # boundary point + return [(M[0,0] - M[1,1])/(2*M[1,0])] + elif M_cls=='elliptic': + d = sqrt(tau - 4) + return [(M[0,0] - M[1,1] + sign(M[1,0])*d)/(2*M[1,0])] + elif M_cls == 'hyperbolic': + if M[1,0]!= 0: #if the isometry doesn't fix infinity + d = sqrt(tau - 4) + p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0]) + p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0]) + return [p_1, p_2] + else: #else, it fixes infinity. + p_1 = M[0,1]/(M[1,1]-M[0,0]) + p_2 = infinity + return [p_1, p_2] + else: + # raise NotImplementedError("fixed point set not implemented for" + # " isometries of type {0}".format(M_cls)) + from sage.rings.real_mpfr import is_RealField + if is_RealField(M.base_ring()): + M = M.change_ring(RDF) + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + if p[1] == 0: + p = infinity + else: + p = p[0]/p[1] + if q[1] == 0: + q = infinity + else: + q = q[0]/q[1] + pts = [p, q] + return [k for k in pts if imag(k) >= 0] + + + @classmethod + def repelling_fixed_point(cls, M): + r""" + For a hyperbolic isometry, return the attracting fixed point. + Otherwise raise a ValueError. + + OUTPUT: + + - a hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = matrix(2,[4,0,0,1/4]) + sage: HyperbolicMethodsUHP.repelling_fixed_point(A) + 0 + """ + if cls.classification(M) not in ['hyperbolic', + 'orientation-reversing hyperbolic']: + raise ValueError("Repelling fixed point is defined only" + + "for hyperbolic isometries.") + v = M.eigenmatrix_right()[1].column(1) + if v[1] == 0: + return infinity + return v[0]/v[1] + + @classmethod + def attracting_fixed_point(cls, M): + r""" + For a hyperbolic isometry, return the attracting fixed point. + Otherwise raise a ValueError. + + OUTPUT: + + - a hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = matrix(2,[4,0,0,1/4]) + sage: HyperbolicMethodsUHP.attracting_fixed_point(A) + +Infinity + """ + if cls.classification(M) not in \ + ['hyperbolic', 'orientation-reversing hyperbolic']: + raise ValueError("Attracting fixed point is defined only" + + "for hyperbolic isometries.") + v = M.eigenmatrix_right()[1].column(0) + if v[1] == 0: + return infinity + return v[0]/v[1] + + @classmethod + def random_isometry(cls, preserve_orientation = True, **kwargs): + r""" + Return a random isometry in the Upper Half Plane model. + + INPUT: + + - ``preserve_orientation`` -- if ``True`` return an orientation-preserving isometry. + + OUTPUT: + + - a hyperbolic isometry. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = HyperbolicMethodsUHP.random_isometry() + sage: B = HyperbolicMethodsUHP.random_isometry(preserve_orientation=False) + sage: B.det() < 0 + True + """ + [a,b,c,d] = [RR.random_element() for k in range(4)] + while abs(a*d - b*c) < EPSILON: + [a,b,c,d] = [RR.random_element() for k in range(4)] + M = matrix(2,[a,b,c,d]) + M = M/(M.det()).abs().sqrt() + if M.det() > 0: + if preserve_orientation: + return M + else: + return M*matrix(2,[0,1,1,0]) + else: + if preserve_orientation: + return M*matrix(2,[0,1,1,0]) + else: + return M + +################### +# Helping Methods # +################### + + + @classmethod + def _to_std_geod(cls, start, p, end): + r""" + Given the points of a geodesic in hyperbolic space, return the + hyperbolic isometry that sends that geodesic to the geodesic + through 0 and infinity that also sends the point ``p`` to ``I``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] + sage: A = HyperbolicMethodsUHP._to_std_geod(p_1, p_2, p_3) + sage: bool(abs(_mobius_transform(A, p_1)) < 10**-9) + True + sage: bool(abs(_mobius_transform(A, p_2) - I) < 10**-9) + True + sage: bool(_mobius_transform(A, p_3) == infinity) + True + """ + B = matrix(2, [1, 0, 0, -I]) + return B*cls._crossratio_matrix(start, p, end) + + @classmethod + def _crossratio_matrix(cls, p_0, p_1, p_2): + r""" + Given three points (the list `p`) in `\mathbb{CP}^{1}' in affine + coordinates, return the linear fractional transformation taking + the elements of `p` to `0`,`1`, and `\infty'. + + INPUT: + + - A list of three distinct elements of three distinct elements of `\mathbb{CP}^1` in affine coordinates. That is, each element must be a complex number, `\infty`, or symbolic. + + OUTPUT: + + - An element of '\GL(2,\CC)`. + + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] + sage: A = HyperbolicMethodsUHP._crossratio_matrix(p_1, p_2, p_3) + sage: bool(abs(_mobius_transform(A, p_1) < 10**-9)) + True + sage: bool(abs(_mobius_transform(A, p_2) - 1) < 10**-9) + True + sage: bool(_mobius_transform(A, p_3) == infinity) + True + sage: (x,y,z) = var('x,y,z'); HyperbolicMethodsUHP._crossratio_matrix(x,y,z) + [ y - z -x*(y - z)] + [ -x + y (x - y)*z] + """ + if p_0 == infinity: + return matrix(2,[0,-(p_1 - p_2), -1, p_2]) + elif p_1 == infinity: + return matrix(2,[1, -p_0,1,-p_2]) + elif p_2 == infinity: + return matrix(2,[1,-p_0, 0, p_1 - p_0]) + else: + return matrix(2,[p_1 - p_2, (p_1 - p_2)*(-p_0), p_1 - p_0, + ( p_1 - p_0)*(-p_2)]) + + + @classmethod + def _mobius_sending(cls, list1, list2): + r""" + Given two lists ``list1``, ``list2``, of three points each in + `\mathbb{CP}^1`, return the linear fractional transformation + taking the points in ``list1`` to the points in ``list2``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) + True + sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) + True + sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) + True + """ + if len(list1) != 3 or len(list2) != 3: + raise TypeError("mobius_sending requires each list to be three" + "points long.") + pl = list1 + list2 + z = pl[0:3] + w = pl[3:6] + A = cls._crossratio_matrix(z[0],z[1],z[2]) + B = cls._crossratio_matrix(w[0],w[1],w[2]) + return B.inverse()*A diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py new file mode 100644 index 00000000000..cdd7f6c72db --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -0,0 +1,1030 @@ +r""" +Hyperbolic Models + +In this module, a hyperbolic model is a collection of data that allow +the user to implement new models of hyperbolic space with minimal effort. +The data include facts about the underlying set (such as whether the +model is bounded), facts about the metric (such as whether the model is +conformal), facts about the isometry group (such as whether it is a +linear or projective group), and more. Generally speaking, any data +or method that pertains to the model itself -- rather than the points, +geodesics, or isometries of the model -- is implemented in this module. + +Abstractly, a model of hyperbolic space is a connected, simply connected +manifold equipped with a complete Riemannian metric of constant curvature +`-1`. This module records information sufficient to enable computations +in hyperbolic space without explicitly specifying the underlying set or +its Riemannian metric. Although, see the `SageManifolds `_ project if +you would like to take this approach. + +This module implements the abstract base class for a model of hyperbolic +space of arbitrary dimension. It also contains the implementations of +specific models of hyperbolic geometry. + +AUTHORS: + +- Greg Laun (2013): Initial version. + +EXAMPLES: + +We illustrate how the classes in this module encode data by comparing +the upper half plane (UHP), Poincare disk (PD) and hyperboloid (HM) +models. First we import:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP as U + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD as P + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelHM as H + +We note that the UHP and PD models are bounded while the HM model is +not:: + + sage: U.bounded and P.bounded + True + sage: H.bounded + False + +The isometry groups of UHP and PD are projective, while that of HM is +linear: + + sage: U.isometry_group_is_projective + True + sage: H.isometry_group_is_projective + False + +The models are responsible for determining if the coordinates of points +and the matrix of linear maps are appropriate for constructing points +and isometries in hyperbolic space: + + sage: U.point_in_model(2 + I) + True + sage: U.point_in_model(2 - I) + False + sage: U.point_in_model(2) + False + sage: U.bdry_point_in_model(2) + True + +""" + +#*********************************************************************** +# +# Copyright (C) 2013 Greg Laun +# +# +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.lazy_import import lazy_import +from sage.functions.other import imag, real +from sage.rings.all import CC, RR +from sage.symbolic.pynac import I +from sage.rings.infinity import infinity +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON + +lazy_import('sage.misc.misc', 'attrcall') +lazy_import('sage.matrix.all', 'matrix') +lazy_import('sage.rings.integer', 'Integer') +lazy_import('sage.modules.free_module_element', 'vector') + +lazy_import('sage.functions.other','sqrt') + + +lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', '_SL2R_to_SO21') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', '_SO21_to_SL2R') + + + + +class HyperbolicModel(UniqueRepresentation): + r""" + Abstract base class for Hyperbolic Models. + """ + name = "Abstract Hyperbolic Model" + short_name = "Abstract" + bounded = False + conformal = False + dimension = 0 + isometry_group = None + isometry_group_is_projective = False + pt_conversion_dict = {} + isom_conversion_dict = {} + + @classmethod + def point_in_model(cls, p): #Abstract + r""" + Return true if the point is in the given model and false + otherwise. + + INPUT: + + - Any object that can converted into a complex number. + + OUTPUT: + + - Boolean. + + EXAMPLES:: + + sage: UHP.point_in_model(I) + True + sage: UHP.point_in_model(-I) + False + """ + return True + + @classmethod + def point_test(cls, p): #Abstract + r""" + Test whether a point is in the model. If the point is in the + model, do nothing. Otherwise, raise a ValueError. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP + sage: HyperbolicModelUHP.point_test(2 + I) + sage: HyperbolicModelUHP.point_test(2 - I) + Traceback (most recent call last): + ... + ValueError: -I + 2 is not a valid point in the UHP model. + """ + if not (cls.point_in_model(p) or cls.bdry_point_in_model(p)): + error_string = "{0} is not a valid point in the {1} model." + raise ValueError(error_string.format(p, cls.short_name)) + + @classmethod + def bdry_point_in_model(cls,p): #Abstract + r""" + Return true if the point is on the ideal boundary of hyperbolic + space and false otherwise. + + INPUT: + + - Any object that can converted into a complex number. + + OUTPUT: + + - Boolean. + + EXAMPLES:: + + sage: UHP.bdry_point_in_model(I) + False + """ + return True + + @classmethod + def bdry_point_test(cls, p): #Abstract + r""" + Test whether a point is in the model. If the point is in the + model, do nothing. Otherwise, raise a ValueError. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP + sage: HyperbolicModelUHP.bdry_point_test(2) + sage: HyperbolicModelUHP.bdry_point_test(1 + I) + Traceback (most recent call last): + ... + ValueError: I + 1 is not a valid boundary point in the UHP model. + """ + if not cls.bounded or not cls.bdry_point_in_model(p): + error_string = "{0} is not a valid boundary point in the {1} model." + raise ValueError(error_string.format(p, cls.short_name)) + + @classmethod + def isometry_in_model(cls, A): #Abstract + r""" + Return true if the input matrix represents an isometry of the + given model and false otherwise. + + INPUT: + + - A matrix that represents an isometry in the appropriate model. + + OUTPUT: + + - Boolean. + + EXAMPLES:: + + sage: UHP.isometry_in_model(identity_matrix(2)) + True + + sage: UHP.isometry_in_model(identity_matrix(3)) + False + """ + return True + + @classmethod + def isometry_act_on_point(cls, A, p): + r""" + Given an isometry ``A`` and a point ``p`` in the current model, + return image of ``p`` unduer the action ``A \cdot p``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP + sage: I2 = identity_matrix(2) + sage: p = UHP.random_point().coordinates() + sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) + True + """ + return A*vector(p) + + @classmethod + def isometry_test(cls, A): #Abstract + r""" + Test whether an isometry is in the model. If the isometry is in + the model, do nothing. Otherwise, raise a ValueError. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP + sage: HyperbolicModelUHP.isometry_test(identity_matrix(2)) + sage: HyperbolicModelUHP.isometry_test(matrix(2, [I,1,2,1])) + Traceback (most recent call last): + ... + ValueError: + [I 1] + [2 1] is not a valid isometry in the UHP model. + """ + if not cls.isometry_in_model(A): + error_string = "\n{0} is not a valid isometry in the {1} model." + raise ValueError(error_string.format(A, cls.short_name)) + + @classmethod + def point_to_model(cls, coordinates, model_name): #Abstract + r""" + Convert ``coordinates`` from the current model to the model + specified in ``model_name``. + + INPUT: + + - ``coordinates`` -- the coordinates of a valid point in the current model. + - ``model_name`` -- a string denoting the model to be converted to. + + OUTPUT: + + - the coordinates of a point in the ``short_name`` model. + + EXAMPLES:: + + sage: UHP.point_to_model(I, 'UHP') + I + sage: UHP.point_to_model(I, 'PD') + 0 + sage: UHP.point_to_model(3 + I, 'KM') + (6/11, 9/11) + sage: UHP.point_to_model(3 + I, 'HM') + (3, 9/2, 11/2) + sage: UHP.point_to_model(I, 'PD') + 0 + sage: PD.point_to_model(0, 'UHP') + I + sage: UHP.point_to_model(I, 'UHP') + I + sage: KM.point_to_model((0, 0), 'UHP') + I + sage: KM.point_to_model((0, 0), 'HM') + (0, 0, 1) + sage: HM.point_to_model(vector((0,0,1)), 'UHP') + I + sage: HM.point_to_model(vector((0,0,1)), 'KM') + (0, 0) + + It is an error to try to convert a boundary point to a model + that doesn't support boundary points:: + + sage: UHP.point_to_model(infinity, 'HM') + Traceback (most recent call last): + ... + NotImplementedError: Boundary points are not implemented for the HM model. + """ + cls.point_test(coordinates) + model = ModelFactory.find_model(model_name) + if (not model.bounded) and cls.bdry_point_in_model(coordinates): + raise NotImplementedError("Boundary points are not implemented for" + " the {0} model.".format(model_name)) + return cls.pt_conversion_dict[model_name](coordinates) + + @classmethod + def isometry_to_model(cls, A, model_name): #Abstract + r""" + Convert ``A`` from the current model to the model specified in + ``model_name``. + + INPUT: + + - ``A`` -- a matrix in the current model. + - ``model_name`` -- a string denoting the model to be converted to. + + OUTPUT: + + - the coordinates of a point in the ``short_name`` model. + + EXAMPLES:: + + sage: A = matrix(2,[I, 0, 0, -I]) + sage: PD.isometry_to_model(A, 'UHP') + [ 0 1] + [-1 0] + + sage: PD.isometry_to_model(A, 'HM') + [-1 0 0] + [ 0 -1 0] + [ 0 0 1] + + sage: PD.isometry_to_model(A, 'KM') + [-1 0 0] + [ 0 -1 0] + [ 0 0 1] + + sage: B = diagonal_matrix([-1, -1, 1]) + sage: HM.isometry_to_model(B, 'UHP') + [ 0 -1] + [ 1 0] + + sage: HM.isometry_to_model(B, 'PD') + [-I 0] + [ 0 I] + + sage: HM.isometry_to_model(B, 'KM') + [-1 0 0] + [ 0 -1 0] + [ 0 0 1] + """ + cls.isometry_test(A) + return cls.isom_conversion_dict[model_name](A) + +class HyperbolicModelUHP (HyperbolicModel, UniqueRepresentation): + r""" + Upper Half Plane model. + """ + name = "Upper Half Plane Model" + short_name = "UHP" + bounded = True + conformal = True + dimension = 2 + isometry_group = "PSL (2,\\Bold{R})" + isometry_group_is_projective = True + pt_conversion_dict = { + 'UHP' : lambda p : p, + 'PD' : lambda p : (p - I)/(Integer(1) - I*p), + 'HM' : lambda p : vector((real(p)/imag(p), + (real(p)**2 + imag(p)**2 - 1)/(2*imag(p)), + (real(p)**2 + imag(p)**2 + 1)/(2*imag(p)))), + 'KM' : lambda p : ((2*real(p))/(real(p)**2 + imag(p)**2 + 1), + (real(p)**2 + imag(p)**2 - 1)/(real(p)**2 + + imag(p)**2 + 1)) + } + isom_conversion_dict = { + 'UHP': lambda A : A, + 'PD' : lambda A : matrix(2,[1,-I,-I,1])*A*\ + matrix(2,[1,I,I,1])/Integer(2), + 'HM' : lambda A : _SL2R_to_SO21(A), + 'KM' : lambda A : _SL2R_to_SO21(A) + } + + @classmethod + def point_in_model(cls, p): #UHP + r""" + Check whether a complex number lies in the open upper half + plane. In the UHP.model_name_name, this is hyperbolic space. + + EXAMPLES:: + + sage: UHP.point_in_model(1 + I) + True + sage: UHP.point_in_model(infinity) + False + sage: UHP.point_in_model(CC(infinity)) + False + sage: UHP.point_in_model(RR(infinity)) + False + sage: UHP.point_in_model(1) + False + sage: UHP.point_in_model(12) + False + sage: UHP.point_in_model(1 - I) + False + sage: UHP.point_in_model(-2*I) + False + """ + return bool(imag(CC(p)) > 0) + + @classmethod + def bdry_point_in_model(cls,p): #UHP + r""" + Check whether a complex number is a real number or ``\infty``. + In the UHP.model_name_name, this is the ideal boundary of + hyperbolic space. + + EXAMPLES:: + + sage: UHP.bdry_point_in_model(1 + I) + False + sage: UHP.bdry_point_in_model(infinity) + True + sage: UHP.bdry_point_in_model(CC(infinity)) + True + sage: UHP.bdry_point_in_model(RR(infinity)) + True + sage: UHP.bdry_point_in_model(1) + True + sage: UHP.bdry_point_in_model(12) + True + sage: UHP.bdry_point_in_model(1 - I) + False + sage: UHP.bdry_point_in_model(-2*I) + False + """ + im = abs(imag(CC(p)).n()) + return bool( (im < EPSILON) or (p == infinity)) + + @classmethod + def isometry_act_on_point(cls, A, p): + r""" + Given an isometry ``A`` and a point ``p`` in the current model, + return image of ``p`` unduer the action ``A \cdot p``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP + sage: I2 = identity_matrix(2) + sage: p = UHP.random_point().coordinates() + sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) + True + """ + return _mobius_transform(A, p) + + @classmethod + def isometry_in_model(cls,A): #UHP + r""" + Check that ``A`` acts as an isometry on the upper half plane. + That is, ``A`` must be an invertible ``2 x 2`` matrix with real + entries. + + EXAMPLES:: + + sage: A = matrix(2,[1,2,3,4]) + sage: UHP.isometry_in_model(A) + True + sage: B = matrix(2,[I,2,4,1]) + sage: UHP.isometry_in_model(B) + False + """ + return (A.ncols() == 2 and A.nrows() == 2 and + sum([k in RR for k in A.list()]) == 4 and + abs(A.det()) > -EPSILON) + + @classmethod + def point_to_model(cls, coordinates, model_name): #UHP + r""" + Convert ``coordinates`` from the current model to the model + specified in ``model_name``. + + INPUT: + + - ``coordinates`` -- the coordinates of a valid point in the current model. + - ``model_name`` -- a string denoting the model to be converted to. + + OUTPUT: + + - the coordinates of a point in the ``short_name`` model. + + EXAMPLES:: + + sage: UHP.point_to_model(I, 'UHP') + I + sage: UHP.point_to_model(I, 'PD') + 0 + sage: UHP.point_to_model(3 + I, 'KM') + (6/11, 9/11) + sage: UHP.point_to_model(3 + I, 'HM') + (3, 9/2, 11/2) + + It is an error to try to convert a boundary point to a model + that doesn't support boundary points:: + + sage: UHP.point_to_model(infinity, 'HM') + Traceback (most recent call last): + ... + NotImplementedError: Boundary points are not implemented for the HM model. + """ + p = coordinates + if (cls.bdry_point_in_model(p) and not + ModelFactory.find_model(model_name).bounded): + raise NotImplementedError("Boundary points are not implemented for" + " the {0} model.".format(model_name)) + if p == infinity: + return { + 'UHP' : p, + 'PD' : I, + 'KM' : (0, 1) + }[model_name] + return cls.pt_conversion_dict[model_name](coordinates) + +class HyperbolicModelPD (HyperbolicModel, UniqueRepresentation): + r""" + Poincare Disk Model. + """ + name = "Poincare Disk Model" + short_name = "PD" + bounded = True + conformal = True + dimension = 2 + isometry_group = "PU (1, 1)" + isometry_group_is_projective = True + pt_conversion_dict = { + 'PD': lambda p : p, + 'UHP': lambda p : (p + I)/(Integer(1) + I*p), + 'HM' : lambda p : vector(( + 2*real(p)/(1 - real(p)**2 - imag(p)**2), + 2*imag(p)/(1 - real(p)**2 - imag(p)**2), + (real(p)**2 + imag(p)**2 + 1)/(1 -real(p)**2 - imag(p)**2) + )), + 'KM' : lambda p : ( + 2*real(p)/(Integer(1) + real(p)**2 +imag(p)**2), + 2*imag(p)/(Integer(1) + real(p)**2 + imag(p)**2) + ) + } + isom_conversion_dict = { + 'PD' : lambda A : A, + 'UHP': lambda A : (matrix(2,[1,I,I,1])*A* + matrix(2,[1,-I,-I,1])/Integer(2)), + 'KM' : lambda A : _SL2R_to_SO21( matrix(2,[1,I,I,1])*A* + matrix(2,[1,-I,-I,1])/Integer(2)), + 'HM' : lambda A : _SL2R_to_SO21( matrix(2,[1,I,I,1])*A* + matrix(2,[1,-I,-I,1])/Integer(2)) + } + + @classmethod + def point_in_model(cls, p): #PD + r""" + Check whether a complex number lies in the open unit disk. + + EXAMPLES:: + + sage: PD.point_in_model(1.00) + False + + sage: PD.point_in_model(1/2 + I/2) + True + + sage: PD.point_in_model(1 + .2*I) + False + """ + return bool(abs(CC(p)) < 1) + + + @classmethod + def bdry_point_in_model(cls,p): #PD + r""" + Check whether a complex number lies in the open unit disk. + + EXAMPLES:: + + sage: PD.bdry_point_in_model(1.00) + True + + sage: PD.bdry_point_in_model(1/2 + I/2) + False + + sage: PD.bdry_point_in_model(1 + .2*I) + False + """ + return bool(abs(abs(CC(p))- 1) < EPSILON) + + + @classmethod + def isometry_act_on_point(cls, A, p): + r""" + Given an isometry ``A`` and a point ``p`` in the current model, + return image of ``p`` unduer the action ``A \cdot p``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD + sage: I2 = identity_matrix(2) + sage: q = PD.random_point().coordinates() + sage: bool(norm(HyperbolicModelPD.isometry_act_on_point(I2, q) - q) < 10**-9) + True + """ + return _mobius_transform(A, p) + + @classmethod + def isometry_in_model(cls, A): #PD + r""" + Check if the given matrix A is in the group U(1,1). + + EXAMPLES:: + + sage: z = [CC.random_element() for k in range(2)]; z.sort(key=abs) + sage: A = matrix(2,[z[1], z[0],z[0].conjugate(),z[1].conjugate()]) + sage: PD.isometry_in_model(A) + True + """ + alpha = A[0][0] + beta = A[0][1] + return A[1][0] == beta.conjugate() and A[1][1] == alpha.conjugate() \ + and abs(alpha) - abs(beta) != 0 + + @classmethod + def point_to_model(cls, coordinates, model_name): #KM + r""" + Convert ``coordinates`` from the current model to the model + specified in ``model_name``. + + INPUT: + + - ``coordinates`` -- the coordinates of a valid point in the current model. + - ``model_name`` -- a string denoting the model to be converted to. + + OUTPUT: + + - the coordinates of a point in the ``short_name`` model. + + EXAMPLES:: + + sage: PD.point_to_model(0, 'UHP') + I + + sage: PD.point_to_model(I, 'UHP') + +Infinity + + sage: PD.point_to_model(-I, 'UHP') + 0 + """ + if model_name == 'UHP' and coordinates == I: + return infinity + return super(HyperbolicModelPD, cls).point_to_model(coordinates, + model_name) + + +class HyperbolicModelKM (HyperbolicModel, UniqueRepresentation): + r""" + Klein Model. + """ + name = "Klein Disk Model" + short_name = "KM" + bounded = True + conformal = False + dimension = 2 + isometry_group_is_projective = True + isometry_group = "PSO (2, 1)" + pt_conversion_dict = { + 'UHP' : lambda p: -p[0]/(p[1] - 1) +\ + I*(-(sqrt(-p[0]**2 -p[1]**2 + 1) - p[0]**2 - p[1]**2 + + 1)/((p[1] - 1)*sqrt(-p[0]**2 - p[1]**2 + 1) + p[1] - 1)), + 'PD' : lambda p : (p[0]/(1 + (1 - p[0]**2 - p[1]**2).sqrt()) + \ + I*p[1]/(1 + (1 - p[0]**2 - p[1]**2).sqrt())), + 'KM' : lambda p : p, + 'HM' : lambda p : vector((2*p[0],2*p[1], 1 + p[0]**2 + + p[1]**2))/(1 - p[0]**2 - p[1]**2) + } + isom_conversion_dict = { + 'UHP' : lambda A : _SO21_to_SL2R(A) , + 'PD' : lambda A : matrix(2,[1,-I,-I,1])*_SO21_to_SL2R(A)*\ + matrix(2,[1,I,I,1])/Integer(2), + 'KM' : lambda A : A, + 'HM' : lambda A : A + } + + @classmethod + def point_in_model(cls, p): #KM + r""" + Check whether a point lies in the open unit disk. + + EXAMPLES:: + + sage: KM.point_in_model((1,0)) + False + + sage: KM.point_in_model((1/2 , 1/2)) + True + + sage: KM.point_in_model((1 , .2)) + False + """ + return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) + + @classmethod + def bdry_point_in_model(cls,p): #KM + r""" + Check whether a point lies in the unit circle, which corresponds + to the ideal boundary of the hyperbolic plane in the Klein model. + + EXAMPLES:: + + sage: KM.bdry_point_in_model((1,0)) + True + + sage: KM.bdry_point_in_model((1/2 , 1/2)) + False + + sage: KM.bdry_point_in_model((1 , .2)) + False + """ + return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) + + + @classmethod + def isometry_act_on_point(cls, A, p): + r""" + Given an isometry ``A`` and a point ``p`` in the current model, + return image of ``p`` unduer the action ``A \cdot p``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelKM + sage: I3 = identity_matrix(3) + sage: v = vector(KM.random_point().coordinates()) + sage: bool(norm(HyperbolicModelKM.isometry_act_on_point(I3, v) - v) < 10**-9) + True + """ + v = A*vector((list(p) + [1])) + if v[2] == 0: + return infinity + return v[0:2]/v[2] + + @classmethod + def isometry_in_model(cls, A): #KM + r""" + Check if the given matrix A is in the group SO(2,1). + + EXAMPLES:: + + sage: A = matrix(3,[1, 0, 0, 0, 17/8, 15/8, 0, 15/8, 17/8]) + sage: KM.isometry_in_model(A) + True + """ + from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM + return ((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < + EPSILON) + + @classmethod + def point_to_model(cls, coordinates, model_name): #KM + r""" + Convert ``coordinates`` from the current model to the model + specified in ``model_name``. + + INPUT: + + - ``coordinates`` -- the coordinates of a valid point in the current model. + - ``model_name`` -- a string denoting the model to be converted to. + + OUTPUT: + + - the coordinates of a point in the ``short_name`` model. + + EXAMPLES:: + + sage: KM.point_to_model((0, 0), 'UHP') + I + + sage: KM.point_to_model((0, 0), 'HM') + (0, 0, 1) + + sage: KM.point_to_model((0,1), 'UHP') + +Infinity + """ + if model_name == 'UHP' and tuple(coordinates) == (0,1): + return infinity + return super(HyperbolicModelKM, cls).point_to_model(coordinates, + model_name) + + +class HyperbolicModelHM (HyperbolicModel, UniqueRepresentation): + r""" + Hyperboloid Model. + """ + name = "Hyperboloid Model" + short_name = "HM" + bounded = False + conformal = True + dimension = 2 + isometry_group = "SO (2, 1)" + pt_conversion_dict = { + 'UHP' : lambda p : -((p[0]*p[2] + p[0]) + + I*(p[2] +1))/((p[1] - 1)*p[2] - p[0]**2 - + p[1]**2 + p[1] - 1), + 'PD' : lambda p : p[0]/(1 + p[2]) + I* (p[1]/(1 + p[2])), + 'KM' : lambda p : (p[0]/(1 + p[2]), p[1]/(1 + p[2])), + 'HM' : lambda p : p + } + isom_conversion_dict = { + 'UHP' : lambda A : _SO21_to_SL2R(A) , + 'PD' : lambda A : matrix(2,[1,-I,-I,1])*_SO21_to_SL2R(A)*\ + matrix(2,[1,I,I,1])/Integer(2), + 'KM' : lambda A : A, + 'HM' : lambda A : A + } + + @classmethod + def point_in_model(cls, p): #HM + r""" + Check whether a complex number lies in the hyperboloid. + + EXAMPLES:: + + sage: HM.point_in_model((0,0,1)) + True + + sage: HM.point_in_model((1,0,sqrt(2))) + True + + sage: HM.point_in_model((1,2,1)) + False + """ + return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 +1 < EPSILON) + + @classmethod + def bdry_point_in_model(cls,p): #HM + r""" + Return False since the Hyperboloid model has no boundary points. + + EXAMPLES:: + + sage: HM.bdry_point_in_model((0,0,1)) + False + + sage: HM.bdry_point_in_model((1,0,sqrt(2))) + False + + sage: HM.bdry_point_in_model((1,2,1)) + False + """ + return False + + + @classmethod + def isometry_in_model(cls, A): #HM + r""" + Test that the matrix ``A`` is in the group ``SO (2,1)^+``. + + EXAMPLES:: + + sage: A = diagonal_matrix([1,1,-1]) + sage: HM.isometry_in_model(A) + True + """ + from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM + return (A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON + +def _SL2R_to_SO21 (A): + r""" + Given a matrix in SL(2,R) return its irreducible representation in + O(2,1). + + Note that this is not the only homomorphism, but it is the only one + that works in the context of the implemented 2D hyperbolic geometry + models. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _SL2R_to_SO21 + sage: A = _SL2R_to_SO21(identity_matrix(2)) + sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix + sage: norm(A.transpose()*J*A - J) < 10**-4 + True + """ + a,b,c,d = (A/A.det().sqrt()).list() + B = matrix(3, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, + Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, + Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, + a*b + c*d, Integer(1)/Integer(2)*a**2 - + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - + Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + + Integer(1)/Integer(2)*d**2]) + B = B.apply_map(attrcall('real')) # Kill ~0 imaginary parts + if A.det() > 0: + return B + else: + # Orientation-reversing isometries swap the nappes of + # the lightcone. This fixes that issue. + return -B + +def _SO21_to_SL2R (M): + r""" + A homomorphism from ``SO(2 ,1)``to ``SL (2, \Bold{R})``. + + Note that this is not the only homomorphism, but it is the only one + that works in the context of the implemented 2D hyperbolic geometry + models. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _SO21_to_SL2R + sage: (_SO21_to_SL2R(identity_matrix(3)) - identity_matrix(2)).norm() < 10**-4 + True + """ + #################################################################### + # SL (2,R) is the double cover of SO (2,1)^+, so we need to choose # + # a lift. I have formulas for the absolute values of each entry # + # a,b ,c,d of the lift matrix(2,[a,b,c,d]), but we need to choose # + # one entry to be positive. I choose d for no particular reason, # + # unless d = 0, then we choose c > 0. The basic strategy for this # + # function is to find the linear map induced by the SO (2,1) # + # element on the Lie algebra sl (2, R). This corresponds to the # + # Adjoint action by a matrix A or -A in SL (2,R). To find which # + # matrix let X,Y,Z be a basis for sl(2,R) and look at the images # + # of X,Y,Z as well as the second and third standard basis vectors # + # for 2x2 matrices (these are traceless, so are in the Lie # + # algebra). These corresponds to AXA^-1 etc and give formulas # + # for the entries of A. # + #################################################################### + (m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8,m_9) = M.list() + d = sqrt(Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 - + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + if M.det() > 0: #EPSILON? + det_sign = 1 + elif M.det() < 0: #EPSILON? + det_sign = -1 + if d > 0: #EPSILON? + c = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/d + b = (-Integer(1)/Integer(2)*m_2 + Integer(1)/Integer(2)*m_3)/d + ad = det_sign*1 + b*c # ad - bc = pm 1 + a = ad/d + else: # d is 0, so we make c > 0 + c = sqrt(-Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 + + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + d = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/c + #d = 0, so ad - bc = -bc = pm 1. + b = - (det_sign*1)/c + a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b + A = matrix(2,[a,b,c,d]) + return A + +def _mobius_transform(A, z): + r""" + Given a matrix `A' in `GL(2,\mathbb{C})' and a point `z' in + the complex plan return the mobius transformation action of `A' + on `z'. + + INPUT: + + - ``A`` -- a `2`x`2` invertible matrix over the complex numbers. + - `z` -- a complex number or infinity. + + OUTPUT: + + - A complex number or infinity. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform as mobius_transform + sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) + 2/109*I + 43/109 + sage: y = var('y') + sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) + x + I*y + + The matrix must be square and `2`x`2`:: + + sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring. + + sage: mobius_transform(identity_matrix(3),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring. + + The matrix can be symbolic or can be a matrix over the real + or complex numbers, but must be invertible:: + + sage: (a,b,c,d) = var('a,b,c,d'); + sage: mobius_transform(matrix(2,[a,b,c,d]),I) + (I*a + b)/(I*c + d) + + sage: mobius_transform(matrix(2,[0,0,0,0]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring. + """ + if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: + (a,b,c,d) = A.list() + if z == infinity: + if c == 0: + return infinity + return a/c + if a*d - b*c < 0: + w = z.conjugate() # Reverses orientation + else: + w = z + if c*z + d == 0: + return infinity + else: + return (a*w + b)/(c*w + d) + else: + raise TypeError("A must be an invertible 2x2 matrix over the" + " complex numbers or a symbolic ring.") diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py new file mode 100644 index 00000000000..ed6df25ab71 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -0,0 +1,656 @@ +r""" +Hyperbolic Points + +This module implements the abstract base class for points in +hyperbolic space of arbitrary dimension. It also contains the +implementations for specific models of hyperbolic geometry. + +AUTHORS: + +- Greg Laun (2013): initial version + +EXAMPLES: + +We can construct points in the upper half plane model, abbreviated +UHP for convenience:: + + sage: UHP.point(2 + I) + sage: g = UHP.point(3 + I) + sage: g.dist(UHP.point(I)) + arccosh(11/2) +""" + +#*********************************************************************** +# Copyright (C) 2013 Greg Laun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** + +from sage.structure.sage_object import SageObject +from sage.symbolic.pynac import I +from sage.misc.lazy_import import lazy_import +from sage.misc.lazy_attribute import lazy_attribute +from sage.misc.latex import latex + +lazy_import('sage.plot.point', 'point') +lazy_import('sage.rings.all', 'RR') +lazy_import('sage.rings.all', 'CC') +lazy_import('sage.functions.other', 'real') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') +lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') + +class HyperbolicPoint(SageObject): + r""" + Abstract base class for hyperbolic points. This class should never + be instantiated. + + INPUT: + + - The coordinates of a hyperbolic point in the appropriate model. + + OUTPUT: + + - A hyperbolic point. + + EXAMPLES: + + Note that the coordinate representation does not differentiate the + different models:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p = HyperbolicPointUHP(.2 + .3*I); p + Point in UHP 0.200000000000000 + 0.300000000000000*I. + + sage: q = HyperbolicPointPD(0.2 + 0.3*I); q + Point in PD 0.200000000000000 + 0.300000000000000*I. + + sage: p == q + False + + sage: bool(p.coordinates() == q.coordinates()) + True + + It is an error to specify a point that does not lie in the + appropriate model:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(0.2 - 0.3*I) + Traceback (most recent call last): + ... + ValueError: 0.200000000000000 - 0.300000000000000*I is not a valid point in the UHP model. + + sage: HyperbolicPointPD(1.2) + Traceback (most recent call last): + ... + ValueError: 1.20000000000000 is not a valid point in the PD model. + + sage: HyperbolicPointKM((1,1)) + Traceback (most recent call last): + ... + ValueError: (1, 1) is not a valid point in the KM model. + + sage: HyperbolicPointHM((1, 1, 1)) + Traceback (most recent call last): + ... + ValueError: (1, 1, 1) is not a valid point in the HM model. + """ + HFactory = HyperbolicAbstractFactory + HMethods = HyperbolicAbstractMethods + def __init__(self, coordinates, **graphics_options): + r""" + See ``HyperbolicPoint`` for full documentation. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p = HyperbolicPointUHP(I) + sage: p + Point in UHP I. + """ + if self.model().point_in_model(coordinates): + self._coordinates = coordinates + else: + raise ValueError( + "{0} is not a valid".format(coordinates) + + " point in the {0} model.".format(self.model().short_name) + ) + self._graphics_options = graphics_options + + ##################### + # "Private" Methods # + ##################### + + @lazy_attribute + def _cached_coordinates(self): + r""" + The representation of the current point used for calculations. + For example, if the current model uses the HyperbolicMethodsUHP + class, then _cached_coordinates will hold the upper half plane + representation of self.coordinates(). + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: A = HyperbolicPointHM((0, 0, 1)) + sage: A._cached_coordinates + I + """ + return self.model().point_to_model(self.coordinates(), + self.HMethods.model_name()) + def __repr__(self): + r""" + Return a string representation of ``self``. + + OUTPUT: + + - string. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(3 + 4*I).__repr__() + 'Point in UHP 4*I + 3.' + + sage: HyperbolicPointPD(1/2 + I/2).__repr__() + 'Point in PD 1/2*I + 1/2.' + + sage: HyperbolicPointKM((1/2, 1/2)).__repr__() + 'Point in KM (1/2, 1/2).' + + sage: HyperbolicPointHM((0,0,1)).__repr__() + 'Point in HM (0, 0, 1).' + """ + return "Point in {0} {1}.".format(self.model_name(), + self.coordinates()) + + def _latex_(self): + """ + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p = UHP.point(0) + sage: latex(p) + 0 + sage: q = HM.point((0,0,1)) + sage: latex(q) + \left(0, 0, 1\right) + """ + return latex(self.coordinates()) + + def __eq__(self, other): + r""" + Return True if self is equal to other. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p1 = HyperbolicPointUHP(1 + I) + sage: p2 = HyperbolicPointUHP(2 + I) + sage: p1 == p2 + False + sage: p1 == p1 + True + + :: + + sage: p1 = HyperbolicPointPD(0) + sage: p2 = HyperbolicPointPD(1/2 + 2*I/3) + sage: p1 == p2 + False + sage: p1 == p1 + True + + :: + + sage: p1 = HyperbolicPointKM((0,0)) + sage: p2 = HyperbolicPointKM((0, 1/2)) + sage: p1 == p2 + False + + :: + + sage: p1 = HyperbolicPointHM((0,0,1)) + sage: p2 = HyperbolicPointHM((0,0,1/1)) + sage: p1 == p2 + True + """ + return (self.model_name() == other.model_name() and + bool(self.coordinates() == other.coordinates())) + + def __rmul__(self, other): + r""" + Implement the action of matrices on points of hyperbolic space. + + EXAMPLES:: + + sage: A = matrix(2, [0, 1, 1, 0]) + sage: A*UHP.point(2 + I) + Point in UHP 1/5*I + 2/5. + sage: B = diagonal_matrix([-1, -1, 1]) + sage: B*HM.point((0, 1, sqrt(2))) + Point in HM (0, -1, sqrt(2)). + """ + from sage.matrix.all import is_Matrix + if is_Matrix(other): + A = self.HFactory.get_isometry(other) + return A*self + else: + raise TypeError("unsupported operand type(s) for *:" + "{0} and {1}".format(self, other)) + +####################### +# Setters and Getters # +####################### + + def coordinates(self): + r""" + Return the coordinates of the point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(2 + I).coordinates() + I + 2 + + sage: HyperbolicPointPD(1/2 + 1/2*I).coordinates() + 1/2*I + 1/2 + + sage: HyperbolicPointKM((1/3, 1/4)).coordinates() + (1/3, 1/4) + + sage: HyperbolicPointHM((0,0,1)).coordinates() + (0, 0, 1) + """ + return self._coordinates + + @classmethod + def model(cls): + r""" + Return the model to which the HyperbolicPoint belongs. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(I).model() + + + sage: HyperbolicPointPD(0).model() + + + sage: HyperbolicPointKM((0,0)).model() + + + sage: HyperbolicPointHM((0,0,1)).model() + + """ + return cls.HFactory.get_model() + + @classmethod + def model_name(cls): + r""" + Return the short name of the hyperbolic model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(I).model_name() + 'UHP' + + sage: HyperbolicPointPD(0).model_name() + 'PD' + + sage: HyperbolicPointKM((0,0)).model_name() + 'KM' + + sage: HyperbolicPointHM((0,0,1)).model_name() + 'HM' + """ + return cls.model().short_name + + def to_model(self, model_name): + r""" + Convert the current object to image in another model. + + INPUT: + + - ``model_name`` -- a string representing the image model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(I).to_model('PD') + Point in PD 0. + """ + factory = ModelFactory.find_factory(model_name) + coordinates = self.model().point_to_model(self.coordinates(), + model_name) + return factory.get_point(coordinates) + + def update_graphics(self, update = False, **options): + r""" + Update the graphics options of a HyperbolicPoint. If update is + True, update rather than overwrite. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p = HyperbolicPointUHP(I); p.graphics_options() + {} + + sage: p.update_graphics(color = "red"); p.graphics_options() + {'color': 'red'} + + sage: p.update_graphics(color = "blue"); p.graphics_options() + {'color': 'blue'} + + sage: p.update_graphics(True, size = 20); p.graphics_options() + {'color': 'blue', 'size': 20} + + """ + if not update: + self._graphics_options = {} + self._graphics_options.update(**options) + + def graphics_options(self): + r""" + Return the graphics options of the current point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p = HyperbolicPointUHP(2 + I, color="red") + sage: p.graphics_options() + {'color': 'red'} + """ + return self._graphics_options + +################################### +# Methods implemented in HMethods # +################################### + + def dist(self, other): + r""" + Calculate the hyperbolic distance between two points in the same + model. + + INPUTS: + + - ``other`` -- a hyperbolic point in the same model as ``self``. + + OUTPUTS: + + - the hyperbolic distance. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p1 = HyperbolicPointUHP(5 + 7*I) + sage: p2 = HyperbolicPointUHP(1.0 + I) + sage: p1.dist(p2) + 2.23230104635820 + + sage: p1 = HyperbolicPointPD(0) + sage: p2 = HyperbolicPointPD(I/2) + sage: p1.dist(p2) + arccosh(5/3) + + sage: p1.to_model('UHP').dist(p2.to_model('UHP')) + arccosh(5/3) + + sage: p1 = HyperbolicPointKM((0, 0)) + sage: p2 = HyperbolicPointKM((1/2, 1/2)) + sage: numerical_approx(p1.dist(p2)) + 0.881373587019543 + + sage: p1 = HyperbolicPointHM((0,0,1)) + sage: p2 = HyperbolicPointHM((1,0,sqrt(2))) + sage: numerical_approx(p1.dist(p2)) + 0.881373587019543 + + Distance between a point and itself is 0:: + + sage: p = HyperbolicPointUHP(47 + I) + sage: p.dist(p) + 0 + """ + tmp_other = other.to_model(self.model_name()) + if isinstance(other, HyperbolicPoint): + return self.HMethods.point_dist(self._cached_coordinates, tmp_other._cached_coordinates) + elif isinstance(other, HyperbolicGeodesic): + return self.HMethods.geod_dist_from_point( + *(other._cached_endpoints + self._cached_coordinates + )) + + def symmetry_in (self): + r""" + Return the involutary isometry fixing the given point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: z = HyperbolicPointUHP(3 + 2*I) + sage: z.symmetry_in() + Isometry in UHP + [ 3/2 -13/2] + [ 1/2 -3/2]. + + sage: HyperbolicPointUHP(I).symmetry_in() + Isometry in UHP + [ 0 -1] + [ 1 0]. + + sage: HyperbolicPointPD(0).symmetry_in() + Isometry in PD + [-I 0] + [ 0 I]. + + sage: HyperbolicPointKM((0, 0)).symmetry_in() + Isometry in KM + [-1 0 0] + [ 0 -1 0] + [ 0 0 1]. + + sage: HyperbolicPointHM((0,0,1)).symmetry_in() + Isometry in HM + [-1 0 0] + [ 0 -1 0] + [ 0 0 1]. + + sage: p = HyperbolicPointUHP.random_element() + sage: A = p.symmetry_in() + sage: A*p == p + True + + sage: A.orientation_preserving() + True + + sage: A*A == UHP.isometry(identity_matrix(2)) + True + """ + A = self.HMethods.symmetry_in(self._cached_coordinates) + A = self.HMethods.model().isometry_to_model(A, self.model_name()) + return self.HFactory.get_isometry(A) + + @classmethod + def random_element(cls, **kwargs): + r""" + Return a random point in the upper half + plane. The points are uniformly distributed over the rectangle + `[-10, 10] \times [-10 i, 10 i]`. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: p = HyperbolicPointUHP.random_element() + sage: bool((p.coordinates().imag()) > 0) + True + + sage: p = HyperbolicPointPD.random_element() + sage: PD.point_in_model(p.coordinates()) + True + + sage: p = HyperbolicPointKM.random_element() + sage: KM.point_in_model(p.coordinates()) + True + + sage: p = HyperbolicPointHM.random_element().coordinates() + sage: bool((p[0]**2 + p[1]**2 - p[2]**2 - 1) < 10**-8) + True + """ + p = cls.HMethods.random_point(**kwargs) + p = cls.HMethods.model().point_to_model(p, cls.model().short_name) + return cls.HFactory.get_point(p) + +########### +# Display # +########### + + def show(self, boundary = True, **options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(I).show() + sage: HyperbolicPointPD(0).show() + sage: HyperbolicPointKM((0,0)).show() + sage: HyperbolicPointHM((0,0,1)).show() + """ + opts = dict([('axes', False),('aspect_ratio',1)]) + opts.update(self.graphics_options()) + opts.update(options) + from sage.misc.functional import numerical_approx + p = self.coordinates() + if p in RR: + p = CC(p) + elif hasattr(p, 'iteritems') or hasattr(p, '__iter__'): + p = map(numerical_approx, p) + else: + p = numerical_approx(p) + pic = point(p, **opts) + if boundary: + bd_pic = self.HFactory.get_background_graphic() + pic = bd_pic + pic + return pic + +class HyperbolicPointUHP (HyperbolicPoint): + r""" + Create a point in the UHP model. + + INPUT: + + - The coordinates of a point in the unit disk in the complex plane `\CC`. + + OUTPUT: + + - A hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointUHP + sage: HyperbolicPointUHP(0) + Point in UHP 0. + """ + HFactory = HyperbolicFactoryUHP + HMethods = HyperbolicMethodsUHP + + def show(self, boundary = True, **options): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * + sage: HyperbolicPointUHP(I).show() + sage: HyperbolicPointPD(0).show() + sage: HyperbolicPointKM((0,0)).show() + sage: HyperbolicPointHM((0,0,1)).show() + """ + opts = dict([('axes', False),('aspect_ratio',1)]) + opts.update(self.graphics_options()) + opts.update(options) + from sage.misc.functional import numerical_approx + p = self.coordinates() + 0*I + p = numerical_approx(p) + pic = point(p, **opts) + if boundary: + cent = real(p) + bd_pic = self.HFactory.get_background_graphic(bd_min = cent - 1, + bd_max = cent + 1) + pic = bd_pic + pic + return pic + +class HyperbolicPointPD (HyperbolicPoint): + r""" + Create a point in the PD model. + + INPUT: + + - The coordinates of a point in the unit disk in the complex plane `\CC`. + + OUTPUT: + + - A hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointPD + sage: HyperbolicPointPD(0) + Point in PD 0. + """ + HFactory = HyperbolicFactoryPD + HMethods = HyperbolicMethodsUHP + +class HyperbolicPointKM (HyperbolicPoint): + r""" + Create a point in the KM model. + + INPUT: + + - The coordinates of a point in the unit disk in the real plane `\RR^2`. + + OUTPUT: + + - A hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointKM + sage: HyperbolicPointKM((0,0)) + Point in KM (0, 0). + """ + HFactory = HyperbolicFactoryKM + HMethods = HyperbolicMethodsUHP + +class HyperbolicPointHM (HyperbolicPoint): + r""" + Create a point in the HM model. + + INPUT: + + - The coordinates of a point in the hyperboloid given by `x^2 + y^2 - z^2 = -1`. + + OUTPUT: + + - A hyperbolic point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointHM + sage: HyperbolicPointHM((0,0,1)) + Point in HM (0, 0, 1). + """ + HFactory = HyperbolicFactoryHM + HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/model_factory.py b/src/sage/geometry/hyperbolic_space/model_factory.py new file mode 100644 index 00000000000..b053415fdf2 --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/model_factory.py @@ -0,0 +1,66 @@ +r""" +AUTHORS: +- Greg Laun (2013): initial version + + +#***************************************************************************** +# Copyright (C) 2013 Greg Laun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** +""" +from sage.structure.unique_representation import UniqueRepresentation +from sage.misc.lazy_import import lazy_import + + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelKM') + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') + + + +class ModelFactory (UniqueRepresentation): + @classmethod + def find_model(cls, model_name): + r""" + Given the short name of a hyperbolic model, return that model. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory + sage: ModelFactory.find_model('UHP') + + """ + return { + 'UHP': HyperbolicModelUHP, + 'PD' : HyperbolicModelPD, + 'HM' : HyperbolicModelHM, + 'KM' : HyperbolicModelKM + }[model_name] + + @classmethod + def find_factory(cls, model_name): + r""" + Given the short name of a hyperbolic model, return the associated factory. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory + sage: ModelFactory.find_factory('UHP') + + """ + return { + 'UHP' : HyperbolicFactoryUHP, + 'PD' : HyperbolicFactoryPD, + 'HM' : HyperbolicFactoryHM, + 'KM' : HyperbolicFactoryKM + }[model_name] From 4675f7ecf61fa150974a07273d16892695b1bac0 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Thu, 19 Dec 2013 21:08:19 -0500 Subject: [PATCH 002/129] Fixed bug in fixed_point_set in hyperbolic_methods. For UHP matrices, the base_ring is now always changed to RDF before eigenvector computations. --- src/sage/geometry/hyperbolic_space/hyperbolic_methods.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index beaa6df2fec..012e1662ce8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -707,9 +707,7 @@ def fixed_point_set(cls, M): else: # raise NotImplementedError("fixed point set not implemented for" # " isometries of type {0}".format(M_cls)) - from sage.rings.real_mpfr import is_RealField - if is_RealField(M.base_ring()): - M = M.change_ring(RDF) + M = M.change_ring(RDF) p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] if p[1] == 0: p = infinity @@ -799,7 +797,7 @@ def random_isometry(cls, preserve_orientation = True, **kwargs): [a,b,c,d] = [RR.random_element() for k in range(4)] while abs(a*d - b*c) < EPSILON: [a,b,c,d] = [RR.random_element() for k in range(4)] - M = matrix(2,[a,b,c,d]) + M = matrix(RDF, 2,[a,b,c,d]) M = M/(M.det()).abs().sqrt() if M.det() > 0: if preserve_orientation: From 556f3f7718fdd82e23245f65868d55ee506e45f7 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Thu, 19 Dec 2013 21:22:00 -0500 Subject: [PATCH 003/129] In the function fixed_point in the module hyperbolic methods for UHP (SL2R) matrices, catch an index error that indicates eigenvectors can't be computed. Try converting the matrix base_ring to RDF and attempt the computation again. Fixes a bug for computations with symoblic matrices. --- src/sage/geometry/hyperbolic_space/hyperbolic_methods.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index 012e1662ce8..3834fc8457d 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -707,8 +707,11 @@ def fixed_point_set(cls, M): else: # raise NotImplementedError("fixed point set not implemented for" # " isometries of type {0}".format(M_cls)) - M = M.change_ring(RDF) - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + try: + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + except (IndexError): + M = M.change_ring(RDF) + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] if p[1] == 0: p = infinity else: From 22773f0ef46e236ab2bd7a86a090957e01e67fc9 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Thu, 27 Feb 2014 21:36:37 -0500 Subject: [PATCH 004/129] Fixed a bug in the drawing of complete geodesics in the Poincare disk model. FIxed testing errors. --- .../hyperbolic_space/hyperbolic_bdry_point.py | 19 +++++++++- .../hyperbolic_space/hyperbolic_factory.py | 10 +---- .../hyperbolic_space/hyperbolic_geodesic.py | 37 ++++++++++++------- .../hyperbolic_space/hyperbolic_interface.py | 2 +- .../hyperbolic_space/hyperbolic_point.py | 7 ++-- 5 files changed, 48 insertions(+), 27 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py index 9ab5bdfe589..d0dd0d0c1f7 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py @@ -106,7 +106,7 @@ def __init__(self, coordinates, **graphics_options): if not self.model().bounded: raise NotImplementedError( "{0} is not a bounded model; boundary" - "points not implemented.".format(self.model_name())) + " points not implemented.".format(self.model_name())) elif self.model().bdry_point_in_model(coordinates): self._coordinates = coordinates else: @@ -229,3 +229,20 @@ class HyperbolicBdryPointKM (HyperbolicBdryPoint): """ HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP + +class HyperbolicBdryPointHM (HyperbolicBdryPoint): + r""" + A dummy class for the boundary points of the hyperboloid model. The model + is not bounded, so there are no boundary points. The class is needed for + compatibility reasons. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointHM + sage: q = HyperbolicBdryPointHM((1,0,0)); q + Traceback (most recent call last): + ... + NotImplementedError: HM is not a bounded model; boundary points not implemented. + """ + HFactory = HyperbolicFactoryHM + HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py index 6cdbc295ca7..a865afd10b9 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py @@ -121,13 +121,6 @@ def get_bdry_point(cls, coordinates, **graphics_options): sage: HyperbolicFactoryKM.get_bdry_point((0,-1)) Boundary point in KM (0, -1). - - Note that not every model is bounded:: - - sage: HyperbolicFactoryHM.get_bdry_point((0,1,-1)) - Traceback (most recent call last): - ... - NotImplementedError: HM is not a bounded model; boundarypoints not implemented. """ return cls.HBdryPoint(coordinates, **graphics_options) @@ -337,10 +330,9 @@ def get_background_graphic(cls, **bdry_options): class HyperbolicFactoryHM (HyperbolicAbstractFactory, UniqueRepresentation): HModel = HyperbolicModelHM HPoint = HyperbolicPointHM - HBdryPoint = HyperbolicBdryPointHM HIsometry = HyperbolicIsometryHM HGeodesic = HyperbolicGeodesicHM - + @classmethod def get_background_graphic(cls, **bdry_options): r""" diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 248ba12642d..e7852ea4a7d 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -44,7 +44,7 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.rings.infinity import infinity - +from sage.symbolic.constants import pi lazy_import('sage.rings.all', 'CC') lazy_import('sage.functions.other', 'real') @@ -958,6 +958,7 @@ class HyperbolicGeodesicUHP (HyperbolicGeodesic): EXAMPLES:: + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) sage: g = HyperbolicGeodesicUHP(I, 2 + I) """ @@ -1035,6 +1036,7 @@ class HyperbolicGeodesicPD (HyperbolicGeodesic): EXAMPLES:: + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicGeodesicPD(PD.point(I), PD.point(I/2)) sage: g = HyperbolicGeodesicPD(I, I/2) """ @@ -1066,15 +1068,22 @@ def show(self, boundary = True, **options): # Now we calculate the angles for the parametric plot theta1 = CC(end_1- center).arg() theta2 = CC(end_2 - center).arg() - if theta1 < 0: - theta1 = theta1 + 2*3.14159265358979 - if theta2 < 0: - theta2 = theta2 + 2*3.14159265358979 - [theta1, theta2] = sorted([theta1,theta2]) + if theta2 < theta1: + theta1, theta2 = theta2, theta1 x = var('x') - # We use the parameterization (-cos(x), sin(x)) because - # arg returns an argument between -pi and pi. - pic = parametric_plot((radius*cos(x) + real(center), + mid = (theta1 + theta2)/2.0 + if (radius*cos(mid) + real(center))**2 + \ + (radius*sin(mid) + imag(center))**2 > 1.0: + # Swap theta1 and theta2 + tmp = theta1 + 2*pi + theta1 = theta2 + theta2 = tmp + pic = parametric_plot((radius*cos(x) + real(center), + radius*sin(x) + imag(center)), + (x, theta1, theta2), **opts) + + else: + pic = parametric_plot((radius*cos(x) + real(center), radius*sin(x) + imag(center)), (x, theta1, theta2), **opts) if boundary: @@ -1099,8 +1108,9 @@ class HyperbolicGeodesicKM (HyperbolicGeodesic): EXAMPLES:: - sage: g = HyperbolicGeodesicKM(KM.point(I), KM.point(I/2)) - sage: g = HyperbolicGeodesicKM(I, I/2) + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicKM(KM.point((0,1)), KM.point((0,1/2))) + sage: g = HyperbolicGeodesicKM((0,1), (0,1/2)) """ HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP @@ -1139,8 +1149,9 @@ class HyperbolicGeodesicHM (HyperbolicGeodesic): EXAMPLES:: - sage: g = HyperbolicGeodesicHM(HM.point((1,0,0)), HM.point((0,1,sqrt(2)))) - sage: g = HyperbolicGeodesicHM((1, 0, 0), (0, 1, sqrt(2))) + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: g = HyperbolicGeodesicHM(HM.point((0, 0, 1)), HM.point((0,1,sqrt(2)))) + sage: g = HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))) """ HFactory = HyperbolicFactoryHM HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index d45d0338fc6..d0a1f6583f8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -420,7 +420,7 @@ class PD(HyperbolicUserInterface): EXAMPLES:: sage: PD.point(I) - Point in PD I. + Boundary point in PD I. """ HModel = HyperbolicModelPD HFactory = HyperbolicFactoryPD diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index ed6df25ab71..ac60d81baa9 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -15,6 +15,7 @@ UHP for convenience:: sage: UHP.point(2 + I) + Point in UHP I + 2. sage: g = UHP.point(3 + I) sage: g.dist(UHP.point(I)) arccosh(11/2) @@ -245,7 +246,7 @@ def __rmul__(self, other): sage: B*HM.point((0, 1, sqrt(2))) Point in HM (0, -1, sqrt(2)). """ - from sage.matrix.all import is_Matrix + from sage.matrix.matrix import is_Matrix if is_Matrix(other): A = self.HFactory.get_isometry(other) return A*self @@ -562,8 +563,8 @@ class HyperbolicPointUHP (HyperbolicPoint): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointUHP - sage: HyperbolicPointUHP(0) - Point in UHP 0. + sage: HyperbolicPointUHP(2*I) + Point in UHP 2*I. """ HFactory = HyperbolicFactoryUHP HMethods = HyperbolicMethodsUHP From f8e9c3e62c1b016a41d377a0ccdbcd665aa189dc Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Sun, 9 Mar 2014 22:25:47 +0100 Subject: [PATCH 005/129] Tensors on free modules of finite rank atop of Sage 6.1.1 --- src/doc/en/reference/index.rst | 1 + src/doc/en/reference/modules/index.rst | 1 + .../modules/tensors_free_modules.rst | 23 + .../en/reference/tensors_free_module/comp.rst | 16 + .../en/reference/tensors_free_module/conf.py | 37 + .../finite_free_module.rst | 16 + .../free_module_alt_form.rst | 16 + .../tensors_free_module/free_module_basis.rst | 16 + .../free_module_tensor.rst | 16 + .../free_module_tensor_spec.rst | 16 + .../reference/tensors_free_module/index.rst | 41 + .../tensor_free_module.rst | 16 + src/doc/en/tensors_free_module/comp.rst | 16 + src/doc/en/tensors_free_module/conf.py | 37 + .../finite_free_module.rst | 16 + .../free_module_alt_form.rst | 16 + .../tensors_free_module/free_module_basis.rst | 16 + .../free_module_tensor.rst | 16 + .../free_module_tensor_spec.rst | 16 + src/doc/en/tensors_free_module/index.rst | 41 + .../tensor_free_module.rst | 16 + src/sage/all.py | 1 + src/sage/tensor/modules/__init__.py | 1 + src/sage/tensor/modules/all.py | 1 + src/sage/tensor/modules/comp.py | 3749 +++++++++++++++++ src/sage/tensor/modules/finite_free_module.py | 1335 ++++++ src/sage/tensor/modules/format_utilities.py | 240 ++ .../tensor/modules/free_module_alt_form.py | 501 +++ src/sage/tensor/modules/free_module_basis.py | 395 ++ src/sage/tensor/modules/free_module_tensor.py | 2207 ++++++++++ .../tensor/modules/free_module_tensor_spec.py | 661 +++ src/sage/tensor/modules/tensor_free_module.py | 293 ++ 32 files changed, 9789 insertions(+) create mode 100644 src/doc/en/reference/modules/tensors_free_modules.rst create mode 100644 src/doc/en/reference/tensors_free_module/comp.rst create mode 100644 src/doc/en/reference/tensors_free_module/conf.py create mode 100644 src/doc/en/reference/tensors_free_module/finite_free_module.rst create mode 100644 src/doc/en/reference/tensors_free_module/free_module_alt_form.rst create mode 100644 src/doc/en/reference/tensors_free_module/free_module_basis.rst create mode 100644 src/doc/en/reference/tensors_free_module/free_module_tensor.rst create mode 100644 src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst create mode 100644 src/doc/en/reference/tensors_free_module/index.rst create mode 100644 src/doc/en/reference/tensors_free_module/tensor_free_module.rst create mode 100644 src/doc/en/tensors_free_module/comp.rst create mode 100644 src/doc/en/tensors_free_module/conf.py create mode 100644 src/doc/en/tensors_free_module/finite_free_module.rst create mode 100644 src/doc/en/tensors_free_module/free_module_alt_form.rst create mode 100644 src/doc/en/tensors_free_module/free_module_basis.rst create mode 100644 src/doc/en/tensors_free_module/free_module_tensor.rst create mode 100644 src/doc/en/tensors_free_module/free_module_tensor_spec.rst create mode 100644 src/doc/en/tensors_free_module/index.rst create mode 100644 src/doc/en/tensors_free_module/tensor_free_module.rst create mode 100644 src/sage/tensor/modules/__init__.py create mode 100644 src/sage/tensor/modules/all.py create mode 100644 src/sage/tensor/modules/comp.py create mode 100644 src/sage/tensor/modules/finite_free_module.py create mode 100644 src/sage/tensor/modules/format_utilities.py create mode 100644 src/sage/tensor/modules/free_module_alt_form.py create mode 100644 src/sage/tensor/modules/free_module_basis.py create mode 100644 src/sage/tensor/modules/free_module_tensor.py create mode 100644 src/sage/tensor/modules/free_module_tensor_spec.py create mode 100644 src/sage/tensor/modules/tensor_free_module.py diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index c1d40d89baa..0caf0279969 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -84,6 +84,7 @@ Groups, Monoids, Matrices, Modules * :doc:`Monoids ` * :doc:`Matrices and Spaces of Matrices ` * :doc:`Modules ` +* :doc:`Tensors on free modules of finite rank ` Geometry and Topology --------------------- diff --git a/src/doc/en/reference/modules/index.rst b/src/doc/en/reference/modules/index.rst index 71e0c393bf7..cda83d9224a 100644 --- a/src/doc/en/reference/modules/index.rst +++ b/src/doc/en/reference/modules/index.rst @@ -7,6 +7,7 @@ Modules sage/modules/module sage/modules/free_module sage/modules/free_module_element + sage/tensor/modules/finite_free_module sage/modules/complex_double_vector sage/modules/real_double_vector diff --git a/src/doc/en/reference/modules/tensors_free_modules.rst b/src/doc/en/reference/modules/tensors_free_modules.rst new file mode 100644 index 00000000000..b30138946dd --- /dev/null +++ b/src/doc/en/reference/modules/tensors_free_modules.rst @@ -0,0 +1,23 @@ +Tensors on free modules of finite rank +====================================== + +More documentation (including worksheets) can be found on the `SageManifolds `_ home page. + +.. toctree:: + :maxdepth: 2 + + sage/tensor/modules/finite_free_module + + sage/tensor/modules/free_module_basis + + sage/tensor/modules/tensor_free_module + + sage/tensor/modules/free_module_tensor + + sage/tensor/modules/free_module_tensor_spec + + sage/tensor/modules/free_module_alt_form + + sage/tensor/modules/comp + +.. include:: ../footer.txt diff --git a/src/doc/en/reference/tensors_free_module/comp.rst b/src/doc/en/reference/tensors_free_module/comp.rst new file mode 100644 index 00000000000..5edc877457f --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/comp.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.comp: + +Components +========== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.comp + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/reference/tensors_free_module/conf.py b/src/doc/en/reference/tensors_free_module/conf.py new file mode 100644 index 00000000000..26cb7636882 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/conf.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# Sage documentation build configuration file, created by +# sphinx-quickstart on Thu Aug 21 20:15:55 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +sys.path.append(os.environ['SAGE_DOC']) +from common.conf import * + +# General information about the project. +project = u"Tensors on free modules" +name = 'tensors_free_modules_ref' +release = "0.1" + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = project + " v" + release +copyright = "2014, Eric Gourgoulhon and Michal Bejger" + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', name+'.tex', u'Tensors on free modules', + u'', 'manual'), +] + diff --git a/src/doc/en/reference/tensors_free_module/finite_free_module.rst b/src/doc/en/reference/tensors_free_module/finite_free_module.rst new file mode 100644 index 00000000000..9c41e1ef8be --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/finite_free_module.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.finite_free_module: + +Free modules of finite rank +=========================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.finite_free_module + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/reference/tensors_free_module/free_module_alt_form.rst b/src/doc/en/reference/tensors_free_module/free_module_alt_form.rst new file mode 100644 index 00000000000..8c481f2dd81 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/free_module_alt_form.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_alt_form: + +Alternating forms on free modules +================================= + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_alt_form + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/reference/tensors_free_module/free_module_basis.rst b/src/doc/en/reference/tensors_free_module/free_module_basis.rst new file mode 100644 index 00000000000..0fdc17da7a5 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/free_module_basis.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_basis: + +Bases of free modules +===================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_basis + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/reference/tensors_free_module/free_module_tensor.rst b/src/doc/en/reference/tensors_free_module/free_module_tensor.rst new file mode 100644 index 00000000000..128eaf03085 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/free_module_tensor.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_tensor: + +Tensors on free modules +======================= + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_tensor + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst b/src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst new file mode 100644 index 00000000000..e9c7512966d --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_tensor_spec: + +Specific tensors on free modules +================================ + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_tensor_spec + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/reference/tensors_free_module/index.rst b/src/doc/en/reference/tensors_free_module/index.rst new file mode 100644 index 00000000000..a08660c8f71 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/index.rst @@ -0,0 +1,41 @@ +Tensors on free modules of finite rank +====================================== + +This is the reference manual for tensors on free modules of finite rank over +a commutative ring. + +This work is part of the `SageManifolds project `_ +but it does not depend upon other SageManifolds classes. In other words, it +constitutes a self-consistent subset that can be used independently of +SageManifolds. + + +This document is licensed under a `Creative Commons Attribution-Share Alike +3.0 License`__. + +__ http://creativecommons.org/licenses/by-sa/3.0/ + + +.. toctree:: + :maxdepth: 2 + + finite_free_module + + free_module_basis + + tensor_free_module + + free_module_tensor + + free_module_tensor_spec + + free_module_alt_form + + comp + +Indices and Tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/src/doc/en/reference/tensors_free_module/tensor_free_module.rst b/src/doc/en/reference/tensors_free_module/tensor_free_module.rst new file mode 100644 index 00000000000..eb3dc6994a3 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/tensor_free_module.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.tensor_free_module: + +Tensor products of free modules +=============================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.tensor_free_module + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/comp.rst b/src/doc/en/tensors_free_module/comp.rst new file mode 100644 index 00000000000..5edc877457f --- /dev/null +++ b/src/doc/en/tensors_free_module/comp.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.comp: + +Components +========== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.comp + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/conf.py b/src/doc/en/tensors_free_module/conf.py new file mode 100644 index 00000000000..26cb7636882 --- /dev/null +++ b/src/doc/en/tensors_free_module/conf.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# +# Sage documentation build configuration file, created by +# sphinx-quickstart on Thu Aug 21 20:15:55 2008. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# The contents of this file are pickled, so don't put values in the namespace +# that aren't pickleable (module imports are okay, they're removed automatically). +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys, os +sys.path.append(os.environ['SAGE_DOC']) +from common.conf import * + +# General information about the project. +project = u"Tensors on free modules" +name = 'tensors_free_modules_ref' +release = "0.1" + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +html_title = project + " v" + release +copyright = "2014, Eric Gourgoulhon and Michal Bejger" + +# Output file base name for HTML help builder. +htmlhelp_basename = name + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, document class [howto/manual]). +latex_documents = [ + ('index', name+'.tex', u'Tensors on free modules', + u'', 'manual'), +] + diff --git a/src/doc/en/tensors_free_module/finite_free_module.rst b/src/doc/en/tensors_free_module/finite_free_module.rst new file mode 100644 index 00000000000..9c41e1ef8be --- /dev/null +++ b/src/doc/en/tensors_free_module/finite_free_module.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.finite_free_module: + +Free modules of finite rank +=========================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.finite_free_module + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/free_module_alt_form.rst b/src/doc/en/tensors_free_module/free_module_alt_form.rst new file mode 100644 index 00000000000..8c481f2dd81 --- /dev/null +++ b/src/doc/en/tensors_free_module/free_module_alt_form.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_alt_form: + +Alternating forms on free modules +================================= + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_alt_form + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/free_module_basis.rst b/src/doc/en/tensors_free_module/free_module_basis.rst new file mode 100644 index 00000000000..0fdc17da7a5 --- /dev/null +++ b/src/doc/en/tensors_free_module/free_module_basis.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_basis: + +Bases of free modules +===================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_basis + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/free_module_tensor.rst b/src/doc/en/tensors_free_module/free_module_tensor.rst new file mode 100644 index 00000000000..128eaf03085 --- /dev/null +++ b/src/doc/en/tensors_free_module/free_module_tensor.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_tensor: + +Tensors on free modules +======================= + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_tensor + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst new file mode 100644 index 00000000000..e9c7512966d --- /dev/null +++ b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.free_module_tensor_spec: + +Specific tensors on free modules +================================ + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.free_module_tensor_spec + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/doc/en/tensors_free_module/index.rst b/src/doc/en/tensors_free_module/index.rst new file mode 100644 index 00000000000..a08660c8f71 --- /dev/null +++ b/src/doc/en/tensors_free_module/index.rst @@ -0,0 +1,41 @@ +Tensors on free modules of finite rank +====================================== + +This is the reference manual for tensors on free modules of finite rank over +a commutative ring. + +This work is part of the `SageManifolds project `_ +but it does not depend upon other SageManifolds classes. In other words, it +constitutes a self-consistent subset that can be used independently of +SageManifolds. + + +This document is licensed under a `Creative Commons Attribution-Share Alike +3.0 License`__. + +__ http://creativecommons.org/licenses/by-sa/3.0/ + + +.. toctree:: + :maxdepth: 2 + + finite_free_module + + free_module_basis + + tensor_free_module + + free_module_tensor + + free_module_tensor_spec + + free_module_alt_form + + comp + +Indices and Tables +------------------ + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/src/doc/en/tensors_free_module/tensor_free_module.rst b/src/doc/en/tensors_free_module/tensor_free_module.rst new file mode 100644 index 00000000000..eb3dc6994a3 --- /dev/null +++ b/src/doc/en/tensors_free_module/tensor_free_module.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.tensor_free_module: + +Tensor products of free modules +=============================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.tensor_free_module + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/sage/all.py b/src/sage/all.py index bc18bb5a626..a19a4beaaf2 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -166,6 +166,7 @@ sage.misc.lazy_import.lazy_import('sage.sandpiles.all', '*', globals()) from sage.tensor.all import * +from sage.tensor.modules.all import * from sage.matroids.all import * diff --git a/src/sage/tensor/modules/__init__.py b/src/sage/tensor/modules/__init__.py new file mode 100644 index 00000000000..932b79829cf --- /dev/null +++ b/src/sage/tensor/modules/__init__.py @@ -0,0 +1 @@ +# Empty file diff --git a/src/sage/tensor/modules/all.py b/src/sage/tensor/modules/all.py new file mode 100644 index 00000000000..177dce6e060 --- /dev/null +++ b/src/sage/tensor/modules/all.py @@ -0,0 +1 @@ +from finite_free_module import FiniteFreeModule diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py new file mode 100644 index 00000000000..f21d4c69d70 --- /dev/null +++ b/src/sage/tensor/modules/comp.py @@ -0,0 +1,3749 @@ +r""" +Components + +The class :class:`Components` is a technical class to take in charge the +storage of some ring elements that represent the components of +a "mathematical entity" with respect to some "frame". +Examples of "entity/frame" are "vector/vector-space basis" or +"vector field/vector frame on some manifold". More generally, the components +can be those of a tensor on a free module or those of a tensor field on a +manifold. They can also be non-tensorial quantities, like connection +coefficients or structure coefficients of a vector frame. + +The individual components are assumed to belong to a given ring and are +labelled by *indices*, i.e. tuple of integers. +The following operations are implemented on components with respect +to a given frame: + +* arithmetics (addition, subtraction, multiplication by a ring element) +* handling of symmetries or antisymmetries on the indices +* symmetrization and antisymmetrization +* tensor product +* contraction + +Various subclasses of the class :class:`Components` are + +* :class:`CompWithSym` for storing components with symmetries (symmetric and/or + antisymmetric indices) + + * :class:`CompFullySym` for storing fully symmetric components + + * :class:`KroneckerDelta` for the Kronecker delta symbol + + * :class:`CompFullyAntiSym` for storing fully antisymmetric components + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Joris Vankerschaver (2010): for the idea of storing only the non-zero + components as dictionaries, which keys are the component indices (see + class :class:`~sage.tensor.differential_form_element.DifferentialForm`) + +EXAMPLES: + + Set of components with 2 indices on a 3-dimensional vector space, the frame + being some basis of the vector space:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: basis = V.basis() ; basis + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c = Components(QQ, basis, 2) ; c + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + + Actually, the frame can be any object that has some length, i.e. on which + the function :func:`len()` can be called:: + + sage: basis1 = V.gens() ; basis1 + ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sage: c1 = Components(QQ, basis1, 2) ; c1 + 2-indices components w.r.t. ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sage: basis2 = ['a', 'b' , 'c'] + sage: c2 = Components(QQ, basis2, 2) ; c2 + 2-indices components w.r.t. ['a', 'b', 'c'] + + A just created set of components is initialized to zero:: + + sage: c.is_zero() + True + sage: c == 0 + True + + This can also be checked on the list of components, which is returned by + the operator ``[:]``:: + + sage: c[:] + [0 0 0] + [0 0 0] + [0 0 0] + + Individual components are accessed by providing their indices inside + square brackets:: + + sage: c[1,2] = -3 + sage: c[:] + [ 0 0 0] + [ 0 0 -3] + [ 0 0 0] + sage: v = Components(QQ, basis, 1) + sage: v[:] + [0, 0, 0] + sage: v[0] + 0 + sage: v[:] = (-1,3,2) + sage: v[:] + [-1, 3, 2] + sage: v[0] + -1 + + By default, the indices range from `0` to `n-1`, where `n` is the length + of the frame. This can be changed via the argument ``start_index`` in + the :class:`Components` constructor:: + + sage: v1 = Components(QQ, basis, 1, start_index=1) + sage: v1[:] + [0, 0, 0] + sage: v1[0] + Traceback (most recent call last): + ... + IndexError: Index out of range: 0 not in [1,3] + sage: v1[1] + 0 + sage: v1[:] = v[:] # list copy of all components + sage: v1[:] + [-1, 3, 2] + sage: v1[1], v1[2], v1[3] + (-1, 3, 2) + sage: v[0], v[1], v[2] + (-1, 3, 2) + + If some formatter function or unbound method is provided via the argument + ``output_formatter`` in the :class:`Components` constructor, it is used to + change the ouput of the access operator ``[...]``:: + + sage: a = Components(QQ, basis, 2, output_formatter=Rational.numerical_approx) + sage: a[1,2] = 1/3 + sage: a[1,2] + 0.333333333333333 + + The format can be passed to the formatter as the last argument of the + access operator ``[...]``:: + + sage: a[1,2,10] # here the format is 10, for 10 bits of precision + 0.33 + sage: a[1,2,100] + 0.33333333333333333333333333333 + + The raw (unformatted) components are then accessed by the double bracket + operator:: + + sage: a[[1,2]] + 1/3 + + For sets of components declared without any output formatter, there is no + difference between ``[...]`` and ``[[...]]``:: + + sage: c[1,2] = 1/3 + sage: c[1,2], c[[1,2]] + (1/3, 1/3) + + The formatter is also used for the complete list of components:: + + sage: a[:] + [0.000000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 0.333333333333333] + [0.000000000000000 0.000000000000000 0.000000000000000] + sage: a[:,10] # with a format different from the default one (53 bits) + [0.00 0.00 0.00] + [0.00 0.00 0.33] + [0.00 0.00 0.00] + + The complete list of components in raw form can be recovered by the double + bracket operator, replacing ``:`` by ``slice(None)`` (since ``a[[:]]`` + generates a Python syntax error):: + + sage: a[[slice(None)]] + [ 0 0 0] + [ 0 0 1/3] + [ 0 0 0] + + Another example of formatter: the Python built-in function :func:`str` + to generate string outputs:: + + sage: b = Components(QQ, V.basis(), 1, output_formatter=str) + sage: b[:] = (1, 0, -4) + sage: b[:] + ['1', '0', '-4'] + + For such a formatter, 2-indices components are no longer displayed as a + matrix:: + + sage: b = Components(QQ, basis, 2, output_formatter=str) + sage: b[0,1] = 1/3 + sage: b[:] + [['0', '1/3', '0'], ['0', '0', '0'], ['0', '0', '0']] + + But unformatted outputs still are:: + + sage: b[[slice(None)]] + [ 0 1/3 0] + [ 0 0 0] + [ 0 0 0] + + Internally, the components are stored as a dictionary (:attr:`_comp`) whose + keys are the indices; only the non-zero components are stored:: + + sage: a[:] + [0.000000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 0.333333333333333] + [0.000000000000000 0.000000000000000 0.000000000000000] + sage: a._comp + {(1, 2): 1/3} + sage: v[:] = (-1, 0, 3) + sage: v._comp # random output order of the component dictionary + {(0,): -1, (2,): 3} + + In case of symmetries, only non-redundant components are stored:: + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: c = CompFullyAntiSym(QQ, basis, 2) + sage: c[0,1] = 3 + sage: c[:] + [ 0 3 0] + [-3 0 0] + [ 0 0 0] + sage: c._comp + {(0, 1): 3} + +""" + +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.rings.integer import Integer + +class Components(SageObject): + r""" + Class for storing components with respect to a given "frame". + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). + The stored quantities can be tensor components or non-tensorial quantities, + such as connection coefficients or structure coefficents. The symmetries + over some indices are dealt by subclasses of the class :class:`Components`. + + INPUT: + + - ``ring`` -- ring in which each component takes its value + - ``frame`` -- frame with respect to which the components are defined; + whatever type ``frame`` is, it should have some method ``__len__()`` + implemented, so that ``len(frame)`` returns the dimension, i.e. the size + of a single index range + - ``nb_indices`` -- number of indices labeling the components + - ``start_index`` -- (default: 0) first value of a single index; + accordingly a component index i must obey + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access + operator ``[...]`` (method __getitem__); ``output_formatter`` must take + 1 or 2 arguments: the 1st argument must be an element of ``ring`` and + the second one, if any, some format specification. + + EXAMPLES: + + Set of components with 2 indices on a 3-dimensional vector space, the frame + being some basis of the vector space:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: basis = V.basis() ; basis + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c = Components(QQ, basis, 2) ; c + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + + Actually, the frame can be any object that has some length, i.e. on which + the function :func:`len()` can be called:: + + sage: basis1 = V.gens() ; basis1 + ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sage: c1 = Components(QQ, basis1, 2) ; c1 + 2-indices components w.r.t. ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sage: basis2 = ['a', 'b' , 'c'] + sage: c2 = Components(QQ, basis2, 2) ; c2 + 2-indices components w.r.t. ['a', 'b', 'c'] + + By default, the indices range from `0` to `n-1`, where `n` is the length + of the frame. This can be changed via the argument ``start_index``:: + + sage: c1 = Components(QQ, basis, 2, start_index=1) + sage: c1[0,1] + Traceback (most recent call last): + ... + IndexError: Index out of range: 0 not in [1,3] + sage: c[0,1] # for c, the index 0 is OK + 0 + sage: c[0,1] = -3 + sage: c1[:] = c[:] # list copy of all components + sage: c1[1,2] # (1,2) = (0,1) shifted by 1 + -3 + + If some formatter function or unbound method is provided via the argument + ``output_formatter``, it is used to change the ouput of the access + operator ``[...]``:: + + sage: a = Components(QQ, basis, 2, output_formatter=Rational.numerical_approx) + sage: a[1,2] = 1/3 + sage: a[1,2] + 0.333333333333333 + + The format can be passed to the formatter as the last argument of the + access operator ``[...]``:: + + sage: a[1,2,10] # here the format is 10, for 10 bits of precision + 0.33 + sage: a[1,2,100] + 0.33333333333333333333333333333 + + The raw (unformatted) components are then accessed by the double bracket + operator:: + + sage: a[[1,2]] + 1/3 + + For sets of components declared without any output formatter, there is no + difference between ``[...]`` and ``[[...]]``:: + + sage: c[1,2] = 1/3 + sage: c[1,2], c[[1,2]] + (1/3, 1/3) + + The formatter is also used for the complete list of components:: + + sage: a[:] + [0.000000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 0.333333333333333] + [0.000000000000000 0.000000000000000 0.000000000000000] + sage: a[:,10] # with a format different from the default one (53 bits) + [0.00 0.00 0.00] + [0.00 0.00 0.33] + [0.00 0.00 0.00] + + The complete list of components in raw form can be recovered by the double + bracket operator, replacing ``:`` by ``slice(None)`` (since ``a[[:]]`` + generates a Python syntax error):: + + sage: a[[slice(None)]] + [ 0 0 0] + [ 0 0 1/3] + [ 0 0 0] + + Another example of formatter: the Python built-in function :func:`str` + to generate string outputs:: + + sage: b = Components(QQ, V.basis(), 1, output_formatter=str) + sage: b[:] = (1, 0, -4) + sage: b[:] + ['1', '0', '-4'] + + For such a formatter, 2-indices components are no longer displayed as a + matrix:: + + sage: b = Components(QQ, basis, 2, output_formatter=str) + sage: b[0,1] = 1/3 + sage: b[:] + [['0', '1/3', '0'], ['0', '0', '0'], ['0', '0', '0']] + + But unformatted outputs still are:: + + sage: b[[slice(None)]] + [ 0 1/3 0] + [ 0 0 0] + [ 0 0 0] + + Internally, the components are stored as a dictionary (:attr:`_comp`) whose + keys are the indices; only the non-zero components are stored:: + + sage: a[:] + [0.000000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 0.333333333333333] + [0.000000000000000 0.000000000000000 0.000000000000000] + sage: a._comp + {(1, 2): 1/3} + sage: v = Components(QQ, basis, 1) + sage: v[:] = (-1, 0, 3) + sage: v._comp # random output order of the component dictionary + {(0,): -1, (2,): 3} + + + ARITHMETIC EXAMPLES: + + Unary plus operator:: + + sage: a = Components(QQ, basis, 1) + sage: a[:] = (-1, 0, 3) + sage: s = +a ; s[:] + [-1, 0, 3] + sage: +a == a + True + + Unary minus operator:: + + sage: s = -a ; s[:] + [1, 0, -3] + + Addition:: + + sage: b = Components(QQ, basis, 1) + sage: b[:] = (2, 1, 4) + sage: s = a + b ; s[:] + [1, 1, 7] + sage: a + b == b + a + True + sage: a + (-a) == 0 + True + + Subtraction:: + + sage: s = a - b ; s[:] + [-3, -1, -1] + sage: s + b == a + True + sage: a - b == - (b - a) + True + + Multiplication by a scalar:: + + sage: s = 2*a ; s[:] + [-2, 0, 6] + + Division by a scalar:: + + sage: s = a/2 ; s[:] + [-1/2, 0, 3/2] + sage: 2*(a/2) == a + True + + Tensor product (by means of the operator ``*``):: + + sage: c = a*b ; c + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: a[:], b[:] + ([-1, 0, 3], [2, 1, 4]) + sage: c[:] + [-2 -1 -4] + [ 0 0 0] + [ 6 3 12] + sage: d = c*a ; d + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: d[:] + [[[2, 0, -6], [1, 0, -3], [4, 0, -12]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[-6, 0, 18], [-3, 0, 9], [-12, 0, 36]]] + sage: d[0,1,2] == a[0]*b[1]*a[2] + True + + """ + def __init__(self, ring, frame, nb_indices, start_index=0, + output_formatter=None): + # For efficiency, no test is performed regarding the type and range of + # the arguments: + self.ring = ring + self.frame = frame + self.nid = nb_indices + self.dim = len(frame) + self.sindex = start_index + self.output_formatter = output_formatter + self._comp = {} # the dictionary of components, with the indices as keys + + def _repr_(self): + r""" + String representation of the object. + """ + description = str(self.nid) + if self.nid == 1: + description += "-index" + else: + description += "-indices" + description += " components w.r.t. " + str(self.frame) + return description + + def _new_instance(self): + r""" + Creates a :class:`Components` instance of the same number of indices + and w.r.t. the same frame. + + This method must be redefined by derived classes of + :class:`Components`. + + """ + return Components(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter) + + def copy(self): + r""" + Returns an exact copy of ``self``. + + EXAMPLES: + + Copy of a set of components with a single index:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: a = Components(QQ, V.basis(), 1) + sage: a[:] = -2, 1, 5 + sage: b = a.copy() ; b + 1-index components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: b[:] + [-2, 1, 5] + sage: b == a + True + sage: b is a # b is a distinct object + False + + """ + result = self._new_instance() + for ind, val in self._comp.items(): + if hasattr(val, 'copy'): + result._comp[ind] = val.copy() + else: + result._comp[ind] = val + return result + + def _del_zeros(self): + r""" + Deletes all the zeros in the dictionary :attr:`_comp` + + """ + # The zeros are first searched; they are deleted in a second stage, to + # avoid changing the dictionary while it is read + zeros = [] + for ind, value in self._comp.items(): + if value == 0: + zeros.append(ind) + for ind in zeros: + del self._comp[ind] + + def _check_indices(self, indices): + r""" + Check the validity of a list of indices and returns a tuple from it + + INPUT: + + - ``indices`` -- list of indices (possibly a single integer if + self is a 1-index object) + + OUTPUT: + + - a tuple containing valid indices + + """ + if isinstance(indices, (int, Integer)): + ind = (indices,) + else: + ind = tuple(indices) + if len(ind) != self.nid: + raise TypeError("Wrong number of indices: " + str(self.nid) + + " expected, while " + str(len(ind)) + + " are provided.") + si = self.sindex + imax = self.dim - 1 + si + for k in range(self.nid): + i = ind[k] + if i < si or i > imax: + raise IndexError("Index out of range: " + + str(i) + " not in [" + str(si) + "," + + str(imax) + "]") + return ind + + def __getitem__(self, args): + r""" + Returns the component corresponding to the given indices. + + INPUT: + + - ``args`` -- list of indices (possibly a single integer if + self is a 1-index object) or the character ``:`` for the full list + of components. + + OUTPUT: + + - the component corresponding to ``args`` or, if ``args`` = ``:``, + the full list of components, in the form ``T[i][j]...`` for the components + `T_{ij...}` (for a 2-indices object, a matrix is returned). + + """ + no_format = self.output_formatter is None + format_type = None # default value, possibly redefined below + if isinstance(args, list): # case of [[...]] syntax + no_format = True + if isinstance(args[0], slice): + indices = args[0] + elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] + else: + indices = tuple(args) + else: + # Determining from the input the list of indices and the format + if isinstance(args, (int, Integer)) or isinstance(args, slice): + indices = args + elif isinstance(args[0], slice): + indices = args[0] + format_type = args[1] + elif len(args) == self.nid: + indices = args + else: + format_type = args[-1] + indices = args[:-1] + if isinstance(indices, slice): + return self._get_list(indices, no_format, format_type) + else: + ind = self._check_indices(indices) + if ind in self._comp: + if no_format: + return self._comp[ind] + elif format_type is None: + return self.output_formatter(self._comp[ind]) + else: + return self.output_formatter(self._comp[ind], format_type) + else: # if the value is not stored in self._comp, it is zero: + if no_format: + return self.ring.zero_element() + elif format_type is None: + return self.output_formatter(self.ring.zero_element()) + else: + return self.output_formatter(self.ring.zero_element(), + format_type) + + def _get_list(self, ind_slice, no_format=True, format_type=None): + r""" + Return the list of components. + + INPUT: + + - ``ind_slice`` -- a slice object + + OUTPUT: + + - the full list of components if ``ind_slice`` == ``[:]``, or a slice + of it if ``ind_slice`` == ``[a:b]`` (1-D case), in the form + ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices + object, a matrix is returned). + + """ + from sage.matrix.constructor import matrix + si = self.sindex + nsi = si + self.dim + if self.nid == 1: + if ind_slice.start is None: + start = si + else: + start = ind_slice.start + if ind_slice.stop is None: + stop = nsi + else: + stop = ind_slice.stop + if ind_slice.step is not None: + raise NotImplementedError("Function [start:stop:step] " + + "not implemented.") + if no_format: + return [self[[i]] for i in range(start, stop)] + else: + return [self[i, format_type] for i in range(start, stop)] + if ind_slice.start is not None or ind_slice.stop is not None: + raise NotImplementedError("Function [start:stop] not " + + "implemented for components with " + str(self.nid) + + " indices.") + resu = [self._gen_list([i], no_format, format_type) + for i in range(si, nsi)] + if self.nid == 2: + try: + resu = matrix(resu) # for a nicer output + except TypeError: + pass + return resu + + def _gen_list(self, ind, no_format=True, format_type=None): + r""" + Recursive function to generate the list of values + """ + if len(ind) == self.nid: + if no_format: + return self[ind] + else: + args = tuple(ind + [format_type]) + return self.__getitem__(args) + else: + si = self.sindex + nsi = si + self.dim + return [self._gen_list(ind + [i], no_format, format_type) + for i in range(si, nsi)] + + def __setitem__(self, indices, value): + r""" + Sets the component corresponding to the given indices. + + INPUT: + + - ``indices`` -- list of indices (possibly a single integer if + self is a 1-index object) ; if [:] is provided, all the components + are set. + - ``value`` -- the value to be set or a list of values if ``args`` + == ``[:]`` + + """ + if isinstance(indices, slice): + self._set_list(indices, value) + else: + if isinstance(indices, list): + # to ensure equivalence between [i,j,...] and [[i,j,...]] or + # [[(i,j,...)]] + if isinstance(indices[0], tuple) or isinstance(indices[0], list): + indices = indices[0] + else: + indices = tuple(indices) + ind = self._check_indices(indices) + if value == 0: + # if the component has been set previously, it is deleted, + # otherwise nothing is done: + if ind in self._comp: + del self._comp[ind] + else: + self._comp[ind] = self.ring(value) + + def _set_list(self, ind_slice, values): + r""" + Set the components from a list. + + INPUT: + + - ``ind_slice`` -- a slice object + - ``values`` -- list of values for the components : the full list if + ``ind_slice`` == ``[:]``, in the form ``T[i][j]...`` for the + component `T_{ij...}`. In the 1-D case, ``ind_slice`` can be + a slice of the full list, in the form ``[a:b]`` + + """ + si = self.sindex + nsi = si + self.dim + if self.nid == 1: + if ind_slice.start is None: + start = si + else: + start = ind_slice.start + if ind_slice.stop is None: + stop = nsi + else: + stop = ind_slice.stop + if ind_slice.step is not None: + raise NotImplementedError("Function [start:stop:step] " + + "not implemented.") + for i in range(start, stop): + self[i] = values[i-start] + else: + if ind_slice.start is not None or ind_slice.stop is not None: + raise NotImplementedError("Function [start:stop] not " + + "implemented for components with " + str(self.nid) + + " indices.") + for i in range(si, nsi): + self._set_value_list([i], values[i-si]) + + def _set_value_list(self, ind, val): + r""" + Recursive function to set a list of values to self + """ + if len(ind) == self.nid: + self[ind] = val + else: + si = self.sindex + nsi = si + self.dim + for i in range(si, nsi): + self._set_value_list(ind + [i], val[i-si]) + + def swap_adjacent_indices(self, pos1, pos2, pos3): + r""" + Swap two adjacent sets of indices. + + This method is essentially required to reorder the covariant and + contravariant indices in the computation of a tensor product. + + INPUT: + + - ``pos1`` -- position of the first index of set 1 (with the convention + position=0 for the first slot) + - ``pos2`` -- position of the first index of set 2 = 1 + position of + the last index of set 1 (since the two sets are adjacent) + - ``pos3`` -- 1 + position of the last index of set 2 + + OUTPUT: + + - Components with index set 1 permuted with index set 2. + + EXAMPLES: + + Swap of the two indices of a 2-indices set of components:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ, 3) + sage: c = Components(QQ, V.basis(), 2) + sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: c1 = c.swap_adjacent_indices(0,1,2) + sage: c[:], c1[:] + ( + [1 2 3] [1 4 7] + [4 5 6] [2 5 8] + [7 8 9], [3 6 9] + ) + + Swap of two pairs of indices on a 4-indices set of components:: + + sage: d = c*c1 ; d + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: d1 = d.swap_adjacent_indices(0,2,4) + sage: d[0,1,1,2] + 16 + sage: d1[1,2,0,1] + 16 + sage: d1[0,1,1,2] + 24 + sage: d[1,2,0,1] + 24 + + """ + result = self._new_instance() + for ind, val in self._comp.items(): + new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] + result._comp[new_ind] = val + # the above writing is more efficient than result[new_ind] = val + # it does not work for the derived class CompWithSym, but for the + # latter, the function CompWithSym.swap_adjacent_indices will be + # called and not the present function. + return result + + def is_zero(self): + r""" + Return True if all the components are zero and False otherwise. + + EXAMPLES: + + A just-created set of components is initialized to zero:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: c = Components(QQ, V.basis(), 1) + sage: c.is_zero() + True + sage: c[:] + [0, 0, 0] + sage: c[0] = 1 ; c[:] + [1, 0, 0] + sage: c.is_zero() + False + sage: c[0] = 0 ; c[:] + [0, 0, 0] + sage: c.is_zero() + True + + It is equivalent to use the operator == to compare to zero:: + + sage: c == 0 + True + sage: c != 0 + False + + Comparing to a nonzero number is meaningless:: + + sage: c == 1 + Traceback (most recent call last): + ... + TypeError: Cannot compare a set of components to a number. + + """ + if self._comp == {}: + return True + else: + #!# What follows could be skipped since _comp should not contain + # any zero value + # In other words, the full method should be + # return self.comp == {} + for val in self._comp.values(): + if val != 0: + return False + return True + + def __eq__(self, other): + r""" + Comparison (equality) operator. + + INPUT: + + - ``other`` -- a set of components or 0 + + OUTPUT: + + - True if ``self`` is equal to ``other``, or False otherwise + + """ + if isinstance(other, (int, Integer)): # other is 0 + if other == 0: + return self.is_zero() + else: + raise TypeError("Cannot compare a set of components to a " + + "number.") + else: # other is another Components + if not isinstance(other, Components): + raise TypeError("An instance of Components is expected.") + if other.frame != self.frame: + return False + if other.nid != self.nid: + return False + if other.sindex != self.sindex: + return False + if other.output_formatter != self.output_formatter: + return False + return (self - other).is_zero() + + def __ne__(self, other): + r""" + Inequality operator. + + INPUT: + + - ``other`` -- a set of components or 0 + + OUTPUT: + + - True if ``self`` is different from ``other``, or False otherwise + + """ + return not self.__eq__(other) + + def __pos__(self): + r""" + Unary plus operator. + + OUTPUT: + + - an exact copy of ``self`` + + """ + return self.copy() + + def __neg__(self): + r""" + Unary minus operator. + + OUTPUT: + + - the opposite of the components represented by ``self`` + + """ + result = self._new_instance() + for ind, val in self._comp.items(): + result._comp[ind] = - val + return result + + def __add__(self, other): + r""" + Component addition. + + INPUT: + + - ``other`` -- components of the same number of indices and defined + on the same frame as ``self`` + + OUTPUT: + + - components resulting from the addition of ``self`` and ``other`` + + """ + if other == 0: + return +self + if not isinstance(other, Components): + raise TypeError("The second argument for the addition must be " + + "an instance of Components.") + if isinstance(other, CompWithSym): + return other + self # to deal properly with symmetries + if other.frame != self.frame: + raise TypeError("The two sets of components are not defined on " + + "the same frame.") + if other.nid != self.nid: + raise TypeError("The two sets of components do not have the " + + "same number of indices.") + if other.sindex != self.sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + result = self.copy() + for ind, val in other._comp.items(): + result[[ind]] += val + return result + + def __radd__(self, other): + r""" + Addition on the left with ``other``. + + """ + return self.__add__(other) + + + def __sub__(self, other): + r""" + Component subtraction. + + INPUT: + + - ``other`` -- components, of the same type as ``self`` + + OUTPUT: + + - components resulting from the subtraction of ``other`` from ``self`` + + """ + if other == 0: + return +self + return self.__add__(-other) #!# correct, deals properly with + # symmetries, but is probably not optimal + + def __rsub__(self, other): + r""" + Subtraction from ``other``. + + """ + return (-self).__add__(other) + + + def __mul__(self, other): + r""" + Component tensor product. + + INPUT: + + - ``other`` -- components, on the same frame as ``self`` + + OUTPUT: + + - the tensor product of ``self`` by ``other`` + + """ + if not isinstance(other, Components): + raise TypeError("The second argument for the tensor product " + + "must be an instance of Components.") + if other.frame != self.frame: + raise TypeError("The two sets of components are not defined on " + + "the same frame.") + if other.sindex != self.sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + if isinstance(other, CompWithSym): + sym = [] + if other.sym != []: + for s in other.sym: + ns = tuple(s[i]+self.nid for i in range(len(s))) + sym.append(ns) + antisym = [] + if other.antisym != []: + for s in other.antisym: + ns = tuple(s[i]+self.nid for i in range(len(s))) + antisym.append(ns) + result = CompWithSym(self.ring, self.frame, self.nid + other.nid, + self.sindex, self.output_formatter, sym, + antisym) + elif self.nid == 1 and other.nid == 1: + if self is other: # == would be dangerous here + # the result is symmetric: + result = CompFullySym(self.ring, self.frame, 2, self.sindex, + self.output_formatter) + else: + result = Components(self.ring, self.frame, 2, self.sindex, + self.output_formatter) + else: + result = Components(self.ring, self.frame, self.nid + other.nid, + self.sindex, self.output_formatter) + for ind_s, val_s in self._comp.items(): + for ind_o, val_o in other._comp.items(): + result._comp[ind_s + ind_o] = val_s * val_o + return result + + + def __rmul__(self, other): + r""" + Multiplication on the left by ``other``. + + """ + if isinstance(other, Components): + raise NotImplementedError("Left tensor product not implemented.") + # Left multiplication by a "scalar": + result = self._new_instance() + if other == 0: + return result # because a just created Components is zero + for ind, val in self._comp.items(): + result._comp[ind] = other * val + return result + + + def __div__(self, other): + r""" + Division (by a scalar). + + """ + if isinstance(other, Components): + raise NotImplementedError("Division by an object of type " + + "Components not implemented.") + result = self._new_instance() + for ind, val in self._comp.items(): + result._comp[ind] = val / other + return result + + def self_contract(self, pos1, pos2): + r""" + Index contraction. + + INPUT: + + - ``pos1`` -- position of the first index for the contraction (with the + convention position=0 for the first slot) + - ``pos2`` -- position of the second index for the contraction + + OUTPUT: + + - set of components resulting from the (pos1, pos2) contraction + + EXAMPLES: + + Self-contraction of a set of components with 2 indices:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ, 3) + sage: c = Components(QQ, V.basis(), 2) + sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: c.self_contract(0,1) + 15 + sage: c[0,0] + c[1,1] + c[2,2] # check + 15 + + Three self-contractions of a set of components with 3 indices:: + + sage: v = Components(QQ, V.basis(), 1) + sage: v[:] = (-1,2,3) + sage: a = c*v ; a + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s = a.self_contract(0,1) ; s # contraction on the first two indices + 1-index components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] + [-15, 30, 45] + sage: [sum(a[j,j,i] for j in range(3)) for i in range(3)] # check + [-15, 30, 45] + sage: s = a.self_contract(0,2) ; s[:] # contraction on the first and last indices + [28, 32, 36] + sage: [sum(a[j,i,j] for j in range(3)) for i in range(3)] # check + [28, 32, 36] + sage: s = a.self_contract(1,2) ; s[:] # contraction on the last two indices + [12, 24, 36] + sage: [sum(a[i,j,j] for j in range(3)) for i in range(3)] # check + [12, 24, 36] + + """ + if self.nid < 2: + raise TypeError("Contraction can be perfomed only on " + + "components with at least 2 indices.") + if pos1 < 0 or pos1 > self.nid - 1: + raise IndexError("pos1 out of range.") + if pos2 < 0 or pos2 > self.nid - 1: + raise IndexError("pos2 out of range.") + if pos1 == pos2: + raise IndexError("The two positions must differ for the " + + "contraction to be meaningful.") + si = self.sindex + nsi = si + self.dim + if self.nid == 2: + res = 0 + for i in range(si, nsi): + res += self[[i,i]] + return res + else: + # More than 2 indices + result = Components(self.ring, self.frame, self.nid - 2, + self.sindex, self.output_formatter) + if pos1 > pos2: + pos1, pos2 = (pos2, pos1) + for ind, val in self._comp.items(): + if ind[pos1] == ind[pos2]: + # there is a contribution to the contraction + ind_res = ind[:pos1] + ind[pos1+1:pos2] + ind[pos2+1:] + result[[ind_res]] += val + return result + + + def contract(self, pos1, other, pos2): + r""" + Index contraction with another instance of :class:`Components`. + + INPUT: + + - ``pos1`` -- position of the first index (in ``self``) for the + contraction (with the convention position=0 for the first slot) + - ``other`` -- the set of components to contract with + - ``pos2`` -- position of the second index (in ``other``) for the + contraction (with the convention position=0 for the first slot) + + OUTPUT: + + - set of components resulting from the (pos1, pos2) contraction + + EXAMPLES: + + Contraction of a 1-index set of components with a 2-index one:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ, 3) + sage: a = Components(QQ, V.basis(), 1) + sage: a[:] = (-1, 2, 3) + sage: b = Components(QQ, V.basis(), 2) + sage: b[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: s = a.contract(0, b, 0) ; s + 1-index components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] + [28, 32, 36] + sage: [sum(a[j]*b[j,i] for j in range(3)) for i in range(3)] # check + [28, 32, 36] + sage: s = a.contract(0, b, 1) ; s[:] + [12, 24, 36] + sage: [sum(a[j]*b[i,j] for j in range(3)) for i in range(3)] # check + [12, 24, 36] + + Consistency check with :meth:`self_contract`:: + + sage: a[:] = (1,2,-3) + sage: b = a*a ; b # the tensor product of a with itself + fully symmetric 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: b[:] + [ 1 2 -3] + [ 2 4 -6] + [-3 -6 9] + sage: b.self_contract(0,1) + 14 + sage: a.contract(0, a, 0) == b.self_contract(0,1) + True + + """ + if not isinstance(other, Components): + raise TypeError("For the contraction, other must be an instance " + + "of Components.") + if pos1 < 0 or pos1 > self.nid - 1: + raise IndexError("pos1 out of range.") + if pos2 < 0 or pos2 > other.nid - 1: + raise IndexError("pos2 out of range.") + return (self*other).self_contract(pos1, + pos2+self.nid) #!# correct but not optimal + + def index_generator(self): + r""" + Generator of indices. + + OUTPUT: + + - an iterable index + + EXAMPLES: + + Indices on a 3-dimensional vector space:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: c = Components(QQ, V.basis(), 1) + sage: for ind in c.index_generator(): print ind, + (0,) (1,) (2,) + sage: c = Components(QQ, V.basis(), 1, start_index=1) + sage: for ind in c.index_generator(): print ind, + (1,) (2,) (3,) + sage: c = Components(QQ, V.basis(), 2) + sage: for ind in c.index_generator(): print ind, + (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2) (2, 0) (2, 1) (2, 2) + + """ + si = self.sindex + imax = self.dim - 1 + si + ind = [si for k in range(self.nid)] + ind_end = [si for k in range(self.nid)] + ind_end[0] = imax+1 + while ind != ind_end: + yield tuple(ind) + ret = 1 + for pos in range(self.nid-1,-1,-1): + if ind[pos] != imax: + ind[pos] += ret + ret = 0 + elif ret == 1: + if pos == 0: + ind[pos] = imax + 1 # end point reached + else: + ind[pos] = si + ret = 1 + + def non_redundant_index_generator(self): + r""" + Generator of non redundant indices. + + In the absence of declared symmetries, all possible indices are + generated. So this method is equivalent to :meth:`index_generator`. + Only versions for derived classes with symmetries or antisymmetries + are not trivial. + + OUTPUT: + + - an iterable index + + EXAMPLES: + + Indices on a 3-dimensional vector space:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: c = Components(QQ, V.basis(), 2) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2) (2, 0) (2, 1) (2, 2) + sage: c = Components(QQ, V.basis(), 2, start_index=1) + sage: for ind in c.non_redundant_index_generator(): print ind, + (1, 1) (1, 2) (1, 3) (2, 1) (2, 2) (2, 3) (3, 1) (3, 2) (3, 3) + + """ + for ind in self.index_generator(): + yield ind + + + def symmetrize(self, pos=None): + r""" + Symmetrization over the given index positions + + INPUT: + + - ``pos`` -- (default: None) tuple of index positions involved in the + symmetrization (with the convention position=0 for the first slot); + if none, the symmetrization is performed over all the indices + + OUTPUT: + + - an instance of :class:`CompWithSym` describing the symmetrized + components. + + EXAMPLES: + + Symmetrization of 2-indices components:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ, 3) + sage: c = Components(QQ, V.basis(), 2) + sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: s = c.symmetrize() ; s + fully symmetric 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c[:], s[:] + ( + [1 2 3] [1 3 5] + [4 5 6] [3 5 7] + [7 8 9], [5 7 9] + ) + sage: c.symmetrize() == c.symmetrize((0,1)) + True + + Full symmetrization of 3-indices components:: + + sage: c = Components(QQ, V.basis(), 3) + sage: c[:] = [[[1,2,3], [4,5,6], [7,8,9]], [[10,11,12], [13,14,15], [16,17,18]], [[19,20,21], [22,23,24], [25,26,27]]] + sage: s = c.symmetrize() ; s + fully symmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c[:], s[:] + ([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], + [[10, 11, 12], [13, 14, 15], [16, 17, 18]], + [[19, 20, 21], [22, 23, 24], [25, 26, 27]]], + [[[1, 16/3, 29/3], [16/3, 29/3, 14], [29/3, 14, 55/3]], + [[16/3, 29/3, 14], [29/3, 14, 55/3], [14, 55/3, 68/3]], + [[29/3, 14, 55/3], [14, 55/3, 68/3], [55/3, 68/3, 27]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]+c[i,k,j]+c[j,k,i]+c[j,i,k]+c[k,i,j]+c[k,j,i])/6, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: c.symmetrize() == c.symmetrize((0,1,2)) + True + + Partial symmetrization of 3-indices components:: + + sage: s = c.symmetrize((0,1)) ; s # symmetrization on the first two indices + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: c[:], s[:] + ([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], + [[10, 11, 12], [13, 14, 15], [16, 17, 18]], + [[19, 20, 21], [22, 23, 24], [25, 26, 27]]], + [[[1, 2, 3], [7, 8, 9], [13, 14, 15]], + [[7, 8, 9], [13, 14, 15], [19, 20, 21]], + [[13, 14, 15], [19, 20, 21], [25, 26, 27]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]+c[j,i,k])/2, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s = c.symmetrize([1,2]) ; s # symmetrization on the last two indices + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (1, 2) + sage: c[:], s[:] + ([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], + [[10, 11, 12], [13, 14, 15], [16, 17, 18]], + [[19, 20, 21], [22, 23, 24], [25, 26, 27]]], + [[[1, 3, 5], [3, 5, 7], [5, 7, 9]], + [[10, 12, 14], [12, 14, 16], [14, 16, 18]], + [[19, 21, 23], [21, 23, 25], [23, 25, 27]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]+c[i,k,j])/2, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s = c.symmetrize((0,2)) ; s # symmetrization on the first and last indices + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 2) + sage: c[:], s[:] + ([[[1, 2, 3], [4, 5, 6], [7, 8, 9]], + [[10, 11, 12], [13, 14, 15], [16, 17, 18]], + [[19, 20, 21], [22, 23, 24], [25, 26, 27]]], + [[[1, 6, 11], [4, 9, 14], [7, 12, 17]], + [[6, 11, 16], [9, 14, 19], [12, 17, 22]], + [[11, 16, 21], [14, 19, 24], [17, 22, 27]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]+c[k,j,i])/2, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + + """ + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + if pos is None: + pos = range(self.nid) + else: + if len(pos) < 2: + raise TypeError("At least two index positions must be given.") + if len(pos) > self.nid: + raise TypeError("Number of index positions larger than the " \ + "total number of indices.") + n_sym = len(pos) # number of indices involved in the symmetry + if n_sym == self.nid: + result = CompFullySym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter) + else: + result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter, sym=pos) + sym_group = SymmetricGroup(n_sym) + for ind in result.non_redundant_index_generator(): + sum = 0 + for perm in sym_group.list(): + # action of the permutation on [0,1,...,n_sym-1]: + perm_action = map(lambda x: x-1, perm.domain()) + ind_perm = list(ind) + for k in range(n_sym): + ind_perm[pos[perm_action[k]]] = ind[pos[k]] + sum += self[[ind_perm]] + result[[ind]] = sum / sym_group.order() + return result + + + def antisymmetrize(self, pos=None): + r""" + Antisymmetrization over the given index positions + + INPUT: + + - ``pos`` -- (default: None) tuple of index positions involved in the + antisymmetrization (with the convention position=0 for the first + slot); if none, the antisymmetrization is performed over all the + indices + + OUTPUT: + + - an instance of :class:`CompWithSym` describing the antisymmetrized + components. + + EXAMPLES: + + Antisymmetrization of 2-indices components:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ, 3) + sage: c = Components(QQ, V.basis(), 2) + sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: s = c.antisymmetrize() ; s + fully antisymmetric 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c[:], s[:] + ( + [1 2 3] [ 0 -1 -2] + [4 5 6] [ 1 0 -1] + [7 8 9], [ 2 1 0] + ) + sage: c.antisymmetrize() == c.antisymmetrize((0,1)) + True + + Full antisymmetrization of 3-indices components:: + + sage: c = Components(QQ, V.basis(), 3) + sage: c[:] = [[[-1,-2,3], [4,-5,4], [-7,8,9]], [[10,10,12], [13,-14,15], [-16,17,19]], [[-19,20,21], [1,2,3], [-25,26,27]]] + sage: s = c.antisymmetrize() ; s + fully antisymmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c[:], s[:] + ([[[-1, -2, 3], [4, -5, 4], [-7, 8, 9]], + [[10, 10, 12], [13, -14, 15], [-16, 17, 19]], + [[-19, 20, 21], [1, 2, 3], [-25, 26, 27]]], + [[[0, 0, 0], [0, 0, -13/6], [0, 13/6, 0]], + [[0, 0, 13/6], [0, 0, 0], [-13/6, 0, 0]], + [[0, -13/6, 0], [13/6, 0, 0], [0, 0, 0]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6, + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: c.symmetrize() == c.symmetrize((0,1,2)) + True + + Partial antisymmetrization of 3-indices components:: + + sage: s = c.antisymmetrize((0,1)) ; s # antisymmetrization on the first two indices + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: c[:], s[:] + ([[[-1, -2, 3], [4, -5, 4], [-7, 8, 9]], + [[10, 10, 12], [13, -14, 15], [-16, 17, 19]], + [[-19, 20, 21], [1, 2, 3], [-25, 26, 27]]], + [[[0, 0, 0], [-3, -15/2, -4], [6, -6, -6]], + [[3, 15/2, 4], [0, 0, 0], [-17/2, 15/2, 8]], + [[-6, 6, 6], [17/2, -15/2, -8], [0, 0, 0]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]-c[j,i,k])/2, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s = c.antisymmetrize((1,2)) ; s # antisymmetrization on the last two indices + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (1, 2) + sage: c[:], s[:] + ([[[-1, -2, 3], [4, -5, 4], [-7, 8, 9]], + [[10, 10, 12], [13, -14, 15], [-16, 17, 19]], + [[-19, 20, 21], [1, 2, 3], [-25, 26, 27]]], + [[[0, -3, 5], [3, 0, -2], [-5, 2, 0]], + [[0, -3/2, 14], [3/2, 0, -1], [-14, 1, 0]], + [[0, 19/2, 23], [-19/2, 0, -23/2], [-23, 23/2, 0]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j])/2, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s = c.antisymmetrize((0,2)) ; s # antisymmetrization on the first and last indices + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 2) + sage: c[:], s[:] + ([[[-1, -2, 3], [4, -5, 4], [-7, 8, 9]], + [[10, 10, 12], [13, -14, 15], [-16, 17, 19]], + [[-19, 20, 21], [1, 2, 3], [-25, 26, 27]]], + [[[0, -6, 11], [0, -9, 3/2], [0, 12, 17]], + [[6, 0, -4], [9, 0, 13/2], [-12, 0, -7/2]], + [[-11, 4, 0], [-3/2, -13/2, 0], [-17, 7/2, 0]]]) + sage: # Check of the result: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]-c[k,j,i])/2, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + + The order of index positions in the argument does not matter:: + + sage: c.antisymmetrize((1,0)) == c.antisymmetrize((0,1)) + True + sage: c.antisymmetrize((2,1)) == c.antisymmetrize((1,2)) + True + sage: c.antisymmetrize((2,0)) == c.antisymmetrize((0,2)) + True + + """ + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + if pos is None: + pos = range(self.nid) + else: + if len(pos) < 2: + raise TypeError("At least two index positions must be given.") + if len(pos) > self.nid: + raise TypeError("Number of index positions larger than the " \ + "total number of indices.") + n_sym = len(pos) # number of indices involved in the antisymmetry + if n_sym == self.nid: + result = CompFullyAntiSym(self.ring, self.frame, self.nid, + self.sindex, self.output_formatter) + else: + result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter, antisym=pos) + sym_group = SymmetricGroup(n_sym) + for ind in result.non_redundant_index_generator(): + sum = 0 + for perm in sym_group.list(): + # action of the permutation on [0,1,...,n_sym-1]: + perm_action = map(lambda x: x-1, perm.domain()) + ind_perm = list(ind) + for k in range(n_sym): + ind_perm[pos[perm_action[k]]] = ind[pos[k]] + if perm.sign() == 1: + sum += self[[ind_perm]] + else: + sum -= self[[ind_perm]] + result[[ind]] = sum / sym_group.order() + return result + + +#****************************************************************************** + +class CompWithSym(Components): + r""" + Class for storing components with respect to a given "frame", taking into + account symmetries or antisymmetries among the indices. + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). + The stored quantities can be tensor components or non-tensorial quantities, + such as connection coefficients or structure coefficents. + + Subclasses of :class:`CompWithSym` are + + * :class:`CompFullySym` for storing fully symmetric components. + * :class:`CompFullyAntiSym` for storing fully antisymmetric components. + + INPUT: + + - ``ring`` -- ring in which each component takes its value + - ``frame`` -- frame with respect to which the components are defined; + whatever type ``frame`` is, it should have some method ``__len__()`` + implemented, so that ``len(frame)`` returns the dimension, i.e. the size + of a single index range + - ``nb_indices`` -- number of indices labeling the components + - ``start_index`` -- (default: 0) first value of a single index; + accordingly a component index i must obey + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access + operator ``[...]`` (method __getitem__); ``output_formatter`` must take + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + the second one, if any, some format specification. + - ``sym`` -- (default: None) a symmetry or a list of symmetries among the + indices: each symmetry is described by a tuple containing the positions + of the involved indices, with the convention position=0 for the first + slot. For instance: + + * sym=(0,1) for a symmetry between the 1st and 2nd indices + * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd + indices and a symmetry between the 2nd, 4th and 5th indices. + + - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries + among the indices, with the same convention as for ``sym``. + + EXAMPLES: + + Symmetric components with 2 indices:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym + sage: V = VectorSpace(QQ,3) + sage: c = CompWithSym(QQ, V.basis(), 2, sym=(0,1)) # for demonstration only: it is preferable to use CompFullySym in this case + sage: c[0,1] = 3 + sage: c[:] # note that c[1,0] has been set automatically + [0 3 0] + [3 0 0] + [0 0 0] + + Antisymmetric components with 2 indices:: + + sage: c = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to use CompFullyAntiSym in this case + sage: c[0,1] = 3 + sage: c[:] # note that c[1,0] has been set automatically + [ 0 3 0] + [-3 0 0] + [ 0 0 0] + + Internally, only non-redundant components are stored:: + + sage: c._comp + {(0, 1): 3} + + Components with 6 indices, symmetric among 3 indices (at position ((0,1,5)) + and antisymmetric among 2 indices (at position (2,4)):: + + sage: c = CompWithSym(QQ, V.basis(), 6, sym=(0,1,5), antisym=(2,4)) + sage: c[0,1,2,0,1,2] = 3 + sage: c[1,0,2,0,1,2] # symmetry between indices in position 0 and 1 + 3 + sage: c[2,1,2,0,1,0] # symmetry between indices in position 0 and 5 + 3 + sage: c[0,2,2,0,1,1] # symmetry between indices in position 1 and 5 + 3 + sage: c[0,1,1,0,2,2] # antisymmetry between indices in position 2 and 4 + -3 + + Components with 4 indices, antisymmetric with respect to the first pair of + indices as well as with the second pair of indices:: + + sage: c = CompWithSym(QQ, V.basis(), 4, antisym=[(0,1),(2,3)]) + sage: c[0,1,0,1] = 3 + sage: c[1,0,0,1] # antisymmetry on the first pair of indices + -3 + sage: c[0,1,1,0] # antisymmetry on the second pair of indices + -3 + sage: c[1,0,1,0] # consequence of the above + 3 + + ARITHMETIC EXAMPLES: + + Addition of a symmetric set of components with a non-symmetric one: the + symmetry is lost:: + + sage: V = VectorSpace(QQ, 3) + sage: a = Components(QQ, V.basis(), 2) + sage: a[:] = [[1,-2,3], [4,5,-6], [-7,8,9]] + sage: b = CompWithSym(QQ, V.basis(), 2, sym=(0,1)) # for demonstration only: it is preferable to declare b = CompFullySym(QQ, V.basis(), 2) + sage: b[0,0], b[0,1], b[0,2] = 1, 2, 3 + sage: b[1,1], b[1,2] = 5, 7 + sage: b[2,2] = 11 + sage: s = a + b ; s + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: a[:], b[:], s[:] + ( + [ 1 -2 3] [ 1 2 3] [ 2 0 6] + [ 4 5 -6] [ 2 5 7] [ 6 10 1] + [-7 8 9], [ 3 7 11], [-4 15 20] + ) + sage: a + b == b + a + True + + Addition of two symmetric set of components: the symmetry is preserved:: + + sage: c = CompWithSym(QQ, V.basis(), 2, sym=(0,1)) # for demonstration only: it is preferable to declare c = CompFullySym(QQ, V.basis(), 2) + sage: c[0,0], c[0,1], c[0,2] = -4, 7, -8 + sage: c[1,1], c[1,2] = 2, -4 + sage: c[2,2] = 2 + sage: s = b + c ; s + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: b[:], c[:], s[:] + ( + [ 1 2 3] [-4 7 -8] [-3 9 -5] + [ 2 5 7] [ 7 2 -4] [ 9 7 3] + [ 3 7 11], [-8 -4 2], [-5 3 13] + ) + sage: b + c == c + b + True + + Check of the addition with counterparts not declared symmetric:: + + sage: bn = Components(QQ, V.basis(), 2) + sage: bn[:] = b[:] + sage: bn == b + True + sage: cn = Components(QQ, V.basis(), 2) + sage: cn[:] = c[:] + sage: cn == c + True + sage: bn + cn == b + c + True + + Addition of an antisymmetric set of components with a non-symmetric one: + the antisymmetry is lost:: + + sage: d = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to declare d = CompFullyAntiSym(QQ, V.basis(), 2) + sage: d[0,1], d[0,2], d[1,2] = 4, -1, 3 + sage: s = a + d ; s + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: a[:], d[:], s[:] + ( + [ 1 -2 3] [ 0 4 -1] [ 1 2 2] + [ 4 5 -6] [-4 0 3] [ 0 5 -3] + [-7 8 9], [ 1 -3 0], [-6 5 9] + ) + sage: d + a == a + d + True + + Addition of two antisymmetric set of components: the antisymmetry is preserved:: + + sage: e = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to declare e = CompFullyAntiSym(QQ, V.basis(), 2) + sage: e[0,1], e[0,2], e[1,2] = 2, 3, -1 + sage: s = d + e ; s + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: d[:], e[:], s[:] + ( + [ 0 4 -1] [ 0 2 3] [ 0 6 2] + [-4 0 3] [-2 0 -1] [-6 0 2] + [ 1 -3 0], [-3 1 0], [-2 -2 0] + ) + sage: e + d == d + e + True + + """ + def __init__(self, ring, frame, nb_indices, start_index=0, + output_formatter=None, sym=None, antisym=None): + Components.__init__(self, ring, frame, nb_indices, start_index, + output_formatter) + self.sym = [] + if sym is not None and sym != []: + if isinstance(sym[0], (int, Integer)): + # a single symmetry is provided as a tuple -> 1-item list: + sym = [tuple(sym)] + for isym in sym: + if len(isym) < 2: + raise IndexError("At least two index positions must be " + + "provided to define a symmetry.") + for i in isym: + if i<0 or i>self.nid-1: + raise IndexError("Invalid index position: " + str(i) + + " not in [0," + str(self.nid-1) + "]") + self.sym.append(tuple(isym)) + self.antisym = [] + if antisym is not None and antisym != []: + if isinstance(antisym[0], (int, Integer)): + # a single antisymmetry is provided as a tuple -> 1-item list: + antisym = [tuple(antisym)] + for isym in antisym: + if len(isym) < 2: + raise IndexError("At least two index positions must be " + + "provided to define an antisymmetry.") + for i in isym: + if i<0 or i>self.nid-1: + raise IndexError("Invalid index position: " + str(i) + + " not in [0," + str(self.nid-1) + "]") + self.antisym.append(tuple(isym)) + # Final consistency check: + index_list = [] + for isym in self.sym: + index_list += isym + for isym in self.antisym: + index_list += isym + if len(index_list) != len(set(index_list)): + # There is a repeated index position: + raise IndexError("Incompatible lists of symmetries: the same " + + "index position appears more then once.") + + def _repr_(self): + r""" + String representation of the object. + """ + description = str(self.nid) + if self.nid == 1: + description += "-index" + else: + description += "-indices" + description += " components w.r.t. " + str(self.frame) + for isym in self.sym: + description += ", with symmetry on the index positions " + \ + str(tuple(isym)) + for isym in self.antisym: + description += ", with antisymmetry on the index positions " + \ + str(tuple(isym)) + return description + + def _new_instance(self): + r""" + Creates a :class:`CompWithSym` instance w.r.t. the same frame, + and with the same number of indices and the same symmetries + + """ + return CompWithSym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter, self.sym, self.antisym) + + def _ordered_indices(self, indices): + r""" + Given a set of indices, returns a set of indices with the indices + at the positions of symmetries or antisymmetries being ordered, + as well as some antisymmetry indicator. + + INPUT: + + - ``indices`` -- list of indices (possibly a single integer if + self is a 1-index object) + + OUTPUT: + + - a pair `(s,ind)` where ind is a tuple that differs from the original + list of indices by a reordering at the positions of symmetries and + antisymmetries and + * `s=0` if the value corresponding to ``indices`` vanishes by + antisymmetry (repeated indices); `ind` is then set to None + * `s=1` if the value corresponding to ``indices`` is the same as + that corresponding to `ind` + * `s=-1` if the value corresponding to ``indices`` is the opposite + of that corresponding to `ind` + + """ + from sage.combinat.permutation import Permutation + ind = list(self._check_indices(indices)) + for isym in self.sym: + indsym = [] + for pos in isym: + indsym.append(ind[pos]) + indsym_ordered = sorted(indsym) + for k, pos in enumerate(isym): + ind[pos] = indsym_ordered[k] + sign = 1 + for isym in self.antisym: + indsym = [] + for pos in isym: + indsym.append(ind[pos]) + # Returns zero if some index appears twice: + if len(indsym) != len(set(indsym)): + return (0, None) + # From here, all the indices in indsym are distinct and we need + # to determine whether they form an even permutation of their + # ordered series + indsym_ordered = sorted(indsym) + for k, pos in enumerate(isym): + ind[pos] = indsym_ordered[k] + if indsym_ordered != indsym: + # Permutation linking indsym_ordered to indsym: + # (the +1 is required to fulfill the convention of Permutation) + perm = [indsym.index(i) +1 for i in indsym_ordered] + #c# print "indsym_ordered, indsym: ", indsym_ordered, indsym + #c# print "Permutation: ", Permutation(perm), " signature = ", \ + #c# Permutation(perm).signature() + sign *= Permutation(perm).signature() + ind = tuple(ind) + return (sign, ind) + + def __getitem__(self, args): + r""" + Returns the component corresponding to the given indices. + + INPUT: + + - ``args`` -- list of indices (possibly a single integer if + self is a 1-index object) or the character ``:`` for the full list + of components. + + OUTPUT: + + - the component corresponding to ``args`` or, if ``args`` = ``:``, + the full list of components, in the form ``T[i][j]...`` for the components + `T_{ij...}` (for a 2-indices object, a matrix is returned). + + """ + no_format = self.output_formatter is None + format_type = None # default value, possibly redefined below + if isinstance(args, list): # case of [[...]] syntax + no_format = True + if isinstance(args[0], slice): + indices = args[0] + elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] + else: + indices = tuple(args) + else: + # Determining from the input the list of indices and the format + if isinstance(args, (int, Integer)) or isinstance(args, slice): + indices = args + elif isinstance(args[0], slice): + indices = args[0] + format_type = args[1] + elif len(args) == self.nid: + indices = args + else: + format_type = args[-1] + indices = args[:-1] + if isinstance(indices, slice): + return self._get_list(indices, no_format, format_type) + else: + sign, ind = self._ordered_indices(indices) + if (sign == 0) or (ind not in self._comp): # the value is zero: + if no_format: + return self.ring.zero_element() + elif format_type is None: + return self.output_formatter(self.ring.zero_element()) + else: + return self.output_formatter(self.ring.zero_element(), + format_type) + else: # non zero value + if no_format: + if sign == 1: + return self._comp[ind] + else: # sign = -1 + return -self._comp[ind] + elif format_type is None: + if sign == 1: + return self.output_formatter(self._comp[ind]) + else: # sign = -1 + return self.output_formatter(-self._comp[ind]) + else: + if sign == 1: + return self.output_formatter( + self._comp[ind], format_type) + else: # sign = -1 + return self.output_formatter( + -self._comp[ind], format_type) + + def __setitem__(self, indices, value): + r""" + Sets the component corresponding to the given indices. + + INPUT: + + - ``indices`` -- list of indices (possibly a single integer if + self is a 1-index object) ; if [:] is provided, all the components + are set. + - ``value`` -- the value to be set or a list of values if ``args`` + == ``[:]`` + + """ + if isinstance(indices, slice): + self._set_list(indices, value) + else: + if isinstance(indices, list): + # to ensure equivalence between [i,j,...] and [[i,j,...]] or + # [[(i,j,...)]] + if isinstance(indices[0], tuple) or isinstance(indices[0], list): + indices = indices[0] + else: + indices = tuple(indices) + sign, ind = self._ordered_indices(indices) + if sign == 0: + if value != 0: + raise ValueError( + "By antisymmetry, the component cannot have a " + + "nonzero value for the indices " + str(indices)) + if ind in self._comp: + del self._comp[ind] # zero values are not stored + elif value == 0: + if ind in self._comp: + del self._comp[ind] # zero values are not stored + else: + if sign == 1: + self._comp[ind] = self.ring(value) + else: # sign = -1 + self._comp[ind] = -self.ring(value) + + + def swap_adjacent_indices(self, pos1, pos2, pos3): + r""" + Swap two adjacent sets of indices. + + This method is essentially required to reorder the covariant and + contravariant indices in the computation of a tensor product. + + The symmetries are preserved and the corresponding indices are adjusted + consequently. + + INPUT: + + - ``pos1`` -- position of the first index of set 1 (with the convention + position=0 for the first slot) + - ``pos2`` -- position of the first index of set 2 = 1 + position of + the last index of set 1 (since the two sets are adjacent) + - ``pos3`` -- 1 + position of the last index of set 2 + + OUTPUT: + + - Components with index set 1 permuted with index set 2. + + EXAMPLES: + + Swap of the index in position 0 with the pair of indices in position + (1,2) in a set of components antisymmetric with respect to the indices + in position (1,2):: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: V = VectorSpace(QQ, 3) + sage: c = CompWithSym(QQ, V.basis(), 3, antisym=(1,2)) + sage: c[0,0,1], c[0,0,2], c[0,1,2] = (1,2,3) + sage: c[1,0,1], c[1,0,2], c[1,1,2] = (4,5,6) + sage: c[2,0,1], c[2,0,2], c[2,1,2] = (7,8,9) + sage: c[:] + [[[0, 1, 2], [-1, 0, 3], [-2, -3, 0]], + [[0, 4, 5], [-4, 0, 6], [-5, -6, 0]], + [[0, 7, 8], [-7, 0, 9], [-8, -9, 0]]] + sage: c1 = c.swap_adjacent_indices(0,1,3) + sage: c.antisym # c is antisymmetric with respect to the last pair of indices... + [(1, 2)] + sage: c1.antisym #...while c1 is antisymmetric with respect to the first pair of indices + [(0, 1)] + sage: c[0,1,2] + 3 + sage: c1[1,2,0] + 3 + sage: c1[2,1,0] + -3 + + + """ + result = self._new_instance() + # The symmetries: + lpos = range(self.nid) + new_lpos = lpos[:pos1] + lpos[pos2:pos3] + lpos[pos1:pos2] + lpos[pos3:] + result.sym = [] + for s in self.sym: + new_s = [new_lpos.index(pos) for pos in s] + result.sym.append(tuple(sorted(new_s))) + result.antisym = [] + for s in self.antisym: + new_s = [new_lpos.index(pos) for pos in s] + result.antisym.append(tuple(sorted(new_s))) + # The values: + for ind, val in self._comp.items(): + new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] + result[new_ind] = val + return result + + def __add__(self, other): + r""" + Component addition. + + INPUT: + + - ``other`` -- components of the same number of indices and defined + on the same frame as ``self`` + + OUTPUT: + + - components resulting from the addition of ``self`` and ``other`` + + """ + if other == 0: + return +self + if not isinstance(other, Components): + raise TypeError("The second argument for the addition must be a " + + "an instance of Components.") + if other.frame != self.frame: + raise TypeError("The two sets of components are not defined on " + + "the same frame.") + if other.nid != self.nid: + raise TypeError("The two sets of components do not have the " + + "same number of indices.") + if other.sindex != self.sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + if isinstance(other, CompWithSym): + # Are the symmetries of the same type ? + diff_sym = set(self.sym).symmetric_difference(set(other.sym)) + diff_antisym = \ + set(self.antisym).symmetric_difference(set(other.antisym)) + if diff_sym == set() and diff_antisym == set(): + # The symmetries/antisymmetries are identical: + result = self.copy() + for ind, val in other._comp.items(): + result[[ind]] += val + return result + else: + # The symmetries/antisymmetries are different: only the + # common ones are kept + common_sym = [] + for isym in self.sym: + for osym in other.sym: + com = tuple(set(isym).intersection(set(osym))) + if len(com) > 1: + common_sym.append(com) + common_antisym = [] + for isym in self.antisym: + for osym in other.antisym: + com = tuple(set(isym).intersection(set(osym))) + if len(com) > 1: + common_antisym.append(com) + if common_sym != [] or common_antisym != []: + result = CompWithSym(self.ring, self.frame, self.nid, + self.sindex, self.output_formatter, + common_sym, common_antisym) + else: + # no common symmetry -> the result is a generic Components: + result = Components(self.ring, self.frame, self.nid, + self.sindex, self.output_formatter) + else: + # other has no symmetry at all: + result = Components(self.ring, self.frame, self.nid, + self.sindex, self.output_formatter) + for ind in result.non_redundant_index_generator(): + result[[ind]] = self[[ind]] + other[[ind]] + return result + + + def __mul__(self, other): + r""" + Component tensor product. + + INPUT: + + - ``other`` -- components, on the same frame as ``self`` + + OUTPUT: + + - the tensor product of ``self`` by ``other`` + + """ + if not isinstance(other, Components): + raise TypeError("The second argument for the tensor product " + + "be an instance of Components.") + if other.frame != self.frame: + raise TypeError("The two sets of components are not defined on " + + "the same frame.") + if other.sindex != self.sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + sym = list(self.sym) + antisym = list(self.antisym) + if isinstance(other, CompWithSym): + if other.sym != []: + for s in other.sym: + ns = tuple(s[i]+self.nid for i in range(len(s))) + sym.append(ns) + if other.antisym != []: + for s in other.antisym: + ns = tuple(s[i]+self.nid for i in range(len(s))) + antisym.append(ns) + result = CompWithSym(self.ring, self.frame, self.nid + other.nid, + self.sindex, self.output_formatter, sym, antisym) + for ind_s, val_s in self._comp.items(): + for ind_o, val_o in other._comp.items(): + result._comp[ind_s + ind_o] = val_s * val_o + return result + + + def self_contract(self, pos1, pos2): + r""" + Index contraction, , taking care of the symmetries. + + INPUT: + + - ``pos1`` -- position of the first index for the contraction (with + the convention position=0 for the first slot) + - ``pos2`` -- position of the second index for the contraction + + OUTPUT: + + - set of components resulting from the (pos1, pos2) contraction + + EXAMPLES: + + Self-contraction of symmetric 2-indices components:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ + ... CompFullySym, CompFullyAntiSym + sage: V = VectorSpace(QQ, 3) + sage: a = CompFullySym(QQ, V.basis(), 2) + sage: a[:] = [[1,2,3],[2,4,5],[3,5,6]] + sage: a.self_contract(0,1) + 11 + sage: a[0,0] + a[1,1] + a[2,2] + 11 + + Self-contraction of antisymmetric 2-indices components:: + + sage: b = CompFullyAntiSym(QQ, V.basis(), 2) + sage: b[0,1], b[0,2], b[1,2] = (3, -2, 1) + sage: b.self_contract(0,1) # must be zero by antisymmetry + 0 + + + Self-contraction of 3-indices components with one symmetry:: + + sage: v = Components(QQ, V.basis(), 1) + sage: v[:] = (-2, 4, -8) + sage: c = v*b ; c + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (1, 2) + sage: s = c.self_contract(0,1) ; s + 1-index components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] + [-28, 2, 8] + sage: [sum(v[k]*b[k,i] for k in range(3)) for i in range(3)] # check + [-28, 2, 8] + sage: s = c.self_contract(1,2) ; s + 1-index components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] # is zero by antisymmetry + [0, 0, 0] + sage: c = b*v ; c + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: s = c.self_contract(0,1) + sage: s[:] # is zero by antisymmetry + [0, 0, 0] + sage: s = c.self_contract(1,2) ; s[:] + [28, -2, -8] + sage: [sum(b[i,k]*v[k] for k in range(3)) for i in range(3)] # check + [28, -2, -8] + + Self-contraction of 4-indices components with two symmetries:: + + sage: c = a*b ; c + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + sage: s = c.self_contract(0,1) ; s # the symmetry on (0,1) is lost: + fully antisymmetric 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] + [ 0 33 -22] + [-33 0 11] + [ 22 -11 0] + sage: [[sum(c[k,k,i,j] for k in range(3)) for j in range(3)] for i in range(3)] # check + [[0, 33, -22], [-33, 0, 11], [22, -11, 0]] + sage: s = c.self_contract(1,2) ; s # both symmetries are lost by this contraction + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] + [ 0 0 0] + [-2 1 0] + [-3 3 -1] + sage: [[sum(c[i,k,k,j] for k in range(3)) for j in range(3)] for i in range(3)] # check + [[0, 0, 0], [-2, 1, 0], [-3, 3, -1]] + + """ + if self.nid < 2: + raise TypeError("Contraction can be perfomed only on " + + "components with at least 2 indices.") + if pos1 < 0 or pos1 > self.nid - 1: + raise IndexError("pos1 out of range.") + if pos2 < 0 or pos2 > self.nid - 1: + raise IndexError("pos2 out of range.") + if pos1 == pos2: + raise IndexError("The two positions must differ for the " + + "contraction to take place.") + si = self.sindex + nsi = si + self.dim + if self.nid == 2: + res = 0 + for i in range(si, nsi): + res += self[[i,i]] + return res + else: + # More than 2 indices + if pos1 > pos2: + pos1, pos2 = (pos2, pos1) + # Determination of the remaining symmetries: + sym_res = list(self.sym) + for isym in self.sym: + isym_res = list(isym) + if pos1 in isym: + isym_res.remove(pos1) + if pos2 in isym: + isym_res.remove(pos2) + if len(isym_res) < 2: # the symmetry is lost + sym_res.remove(isym) + else: + sym_res[sym_res.index(isym)] = tuple(isym_res) + antisym_res = list(self.antisym) + for isym in self.antisym: + isym_res = list(isym) + if pos1 in isym: + isym_res.remove(pos1) + if pos2 in isym: + isym_res.remove(pos2) + if len(isym_res) < 2: # the symmetry is lost + antisym_res.remove(isym) + else: + antisym_res[antisym_res.index(isym)] = tuple(isym_res) + # Shift of the index positions to take into account the + # suppression of 2 indices: + max_sym = 0 + for k in range(len(sym_res)): + isym_res = [] + for pos in sym_res[k]: + if pos < pos1: + isym_res.append(pos) + elif pos < pos2: + isym_res.append(pos-1) + else: + isym_res.append(pos-2) + max_sym = max(max_sym, len(isym_res)) + sym_res[k] = tuple(isym_res) + max_antisym = 0 + for k in range(len(antisym_res)): + isym_res = [] + for pos in antisym_res[k]: + if pos < pos1: + isym_res.append(pos) + elif pos < pos2: + isym_res.append(pos-1) + else: + isym_res.append(pos-2) + max_antisym = max(max_antisym, len(isym_res)) + antisym_res[k] = tuple(isym_res) + # Construction of the appropriate object in view of the + # remaining symmetries: + nid_res = self.nid - 2 + if max_sym == 0 and max_antisym == 0: + result = Components(self.ring, self.frame, nid_res, self.sindex, + self.output_formatter) + elif max_sym == nid_res: + result = CompFullySym(self.ring, self.frame, nid_res, + self.sindex, self.output_formatter) + elif max_antisym == nid_res: + result = CompFullyAntiSym(self.ring, self.frame, nid_res, + self.sindex, self.output_formatter) + else: + result = CompWithSym(self.ring, self.frame, nid_res, + self.sindex, self.output_formatter, + sym=sym_res, antisym=antisym_res) + # The contraction itself: + for ind_res in result.index_generator(): + ind = list(ind_res) + ind.insert(pos1, 0) + ind.insert(pos2, 0) + res = 0 + for i in range(si, nsi): + ind[pos1] = i + ind[pos2] = i + res += self[[ind]] + result[[ind_res]] = res + return result + + + def non_redundant_index_generator(self): + r""" + Generator of indices, with only ordered indices in case of symmetries, + so that only non-redundant indices are generated. + + OUTPUT: + + - an iterable index + + EXAMPLES: + + Indices on a 2-dimensional space:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ + ... CompFullySym, CompFullyAntiSym + sage: V = VectorSpace(QQ, 2) + sage: c = CompFullySym(QQ, V.basis(), 2) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 0) (0, 1) (1, 1) + sage: c = CompFullySym(QQ, V.basis(), 2, start_index=1) + sage: for ind in c.non_redundant_index_generator(): print ind, + (1, 1) (1, 2) (2, 2) + sage: c = CompFullyAntiSym(QQ, V.basis(), 2) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 1) + + Indices on a 3-dimensional space:: + + sage: V = VectorSpace(QQ, 3) + sage: c = CompFullySym(QQ, V.basis(), 2) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 0) (0, 1) (0, 2) (1, 1) (1, 2) (2, 2) + sage: c = CompFullySym(QQ, V.basis(), 2, start_index=1) + sage: for ind in c.non_redundant_index_generator(): print ind, + (1, 1) (1, 2) (1, 3) (2, 2) (2, 3) (3, 3) + sage: c = CompFullyAntiSym(QQ, V.basis(), 2) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 1) (0, 2) (1, 2) + sage: c = CompWithSym(QQ, V.basis(), 3, sym=(1,2)) # symmetry on the last two indices + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 1) (0, 1, 2) (0, 2, 2) (1, 0, 0) (1, 0, 1) (1, 0, 2) (1, 1, 1) (1, 1, 2) (1, 2, 2) (2, 0, 0) (2, 0, 1) (2, 0, 2) (2, 1, 1) (2, 1, 2) (2, 2, 2) + sage: c = CompWithSym(QQ, V.basis(), 3, antisym=(1,2)) # antisymmetry on the last two indices + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 0, 1) (0, 0, 2) (0, 1, 2) (1, 0, 1) (1, 0, 2) (1, 1, 2) (2, 0, 1) (2, 0, 2) (2, 1, 2) + sage: c = CompFullySym(QQ, V.basis(), 3) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 1) (0, 1, 2) (0, 2, 2) (1, 1, 1) (1, 1, 2) (1, 2, 2) (2, 2, 2) + sage: c = CompFullyAntiSym(QQ, V.basis(), 3) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 1, 2) + + Indices on a 4-dimensional space:: + + sage: V = VectorSpace(QQ, 4) + sage: c = Components(QQ, V.basis(), 1) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0,) (1,) (2,) (3,) + sage: c = CompFullyAntiSym(QQ, V.basis(), 2) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 1) (0, 2) (0, 3) (1, 2) (1, 3) (2, 3) + sage: c = CompFullyAntiSym(QQ, V.basis(), 3) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 1, 2) (0, 1, 3) (0, 2, 3) (1, 2, 3) + sage: c = CompFullyAntiSym(QQ, V.basis(), 4) + sage: for ind in c.non_redundant_index_generator(): print ind, + (0, 1, 2, 3) + sage: c = CompFullyAntiSym(QQ, V.basis(), 5) + sage: for ind in c.non_redundant_index_generator(): print ind, # nothing since c is identically zero in this case (for 5 > 4) + + """ + si = self.sindex + imax = self.dim - 1 + si + ind = [si for k in range(self.nid)] + ind_end = [si for k in range(self.nid)] + ind_end[0] = imax+1 + while ind != ind_end: + ordered = True + for isym in self.sym: + for k in range(len(isym)-1): + if ind[isym[k+1]] < ind[isym[k]]: + ordered = False + break + for isym in self.antisym: + for k in range(len(isym)-1): + if ind[isym[k+1]] <= ind[isym[k]]: + ordered = False + break + if ordered: + yield tuple(ind) + ret = 1 + for pos in range(self.nid-1,-1,-1): + if ind[pos] != imax: + ind[pos] += ret + ret = 0 + elif ret == 1: + if pos == 0: + ind[pos] = imax + 1 # end point reached + else: + ind[pos] = si + ret = 1 + + def symmetrize(self, pos=None): + r""" + Symmetrization over the given index positions + + INPUT: + + - ``pos`` -- (default: None) list of index positions involved in the + symmetrization (with the convention position=0 for the first slot); + if none, the symmetrization is performed over all the indices + + OUTPUT: + + - an instance of :class:`CompWithSym` describing the symmetrized + components. + + EXAMPLES: + + Symmetrization of 3-indices components on a 3-dimensional space:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ + ... CompFullySym, CompFullyAntiSym + sage: V = VectorSpace(QQ, 3) + sage: c = Components(QQ, V.basis(), 3) + sage: c[:] = [[[1,2,3], [4,5,6], [7,8,9]], [[10,11,12], [13,14,15], [16,17,18]], [[19,20,21], [22,23,24], [25,26,27]]] + sage: cs = c.symmetrize((0,1)) ; cs + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: s = cs.symmetrize() ; s + fully symmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: cs[:], s[:] + ([[[1, 2, 3], [7, 8, 9], [13, 14, 15]], + [[7, 8, 9], [13, 14, 15], [19, 20, 21]], + [[13, 14, 15], [19, 20, 21], [25, 26, 27]]], + [[[1, 16/3, 29/3], [16/3, 29/3, 14], [29/3, 14, 55/3]], + [[16/3, 29/3, 14], [29/3, 14, 55/3], [14, 55/3, 68/3]], + [[29/3, 14, 55/3], [14, 55/3, 68/3], [55/3, 68/3, 27]]]) + sage: s == c.symmetrize() # should be true + True + sage: s1 = cs.symmetrize((0,1)) ; s1 # should return a copy of cs + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: s1 == cs # check that s1 is a copy of cs + True + + Let us now start with a symmetry on the last two indices:: + + sage: cs1 = c.symmetrize((1,2)) ; cs1 + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (1, 2) + sage: s2 = cs1.symmetrize() ; s2 + fully symmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s2 == c.symmetrize() + True + + Symmetrization alters pre-existing symmetries: let us symmetrize w.r.t. + the index positions (1,2) a set of components that is symmetric w.r.t. + the index positions (0,1):: + + sage: cs = c.symmetrize((0,1)) ; cs + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: css = cs.symmetrize((1,2)) + sage: css # the symmetry (0,1) has been lost: + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (1, 2) + sage: css[:] + [[[1, 9/2, 8], [9/2, 8, 23/2], [8, 23/2, 15]], + [[7, 21/2, 14], [21/2, 14, 35/2], [14, 35/2, 21]], + [[13, 33/2, 20], [33/2, 20, 47/2], [20, 47/2, 27]]] + sage: cs[:] + [[[1, 2, 3], [7, 8, 9], [13, 14, 15]], + [[7, 8, 9], [13, 14, 15], [19, 20, 21]], + [[13, 14, 15], [19, 20, 21], [25, 26, 27]]] + sage: css == c.symmetrize() # css differs from the full symmetrized version + False + sage: css.symmetrize() == c.symmetrize() # one has to symmetrize css over all indices to recover it + True + + Another example of symmetry alteration: symmetrization over (0,1) of + a 4-indices set of components that is symmetric w.r.t. (1,2,3):: + + sage: v = Components(QQ, V.basis(), 1) + sage: v[:] = (-2,1,4) + sage: a = v*s ; a + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (1, 2, 3) + sage: a1 = a.symmetrize((0,1)) ; a1 # the symmetry (1,2,3) has been reduced to (2,3): + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) + sage: a1.sym # a1 has two distinct symmetries: + [(0, 1), (2, 3)] + sage: a[0,1,2,0] == a[0,0,2,1] # a is symmetric w.r.t. positions 1 and 3 + True + sage: a1[0,1,2,0] == a1[0,0,2,1] # a1 is not + False + sage: a1[0,1,2,0] == a1[1,0,2,0] # but it is symmetric w.r.t. position 0 and 1 + True + sage: a[0,1,2,0] == a[1,0,2,0] # while a is not + False + + Partial symmetrization of 4-indices components with an antisymmetry on + the last two indices:: + + sage: a = Components(QQ, V.basis(), 2) + sage: a[:] = [[-1,2,3], [4,5,-6], [7,8,9]] + sage: b = CompFullyAntiSym(QQ, V.basis(), 2) + sage: b[0,1], b[0,2], b[1,2] = (2, 4, 8) + sage: c = a*b ; c + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (2, 3) + sage: s = c.symmetrize((0,1)) ; s # symmetrization on the first two indices + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + sage: s[0,1,2,1] == (c[0,1,2,1] + c[1,0,2,1]) / 2 # check of the symmetrization + True + sage: s = c.symmetrize() ; s # symmetrization over all the indices + fully symmetric 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s == 0 # the full symmetrization results in zero due to the antisymmetry on the last two indices + True + sage: s = c.symmetrize((2,3)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (2, 3) + sage: s == 0 # must be zero since the symmetrization has been performed on the antisymmetric indices + True + sage: s = c.symmetrize((0,2)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 2) + sage: s != 0 # s is not zero, but the antisymmetry on (2,3) is lost because the position 2 is involved in the new symmetry + True + + Partial symmetrization of 4-indices components with an antisymmetry on + the last three indices:: + + sage: a = Components(QQ, V.basis(), 1) + sage: a[:] = (1, -2, 3) + sage: b = CompFullyAntiSym(QQ, V.basis(), 3) + sage: b[0,1,2] = 4 + sage: c = a*b ; c + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (1, 2, 3) + sage: s = c.symmetrize((0,1)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + sage: # Note that the antisymmetry on (1, 2, 3) has been reduced to (2, 3) only + sage: s = c.symmetrize((1,2)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (1, 2) + sage: s == 0 # because (1,2) are involved in the original antisymmetry + True + + """ + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + if pos is None: + pos = range(self.nid) + else: + if len(pos) < 2: + raise TypeError("At least two index positions must be given.") + if len(pos) > self.nid: + raise TypeError("Number of index positions larger than the " \ + "total number of indices.") + pos = tuple(pos) + pos_set = set(pos) + # If the symmetry is already present, there is nothing to do: + for isym in self.sym: + if pos_set.issubset(set(isym)): + return self.copy() + # + # Interference of the new symmetry with existing ones: + # + sym_res = [pos] # starting the list of symmetries of the result + for isym in self.sym: + inter = pos_set.intersection(set(isym)) + # if len(inter) == len(isym), isym is included in the new symmetry + # and therefore has not to be included in sym_res + if len(inter) != len(isym): + if len(inter) >= 1: + # some part of isym is lost + isym_set = set(isym) + for k in inter: + isym_set.remove(k) + if len(isym_set) > 1: + # some part of isym remains and must be included in sym_res: + isym_res = tuple(isym_set) + sym_res.append(isym_res) + else: + # case len(inter)=0: no interference: the existing symmetry is + # added to the list of symmetries for the result: + sym_res.append(isym) + # + # Interference of the new symmetry with existing antisymmetries: + # + antisym_res = [] # starting the list of antisymmetries of the result + zero_result = False + for iasym in self.antisym: + inter = pos_set.intersection(set(iasym)) + if len(inter) > 1: + # If at least two of the symmetry indices are already involved + # in the antisymmetry, the outcome is zero: + zero_result = True + elif len(inter) == 1: + # some piece of antisymmetry is lost + k = inter.pop() # the symmetry index position involved in the + # antisymmetry + iasym_set = set(iasym) + iasym_set.remove(k) + if len(iasym_set) > 1: + iasym_res = tuple(iasym_set) + antisym_res.append(iasym_res) + # if len(iasym_set) == 1, the antisymmetry is fully lost, it is + # therefore not appended to antisym_res + else: + # case len(inter)=0: no interference: the antisymmetry is + # added to the list of antisymmetries for the result: + antisym_res.append(iasym) + # + # Creation of the result object + # + max_sym = 0 + for isym in sym_res: + max_sym = max(max_sym, len(isym)) + if max_sym == self.nid: + result = CompFullySym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter) + else: + result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter, sym=sym_res, + antisym=antisym_res) + if zero_result: + return result # since a just created instance is zero + # + # Symmetrization + # + n_sym = len(pos) # number of indices involved in the symmetry + sym_group = SymmetricGroup(n_sym) + for ind in result.non_redundant_index_generator(): + sum = 0 + for perm in sym_group.list(): + # action of the permutation on [0,1,...,n_sym-1]: + perm_action = map(lambda x: x-1, perm.domain()) + ind_perm = list(ind) + for k in range(n_sym): + ind_perm[pos[perm_action[k]]] = ind[pos[k]] + sum += self[[ind_perm]] + result[[ind]] = sum / sym_group.order() + return result + + + def antisymmetrize(self, pos=None): + r""" + Antisymmetrization over the given index positions + + INPUT: + + - ``pos`` -- (default: None) list of index positions involved in the + antisymmetrization (with the convention position=0 for the first slot); + if none, the antisymmetrization is performed over all the indices + + OUTPUT: + + - an instance of :class:`CompWithSym` describing the antisymmetrized + components. + + EXAMPLES: + + Antisymmetrization of 3-indices components on a 3-dimensional space:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ + ... CompFullySym, CompFullyAntiSym + sage: V = VectorSpace(QQ, 3) + sage: a = Components(QQ, V.basis(), 1) + sage: a[:] = (-2,1,3) + sage: b = CompFullyAntiSym(QQ, V.basis(), 2) + sage: b[0,1], b[0,2], b[1,2] = (4,1,2) + sage: c = a*b ; c # tensor product of a by b + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (1, 2) + sage: s = c.antisymmetrize() ; s + fully antisymmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c[:], s[:] + ([[[0, -8, -2], [8, 0, -4], [2, 4, 0]], + [[0, 4, 1], [-4, 0, 2], [-1, -2, 0]], + [[0, 12, 3], [-12, 0, 6], [-3, -6, 0]]], + [[[0, 0, 0], [0, 0, 7/3], [0, -7/3, 0]], + [[0, 0, -7/3], [0, 0, 0], [7/3, 0, 0]], + [[0, 7/3, 0], [-7/3, 0, 0], [0, 0, 0]]]) + sage: # Check of the antisymmetrization: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6, + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + + Antisymmetrization over already antisymmetric indices does not change anything:: + + sage: s1 = s.antisymmetrize((1,2)) ; s1 + fully antisymmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s1 == s + True + sage: c1 = c.antisymmetrize((1,2)) ; c1 + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (1, 2) + sage: c1 == c + True + + But in general, antisymmetrization may alter previous antisymmetries:: + + sage: c2 = c.antisymmetrize((0,1)) ; c2 # the antisymmetry (2,3) is lost: + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: c2 == c + False + sage: c = s*a ; c + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1, 2) + sage: s = c.antisymmetrize((1,3)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (1, 3), with antisymmetry on the index positions (0, 2) + sage: s.antisym # the antisymmetry (0,1,2) has been reduced to (0,2), since 1 is involved in the new antisymmetry (1,3): + [(1, 3), (0, 2)] + + Partial antisymmetrization of 4-indices components with a symmetry on + the first two indices:: + + sage: a = CompFullySym(QQ, V.basis(), 2) + sage: a[:] = [[-2,1,3], [1,0,-5], [3,-5,4]] + sage: b = Components(QQ, V.basis(), 2) + sage: b[:] = [[1,2,3], [5,7,11], [13,17,19]] + sage: c = a*b ; c + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: s = c.antisymmetrize((2,3)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + sage: # Some check of the antisymmetrization: + sage: for i in range(3): + ....: for j in range(i,3): + ....: print (s[2,2,i,j], s[2,2,i,j] == (c[2,2,i,j] - c[2,2,j,i])/2), + ....: + (0, True) (-6, True) (-20, True) (0, True) (-12, True) (0, True) + + The full antisymmetrization results in zero because of the symmetry on the + first two indices:: + + sage: s = c.antisymmetrize() ; s + fully antisymmetric 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s == 0 + True + + Similarly, the partial antisymmetrization on the first two indices results in zero:: + + sage: s = c.antisymmetrize((0,1)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: s == 0 + True + + The partial antisymmetrization on the positions (0,2) destroys the symmetry on (0,1):: + + sage: s = c.antisymmetrize((0,2)) ; s + 4-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 2) + sage: s != 0 + True + sage: s[0,1,2,1] + 27/2 + sage: s[1,0,2,1] # the symmetry (0,1) is lost + -2 + sage: s[2,1,0,1] # the antisymmetry (0,2) holds + -27/2 + + """ + from sage.groups.perm_gps.permgroup_named import SymmetricGroup + if pos is None: + pos = range(self.nid) + else: + if len(pos) < 2: + raise TypeError("At least two index positions must be given.") + if len(pos) > self.nid: + raise TypeError("Number of index positions larger than the " \ + "total number of indices.") + pos = tuple(pos) + pos_set = set(pos) + # If the antisymmetry is already present, there is nothing to do: + for iasym in self.antisym: + if pos_set.issubset(set(iasym)): + return self.copy() + # + # Interference of the new antisymmetry with existing ones + # + antisym_res = [pos] # starting the list of symmetries of the result + for iasym in self.antisym: + inter = pos_set.intersection(set(iasym)) + # if len(inter) == len(iasym), iasym is included in the new + # antisymmetry and therefore has not to be included in antisym_res + if len(inter) != len(iasym): + if len(inter) >= 1: + # some part of iasym is lost + iasym_set = set(iasym) + for k in inter: + iasym_set.remove(k) + if len(iasym_set) > 1: + # some part of iasym remains and must be included in + # antisym_res: + iasym_res = tuple(iasym_set) + antisym_res.append(iasym_res) + else: + # case len(inter)=0: no interference: the existing + # antisymmetry is added to the list of antisymmetries for + # the result: + antisym_res.append(iasym) + # + # Interference of the new antisymmetry with existing symmetries + # + sym_res = [] # starting the list of symmetries of the result + zero_result = False + for isym in self.sym: + inter = pos_set.intersection(set(isym)) + if len(inter) > 1: + # If at least two of the antisymmetry indices are already + # involved in the symmetry, the outcome is zero: + zero_result = True + elif len(inter) == 1: + # some piece of the symmetry is lost + k = inter.pop() # the antisymmetry index position involved in + # the symmetry + isym_set = set(isym) + isym_set.remove(k) + if len(isym_set) > 1: + isym_res = tuple(isym_set) + sym_res.append(isym_res) + # if len(isym_set) == 1, the symmetry is fully lost, it is + # therefore not appended to sym_res + else: + # case len(inter)=0: no interference: the symmetry is + # added to the list of symmetries for the result: + sym_res.append(isym) + # + # Creation of the result object + # + max_sym = 0 + for isym in antisym_res: + max_sym = max(max_sym, len(isym)) + if max_sym == self.nid: + result = CompFullyAntiSym(self.ring, self.frame, self.nid, + self.sindex, self.output_formatter) + else: + result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter, sym=sym_res, + antisym=antisym_res) + if zero_result: + return result # since a just created instance is zero + # + # Antisymmetrization + # + n_sym = len(pos) # number of indices involved in the antisymmetry + sym_group = SymmetricGroup(n_sym) + for ind in result.non_redundant_index_generator(): + sum = 0 + for perm in sym_group.list(): + # action of the permutation on [0,1,...,n_sym-1]: + perm_action = map(lambda x: x-1, perm.domain()) + ind_perm = list(ind) + for k in range(n_sym): + ind_perm[pos[perm_action[k]]] = ind[pos[k]] + if perm.sign() == 1: + sum += self[[ind_perm]] + else: + sum -= self[[ind_perm]] + result[[ind]] = sum / sym_group.order() + return result + + +#****************************************************************************** + +class CompFullySym(CompWithSym): + r""" + Class for storing fully symmetric components with respect to a given + "frame". + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). + The stored quantities can be tensor components or non-tensorial quantities. + + INPUT: + + - ``ring`` -- ring in which each component takes its value + - ``frame`` -- frame with respect to which the components are defined; + whatever type ``frame`` is, it should have some method ``__len__()`` + implemented, so that ``len(frame)`` returns the dimension, i.e. the size + of a single index range + - ``nb_indices`` -- number of indices labeling the components + - ``start_index`` -- (default: 0) first value of a single index; + accordingly a component index i must obey + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access + operator ``[...]`` (method __getitem__); ``output_formatter`` must take + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + the second one, if any, some format specification. + + EXAMPLES: + + Symmetric components with 2 indices on a 3-dimensional space:: + + sage: from sage.tensor.modules.comp import CompFullySym, CompWithSym + sage: V = VectorSpace(QQ, 3) + sage: c = CompFullySym(QQ, V.basis(), 2) + sage: c[0,0], c[0,1], c[1,2] = 1, -2, 3 + sage: c[:] # note that c[1,0] and c[2,1] have been updated automatically (by symmetry) + [ 1 -2 0] + [-2 0 3] + [ 0 3 0] + + Internally, only non-redundant and non-zero components are stored:: + + sage: c._comp # random output order of the component dictionary + {(0, 0): 1, (0, 1): -2, (1, 2): 3} + + Same thing, but with the starting index set to 1:: + + sage: c1 = CompFullySym(QQ, V.basis(), 2, start_index=1) + sage: c1[1,1], c1[1,2], c1[2,3] = 1, -2, 3 + sage: c1[:] + [ 1 -2 0] + [-2 0 3] + [ 0 3 0] + + The values stored in ``c`` and ``c1`` are equal:: + + sage: c1[:] == c[:] + True + + but not ``c`` and ``c1``, since their starting indices differ:: + + sage: c1 == c + False + + Fully symmetric components with 3 indices on a 3-dimensional space:: + + sage: a = CompFullySym(QQ, V.basis(), 3) + sage: a[0,1,2] = 3 + sage: a[:] + [[[0, 0, 0], [0, 0, 3], [0, 3, 0]], + [[0, 0, 3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [3, 0, 0], [0, 0, 0]]] + sage: a[0,1,0] = 4 + sage: a[:] + [[[0, 4, 0], [4, 0, 3], [0, 3, 0]], + [[4, 0, 3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [3, 0, 0], [0, 0, 0]]] + + The full symmetry is preserved by the arithmetics:: + + sage: b = CompFullySym(QQ, V.basis(), 3) + sage: b[0,0,0], b[0,1,0], b[1,0,2], b[1,2,2] = -2, 3, 1, -5 + sage: s = a + 2*b ; s + fully symmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: a[:], b[:], s[:] + ([[[0, 4, 0], [4, 0, 3], [0, 3, 0]], + [[4, 0, 3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [3, 0, 0], [0, 0, 0]]], + [[[-2, 3, 0], [3, 0, 1], [0, 1, 0]], + [[3, 0, 1], [0, 0, 0], [1, 0, -5]], + [[0, 1, 0], [1, 0, -5], [0, -5, 0]]], + [[[-4, 10, 0], [10, 0, 5], [0, 5, 0]], + [[10, 0, 5], [0, 0, 0], [5, 0, -10]], + [[0, 5, 0], [5, 0, -10], [0, -10, 0]]]) + + It is lost if the added object is not fully symmetric:: + + sage: b1 = CompWithSym(QQ, V.basis(), 3, sym=(0,1)) # b1 has only symmetry on index positions (0,1) + sage: b1[0,0,0], b1[0,1,0], b1[1,0,2], b1[1,2,2] = -2, 3, 1, -5 + sage: s = a + 2*b1 ; s # the result has the same symmetry as b1: + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: a[:], b1[:], s[:] + ([[[0, 4, 0], [4, 0, 3], [0, 3, 0]], + [[4, 0, 3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [3, 0, 0], [0, 0, 0]]], + [[[-2, 0, 0], [3, 0, 1], [0, 0, 0]], + [[3, 0, 1], [0, 0, 0], [0, 0, -5]], + [[0, 0, 0], [0, 0, -5], [0, 0, 0]]], + [[[-4, 4, 0], [10, 0, 5], [0, 3, 0]], + [[10, 0, 5], [0, 0, 0], [3, 0, -10]], + [[0, 3, 0], [3, 0, -10], [0, 0, 0]]]) + sage: s = 2*b1 + a ; s + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with symmetry on the index positions (0, 1) + sage: 2*b1 + a == a + 2*b1 + True + + """ + def __init__(self, ring, frame, nb_indices, start_index=0, + output_formatter=None): + CompWithSym.__init__(self, ring, frame, nb_indices, start_index, + output_formatter, sym=range(nb_indices)) + + def _repr_(self): + r""" + String representation of the object. + """ + return "fully symmetric " + str(self.nid) + "-indices" + \ + " components w.r.t. " + str(self.frame) + + def _new_instance(self): + r""" + Creates a :class:`CompFullySym` instance w.r.t. the same frame, + and with the same number of indices. + + """ + return CompFullySym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter) + + def __getitem__(self, args): + r""" + Returns the component corresponding to the given indices. + + INPUT: + + - ``args`` -- list of indices (possibly a single integer if + self is a 1-index object) or the character ``:`` for the full list + of components. + + OUTPUT: + + - the component corresponding to ``args`` or, if ``args`` = ``:``, + the full list of components, in the form ``T[i][j]...`` for the components + `T_{ij...}` (for a 2-indices object, a matrix is returned). + + """ + no_format = self.output_formatter is None + format_type = None # default value, possibly redefined below + if isinstance(args, list): # case of [[...]] syntax + no_format = True + if isinstance(args[0], slice): + indices = args[0] + elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] + else: + indices = tuple(args) + else: + # Determining from the input the list of indices and the format + if isinstance(args, (int, Integer)) or isinstance(args, slice): + indices = args + elif isinstance(args[0], slice): + indices = args[0] + format_type = args[1] + elif len(args) == self.nid: + indices = args + else: + format_type = args[-1] + indices = args[:-1] + if isinstance(indices, slice): + return self._get_list(indices, no_format, format_type) + else: + ind = self._ordered_indices(indices)[1] # [0]=sign is not used + if ind in self._comp: # non zero value + if no_format: + return self._comp[ind] + elif format_type is None: + return self.output_formatter(self._comp[ind]) + else: + return self.output_formatter(self._comp[ind], format_type) + else: # the value is zero + if no_format: + return self.ring.zero_element() + elif format_type is None: + return self.output_formatter(self.ring.zero_element()) + else: + return self.output_formatter(self.ring.zero_element(), + format_type) + + def __setitem__(self, indices, value): + r""" + Sets the component corresponding to the given indices. + + INPUT: + + - ``indices`` -- list of indices (possibly a single integer if + self is a 1-index object) ; if [:] is provided, all the components + are set. + - ``value`` -- the value to be set or a list of values if ``args`` + == ``[:]`` + + """ + if isinstance(indices, slice): + self._set_list(indices, value) + else: + if isinstance(indices, list): + # to ensure equivalence between [i,j,...] and [[i,j,...]] or + # [[(i,j,...)]] + if isinstance(indices[0], tuple) or isinstance(indices[0], list): + indices = indices[0] + else: + indices = tuple(indices) + ind = self._ordered_indices(indices)[1] # [0]=sign is not used + if value == 0: + if ind in self._comp: + del self._comp[ind] # zero values are not stored + else: + self._comp[ind] = self.ring(value) + + + def __add__(self, other): + r""" + Component addition. + + INPUT: + + - ``other`` -- components of the same number of indices and defined + on the same frame as ``self`` + + OUTPUT: + + - components resulting from the addition of ``self`` and ``other`` + + """ + if other == 0: + return +self + if not isinstance(other, Components): + raise TypeError("The second argument for the addition must be a " + + "an instance of Components.") + if isinstance(other, CompFullySym): + if other.frame != self.frame: + raise TypeError("The two sets of components are not defined " + + "on the same frame.") + if other.nid != self.nid: + raise TypeError("The two sets of components do not have the " + + "same number of indices.") + if other.sindex != self.sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + result = self.copy() + for ind, val in other._comp.items(): + result[[ind]] += val + return result + else: + return CompWithSym.__add__(self, other) + + +#****************************************************************************** + +class CompFullyAntiSym(CompWithSym): + r""" + Class for storing fully antisymmetric components with respect to a given + "frame". + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). + The stored quantities can be tensor components or non-tensorial quantities. + + INPUT: + + - ``ring`` -- ring in which each component takes its value + - ``frame`` -- frame with respect to which the components are defined; + whatever type ``frame`` is, it should have some method ``__len__()`` + implemented, so that ``len(frame)`` returns the dimension, i.e. the size + of a single index range + - ``nb_indices`` -- number of indices labeling the components + - ``start_index`` -- (default: 0) first value of a single index; + accordingly a component index i must obey + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access + operator ``[...]`` (method __getitem__); ``output_formatter`` must take + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + the second one, if any, some format specification. + + EXAMPLES: + + Antisymmetric components with 2 indices on a 3-dimensional space:: + + sage: from sage.tensor.modules.comp import CompWithSym, CompFullyAntiSym + sage: V = VectorSpace(QQ, 3) + sage: c = CompFullyAntiSym(QQ, V.basis(), 2) + sage: c[0,1], c[0,2], c[1,2] = 3, 1/2, -1 + sage: c[:] # note that all components have been set according to the antisymmetry + [ 0 3 1/2] + [ -3 0 -1] + [-1/2 1 0] + + Internally, only non-redundant and non-zero components are stored:: + + sage: c._comp # random output order of the component dictionary + {(0, 1): 3, (0, 2): 1/2, (1, 2): -1} + + Same thing, but with the starting index set to 1:: + + sage: c1 = CompFullyAntiSym(QQ, V.basis(), 2, start_index=1) + sage: c1[1,2], c1[1,3], c1[2,3] = 3, 1/2, -1 + sage: c1[:] + [ 0 3 1/2] + [ -3 0 -1] + [-1/2 1 0] + + The values stored in ``c`` and ``c1`` are equal:: + + sage: c1[:] == c[:] + True + + but not ``c`` and ``c1``, since their starting indices differ:: + + sage: c1 == c + False + + Fully antisymmetric components with 3 indices on a 3-dimensional space:: + + sage: a = CompFullyAntiSym(QQ, V.basis(), 3) + sage: a[0,1,2] = 3 # the only independent component in dimension 3 + sage: a[:] + [[[0, 0, 0], [0, 0, 3], [0, -3, 0]], + [[0, 0, -3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [-3, 0, 0], [0, 0, 0]]] + + Setting a nonzero value incompatible with the antisymmetry results in an + error:: + + sage: a[0,1,0] = 4 + Traceback (most recent call last): + ... + ValueError: By antisymmetry, the component cannot have a nonzero value for the indices (0, 1, 0) + sage: a[0,1,0] = 0 # OK + sage: a[2,0,1] = 3 # OK + + The full antisymmetry is preserved by the arithmetics:: + + sage: b = CompFullyAntiSym(QQ, V.basis(), 3) + sage: b[0,1,2] = -4 + sage: s = a + 2*b ; s + fully antisymmetric 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: a[:], b[:], s[:] + ([[[0, 0, 0], [0, 0, 3], [0, -3, 0]], + [[0, 0, -3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [-3, 0, 0], [0, 0, 0]]], + [[[0, 0, 0], [0, 0, -4], [0, 4, 0]], + [[0, 0, 4], [0, 0, 0], [-4, 0, 0]], + [[0, -4, 0], [4, 0, 0], [0, 0, 0]]], + [[[0, 0, 0], [0, 0, -5], [0, 5, 0]], + [[0, 0, 5], [0, 0, 0], [-5, 0, 0]], + [[0, -5, 0], [5, 0, 0], [0, 0, 0]]]) + + It is lost if the added object is not fully antisymmetric:: + + sage: b1 = CompWithSym(QQ, V.basis(), 3, antisym=(0,1)) # b1 has only antisymmetry on index positions (0,1) + sage: b1[0,1,2] = -4 + sage: s = a + 2*b1 ; s # the result has the same symmetry as b1: + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: a[:], b1[:], s[:] + ([[[0, 0, 0], [0, 0, 3], [0, -3, 0]], + [[0, 0, -3], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [-3, 0, 0], [0, 0, 0]]], + [[[0, 0, 0], [0, 0, -4], [0, 0, 0]], + [[0, 0, 4], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [0, 0, 0], [0, 0, 0]]], + [[[0, 0, 0], [0, 0, -5], [0, -3, 0]], + [[0, 0, 5], [0, 0, 0], [3, 0, 0]], + [[0, 3, 0], [-3, 0, 0], [0, 0, 0]]]) + sage: s = 2*b1 + a ; s + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ], with antisymmetry on the index positions (0, 1) + sage: 2*b1 + a == a + 2*b1 + True + + """ + def __init__(self, ring, frame, nb_indices, start_index=0, + output_formatter=None): + CompWithSym.__init__(self, ring, frame, nb_indices, start_index, + output_formatter, antisym=range(nb_indices)) + + def _repr_(self): + r""" + String representation of the object. + """ + return "fully antisymmetric " + str(self.nid) + "-indices" + \ + " components w.r.t. " + str(self.frame) + + def _new_instance(self): + r""" + Creates a :class:`CompFullyAntiSym` instance w.r.t. the same frame, + and with the same number of indices. + + """ + return CompFullyAntiSym(self.ring, self.frame, self.nid, self.sindex, + self.output_formatter) + + + def __add__(self, other): + r""" + Component addition. + + INPUT: + + - ``other`` -- components of the same number of indices and defined + on the same frame as ``self`` + + OUTPUT: + + - components resulting from the addition of ``self`` and ``other`` + + """ + if other == 0: + return +self + if not isinstance(other, Components): + raise TypeError("The second argument for the addition must be a " + + "an instance of Components.") + if isinstance(other, CompFullyAntiSym): + if other.frame != self.frame: + raise TypeError("The two sets of components are not defined " + + "on the same frame.") + if other.nid != self.nid: + raise TypeError("The two sets of components do not have the " + + "same number of indices.") + if other.sindex != self.sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + result = self.copy() + for ind, val in other._comp.items(): + result[[ind]] += val + return result + else: + return CompWithSym.__add__(self, other) + + +#****************************************************************************** + +class KroneckerDelta(CompFullySym): + r""" + Kronecker delta `\delta_{ij}`. + + INPUT: + + - ``ring`` -- ring in which each component takes its value + - ``frame`` -- frame with respect to which the components are defined; + whatever type ``frame`` is, it should have some method ``__len__()`` + implemented, so that ``len(frame)`` returns the dimension, i.e. the size + of a single index range + - ``start_index`` -- (default: 0) first value of a single index; + accordingly a component index i must obey + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access + operator ``[...]`` (method __getitem__); ``output_formatter`` must take + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + the second one, if any, some format specification. + + EXAMPLES: + + The Kronecker delta on a 3-dimensional space:: + + sage: from sage.tensor.modules.comp import KroneckerDelta + sage: V = VectorSpace(QQ,3) + sage: d = KroneckerDelta(QQ, V.basis()) ; d + Kronecker delta of size 3x3 + sage: d[:] + [1 0 0] + [0 1 0] + [0 0 1] + + One can read, but not set, the components of a Kronecker delta:: + + sage: d[1,1] + 1 + sage: d[1,1] = 2 + Traceback (most recent call last): + ... + NotImplementedError: The components of a Kronecker delta cannot be changed. + + Examples of use with output formatters:: + + sage: d = KroneckerDelta(QQ, V.basis(), output_formatter=Rational.numerical_approx) + sage: d[:] # default format (53 bits of precision) + [ 1.00000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 1.00000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 1.00000000000000] + sage: d[:,10] # format = 10 bits of precision + [ 1.0 0.00 0.00] + [0.00 1.0 0.00] + [0.00 0.00 1.0] + sage: d = KroneckerDelta(QQ, V.basis(), output_formatter=str) + sage: d[:] + [['1', '0', '0'], ['0', '1', '0'], ['0', '0', '1']] + + + """ + def __init__(self, ring, frame, start_index=0, output_formatter=None): + CompFullySym.__init__(self, ring, frame, 2, start_index, + output_formatter) + for i in range(self.sindex, self.dim + self.sindex): + self._comp[(i,i)] = self.ring(1) + + def _repr_(self): + r""" + String representation of the object. + """ + n = str(self.dim) + return "Kronecker delta of size " + n + "x" + n + + def __setitem__(self, args, value): + r""" + Should not be used (the components of a Kronecker delta are constant) + """ + raise NotImplementedError("The components of a Kronecker delta " + + "cannot be changed.") diff --git a/src/sage/tensor/modules/finite_free_module.py b/src/sage/tensor/modules/finite_free_module.py new file mode 100644 index 00000000000..e793914cd94 --- /dev/null +++ b/src/sage/tensor/modules/finite_free_module.py @@ -0,0 +1,1335 @@ +r""" +Free modules of finite rank + +The class :class:`FiniteFreeModule` implements free modules of finite rank +over a commutative ring. + +A *free module of finite rank* over a commutative ring `R` is a module `M` over +`R` that admits a *finite basis*, i.e. a finite familly of linearly independent +generators. Since `R` is commutative, it has the invariant basis number +property, so that the rank of the free module `M` is defined uniquely, as the +cardinality of any basis of `M`. + +No distinguished basis of `M` is assumed. On the contrary, many bases can be +introduced on the free module along with change-of-basis rules (as module +automorphisms). Each +module element has then various representations over the various bases. + +.. NOTE:: + + The class :class:`FiniteFreeModule` does not inherit from + :class:`~sage.modules.free_module.FreeModule_generic` since the latter + is a derived class of :class:`~sage.modules.module.Module_old`, + which does not conform to the new coercion model. + Moreover, the class :class:`~sage.modules.free_module.FreeModule_generic` + seems to assume a distinguished basis (cf. its method + :meth:`~sage.modules.free_module.FreeModule_generic.basis`). + Besides, the class :class:`FiniteFreeModule` does not inherit + from the class + :class:`~sage.combinat.free_module.CombinatorialFreeModule`, which conforms + to the new coercion model, since this class is devoted to modules with a + distinguished basis. + +For the above reasons, the class :class:`FiniteFreeModule` inherits directly from +the generic class :class:`~sage.modules.module.Module` and each instance of +:class:`FiniteFreeModule` belongs to the category +:class:`~sage.categories.modules.Modules` +and not to the category +:class:`~sage.categories.modules_with_basis.ModulesWithBasis`. + +TODO: + +- implement submodules +- implement free module homomorphisms (at the moment, only two specific kinds + of homomorphisms are implemented: endomorphisms, cf. :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism`, + and linear forms, cf. + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm`) +- create a FreeModules category (cf. the *TODO* statement in the documentation + of :class:`~sage.categories.modules.Modules`: *Implement a FreeModules(R) + category, when so prompted by a concrete use case*) + + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +EXAMPLES: + +Let us define a free module of rank 2 over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 2, name='M') ; M + rank-2 free module M over the Integer Ring + +We introduce a first basis on M:: + + sage: e = M.basis('e') ; e + basis (e_0,e_1) on the rank-2 free module M over the Integer Ring + +The elements of the basis are of course module elements:: + + sage: e[0] + element e_0 of the rank-2 free module M over the Integer Ring + sage: e[1] + element e_1 of the rank-2 free module M over the Integer Ring + sage: e[0].parent() + rank-2 free module M over the Integer Ring + +We define a module element by its components w.r.t. basis e:: + + sage: u = M([2,-3], basis=e, name='u') + sage: u.view(basis=e) + u = 2 e_0 - 3 e_1 + +Since the first defined basis is considered as the default one on the module, +the above can be abridged to:: + + sage: u = M([2,-3], name='u') + sage: u.view() + u = 2 e_0 - 3 e_1 + +Module elements can be compared:: + + sage: u == 2*e[0] - 3*e[1] + True + +We define a second basis on M by linking it to e via a module automorphism:: + + sage: a = M.automorphism() + sage: a.set_comp(basis=e)[0,1] = -1 ; a.set_comp(basis=e)[1,0] = 1 # only the non-zero components have to be set + sage: a[:] # a matrix view of the automorphism in the module's default basis + [ 0 -1] + [ 1 0] + sage: f = e.new_basis(a, 'f') ; f + basis (f_0,f_1) on the rank-2 free module M over the Integer Ring + sage: f[0].view() + f_0 = e_1 + sage: f[1].view() + f_1 = -e_0 + +We may check that the basis f is the image of e by the automorphism a:: + + sage: f[0] == a(e[0]) + True + sage: f[1] == a(e[1]) + True + +We introduce a new module element via its components w.r.t. basis f:: + + sage: v = M([2,4], basis=f, name='v') + sage: v.view(basis=f) + v = 2 f_0 + 4 f_1 + +The sum of the two module elements u and v can be performed even if they have +been defined on different bases, thanks to the known relation between the +two bases:: + + sage: s = u + v ; s + element u+v of the rank-2 free module M over the Integer Ring + +We can view the result in either basis:: + + sage: s.view(basis=e) # a shortcut is s.view(), e being the default basis + u+v = -2 e_0 - e_1 + sage: s.view(basis=f) + u+v = -f_0 + 2 f_1 + +Of course, we can view each of the individual element in either basis:: + + sage: u.view(basis=f) # recall: u was introduced via basis e + u = -3 f_0 - 2 f_1 + sage: v.view(basis=e) # recall: v was introduced via basis f + v = -4 e_0 + 2 e_1 + +Tensor products are implemented:: + + sage: t = u*v ; t + type-(2,0) tensor u*v on the rank-2 free module M over the Integer Ring + sage: t.parent() + free module of type-(2,0) tensors on the rank-2 free module M over the Integer Ring + sage: t.view() + u*v = -8 e_0*e_0 + 4 e_0*e_1 + 12 e_1*e_0 - 6 e_1*e_1 + +The automorphism a is considered as a tensor of type (1,1) on M:: + + sage: a.parent() + free module of type-(1,1) tensors on the rank-2 free module M over the Integer Ring + sage: a.view() + -e_0*e^1 + e_1*e^0 + +As such, we can form its tensor product with t, yielding a tensor of type (3,1):: + + sage: (t*a).parent() + free module of type-(3,1) tensors on the rank-2 free module M over the Integer Ring + sage: (t*a).view() + 8 e_0*e_0*e_0*e^1 - 8 e_0*e_0*e_1*e^0 - 4 e_0*e_1*e_0*e^1 + 4 e_0*e_1*e_1*e^0 - 12 e_1*e_0*e_0*e^1 + 12 e_1*e_0*e_1*e^0 + 6 e_1*e_1*e_0*e^1 - 6 e_1*e_1*e_1*e^0 + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.modules.module import Module +from free_module_tensor import FiniteFreeModuleElement +from sage.structure.unique_representation import UniqueRepresentation + +# From sage/modules/module.pyx: +# ---------------------------- +### The new Module class that should be the base of all Modules +### The derived Module class must implement the element +### constructor: +# +# class MyModule(sage.modules.module.Module): +# Element = MyElement +# def _element_constructor_(self, x): +# return self.element_class(x) +# + + +class FiniteFreeModule(UniqueRepresentation, Module): + r""" + Free module of finite rank over a commutative ring `R`. + + This class inherits from the generic class + :class:`~sage.modules.module.Module`. + + .. NOTE:: + + The class :class:`FiniteFreeModule` does not inherit from + :class:`~sage.modules.free_module.FreeModule_generic` since the latter + is a derived class of :class:`~sage.modules.module.Module_old`, + which does not conform to the new coercion model. + Besides, the class :class:`FiniteFreeModule` does not inherit + from the class :class:`CombinatorialFreeModule` since the latter is + devoted to modules *with a basis*. + + The class :class:`FiniteFreeModule` is a Sage *Parent* class whose elements + belong to the class + :class:`~sage.tensor.modules.free_module_tensor.FiniteFreeModuleElement`. + + INPUT: + + - ``ring`` -- commutative ring `R` over which the free module is + constructed. + - ``rank`` -- (positive integer) rank of the free module + - ``name`` -- (string; default: None) name given to the free module + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the free + module; if none is provided, it is set to ``name`` + - ``start_index`` -- (integer; default: 0) lower bound of the range of + indices in bases defined on the free module + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the tensor components; + ``output_formatter`` must take 1 or 2 arguments: the 1st argument must be + an element of the ring `R` and the second one, if any, some format + specification. + + EXAMPLES: + + Free module of rank 3 over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3) ; M + rank-3 free module over the Integer Ring + sage: M = FiniteFreeModule(ZZ, 3, name='M') ; M # declaration with a name + rank-3 free module M over the Integer Ring + sage: M.category() + Category of modules over Integer Ring + sage: M.base_ring() + Integer Ring + sage: M.rank() + 3 + + If the base ring is a field, the free module is in the category of vector + spaces:: + + sage: V = FiniteFreeModule(QQ, 3, name='V') ; V + rank-3 free module V over the Rational Field + sage: V.category() + Category of vector spaces over Rational Field + + The LaTeX output is adjusted via the parameter ``latex_name``:: + + sage: latex(M) # the default is the symbol provided in the string ``name`` + M + sage: M = FiniteFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') + sage: latex(M) + \mathcal{M} + + M is a *parent* object, whose elements are instances of + :class:`~sage.tensor.modules.free_module_tensor.FiniteFreeModuleElement`:: + + sage: v = M.an_element() ; v + element of the rank-3 free module M over the Integer Ring + sage: from sage.tensor.modules.free_module_tensor import FiniteFreeModuleElement + sage: isinstance(v, FiniteFreeModuleElement) + True + sage: v in M + True + sage: M.is_parent_of(v) + True + + The free module M has no distinguished basis:: + + sage: M in ModulesWithBasis(ZZ) + False + sage: M in Modules(ZZ) + True + + Bases have to be introduced by means of the method :meth:`basis`, + the first defined basis being considered as the *default basis*, meaning + it can be skipped in function arguments required a basis (this can + be changed by means of the method :meth:`set_default_basis`):: + + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: M.default_basis() + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + + + Elements can be constructed by means of the __call__ operator acting + on the parent; 0 yields the zero element:: + + sage: M(0) + element zero of the rank-3 free module M over the Integer Ring + sage: M(0) is M.zero() + True + + Non-zero elements are constructed by providing their components in + a given basis:: + + sage: v = M([-1,0,3]) ; v # components in the default basis (e) + element of the rank-3 free module M over the Integer Ring + sage: v.view() + -e_0 + 3 e_2 + sage: f = M.basis('f') + sage: v = M([-1,0,3], basis=f) ; v # components in a specific basis + element of the rank-3 free module M over the Integer Ring + sage: v.view(f) + -f_0 + 3 f_2 + sage: v = M([-1,0,3], basis=f, name='v') ; v + element v of the rank-3 free module M over the Integer Ring + sage: v.view(f) + v = -f_0 + 3 f_2 + + An alternative is to construct the element from an empty list of components + and to set the nonzero components afterwards:: + + sage: v = M([], name='v') + sage: v.set_comp(e)[0] = -1 + sage: v.set_comp(e)[2] = 3 + sage: v.view(e) + v = -e_0 + 3 e_2 + + Indices on the free module, such as indices labelling the element of a + basis, are provided by the generator method :meth:`irange`. By default, + they range from 0 to the module's rank minus one:: + + sage: for i in M.irange(): print i, + 0 1 2 + + This can be changed via the parameter ``start_index`` in the module + construction:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: for i in M.irange(): print i, + 1 2 3 + + The parameter ``output_formatter`` in the constructor of the free module + is used to set the output format of tensor components:: + + sage: M = FiniteFreeModule(QQ, 3, output_formatter=Rational.numerical_approx) + sage: e = M.basis('e') + sage: v = M([1/3, 0, -2], basis=e) + sage: v.comp(e)[:] + [0.333333333333333, 0.000000000000000, -2.00000000000000] + sage: v.view(e) # default format (53 bits of precision) + 0.333333333333333 e_0 - 2.00000000000000 e_2 + sage: v.view(e, format_spec=10) # 10 bits of precision + 0.33 e_0 - 2.0 e_2 + + All the tests from the suite for the category + :class:`~sage.categories.modules.Modules` are passed:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: TestSuite(M).run(verbose=True) + running ._test_additive_associativity() . . . pass + running ._test_an_element() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_nonzero_equal() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + running ._test_some_elements() . . . pass + running ._test_zero() . . . pass + + """ + + Element = FiniteFreeModuleElement + + def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, + output_formatter=None): + if not ring.is_commutative(): + raise TypeError("The module base ring must be commutative.") + Module.__init__(self, ring) + self.ring = ring # for efficiency (to avoid calls to self.base_ring()) + self._rank = rank + self.name = name + if latex_name is None: + self.latex_name = self.name + else: + self.latex_name = latex_name + self.sindex = start_index + self.output_formatter = output_formatter + # Dictionary of the tensor modules built on self + # (dict. keys = (k,l) --the tensor type) + self._tensor_modules = {(1,0): self} # self is considered as the sets of + # tensors of type (1,0) + self.known_bases = [] # List of known bases on the free module + self.def_basis = None # default basis + self.basis_changes = {} # Dictionary of the changes of bases + # Zero element: + if not hasattr(self, '_zero_element'): + self._zero_element = self._element_constructor_(name='zero', + latex_name='0') + + + + #### Methods required for any Parent + def _element_constructor_(self, comp=[], basis=None, name=None, + latex_name=None): + r""" + Construct an element of the module + """ + if comp == 0: + return self._zero_element + resu = self.element_class(self, name=name, latex_name=latex_name) + if comp != []: + resu.set_comp(basis)[:] = comp + return resu + + def _an_element_(self): + r""" + Construct some (unamed) element of the module + """ + resu = self.element_class(self) + if self.def_basis is not None: + resu.set_comp()[:] = [self.ring.an_element() for i in + range(self._rank)] + return resu + + #### End of methods required for any Parent + + def _repr_(self): + r""" + String representation of the object. + """ + description = "rank-" + str(self._rank) + " free module " + if self.name is not None: + description += self.name + " " + description += "over the " + str(self.ring) + return description + + def _latex_(self): + r""" + LaTeX representation of the object. + """ + if self.latex_name is None: + return r'\mbox{' + str(self) + r'}' + else: + return self.latex_name + + def rank(self): + r""" + Return the rank of the free module ``self``. + + Since the ring over which ``self`` is built is assumed to be + commutative (and hence has the invariant basis number property), the + rank is defined uniquely, as the cardinality of any basis of ``self``. + + EXAMPLES: + + Rank of free modules over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3) + sage: M.rank() + 3 + sage: M.tensor_module(0,1).rank() + 3 + sage: M.tensor_module(0,2).rank() + 9 + sage: M.tensor_module(1,0).rank() + 3 + sage: M.tensor_module(1,1).rank() + 9 + sage: M.tensor_module(1,2).rank() + 27 + sage: M.tensor_module(2,2).rank() + 81 + + """ + return self._rank + + def zero(self): + r""" + Return the zero element. + + EXAMPLES: + + Zero elements of free modules over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M.zero() + element zero of the rank-3 free module M over the Integer Ring + sage: M.zero().parent() is M + True + sage: M.zero() is M(0) + True + sage: T = M.tensor_module(1,1) + sage: T.zero() + type-(1,1) tensor zero on the rank-3 free module M over the Integer Ring + sage: T.zero().parent() is T + True + sage: T.zero() is T(0) + True + + Components of the zero element with respect to some basis:: + + sage: e = M.basis('e') + sage: M.zero().comp(e)[:] + [0, 0, 0] + sage: for i in M.irange(): print M.zero().comp(e)[i] == M.base_ring().zero(), + True True True + sage: T.zero().comp(e)[:] + [0 0 0] + [0 0 0] + [0 0 0] + sage: M.tensor_module(1,2).zero().comp(e)[:] + [[[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]]] + + """ + return self._zero_element + + + def tensor_module(self, k, l): + r""" + Return the free module of all tensors of type (k,l) defined on + ``self``. + + INPUT: + + - ``k`` -- (non-negative integer) the contravariant rank, the tensor type + being (k,l) + - ``l`` -- (non-negative integer) the covariant rank, the tensor type + being (k,l) + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` + representing the free module + `T^{(k,l)}(M)` of type-`(k,l)` tensors on the free module ``self``. + + EXAMPLES: + + Tensor modules over a free module over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(1,2) ; T + free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring + sage: T.an_element() + type-(1,2) tensor on the rank-3 free module M over the Integer Ring + + Tensor modules are unique:: + + sage: M.tensor_module(1,2) is T + True + + The base module is itself the module of all type-(1,0) tensors:: + + sage: M.tensor_module(1,0) is M + True + + """ + from tensor_free_module import TensorFreeModule + if (k,l) not in self._tensor_modules: + self._tensor_modules[(k,l)] = TensorFreeModule(self, (k,l)) + return self._tensor_modules[(k,l)] + + def dual(self): + r""" + Return the dual module. + + EXAMPLE: + + Dual of a free module over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M.dual() + dual of the rank-3 free module M over the Integer Ring + sage: latex(M.dual()) + M^* + + The dual is a free module of the same rank as M:: + + sage: isinstance(M.dual(), FiniteFreeModule) + True + sage: M.dual().rank() + 3 + + It is formed by tensors of type (0,1), i.e. linear forms:: + + sage: M.dual() is M.tensor_module(0,1) + True + sage: M.dual().an_element() + type-(0,1) tensor on the rank-3 free module M over the Integer Ring + sage: a = M.linear_form() + sage: a in M.dual() + True + + The elements of a dual basis belong of course to the dual module:: + + sage: e = M.basis('e') + sage: e.dual_basis()[0] in M.dual() + True + + """ + return self.tensor_module(0,1) + + def irange(self, start=None): + r""" + Single index generator, labelling the elements of a basis. + + INPUT: + + - ``start`` -- (integer; default: None) initial value of the index; if none is + provided, ``self.sindex`` is assumed + + OUTPUT: + + - an iterable index, starting from ``start`` and ending at + ``self.sindex + self.rank() -1`` + + EXAMPLES: + + Index range on a rank-3 module:: + + sage: M = FiniteFreeModule(ZZ, 3) + sage: for i in M.irange(): print i, + 0 1 2 + sage: for i in M.irange(start=1): print i, + 1 2 + + The default starting value corresponds to the parameter ``start_index`` + provided at the module construction (the default value being 0):: + + sage: M1 = FiniteFreeModule(ZZ, 3, start_index=1) + sage: for i in M1.irange(): print i, + 1 2 3 + sage: M2 = FiniteFreeModule(ZZ, 3, start_index=-4) + sage: for i in M2.irange(): print i, + -4 -3 -2 + + """ + si = self.sindex + imax = self._rank + si + if start is None: + i = si + else: + i = start + while i < imax: + yield i + i += 1 + + def basis(self, symbol=None, latex_symbol=None): + r""" + Define a basis of the free module. + + If the basis specified by the given symbol already exists, it is + simply returned. + + INPUT: + + - ``symbol`` -- (string; default: None) a letter (of a few letters) to + denote a generic element of the basis; if None, the module's default + basis is returned. + - ``latex_symbol`` -- (string; default: None) symbol to denote a + generic element of the basis; if None, the value of ``symbol`` is + used. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing a basis on ``self``. + + EXAMPLES: + + Bases on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: e[0] + element e_0 of the rank-3 free module M over the Integer Ring + sage: latex(e) + \left(e_0,e_1,e_2\right) + + The LaTeX symbol can be set explicitely, as the second argument of + :meth:`basis`:: + + sage: eps = M.basis('eps', r'\epsilon') ; eps + basis (eps_0,eps_1,eps_2) on the rank-3 free module M over the Integer Ring + sage: latex(eps) + \left(\epsilon_0,\epsilon_1,\epsilon_2\right) + + If the provided symbol is that of an already defined basis, the latter + is returned (no new basis is created):: + + sage: M.basis('e') is e + True + sage: M.basis('eps') is eps + True + + If no symbol is provided, the module's default basis is returned:: + + sage: M.basis() + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: M.basis() is e + True + sage: M.basis() is M.default_basis() + True + + The individual elements of the basis are labelled according the + parameter ``start_index`` provided at the free module construction:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: e[1] + element e_1 of the rank-3 free module M over the Integer Ring + + """ + from free_module_basis import FreeModuleBasis + if symbol is None: + return self.default_basis() + else: + for other in self.known_bases: + if symbol == other.symbol: + return other + return FreeModuleBasis(self, symbol, latex_symbol) + + def default_basis(self): + r""" + Return the default basis of the free module. + + The *default basis* is simply a basis whose name can be skipped in + methods requiring a basis as an argument. By default, it is the first + basis introduced on the module. It can be changed by the method + :meth:`set_default_basis`. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + + EXAMPLES: + + At the module construction, no default basis is assumed:: + + sage: M = FiniteFreeModule(ZZ, 2, name='M', start_index=1) + sage: M.default_basis() + No default basis has been defined on the rank-2 free module M over the Integer Ring + + The first defined basis becomes the default one:: + + sage: e = M.basis('e') ; e + basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + sage: M.default_basis() + basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + sage: f = M.basis('f') ; f + basis (f_1,f_2) on the rank-2 free module M over the Integer Ring + sage: M.default_basis() + basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + + """ + if self.def_basis is None: + print "No default basis has been defined on the " + str(self) + return self.def_basis + + def set_default_basis(self, basis): + r""" + Sets the default basis of the free module. + + The *default basis* is simply a basis whose name can be skipped in + methods requiring a basis as an argument. By default, it is the first + basis introduced on the module. + + INPUT: + + - ``basis`` -- instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing a basis on ``self`` + + EXAMPLES: + + Changing the default basis on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: f = M.basis('f') ; f + basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + sage: M.default_basis() + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: M.set_default_basis(f) + sage: M.default_basis() + basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + + """ + from free_module_basis import FreeModuleBasis + if not isinstance(basis, FreeModuleBasis): + raise TypeError("The argument is not a free module basis.") + if basis.fmodule is not self: + raise ValueError("The basis is not defined on the current module.") + self.def_basis = basis + + def view_bases(self): + r""" + Display the bases that have been defined on the free module. + + EXAMPLES: + + Bases on a rank-4 free module:: + + sage: M = FiniteFreeModule(ZZ, 4, name='M', start_index=1) + sage: M.view_bases() + No basis has been defined on the rank-4 free module M over the Integer Ring + sage: e = M.basis('e') + sage: M.view_bases() + Bases defined on the rank-4 free module M over the Integer Ring: + - (e_1,e_2,e_3,e_4) (default basis) + sage: f = M.basis('f') + sage: M.view_bases() + Bases defined on the rank-4 free module M over the Integer Ring: + - (e_1,e_2,e_3,e_4) (default basis) + - (f_1,f_2,f_3,f_4) + sage: M.set_default_basis(f) + sage: M.view_bases() + Bases defined on the rank-4 free module M over the Integer Ring: + - (e_1,e_2,e_3,e_4) + - (f_1,f_2,f_3,f_4) (default basis) + + """ + if self.known_bases == []: + print "No basis has been defined on the " + str(self) + else: + print "Bases defined on the " + str(self) + ":" + for basis in self.known_bases: + item = " - " + basis.name + if basis is self.def_basis: + item += " (default basis)" + print item + + def tensor(self, tensor_type, name=None, latex_name=None, sym=None, + antisym=None): + r""" + Construct a tensor on the free module. + + INPUT: + + - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l + the covariant rank + - ``name`` -- (string; default: None) name given to the tensor + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + tensor; if none is provided, the LaTeX symbol is set to ``name`` + - ``sym`` -- (default: None) a symmetry or a list of symmetries among the + tensor arguments: each symmetry is described by a tuple containing + the positions of the involved arguments, with the convention position=0 + for the first argument. For instance: + + * sym=(0,1) for a symmetry between the 1st and 2nd arguments + * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. + + - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries + among the arguments, with the same convention as for ``sym``. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + representing the tensor defined on ``self`` with the provided + characteristics. + + EXAMPLES: + + Tensors on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((1,0), name='t') ; t + element t of the rank-3 free module M over the Integer Ring + sage: t = M.tensor((0,1), name='t') ; t + linear form t on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((1,1), name='t') ; t + endomorphism t on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((0,2), name='t', sym=(0,1)) ; t + symmetric bilinear form t on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((0,2), name='t', antisym=(0,1)) ; t + alternating form t of degree 2 on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((1,2), name='t') ; t + type-(1,2) tensor t on the rank-3 free module M over the Integer Ring + + See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + for more examples and documentation. + + """ + from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement + from free_module_tensor_spec import FreeModuleEndomorphism, \ + FreeModuleSymBilinForm + from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm + if tensor_type==(1,0): + return FiniteFreeModuleElement(self, name=name, latex_name=latex_name) + elif tensor_type==(0,1): + return FreeModuleLinForm(self, name=name, latex_name=latex_name) + elif tensor_type==(1,1): + return FreeModuleEndomorphism(self, name=name, + latex_name=latex_name) + elif tensor_type==(0,2) and sym==(0,1): + return FreeModuleSymBilinForm(self, name=name, + latex_name=latex_name) + elif tensor_type[0]==0 and tensor_type[1]>1 and antisym is not None: + if len(antisym)==tensor_type[1]: + return FreeModuleAltForm(self, tensor_type[1], name=name, + latex_name=latex_name) + else: + return FreeModuleTensor(self, tensor_type, name=name, + latex_name=latex_name, sym=sym, + antisym=antisym) + else: + return FreeModuleTensor(self, tensor_type, name=name, + latex_name=latex_name, sym=sym, + antisym=antisym) + + def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): + r""" + Construct a tensor on the free module from a set of components. + + The tensor symmetries are deduced from those of the components. + + INPUT: + + - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l + the covariant rank + - ``comp`` -- instance of :class:`~sage.tensor.modules.comp.Components` + representing the tensor components in a given basis + - ``name`` -- (string; default: None) name given to the tensor + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the tensor; + if none is provided, the LaTeX symbol is set to ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + representing the tensor defined on ``self`` with the provided + characteristics. + + EXAMPLES: + + Construction of a tensor of rank 1:: + + sage: from sage.tensor.modules.comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: c = Components(ZZ, e, 1) + sage: c[:] + [0, 0, 0] + sage: c[:] = [-1,4,2] + sage: t = M.tensor_from_comp((1,0), c) + sage: t + element of the rank-3 free module M over the Integer Ring + sage: t.view(e) + -e_0 + 4 e_1 + 2 e_2 + sage: t = M.tensor_from_comp((0,1), c) ; t + linear form on the rank-3 free module M over the Integer Ring + sage: t.view(e) + -e^0 + 4 e^1 + 2 e^2 + + Construction of a tensor of rank 2:: + + sage: c = CompFullySym(ZZ, e, 2) + sage: c[0,0], c[1,2] = 4, 5 + sage: t = M.tensor_from_comp((0,2), c) ; t + symmetric bilinear form on the rank-3 free module M over the Integer Ring + sage: t.symmetries() + symmetry: (0, 1); no antisymmetry + sage: t.view(e) + 4 e^0*e^0 + 5 e^1*e^2 + 5 e^2*e^1 + sage: c = CompFullyAntiSym(ZZ, e, 2) + sage: c[0,1], c[1,2] = 4, 5 + sage: t = M.tensor_from_comp((0,2), c) ; t + alternating form of degree 2 on the rank-3 free module M over the Integer Ring + sage: t.view(e) + 4 e^0/\e^1 + 5 e^1/\e^2 + + """ + from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement + from free_module_tensor_spec import FreeModuleEndomorphism, \ + FreeModuleSymBilinForm + from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm + from comp import CompWithSym, CompFullySym, CompFullyAntiSym + # + # 0/ Compatibility checks: + if comp.ring is not self.ring: + raise TypeError("The components are not defined on the same" + + " ring as the module.") + if comp.frame not in self.known_bases: + raise TypeError("The components are not defined on a basis of" + + " the module.") + if comp.nid != tensor_type[0] + tensor_type[1]: + raise TypeError("Number of component indices not compatible with "+ + " the tensor type.") + # + # 1/ Construction of the tensor: + if tensor_type == (1,0): + resu = FiniteFreeModuleElement(self, name=name, latex_name=latex_name) + elif tensor_type == (0,1): + resu = FreeModuleLinForm(self, name=name, latex_name=latex_name) + elif tensor_type == (1,1): + resu = FreeModuleEndomorphism(self, name=name, + latex_name=latex_name) + elif tensor_type == (0,2) and isinstance(comp, CompFullySym): + resu = FreeModuleSymBilinForm(self, name=name, + latex_name=latex_name) + elif tensor_type[0] == 0 and tensor_type[1] > 1 and \ + isinstance(comp, CompFullyAntiSym): + resu = FreeModuleAltForm(self, tensor_type[1], name=name, + latex_name=latex_name) + else: + resu = FreeModuleTensor(self, tensor_type, name=name, + latex_name=latex_name) + # Tensor symmetries deduced from those of comp: + if isinstance(comp, CompWithSym): + resu.sym = comp.sym + resu.antisym = comp.antisym + # + # 2/ Tensor components set to comp: + resu.components[comp.frame] = comp + # + return resu + + def alternating_form(self, degree, name=None, latex_name=None): + r""" + Construct an alternating form on the free module. + + INPUT: + + - ``degree`` -- the degree of the alternating form (i.e. its tensor rank) + - ``name`` -- (string; default: None) name given to the alternating + form + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + alternating form; if none is provided, the LaTeX symbol is set to + ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` + (``degree`` > 1) or + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + (``degree`` = 1) + + EXAMPLES: + + Alternating forms on a rank-3 module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: a = M.alternating_form(2, 'a') ; a + alternating form a of degree 2 on the rank-3 free module M over the Integer Ring + + The nonzero components in a given basis have to be set in a second step, + thereby fully specifying the alternating form:: + + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: a.set_comp(e)[0,1] = 2 + sage: a.set_comp(e)[1,2] = -3 + sage: a.view(e) + a = 2 e^0/\e^1 - 3 e^1/\e^2 + + An alternating form of degree 1 is a linear form:: + + sage: a = M.alternating_form(1, 'a') ; a + linear form a on the rank-3 free module M over the Integer Ring + + To construct such a form, it is preferable to call the method + :meth:`linear_form` instead:: + + sage: a = M.linear_form('a') ; a + linear form a on the rank-3 free module M over the Integer Ring + + See + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` + for further documentation. + + """ + from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm + if degree == 1: + return FreeModuleLinForm(self, name=name, latex_name=latex_name) + else: + return FreeModuleAltForm(self, degree, name=name, + latex_name=latex_name) + + def linear_form(self, name=None, latex_name=None): + r""" + Construct a linear form on the free module. + + A *linear form* on a free module `M` over a ring `R` is a map + `M\rightarrow R` that is linear. It can be viewed as a tensor of type + (0,1) on `M`. + + INPUT: + + - ``name`` -- (string; default: None) name given to the linear + form + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + linear form; if none is provided, the LaTeX symbol is set to + ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + + EXAMPLES: + + Linear form on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form('A') ; a + linear form A on the rank-3 free module M over the Integer Ring + sage: a[:] = [2,-1,3] # components w.r.t. the module's default basis (e) + sage: a.view() + A = 2 e^0 - e^1 + 3 e^2 + + A linear form maps module elements to ring elements:: + + sage: v = M([1,1,1]) + sage: a(v) + 4 + + Test of linearity:: + + sage: u = M([-5,-2,7]) + sage: a(3*u - 4*v) == 3*a(u) - 4*a(v) + True + + See + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + for further documentation. + + """ + from free_module_alt_form import FreeModuleLinForm + return FreeModuleLinForm(self, name=name, latex_name=latex_name) + + def endomorphism(self, name=None, latex_name=None): + r""" + Construct an endomorphism on the free module. + + INPUT: + + - ``name`` -- (string; default: None) name given to the endomorphism + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + endomorphism; if none is provided, the LaTeX symbol is set to + ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + + EXAMPLES: + + Endomorphism on a rank-3 module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.endomorphism('T') ; t + endomorphism T on the rank-3 free module M over the Integer Ring + + An endomorphism is type-(1,1) tensor:: + + sage: t.parent() + free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + sage: t.tensor_type + (1, 1) + + Consequently, an endomorphism can also be created by the method + :meth:`tensor`:: + + sage: t = M.tensor((1,1), name='T') ; t + endomorphism T on the rank-3 free module M over the Integer Ring + + See + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + for further documentation. + + """ + from free_module_tensor_spec import FreeModuleEndomorphism + return FreeModuleEndomorphism(self, name=name, latex_name=latex_name) + + + def automorphism(self, name=None, latex_name=None): + r""" + Construct an automorphism on the free module. + + INPUT: + + - ``name`` -- (string; default: None) name given to the automorphism + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + automorphism; if none is provided, the LaTeX symbol is set to + ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + + EXAMPLES: + + Automorphism on a rank-2 free module (vector space) on `\QQ`:: + + sage: M = FiniteFreeModule(QQ, 2, name='M') + sage: a = M.automorphism('A') ; a + automorphism A on the rank-2 free module M over the Rational Field + + Automorphisms are tensors of type (1,1):: + + sage: a.parent() + free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field + sage: a.tensor_type + (1, 1) + + See + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + for further documentation. + + """ + from free_module_tensor_spec import FreeModuleAutomorphism + return FreeModuleAutomorphism(self, name=name, latex_name=latex_name) + + + def identity_map(self, name='Id', latex_name=None): + r""" + Construct the identity map on the free module. + + INPUT: + + - ``name`` -- (string; default: 'Id') name given to the identity map + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + identity map; if none is provided, the LaTeX symbol is set to + ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + + EXAMPLES: + + Identity map on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.identity_map() ; a + identity map on the rank-3 free module M over the Integer Ring + + The LaTeX symbol is set by default to Id, but can be changed:: + + sage: latex(a) + \mathrm{Id} + sage: a = M.identity_map(latex_name=r'\mathrm{1}') + sage: latex(a) + \mathrm{1} + + The identity map is a tensor of type (1,1) on the free module:: + + sage: a.parent() + free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + sage: a.tensor_type + (1, 1) + + See + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + for further documentation. + + """ + from free_module_tensor_spec import FreeModuleIdentityMap + return FreeModuleIdentityMap(self, name=name, latex_name=latex_name) + + + def sym_bilinear_form(self, name=None, latex_name=None): + r""" + Construct a symmetric bilinear form on the free module. + + INPUT: + + - ``name`` -- (string; default: None) name given to the symmetric + bilinear form + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + symmetric bilinear form; if none is provided, the LaTeX symbol is set to + ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleSymBilinForm` + + EXAMPLES: + + Symmetric bilinear form on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: a = M.sym_bilinear_form('A') ; a + symmetric bilinear form A on the rank-3 free module M over the Integer Ring + + A symmetric bilinear form is a type-(0,2) tensor that is symmetric:: + + sage: a.parent() + free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring + sage: a.tensor_type + (0, 2) + sage: a.symmetries() + symmetry: (0, 1); no antisymmetry + + See + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleSymBilinForm` + for further documentation. + + """ + from free_module_tensor_spec import FreeModuleSymBilinForm + return FreeModuleSymBilinForm(self, name=name, latex_name=latex_name) + + + + + + diff --git a/src/sage/tensor/modules/format_utilities.py b/src/sage/tensor/modules/format_utilities.py new file mode 100644 index 00000000000..f435ea4a9e4 --- /dev/null +++ b/src/sage/tensor/modules/format_utilities.py @@ -0,0 +1,240 @@ +r""" +Formatting utilities. + +This module defines helper functions that are not class methods. + + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014) : initial version +- Joris Vankerschaver (2010): for the function is_atomic() + +""" + +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.sage_object import SageObject + +def is_atomic(expression): + r""" + Helper function to check whether some LaTeX expression is atomic. + + Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written + by Joris Vankerschaver (2010) + + INPUT: + + - ``expression`` -- string representing the expression (e.g. LaTeX string) + + OUTPUT: + + - True if additive operations are enclosed in parentheses, false otherwise. + + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import is_atomic + sage: is_atomic("2*x") + True + sage: is_atomic("2+x") + False + sage: is_atomic("(2+x)") + True + + """ + if not isinstance(expression, basestring): + raise TypeError("The argument must be a string.") + level = 0 + for n, c in enumerate(expression): + if c == '(': + level += 1 + elif c == ')': + level -= 1 + if c == '+' or c == '-': + if level == 0 and n > 0: + return False + return True + + +def is_atomic_wedge_txt(expression): + r""" + Helper function to check whether some text-formatted expression is atomic + in terms of wedge products. + + Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written + by Joris Vankerschaver (2010) + + INPUT: + + - ``expression`` -- string representing the text-formatted expression + + OUTPUT: + + - True if wedge products are enclosed in parentheses, false otherwise. + + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import is_atomic_wedge_txt + sage: is_atomic_wedge_txt("a") + True + sage: is_atomic_wedge_txt(r"a/\b") + False + sage: is_atomic_wedge_txt(r"(a/\b)") + True + sage: is_atomic_wedge_txt(r"(a/\b)/\c") + False + sage: is_atomic_wedge_txt(r"(a/\b/\c)") + True + + """ + if not isinstance(expression, basestring): + raise TypeError("The argument must be a string.") + level = 0 + for n, c in enumerate(expression): + if c == '(': + level += 1 + elif c == ')': + level -= 1 + if c == '/' and expression[n+1:n+2] == '\\': + if level == 0 and n > 0: + return False + return True + + +def is_atomic_wedge_latex(expression): + r""" + Helper function to check whether LaTeX-formatted expression is atomic in + terms of wedge products. + + Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written + by Joris Vankerschaver (2010) + + INPUT: + + - ``expression`` -- string representing the LaTeX expression + + OUTPUT: + + - True if wedge products are enclosed in parentheses, false otherwise. + + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import is_atomic_wedge_latex + sage: is_atomic_wedge_latex(r"a") + True + sage: is_atomic_wedge_latex(r"a\wedge b") + False + sage: is_atomic_wedge_latex(r"(a\wedge b)") + True + sage: is_atomic_wedge_latex(r"(a\wedge b)\wedge c") + False + sage: is_atomic_wedge_latex(r"((a\wedge b)\wedge c)") + True + sage: is_atomic_wedge_latex(r"(a\wedge b\wedge c)") + True + sage: is_atomic_wedge_latex(r"\omega\wedge\theta") + False + sage: is_atomic_wedge_latex(r"(\omega\wedge\theta)") + True + sage: is_atomic_wedge_latex(r"\omega\wedge(\theta+a)") + False + + """ + if not isinstance(expression, basestring): + raise TypeError("The argument must be a string.") + level = 0 + for n, c in enumerate(expression): + if c == '(': + level += 1 + elif c == ')': + level -= 1 + if c == '\\' and expression[n+1:n+6] == 'wedge': + if level == 0 and n > 0: + return False + return True + + +def format_mul_txt(name1, operator, name2): + r""" + Helper function for text-formatted names of results of multiplication or + tensor product. + + """ + if name1 is None or name2 is None: + return None + if not is_atomic(name1) or not is_atomic_wedge_txt(name1): + name1 = '(' + name1 + ')' + if not is_atomic(name2) or not is_atomic_wedge_txt(name2): + name2 = '(' + name2 + ')' + return name1 + operator + name2 + + +def format_mul_latex(name1, operator, name2): + r""" + Helper function for LaTeX names of results of multiplication or tensor + product. + + """ + if name1 is None or name2 is None: + return None + if not is_atomic(name1) or not is_atomic_wedge_latex(name1): + name1 = r'\left(' + name1 + r'\right)' + if not is_atomic(name2) or not is_atomic_wedge_latex(name2): + name2 = r'\left(' + name2 + r'\right)' + return name1 + operator + name2 + + +def format_unop_txt(operator, name): + r""" + Helper function for text-formatted names of results of unary operator. + + """ + if name is None: + return None + if not is_atomic(name) or not is_atomic_wedge_txt(name): + #!# is_atomic_otimes_txt should be added + name = '(' + name + ')' + return operator + name + + +def format_unop_latex(operator, name): + r""" + Helper function for LaTeX names of results of unary operator. + + """ + if name is None: + return None + if not is_atomic(name) or not is_atomic_wedge_latex(name): + #!# is_atomic_otimes_latex should be added + name = r'\left(' + name + r'\right)' + return operator + name + +class FormattedExpansion(SageObject): + r""" + Helper class for displaying tensor expansions. + """ + def __init__(self, tensor): + self.tensor = tensor + self.txt = None + self.latex = None + + def _repr_(self): + r""" + Special Sage function for the string representation of the object. + """ + return self.txt + + def _latex_(self): + r""" + Special Sage function for the LaTeX representation of the object. + """ + return self.latex + + diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py new file mode 100644 index 00000000000..132ff9ec777 --- /dev/null +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -0,0 +1,501 @@ +r""" +Alternating forms on free modules + +The class :class:`FreeModuleAltForm` implement alternating forms on a free +module of finite rank over a commutative ring. + +It is a subclass of +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`, alternating +forms being a special type of tensors. + +A subclass of :class:`FreeModuleAltForm` is :class:`FreeModuleLinForm` for +alternating forms of degree 1, i.e. linear forms. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement +from comp import Components, CompFullyAntiSym + +class FreeModuleAltForm(FreeModuleTensor): + r""" + Alternating form over a free module `M`. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` + (must be an instance of :class:`FiniteFreeModule`) + - ``degree`` -- the degree of the alternating form (i.e. its tensor rank) + - ``name`` -- (default: None) name given to the alternating form + - ``latex_name`` -- (default: None) LaTeX symbol to denote the alternating + form; if none is provided, the LaTeX symbol is set to ``name`` + + """ + def __init__(self, fmodule, degree, name=None, latex_name=None): + FreeModuleTensor.__init__(self, fmodule, (0,degree), name=name, + latex_name=latex_name, antisym=range(degree)) + FreeModuleAltForm._init_derived(self) # initialization of derived quantities + + def _repr_(self): + r""" + String representation of the object. + """ + description = "alternating form " + if self.name is not None: + description += self.name + " " + description += "of degree " + str(self.tensor_rank) + " on the " + \ + str(self.fmodule) + return description + + def _init_derived(self): + r""" + Initialize the derived quantities + """ + FreeModuleTensor._init_derived(self) + + def _del_derived(self): + r""" + Delete the derived quantities + """ + FreeModuleTensor._del_derived(self) + + def _new_comp(self, basis): + r""" + Create some components in the given basis. + + This method, which is already implemented in + :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency + """ + fmodule = self.fmodule # the base free module + if self.tensor_rank == 1: + return Components(fmodule.ring, basis, 1, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + else: + return CompFullyAntiSym(fmodule.ring, basis, self.tensor_rank, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + + def degree(self): + r""" + Return the degree of the alternating form. + """ + return self.tensor_rank + + + def view(self, basis=None, format_spec=None): + r""" + Displays the alternating form in terms of its expansion onto a given + cobasis. + + The output is either text-formatted (console mode) or LaTeX-formatted + (notebook mode). + + INPUT: + + - ``basis`` -- (default: None) basis of the free module with respect to + which the alternating form is expanded; if none is provided, the + module's default basis is assumed + - ``format_spec`` -- (default: None) format specification passed to + ``self.fmodule.output_formatter`` to format the output. + + EXAMPLES: + + Display of an alternating form of degree 1 (linear form) on a rank-3 + free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form('a', latex_name=r'\alpha') + sage: a[:] = [1,-3,4] + sage: a.view() + a = e^0 - 3 e^1 + 4 e^2 + sage: latex(a.view()) # display in the notebook + \alpha = e^0 -3 e^1 + 4 e^2 + + Display of an alternating form of degree 2 on a rank-3 free module:: + + sage: b = M.alternating_form(2, 'b', latex_name=r'\beta') + sage: b[0,1], b[0,2], b[1,2] = 3, 2, -1 + sage: b.view() + b = 3 e^0/\e^1 + 2 e^0/\e^2 - e^1/\e^2 + sage: latex(b.view()) # display in the notebook + \beta = 3 e^0\wedge e^1 + 2 e^0\wedge e^2 -e^1\wedge e^2 + + Display of an alternating form of degree 3 on a rank-3 free module:: + + sage: c = M.alternating_form(3, 'c') + sage: c[0,1,2] = 4 + sage: c.view() + c = 4 e^0/\e^1/\e^2 + sage: latex(c.view()) + c = 4 e^0\wedge e^1\wedge e^2 + + Display of a vanishing alternating form:: + + sage: c[0,1,2] = 0 # the only independent component set to zero + sage: c.is_zero() + True + sage: c.view() + c = 0 + sage: latex(c.view()) + c = 0 + sage: c[0,1,2] = 4 # value restored for what follows + + Display in a basis which is not the default one:: + + sage: aut = M.automorphism() + sage: aut[:] = [[0,1,0], [0,0,-1], [1,0,0]] + sage: f = e.new_basis(aut, 'f') + sage: a.view(f) + a = 4 f^0 + f^1 + 3 f^2 + sage: b.view(f) + b = -2 f^0/\f^1 - f^0/\f^2 - 3 f^1/\f^2 + sage: c.view(f) + c = -4 f^0/\f^1/\f^2 + + The output format can be set via the argument ``output_formatter`` + passed at the module construction:: + + sage: N = FiniteFreeModule(QQ, 3, name='N', start_index=1, output_formatter=Rational.numerical_approx) + sage: e = N.basis('e') + sage: b = N.alternating_form(2, 'b') + sage: b[1,2], b[1,3], b[2,3] = 1/3, 5/2, 4 + sage: b.view() # default format (53 bits of precision) + b = 0.333333333333333 e^1/\e^2 + 2.50000000000000 e^1/\e^3 + 4.00000000000000 e^2/\e^3 + + The output format is then controled by the argument ``format_spec`` of + the method :meth:`view`:: + + sage: b.view(format_spec=10) # 10 bits of precision + b = 0.33 e^1/\e^2 + 2.5 e^1/\e^3 + 4.0 e^2/\e^3 + + """ + from sage.misc.latex import latex + from format_utilities import is_atomic, FormattedExpansion + if basis is None: + basis = self.fmodule.def_basis + cobasis = basis.dual_basis() + comp = self.comp(basis) + terms_txt = [] + terms_latex = [] + for ind in comp.non_redundant_index_generator(): + ind_arg = ind + (format_spec,) + coef = comp[ind_arg] + if coef != 0: + bases_txt = [] + bases_latex = [] + for k in range(self.tensor_rank): + bases_txt.append(cobasis[ind[k]].name) + bases_latex.append(latex(cobasis[ind[k]])) + basis_term_txt = "/\\".join(bases_txt) + basis_term_latex = r"\wedge ".join(bases_latex) + if coef == 1: + terms_txt.append(basis_term_txt) + terms_latex.append(basis_term_latex) + elif coef == -1: + terms_txt.append("-" + basis_term_txt) + terms_latex.append("-" + basis_term_latex) + else: + coef_txt = repr(coef) + coef_latex = latex(coef) + if is_atomic(coef_txt): + terms_txt.append(coef_txt + " " + basis_term_txt) + else: + terms_txt.append("(" + coef_txt + ") " + + basis_term_txt) + if is_atomic(coef_latex): + terms_latex.append(coef_latex + basis_term_latex) + else: + terms_latex.append(r"\left(" + coef_latex + r"\right)" + + basis_term_latex) + if terms_txt == []: + expansion_txt = "0" + else: + expansion_txt = terms_txt[0] + for term in terms_txt[1:]: + if term[0] == "-": + expansion_txt += " - " + term[1:] + else: + expansion_txt += " + " + term + if terms_latex == []: + expansion_latex = "0" + else: + expansion_latex = terms_latex[0] + for term in terms_latex[1:]: + if term[0] == "-": + expansion_latex += term + else: + expansion_latex += "+" + term + result = FormattedExpansion(self) + if self.name is None: + result.txt = expansion_txt + else: + result.txt = self.name + " = " + expansion_txt + if self.latex_name is None: + result.latex = expansion_latex + else: + result.latex = latex(self) + " = " + expansion_latex + return result + + + def wedge(self, other): + r""" + Exterior product with another alternating form. + + INPUT: + + - ``other``: another alternating form + + OUTPUT: + + - instance of :class:`FreeModuleAltForm` representing the exterior + product self/\\other. + + EXAMPLES: + + Exterior product of two linear forms:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form('A') + sage: a[:] = [1,-3,4] + sage: b = M.linear_form('B') + sage: b[:] = [2,-1,2] + sage: c = a.wedge(b) ; c + alternating form A/\B of degree 2 on the rank-3 free module M over the Integer Ring + sage: c.view() + A/\B = 5 e^0/\e^1 - 6 e^0/\e^2 - 2 e^1/\e^2 + sage: latex(c) + A\wedge B + sage: latex(c.view()) + A\wedge B = 5 e^0\wedge e^1 -6 e^0\wedge e^2 -2 e^1\wedge e^2 + + Test of the computation:: + + sage: a.wedge(b) == a*b - b*a + True + + Exterior product of a linear form and an alternating form of degree 2:: + + sage: d = M.linear_form('D') + sage: d[:] = [-1,2,4] + sage: s = d.wedge(c) ; s + alternating form D/\A/\B of degree 3 on the rank-3 free module M over the Integer Ring + sage: s.view() + D/\A/\B = 34 e^0/\e^1/\e^2 + + Test of the computation:: + + sage: s[0,1,2] == d[0]*c[1,2] + d[1]*c[2,0] + d[2]*c[0,1] + True + + Let us check that the exterior product is associative:: + + sage: d.wedge(a.wedge(b)) == (d.wedge(a)).wedge(b) + True + + and that it is graded anticommutative:: + + sage: a.wedge(b) == - b.wedge(a) + True + sage: d.wedge(c) == c.wedge(d) + True + + """ + from format_utilities import is_atomic + if not isinstance(other, FreeModuleAltForm): + raise TypeError("The second argument for the exterior product " + + "must be an alternating form.") + if other.tensor_rank == 0: + return other*self + if self.tensor_rank == 0: + return self*other + fmodule = self.fmodule + basis = self.common_basis(other) + if basis is None: + raise ValueError("No common basis for the exterior product.") + rank_r = self.tensor_rank + other.tensor_rank + cmp_s = self.components[basis] + cmp_o = other.components[basis] + cmp_r = CompFullyAntiSym(fmodule.ring, basis, rank_r, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + for ind_s, val_s in cmp_s._comp.items(): + for ind_o, val_o in cmp_o._comp.items(): + ind_r = ind_s + ind_o + if len(ind_r) == len(set(ind_r)): # all indices are different + cmp_r[ind_r] += val_s * val_o + result = FreeModuleAltForm(fmodule, rank_r) + result.components[basis] = cmp_r + if self.name is not None and other.name is not None: + sname = self.name + oname = other.name + if not is_atomic(sname): + sname = '(' + sname + ')' + if not is_atomic(oname): + oname = '(' + oname + ')' + result.name = sname + '/\\' + oname + if self.latex_name is not None and other.latex_name is not None: + slname = self.latex_name + olname = other.latex_name + if not is_atomic(slname): + slname = '(' + slname + ')' + if not is_atomic(olname): + olname = '(' + olname + ')' + result.latex_name = slname + r'\wedge ' + olname + return result + + +#****************************************************************************** + +class FreeModuleLinForm(FreeModuleAltForm): + r""" + Linear form on a free module `M` over a commutative ring `R`. + + A *linear form* is a map `M\rightarrow R` that is linear. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` + (must be an instance of :class:`FiniteFreeModule`) + - ``name`` -- (default: None) name given to the linear form + - ``latex_name`` -- (default: None) LaTeX symbol to denote the linear + form; if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Linear form on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form('A') ; a + linear form A on the rank-3 free module M over the Integer Ring + sage: a[:] = [2,-1,3] # components w.r.t. the module's default basis (e) + + The members of a dual basis are linear forms:: + + sage: e.dual_basis()[0] + linear form e^0 on the rank-3 free module M over the Integer Ring + sage: e.dual_basis()[1] + linear form e^1 on the rank-3 free module M over the Integer Ring + sage: e.dual_basis()[2] + linear form e^2 on the rank-3 free module M over the Integer Ring + + Any linear form is expanded onto them:: + + sage: a.view(basis=e) # e being the default basis, it is equivalent to write a.view() + A = 2 e^0 - e^1 + 3 e^2 + + A linear form maps module elements to ring elements:: + + sage: v = M([1,1,1]) + sage: a(v) + 4 + sage: a(v) in M.base_ring() + True + + Test of linearity:: + + sage: u = M([-5,-2,7]) + sage: a(3*u - 4*v) == 3*a(u) - 4*a(v) + True + + A linear form is an element of the dual module:: + + sage: a.parent() + dual of the rank-3 free module M over the Integer Ring + + As such, it is a tensor of type (0,1):: + + sage: a.tensor_type + (0, 1) + + """ + def __init__(self, fmodule, name=None, latex_name=None): + FreeModuleAltForm.__init__(self, fmodule, 1, name=name, + latex_name=latex_name) + + def _repr_(self): + r""" + String representation of the object. + """ + description = "linear form " + if self.name is not None: + description += self.name + " " + description += "on the " + str(self.fmodule) + return description + + def _new_comp(self, basis): + r""" + Create some components in the given basis. + + This method, which is already implemented in + :meth:`FreeModuleAltForm._new_comp`, is redefined here for efficiency + """ + fmodule = self.fmodule # the base free module + return Components(fmodule.ring, basis, 1, start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + + def __call__(self, vector): + r""" + The linear form acting on an element of the module. + + INPUT: + + - ``vector`` -- an element of the module (instance of + :class:`FiniteFreeModuleElement`) + + OUTPUT: + + - ring element `\langle \omega, v \rangle` + + """ + if not isinstance(vector, FiniteFreeModuleElement): + raise TypeError("The argument must be a free module element.") + basis = self.common_basis(vector) + if basis is None: + raise ValueError("No common basis for the components.") + omega = self.components[basis] + vv = vector.components[basis] + resu = 0 + for i in self.fmodule.irange(): + resu += omega[[i]]*vv[[i]] + # Name and LaTeX symbol of the output: + if hasattr(resu, 'name'): + if self.name is not None and vector.name is not None: + resu.name = self.name + "(" + vector.name + ")" + if hasattr(resu, 'latex_name'): + if self.latex_name is not None and vector.latex_name is not None: + resu.latex_name = self.latex_name + r"\left(" + \ + vector.latex_name + r"\right)" + return resu + + + + + + + + + + + + + + + + diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py new file mode 100644 index 00000000000..3e8884f2797 --- /dev/null +++ b/src/sage/tensor/modules/free_module_basis.py @@ -0,0 +1,395 @@ +r""" +Free module bases + +The class :class:`FreeModuleBasis` implements bases on a free module `M` of +finite rank over a commutative ring, +while the class :class:`FreeModuleCoBasis` implements the dual bases (i.e. +bases of the dual module `M^*`). + + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.sage_object import SageObject +from sage.structure.unique_representation import UniqueRepresentation + +class FreeModuleBasis(UniqueRepresentation, SageObject): + r""" + Basis of a free module over a commutative ring `R`. + + INPUT: + + - ``fmodule`` -- free module `M` (must be an instance of + :class:`FiniteFreeModule`) + - ``symbol`` -- (string) a letter (of a few letters) to denote a generic + element of the basis + - ``latex_symbol`` -- (string; default: None) symbol to denote a generic + element of the basis; if None, the value of ``symbol`` is used. + + EXAMPLES: + + A basis on a rank-3 free module over `\ZZ`:: + + sage: M0 = FiniteFreeModule(ZZ, 3, name='M_0') + sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis + sage: e = FreeModuleBasis(M0, 'e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M_0 over the Integer Ring + + Instead of importing FreeModuleBasis in the global name space, one can + use the module's method :meth:`basis`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + + The individual elements constituting the basis are accessed via the + square bracket operator:: + + sage: e[0] + element e_0 of the rank-3 free module M over the Integer Ring + sage: e[0] in M + True + + The LaTeX symbol can be set explicitely, as the second argument of + :meth:`basis`:: + + sage: latex(e) + \left(e_0,e_1,e_2\right) + sage: eps = M.basis('eps', r'\epsilon') ; eps + basis (eps_0,eps_1,eps_2) on the rank-3 free module M over the Integer Ring + sage: latex(eps) + \left(\epsilon_0,\epsilon_1,\epsilon_2\right) + + The individual elements of the basis are labelled according the + parameter ``start_index`` provided at the free module construction:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: e[1] + element e_1 of the rank-3 free module M over the Integer Ring + + """ + def __init__(self, fmodule, symbol, latex_symbol=None): + from free_module_tensor import FiniteFreeModuleElement + self.fmodule = fmodule + self.name = "(" + \ + ",".join([symbol + "_" + str(i) for i in self.fmodule.irange()]) +")" + if latex_symbol is None: + latex_symbol = symbol + self.latex_name = r"\left(" + \ + ",".join([latex_symbol + "_" + str(i) + for i in self.fmodule.irange()]) + r"\right)" + self.symbol = symbol + # The basis is added to the module list of bases + for other in self.fmodule.known_bases: + if symbol == other.symbol: + raise ValueError("The " + str(other) + " already exist on the " + + str(self.fmodule)) + self.fmodule.known_bases.append(self) + # The individual vectors: + vl = list() + for i in self.fmodule.irange(): + v_name = symbol + "_" + str(i) + v_symb = latex_symbol + "_" + str(i) + v = FiniteFreeModuleElement(self.fmodule, name=v_name, latex_name=v_symb) + for j in self.fmodule.irange(): + v.set_comp(self)[j] = 0 + v.set_comp(self)[i] = 1 + vl.append(v) + self.vec = tuple(vl) + # The dual basis + self._dual_basis = FreeModuleCoBasis(self, symbol, + latex_symbol=latex_symbol) + # The first defined basis is considered as the default one + # and is used to initialize the components of the zero elements of + # all tensor modules constructed up to now (including the base module + # itself, since it is considered as a type-(1,0) tensor module) + if self.fmodule.def_basis is None: + self.fmodule.def_basis = self + for t in self.fmodule._tensor_modules.values(): + t._zero_element.components[self] = \ + t._zero_element._new_comp(self) + # (since new components are initialized to zero) + + def _repr_(self): + r""" + String representation of the object. + """ + return "basis " + self.name + " on the " + str(self.fmodule) + + def _latex_(self): + r""" + LaTeX representation of the object. + """ + return self.latex_name + + def __hash__(self): + r""" + Hash function (since instances of :class:`FreeModuleBasis` are used as + dictionary keys). + """ + return id(self) + + def __eq__(self, other): + r""" + Comparison operator + """ + return other is self + + def __getitem__(self, index): + r""" + Returns the basis element corresponding to a given index. + + INPUT: + + - ``index`` -- the index of the basis element + + """ + n = self.fmodule._rank + si = self.fmodule.sindex + i = index - si + if i < 0 or i > n-1: + raise ValueError("Index out of range: " + + str(i+si) + " not in [" + str(si) + "," + + str(n-1+si) + "]") + return self.vec[i] + + def __len__(self): + r""" + Return the basis length, i.e. the rank of the free module. + """ + return self.fmodule._rank + + def new_basis(self, change_of_basis, symbol, latex_symbol=None): + r""" + Define a new module basis from the current one. + + The new basis is defined by means of a module automorphism. + + INPUT: + + - ``change_of_basis`` -- instance of + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + describing the automorphism `P` that relates the current basis + `(e_i)` (described by ``self``) to the new basis `(n_i)` according + to `n_i = P(e_i)` + - ``symbol`` -- (string) a letter (of a few letters) to denote a + generic element of the basis + - ``latex_symbol`` -- (string; default: None) symbol to denote a + generic element of the basis; if None, the value of ``symbol`` is + used. + + OUTPUT: + + - the new basis `(n_i)`, as an instance of :class:`FreeModuleBasis` + + EXAMPLES: + + Change of basis on a rank-2 free module:: + + sage: M = FiniteFreeModule(QQ, 2, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.automorphism() + sage: a[:] = [[1, 2], [-1, 3]] + sage: f = e.new_basis(a, 'f') ; f + basis (f_1,f_2) on the rank-2 free module M over the Rational Field + sage: f[1].view() + f_1 = e_1 - e_2 + sage: f[2].view() + f_2 = 2 e_1 + 3 e_2 + sage: e[1].view(f) + e_1 = 3/5 f_1 + 1/5 f_2 + sage: e[2].view(f) + e_2 = -2/5 f_1 + 1/5 f_2 + + """ + from free_module_tensor_spec import FreeModuleAutomorphism + if not isinstance(change_of_basis, FreeModuleAutomorphism): + raise TypeError("The argument change_of_basis must be some " + + "instance of FreeModuleAutomorphism.") + fmodule = self.fmodule + the_new_basis = FreeModuleBasis(fmodule, symbol, latex_symbol) + transf = change_of_basis.copy() + inv_transf = change_of_basis.inverse().copy() + si = fmodule.sindex + # Components of the new basis vectors in the old basis: + for i in fmodule.irange(): + for j in fmodule.irange(): + the_new_basis.vec[i-si].add_comp(self)[[j]] = \ + transf.comp(self)[[j,i]] + # Components of the new dual-basis elements in the old dual basis: + for i in fmodule.irange(): + for j in fmodule.irange(): + the_new_basis._dual_basis.form[i-si].add_comp(self)[[j]] = \ + inv_transf.comp(self)[[i,j]] + # The components of the transformation and its inverse are the same in + # the two bases: + for i in fmodule.irange(): + for j in fmodule.irange(): + transf.add_comp(the_new_basis)[[i,j]] = transf.comp(self)[[i,j]] + inv_transf.add_comp(the_new_basis)[[i,j]] = \ + inv_transf.comp(self)[[i,j]] + # Components of the old basis vectors in the new basis: + for i in fmodule.irange(): + for j in fmodule.irange(): + self.vec[i-si].add_comp(the_new_basis)[[j]] = \ + inv_transf.comp(self)[[j,i]] + # Components of the old dual-basis elements in the new cobasis: + for i in fmodule.irange(): + for j in fmodule.irange(): + self._dual_basis.form[i-si].add_comp(the_new_basis)[[j]] = \ + transf.comp(self)[[i,j]] + # The automorphism and its inverse are added to the module's dictionary + # of changes of bases: + fmodule.basis_changes[(self, the_new_basis)] = transf + fmodule.basis_changes[(the_new_basis, self)] = inv_transf + # + return the_new_basis + + def dual_basis(self): + r""" + Return the basis dual to ``self``. + + OUTPUT: + + - instance of :class:`FreeModuleCoBasis` representing the dual of + ``self`` + + EXAMPLES: + + Dual basis on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: f = e.dual_basis() ; f + dual basis (e^1,e^2,e^3) on the rank-3 free module M over the Integer Ring + + Let us check that the elements of f are tensors of type (0,1) on M:: + + sage: f[1] in M.tensor_module(0,1) + True + sage: f[1] + linear form e^1 on the rank-3 free module M over the Integer Ring + + and that f is indeed the dual of e:: + + sage: f[1](e[1]), f[1](e[2]), f[1](e[3]) + (1, 0, 0) + sage: f[2](e[1]), f[2](e[2]), f[2](e[3]) + (0, 1, 0) + sage: f[3](e[1]), f[3](e[2]), f[3](e[3]) + (0, 0, 1) + + """ + return self._dual_basis + +#****************************************************************************** + +class FreeModuleCoBasis(SageObject): + r""" + Dual basis of a free module over a commutative ring. + + INPUT: + + - ``basis`` -- basis of a free module `M` of which ``self`` is the dual + (must be an instance of :class:`FreeModuleBasis`) + - ``symbol`` -- a letter (of a few letters) to denote a generic element of + the cobasis + - ``latex_symbol`` -- (default: None) symbol to denote a generic element of + the cobasis; if None, the value of ``symbol`` is used. + + EXAMPLES: + + Dual basis on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: from sage.tensor.modules.free_module_basis import FreeModuleCoBasis + sage: f = FreeModuleCoBasis(e, 'f') ; f + dual basis (f^1,f^2,f^3) on the rank-3 free module M over the Integer Ring + + Let us check that the elements of f are tensors of type (0,1) on M:: + + sage: f[1] in M.tensor_module(0,1) + True + sage: f[1] + linear form f^1 on the rank-3 free module M over the Integer Ring + + and that f is indeed the dual of e:: + + sage: f[1](e[1]), f[1](e[2]), f[1](e[3]) + (1, 0, 0) + sage: f[2](e[1]), f[2](e[2]), f[2](e[3]) + (0, 1, 0) + sage: f[3](e[1]), f[3](e[2]), f[3](e[3]) + (0, 0, 1) + + """ + def __init__(self, basis, symbol, latex_symbol=None): + from free_module_alt_form import FreeModuleLinForm + self.basis = basis + self.fmodule = basis.fmodule + self.name = "(" + \ + ",".join([symbol + "^" + str(i) for i in self.fmodule.irange()]) +")" + if latex_symbol is None: + latex_symbol = symbol + self.latex_name = r"\left(" + \ + ",".join([latex_symbol + "^" + str(i) + for i in self.fmodule.irange()]) + r"\right)" + # The individual linear forms: + vl = list() + for i in self.fmodule.irange(): + v_name = symbol + "^" + str(i) + v_symb = latex_symbol + "^" + str(i) + v = FreeModuleLinForm(self.fmodule, name=v_name, latex_name=v_symb) + for j in self.fmodule.irange(): + v.set_comp(basis)[j] = 0 + v.set_comp(basis)[i] = 1 + vl.append(v) + self.form = tuple(vl) + + def _repr_(self): + r""" + String representation of the object. + """ + return "dual basis " + self.name + " on the " + str(self.fmodule) + + def _latex_(self): + r""" + LaTeX representation of the object. + """ + return self.latex_name + + def __getitem__(self, index): + r""" + Returns the basis linear form corresponding to a given index. + + INPUT: + + - ``index`` -- the index of the linear form + + """ + n = self.fmodule._rank + si = self.fmodule.sindex + i = index - si + if i < 0 or i > n-1: + raise ValueError("Index out of range: " + + str(i+si) + " not in [" + str(si) + "," + + str(n-1+si) + "]") + return self.form[i] diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py new file mode 100644 index 00000000000..d1e6beb7e44 --- /dev/null +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -0,0 +1,2207 @@ +r""" +Tensors on free modules + +The class :class:`FreeModuleTensor` implements tensors over a free module `M`, +i.e. elements of the free module `T^{(k,l)}(M)` of tensors of type `(k,l)` +acting as multilinear forms on `M`. + +A *tensor of type* `(k,l)` is a multilinear map: + +.. MATH:: + + \underbrace{M^*\times\cdots\times M^*}_{k\ \; \mbox{times}} + \times \underbrace{M\times\cdots\times M}_{l\ \; \mbox{times}} + \longrightarrow R + +where `R` is the commutative ring over which the free module `M` is defined and +`M^*=\mathrm{Hom}_R(M,R)` is the dual of `M`. The integer `k+l` is called the +*tensor rank*. + +Various derived classes of :class:`FreeModuleTensor` are devoted to specific +tensors: + +* :class:`FiniteFreeModuleElement` for elements of `M`, considered as + type-(1,0) tensors thanks to the canonical identification `M^{**}=M`, which + holds since `M` is a free module of finite rank + +* :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` for + fully antisymmetric type-`(0,l)` tensors (alternating forms) + + * :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` for + type-(0,1) tensors (linear forms) + +* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + for type-(1,1) tensors (endomorphisms) + + * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + for invertible endomorphisms + + * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + for the identity map on a free module + +* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleSymBilinForm` + for symmetric type-(0,2) tensors (symmetric bilinear forms) + +:class:`FreeModuleTensor` is a Sage *element* class, the corresponding *parent* +class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. + + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +EXAMPLES: + + A tensor of type (1,1) on a rank-3 free module over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((1,1), name='t') ; t + endomorphism t on the rank-3 free module M over the Integer Ring + sage: t.parent() + free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + sage: t.parent() is M.tensor_module(1,1) + True + sage: t in M.tensor_module(1,1) + True + + Setting some component of the tensor in a given basis:: + + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: t.set_comp(e)[0,0] = -3 # the component [0,0] w.r.t. basis e is set to -3 + + The unset components are assumed to be zero:: + + sage: t.comp(e)[:] # list of all components w.r.t. basis e + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + sage: t.view(e) # expansion of t on the basis e_i*e^j of T^(1,1)(M) + t = -3 e_0*e^0 + + Since e is M's default basis, shorcuts for the above writings are:: + + sage: t[0,0] = -3 + sage: t[:] + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + + Tensor components can be modified (reset) at any time:: + + sage: t[0,0] = 0 + sage: t[:] + [0 0 0] + [0 0 0] + [0 0 0] + + Checking that t is zero:: + + sage: t.is_zero() + True + sage: t == 0 + True + sage: t == M.tensor_module(1,1).zero() # the zero element of the module of all type-(1,1) tensors on M + True + + The components are managed by the class :class:`~sage.tensor.modules.comp.Components`:: + + sage: type(t.comp(e)) + + + Only non-zero components are actually stored, in the dictionary :attr:`_comp` + of class :class:`~sage.tensor.modules.comp.Components`, whose keys are the indices:: + + sage: t.comp(e)._comp + {} + sage: t.set_comp(e)[0,0] = -3 ; t.set_comp(e)[1,2] = 2 + sage: t.comp(e)._comp # random output order (dictionary) + {(0, 0): -3, (1, 2): 2} + sage: t.view(e) + t = -3 e_0*e^0 + 2 e_1*e^2 + + Further tests of the comparison operator:: + + sage: t.is_zero() + False + sage: t == 0 + False + sage: t == M.tensor_module(1,1).zero() + False + sage: t1 = t.copy() + sage: t1 == t + True + sage: t1[2,0] = 4 + sage: t1 == t + False + + As a multilinear map `M^*\times M \rightarrow \ZZ`, the type-(1,1) tensor t + acts on pairs formed by a linear form and a module element:: + + sage: a = M.linear_form(name='a') ; a[:] = (2, 1, -3) ; a + linear form a on the rank-3 free module M over the Integer Ring + sage: b = M([1,-6,2], name='b') ; b + element b of the rank-3 free module M over the Integer Ring + sage: t(a,b) + -2 + + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.rings.integer import Integer +from sage.structure.element import ModuleElement +#!# or from sage.structure.element import Element +# to avoid arithmetics defined in ModuleElement ?? + +from comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym + +class FreeModuleTensor(ModuleElement): + r""" + Tensor over a free module of finite rank over a commutative ring. + + INPUT: + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an + instance of :class:`FiniteFreeModule`) + - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l + the covariant rank + - ``name`` -- (default: None) name given to the tensor + - ``latex_name`` -- (default: None) LaTeX symbol to denote the tensor; + if none is provided, the LaTeX symbol is set to ``name`` + - ``sym`` -- (default: None) a symmetry or a list of symmetries among the + tensor arguments: each symmetry is described by a tuple containing + the positions of the involved arguments, with the convention position=0 + for the first argument. For instance: + + * sym=(0,1) for a symmetry between the 1st and 2nd arguments + * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. + + - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries + among the arguments, with the same convention as for ``sym``. + + EXAMPLES: + + A tensor of type (1,1) on a rank-3 free module over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((1,1), name='t') ; t + endomorphism t on the rank-3 free module M over the Integer Ring + + Tensors are *Element* objects whose parents are tensor free modules:: + + sage: t.parent() + free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + sage: t.parent() is M.tensor_module(1,1) + True + + """ + def __init__(self, fmodule, tensor_type, name=None, latex_name=None, + sym=None, antisym=None): + ModuleElement.__init__(self, fmodule.tensor_module(*tensor_type)) + self.fmodule = fmodule + self.tensor_type = tuple(tensor_type) + self.tensor_rank = self.tensor_type[0] + self.tensor_type[1] + self.name = name + if latex_name is None: + self.latex_name = self.name + else: + self.latex_name = latex_name + self.components = {} # components on various bases (not set yet) + # Treatment of symmetry declarations: + self.sym = [] + if sym is not None and sym != []: + if isinstance(sym[0], (int, Integer)): + # a single symmetry is provided as a tuple -> 1-item list: + sym = [tuple(sym)] + for isym in sym: + if len(isym) > 1: + for i in isym: + if i<0 or i>self.tensor_rank-1: + raise IndexError("Invalid position: " + str(i) + + " not in [0," + str(self.tensor_rank-1) + "]") + self.sym.append(tuple(isym)) + self.antisym = [] + if antisym is not None and antisym != []: + if isinstance(antisym[0], (int, Integer)): + # a single antisymmetry is provided as a tuple -> 1-item list: + antisym = [tuple(antisym)] + for isym in antisym: + if len(isym) > 1: + for i in isym: + if i<0 or i>self.tensor_rank-1: + raise IndexError("Invalid position: " + str(i) + + " not in [0," + str(self.tensor_rank-1) + "]") + self.antisym.append(tuple(isym)) + # Final consistency check: + index_list = [] + for isym in self.sym: + index_list += isym + for isym in self.antisym: + index_list += isym + if len(index_list) != len(set(index_list)): + # There is a repeated index position: + raise IndexError("Incompatible lists of symmetries: the same " + + "position appears more than once.") + # Initialization of derived quantities: + FreeModuleTensor._init_derived(self) + + ####### Required methods for ModuleElement (beside arithmetic) ####### + + def __nonzero__(self): + r""" + Return True if ``self`` is nonzero and False otherwise. + + This method is called by self.is_zero(). + """ + basis = self.pick_a_basis() + return not self.components[basis].is_zero() + + ####### End of required methods for ModuleElement (beside arithmetic) ####### + + def _repr_(self): + r""" + String representation of the object. + """ + description = "type-(%s,%s) tensor" % \ + (str(self.tensor_type[0]), str(self.tensor_type[1])) + if self.name is not None: + description += " " + self.name + description += " on the " + str(self.fmodule) + return description + + def _latex_(self): + r""" + LaTeX representation of the object. + """ + if self.latex_name is None: + return r'\mbox{' + str(self) + r'}' + else: + return self.latex_name + + def _init_derived(self): + r""" + Initialize the derived quantities + """ + pass # no derived quantities + + def _del_derived(self): + r""" + Delete the derived quantities + """ + pass # no derived quantities + + def view(self, basis=None, format_spec=None): + r""" + Displays the tensor in terms of its expansion onto a given basis. + + The output is either text-formatted (console mode) or LaTeX-formatted + (notebook mode). + + INPUT: + + - ``basis`` -- (default: None) basis of the free module with respect to + which the tensor is expanded; if none is provided, the module's + default basis is assumed + - ``format_spec`` -- (default: None) format specification passed to + ``self.fmodule.output_formatter`` to format the output. + + EXAMPLES: + + Display of a module element (type-(1,0) tensor):: + + sage: M = FiniteFreeModule(QQ, 2, name='M', start_index=1) + sage: e = M.basis('e') + sage: v = M([1/3,-2], name='v') + sage: v.view() + v = 1/3 e_1 - 2 e_2 + sage: latex(v.view()) # display in the notebook + v = \frac{1}{3} e_1 -2 e_2 + + Display of a linear form (type-(0,1) tensor):: + + sage: de = e.dual_basis() + sage: w = - 3/4 * de[1] + de[2] ; w + linear form on the rank-2 free module M over the Rational Field + sage: w.set_name('w', latex_name='\omega') + sage: w.view() + w = -3/4 e^1 + e^2 + sage: latex(w.view()) # display in the notebook + \omega = -\frac{3}{4} e^1 +e^2 + + Display of a type-(1,1) tensor:: + + sage: t = v*w ; t # the type-(1,1) is formed as the tensor product of v by w + endomorphism v*w on the rank-2 free module M over the Rational Field + sage: t.view() + v*w = -1/4 e_1*e^1 + 1/3 e_1*e^2 + 3/2 e_2*e^1 - 2 e_2*e^2 + sage: latex(t.view()) # display in the notebook + v\otimes \omega = -\frac{1}{4} e_1\otimes e^1 + \frac{1}{3} e_1\otimes e^2 + \frac{3}{2} e_2\otimes e^1 -2 e_2\otimes e^2 + + Display in a basis which is not the default one:: + + sage: a = M.automorphism() + sage: a[:] = [[1,2],[3,4]] + sage: f = e.new_basis(a, 'f') + sage: v.view(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a + v = -8/3 f_1 + 3/2 f_2 + sage: w.view(f) + w = 9/4 f^1 + 5/2 f^2 + sage: t.view(f) + v*w = -6 f_1*f^1 - 20/3 f_1*f^2 + 27/8 f_2*f^1 + 15/4 f_2*f^2 + + The output format can be set via the argument ``output_formatter`` + passed at the module construction:: + + sage: N = FiniteFreeModule(QQ, 2, name='N', start_index=1, output_formatter=Rational.numerical_approx) + sage: e = N.basis('e') + sage: v = N([1/3,-2], name='v') + sage: v.view() # default format (53 bits of precision) + v = 0.333333333333333 e_1 - 2.00000000000000 e_2 + sage: latex(v.view()) + v = 0.333333333333333 e_1 -2.00000000000000 e_2 + + The output format is then controled by the argument ``format_spec`` of + the method :meth:`view`:: + + sage: v.view(format_spec=10) # 10 bits of precision + v = 0.33 e_1 - 2.0 e_2 + + """ + from sage.misc.latex import latex + from format_utilities import is_atomic, FormattedExpansion + if basis is None: + basis = self.fmodule.def_basis + cobasis = basis.dual_basis() + comp = self.comp(basis) + terms_txt = [] + terms_latex = [] + n_con = self.tensor_type[0] + for ind in comp.index_generator(): + ind_arg = ind + (format_spec,) + coef = comp[ind_arg] + if coef != 0: + bases_txt = [] + bases_latex = [] + for k in range(n_con): + bases_txt.append(basis[ind[k]].name) + bases_latex.append(latex(basis[ind[k]])) + for k in range(n_con, self.tensor_rank): + bases_txt.append(cobasis[ind[k]].name) + bases_latex.append(latex(cobasis[ind[k]])) + basis_term_txt = "*".join(bases_txt) + basis_term_latex = r"\otimes ".join(bases_latex) + if coef == 1: + terms_txt.append(basis_term_txt) + terms_latex.append(basis_term_latex) + elif coef == -1: + terms_txt.append("-" + basis_term_txt) + terms_latex.append("-" + basis_term_latex) + else: + coef_txt = repr(coef) + coef_latex = latex(coef) + if is_atomic(coef_txt): + terms_txt.append(coef_txt + " " + basis_term_txt) + else: + terms_txt.append("(" + coef_txt + ") " + + basis_term_txt) + if is_atomic(coef_latex): + terms_latex.append(coef_latex + basis_term_latex) + else: + terms_latex.append(r"\left(" + coef_latex + r"\right)" + + basis_term_latex) + if terms_txt == []: + expansion_txt = "0" + else: + expansion_txt = terms_txt[0] + for term in terms_txt[1:]: + if term[0] == "-": + expansion_txt += " - " + term[1:] + else: + expansion_txt += " + " + term + if terms_latex == []: + expansion_latex = "0" + else: + expansion_latex = terms_latex[0] + for term in terms_latex[1:]: + if term[0] == "-": + expansion_latex += term + else: + expansion_latex += "+" + term + result = FormattedExpansion(self) + if self.name is None: + result.txt = expansion_txt + else: + result.txt = self.name + " = " + expansion_txt + if self.latex_name is None: + result.latex = expansion_latex + else: + result.latex = latex(self) + " = " + expansion_latex + return result + + def symmetries(self): + r""" + Print the list of symmetries and antisymmetries. + + EXAMPLES: + + Various symmetries / antisymmetries for a rank-4 tensor:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((4,0), name='T') # no symmetry declared + sage: t.symmetries() + no symmetry; no antisymmetry + sage: t = M.tensor((4,0), name='T', sym=(0,1)) + sage: t.symmetries() + symmetry: (0, 1); no antisymmetry + sage: t = M.tensor((4,0), name='T', sym=[(0,1), (2,3)]) + sage: t.symmetries() + symmetries: [(0, 1), (2, 3)]; no antisymmetry + sage: t = M.tensor((4,0), name='T', sym=(0,1), antisym=(2,3)) + sage: t.symmetries() + symmetry: (0, 1); antisymmetry: (2, 3) + + """ + if len(self.sym) == 0: + s = "no symmetry; " + elif len(self.sym) == 1: + s = "symmetry: " + str(self.sym[0]) + "; " + else: + s = "symmetries: " + str(self.sym) + "; " + if len(self.antisym) == 0: + a = "no antisymmetry" + elif len(self.antisym) == 1: + a = "antisymmetry: " + str(self.antisym[0]) + else: + a = "antisymmetries: " + str(self.antisym) + print s, a + + def set_name(self, name, latex_name=None): + r""" + Set (or change) the text name and LaTeX name of the tensor. + + INPUT: + + - ``name`` -- name given to the tensor + - ``latex_name`` -- (default: None) LaTeX symbol to denote the tensor; + if none is provided, the LaTeX symbol is set to ``name`` + + """ + self.name = name + if latex_name is None: + self.latex_name = self.name + else: + self.latex_name = latex_name + + def _new_instance(self): + r""" + Create a :class:`FreeModuleTensor` instance of the same tensor type and + with the same symmetries. + + This method must be redefined by derived classes of + :class:`FreeModuleTensor`. + + """ + return FreeModuleTensor(self.fmodule, self.tensor_type, sym=self.sym, + antisym=self.antisym) + + def _new_comp(self, basis): + r""" + Create some components in the given basis. + + This method, to be called by :meth:`comp`, must be redefined by derived + classes to adapt the output to the relevant subclass of + :class:`~sage.tensor.modules.comp.Components`. + + OUTPUT: + + - an instance of :class:`~sage.tensor.modules.comp.Components` (or of one of its subclass) + + """ + fmodule = self.fmodule # the base free module + if self.sym == [] and self.antisym == []: + return Components(fmodule.ring, basis, self.tensor_rank, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + for isym in self.sym: + if len(isym) == self.tensor_rank: + return CompFullySym(fmodule.ring, basis, self.tensor_rank, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + for isym in self.antisym: + if len(isym) == self.tensor_rank: + return CompFullyAntiSym(fmodule.ring, basis, self.tensor_rank, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + return CompWithSym(fmodule.ring, basis, self.tensor_rank, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter, + sym=self.sym, antisym=self.antisym) + + def comp(self, basis=None, from_basis=None): + r""" + Return the components in a given basis. + + If the components are not known already, they are computed by the tensor + change-of-basis formula from components in another basis. + + INPUT: + + - ``basis`` -- (default: None) basis in which the components are + required; if none is provided, the components are assumed to refer to + the module's default basis + - ``from_basis`` -- (default: None) basis from which the + required components are computed, via the tensor change-of-basis + formula, if they are not known already in the basis ``basis``; + if none, a basis is picked in ``self.components``. + + OUTPUT: + + - components in the basis ``basis``, as an instance of the + class :class:`~sage.tensor.modules.comp.Components` + + EXAMPLES: + + Components of a tensor of type-(1,1):: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: t = M.tensor((1,1), name='t') + sage: t[1,2] = -3 ; t[3,3] = 2 + sage: t.comp() + 2-indices components w.r.t. basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: t.comp() is t.comp(e) # since e is M's default basis + True + sage: t.comp()[:] + [ 0 -3 0] + [ 0 0 0] + [ 0 0 2] + + A direct access to the components w.r.t. the module's default basis is + provided by the square brackets applied to the tensor itself:: + + sage: t[1,2] is t.comp(e)[1,2] + True + sage: t[:] + [ 0 -3 0] + [ 0 0 0] + [ 0 0 2] + + Components computed via a change-of-basis formula:: + + sage: a = M.automorphism() + sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] + sage: f = e.new_basis(a, 'f') + sage: t.comp(f) + 2-indices components w.r.t. basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + sage: t.comp(f)[:] + [ 0 0 0] + [ 0 2 0] + [-3 0 0] + + """ + fmodule = self.fmodule + if basis is None: + basis = fmodule.def_basis + if basis not in self.components: + # The components must be computed from + # those in the basis from_basis + if from_basis is None: + for known_basis in self.components: + if (known_basis, basis) in self.fmodule.basis_changes \ + and (basis, known_basis) in self.fmodule.basis_changes: + from_basis = known_basis + break + if from_basis is None: + raise ValueError("No basis could be found for computing " + + "the components in the " + str(basis)) + elif from_basis not in self.components: + raise ValueError("The tensor components are not known in the " + + "basis "+ str(from_basis)) + (n_con, n_cov) = self.tensor_type + if n_cov > 0: + if (from_basis, basis) not in fmodule.basis_changes: + raise ValueError("The change-of-basis matrix from the " + + str(from_basis) + " to the " + str(basis) + + " has not been set.") + pp = \ + fmodule.basis_changes[(from_basis, basis)].comp(from_basis) + # pp not used if n_cov = 0 (pure contravariant tensor) + if n_con > 0: + if (basis, from_basis) not in fmodule.basis_changes: + raise ValueError("The change-of-basis matrix from the " + + str(basis) + " to the " + str(from_basis) + + " has not been set.") + ppinv = \ + fmodule.basis_changes[(basis, from_basis)].comp(from_basis) + # ppinv not used if n_con = 0 (pure covariant tensor) + old_comp = self.components[from_basis] + new_comp = self._new_comp(basis) + rank = self.tensor_rank + # loop on the new components: + for ind_new in new_comp.non_redundant_index_generator(): + # Summation on the old components multiplied by the proper + # change-of-basis matrix elements (tensor formula): + res = 0 + for ind_old in old_comp.index_generator(): + t = old_comp[[ind_old]] + for i in range(n_con): # loop on contravariant indices + t *= ppinv[[ind_new[i], ind_old[i]]] + for i in range(n_con,rank): # loop on covariant indices + t *= pp[[ind_old[i], ind_new[i]]] + res += t + new_comp[ind_new] = res + self.components[basis] = new_comp + # end of case where the computation was necessary + return self.components[basis] + + def set_comp(self, basis=None): + r""" + Return the components in a given basis for assignment. + + The components with respect to other bases are deleted, in order to + avoid any inconsistency. To keep them, use the method :meth:`add_comp` + instead. + + INPUT: + + - ``basis`` -- (default: None) basis in which the components are + defined; if none is provided, the components are assumed to refer to + the module's default basis. + + OUTPUT: + + - components in the given basis, as an instance of the + class :class:`~sage.tensor.modules.comp.Components`; if such components did not exist + previously, they are created. + + EXAMPLES: + + Setting components of a type-(1,1) tensor:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: t = M.tensor((1,1), name='t') + sage: t.set_comp()[0,1] = -3 + sage: t.view() + t = -3 e_0*e^1 + sage: t.set_comp()[1,2] = 2 + sage: t.view() + t = -3 e_0*e^1 + 2 e_1*e^2 + sage: t.set_comp(e) + 2-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + + Setting components in a new basis:: + + sage: f = M.basis('f') + sage: t.set_comp(f)[0,1] = 4 + sage: t.components.keys() # the components w.r.t. basis e have been deleted + [basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + sage: t.view(f) + t = 4 f_0*f^1 + + The components w.r.t. basis e can be deduced from those w.r.t. basis f, + once a relation between the two bases has been set:: + + sage: a = M.automorphism() + sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] + sage: M.basis_changes[(e,f)] = a + sage: M.basis_changes[(f,e)] = a.inverse() + sage: t.view(e) + t = -4 e_1*e^2 + sage: t.components.keys() # random output (dictionary keys) + [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, + basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + + """ + if self is self.parent()._zero_element: #!# this is maybe not very efficient + raise ValueError("The zero element cannot be changed.") + if basis is None: + basis = self.fmodule.def_basis + if basis not in self.components: + if basis not in self.fmodule.known_bases: + raise ValueError("The " + str(basis) + " has not been " + + "defined on the " + str(self.fmodule)) + self.components[basis] = self._new_comp(basis) + self._del_derived() # deletes the derived quantities + self.del_other_comp(basis) + return self.components[basis] + + def add_comp(self, basis=None): + r""" + Return the components in a given basis for assignment, keeping the + components in other bases. + + To delete the components in other bases, use the method + :meth:`set_comp` instead. + + INPUT: + + - ``basis`` -- (default: None) basis in which the components are + defined; if none is provided, the components are assumed to refer to + the module's default basis. + + .. WARNING:: + + If the tensor has already components in other bases, it + is the user's responsability to make sure that the components + to be added are consistent with them. + + OUTPUT: + + - components in the given basis, as an instance of the + class :class:`~sage.tensor.modules.comp.Components`; if such components did not exist + previously, they are created. + + EXAMPLES: + + Setting components of a type-(1,1) tensor:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: t = M.tensor((1,1), name='t') + sage: t.add_comp()[0,1] = -3 + sage: t.view() + t = -3 e_0*e^1 + sage: t.add_comp()[1,2] = 2 + sage: t.view() + t = -3 e_0*e^1 + 2 e_1*e^2 + sage: t.add_comp(e) + 2-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + + Adding components in a new basis:: + + sage: f = M.basis('f') + sage: t.add_comp(f)[0,1] = 4 + + The components w.r.t. basis e have been kept:: + + sage: t.components.keys() # # random output (dictionary keys) + [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, + basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + sage: t.view(f) + t = 4 f_0*f^1 + sage: t.view(e) + t = -3 e_0*e^1 + 2 e_1*e^2 + + """ + if basis is None: basis = self.fmodule.def_basis + if basis not in self.components: + if basis not in self.fmodule.known_bases: + raise ValueError("The " + str(basis) + " has not been " + + "defined on the " + str(self.fmodule)) + self.components[basis] = self._new_comp(basis) + self._del_derived() # deletes the derived quantities + return self.components[basis] + + + def del_other_comp(self, basis=None): + r""" + Delete all the components but those corresponding to ``basis``. + + INPUT: + + - ``basis`` -- (default: None) basis in which the components are + kept; if none the module's default basis is assumed + + EXAMPLE: + + Deleting components of a module element:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: u = M([2,1,-5]) + sage: f = M.basis('f') + sage: u.add_comp(f)[:] = [0,4,2] + sage: u.components.keys() # random output (dictionary keys) + [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring, + basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + sage: u.del_other_comp(f) + sage: u.components.keys() + [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + + Let us restore the components w.r.t. e and delete those w.r.t. f:: + + sage: u.add_comp(e)[:] = [2,1,-5] + sage: u.components.keys() # random output (dictionary keys) + [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring, + basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + sage: u.del_other_comp() # default argument: basis = e + sage: u.components.keys() + [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] + + """ + if basis is None: basis = self.fmodule.def_basis + if basis not in self.components: + raise ValueError("The components w.r.t. the " + + str(basis) + " have not been defined.") + to_be_deleted = [] + for other_basis in self.components: + if other_basis != basis: + to_be_deleted.append(other_basis) + for other_basis in to_be_deleted: + del self.components[other_basis] + + def __getitem__(self, indices): + r""" + Return a component w.r.t. the free module's default basis. + + INPUT: + + - ``indices`` -- list of indices defining the component + + """ + return self.comp()[indices] + + def __setitem__(self, indices, value): + r""" + Set a component w.r.t. the free module's default basis. + + INPUT: + + - ``indices`` -- list of indices defining the component + + """ + self.set_comp()[indices] = value + + + def copy(self): + r""" + Returns an exact copy of ``self``. + + The name and the derived quantities are not copied. + + EXAMPLES: + + Copy of a tensor of type (1,1):: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: t = M.tensor((1,1), name='t') + sage: t[1,2] = -3 ; t[3,3] = 2 + sage: t1 = t.copy() + sage: t1[:] + [ 0 -3 0] + [ 0 0 0] + [ 0 0 2] + sage: t1 == t + True + + If the original tensor is modified, the copy is not:: + + sage: t[2,2] = 4 + sage: t1[:] + [ 0 -3 0] + [ 0 0 0] + [ 0 0 2] + sage: t1 == t + False + + """ + resu = self._new_instance() + for basis, comp in self.components.items(): + resu.components[basis] = comp.copy() + return resu + + def common_basis(self, other): + r""" + Find a common basis for the components of ``self`` and ``other``. + + In case of multiple common bases, the free module's default basis is + privileged. + If the current components of ``self`` and ``other`` are all relative to + different bases, a common basis is searched by performing a component + transformation, via the transformations listed in + ``self.fmodule.basis_changes``, still privileging transformations to + the free module's default basis. + + INPUT: + + - ``other`` -- a tensor (instance of :class:`FreeModuleTensor`) + + OUPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing the common basis; if no common basis is found, None is + returned. + + EXAMPLES: + + Common basis for the components of two module elements:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: u = M([2,1,-5]) + sage: f = M.basis('f') + sage: M.basis_changes.clear() # to ensure that bases e and f are unrelated at this stage + sage: v = M([0,4,2], basis=f) + sage: u.common_basis(v) + + The above result is None since u and v have been defined on different + bases and no connection between these bases have been set:: + + sage: u.components.keys() + [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] + sage: v.components.keys() + [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + + Linking bases e and f changes the result:: + + sage: a = M.automorphism() + sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] + sage: M.basis_changes[(e,f)] = a + sage: M.basis_changes[(f,e)] = a.inverse() + sage: u.common_basis(v) + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + + Indeed, v is now known in basis e:: + + sage: v.components.keys() # random output (dictionary keys) + [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring, + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] + + """ + # Compatibility checks: + if not isinstance(other, FreeModuleTensor): + raise TypeError("The argument must be a tensor on a free module.") + fmodule = self.fmodule + if other.fmodule != fmodule: + raise TypeError("The two tensors are not defined on the same " + + "free module.") + def_basis = fmodule.def_basis + # + # 1/ Search for a common basis among the existing components, i.e. + # without performing any component transformation. + # ------------------------------------------------------------- + if def_basis in self.components and def_basis in other.components: + return def_basis # the module's default basis is privileged + for basis1 in self.components: + if basis1 in other.components: + return basis1 + # 2/ Search for a common basis via one component transformation + # ---------------------------------------------------------- + # If this point is reached, it is indeed necessary to perform at least + # one component transformation to get a common basis + if def_basis in self.components: + for obasis in other.components: + if (obasis, def_basis) in fmodule.basis_changes: + other.comp(def_basis, from_basis=obasis) + return def_basis + if def_basis in other.components: + for sbasis in self.components: + if (sbasis, def_basis) in fmodule.basis_changes: + self.comp(def_basis, from_basis=sbasis) + return def_basis + # If this point is reached, then def_basis cannot be a common basis + # via a single component transformation + for sbasis in self.components: + for obasis in other.components: + if (obasis, sbasis) in fmodule.basis_changes: + other.comp(sbasis, from_basis=obasis) + return sbasis + if (sbasis, obasis) in fmodule.basis_changes: + self.comp(obasis, from_basis=sbasis) + return obasis + # + # 3/ Search for a common basis via two component transformations + # ----------------------------------------------------------- + # If this point is reached, it is indeed necessary to perform at two + # component transformation to get a common basis + for sbasis in self.components: + for obasis in other.components: + if (sbasis, def_basis) in fmodule.basis_changes and \ + (obasis, def_basis) in fmodule.basis_changes: + self.comp(def_basis, from_basis=sbasis) + other.comp(def_basis, from_basis=obasis) + return def_basis + for basis in fmodule.known_bases: + if (sbasis, basis) in fmodule.basis_changes and \ + (obasis, basis) in fmodule.basis_changes: + self.comp(basis, from_basis=sbasis) + other.comp(basis, from_basis=obasis) + return basis + # + # If this point is reached, no common basis could be found, even at + # the price of component transformations: + return None + + def pick_a_basis(self): + r""" + Return a basis in which the tensor components are defined. + + The free module's default basis is privileged. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing the basis + + """ + if self.fmodule.def_basis in self.components: + return self.fmodule.def_basis # the default basis is privileged + else: + # a basis is picked arbitrarily: + return self.components.items()[0][0] + + def __eq__(self, other): + r""" + Comparison (equality) operator. + + INPUT: + + - ``other`` -- a tensor or 0 + + OUTPUT: + + - True if ``self`` is equal to ``other`` and False otherwise + + """ + if self.tensor_rank == 0: + raise NotImplementedError("Scalar comparison not implemented.") + if isinstance(other, (int, Integer)): # other should be 0 + if other == 0: + return self.is_zero() + else: + return False + elif not isinstance(other, FreeModuleTensor): + return False + else: # other is another tensor + if other.fmodule != self.fmodule: + return False + if other.tensor_type != self.tensor_type: + return False + basis = self.common_basis(other) + if basis is None: + raise ValueError("No common basis for the comparison.") + return bool(self.components[basis] == other.components[basis]) + + def __ne__(self, other): + r""" + Inequality operator. + + INPUT: + + - ``other`` -- a tensor or 0 + + OUTPUT: + + - True if ``self`` is different from ``other`` and False otherwise + + """ + return not self.__eq__(other) + + def __pos__(self): + r""" + Unary plus operator. + + OUTPUT: + + - an exact copy of ``self`` + + """ + result = self._new_instance() + for basis in self.components: + result.components[basis] = + self.components[basis] + if self.name is not None: + result.name = '+' + self.name + if self.latex_name is not None: + result.latex_name = '+' + self.latex_name + return result + + def __neg__(self): + r""" + Unary minus operator. + + OUTPUT: + + - the tensor `-T`, where `T` is ``self`` + + """ + result = self._new_instance() + for basis in self.components: + result.components[basis] = - self.components[basis] + if self.name is not None: + result.name = '-' + self.name + if self.latex_name is not None: + result.latex_name = '-' + self.latex_name + return result + + ######### ModuleElement arithmetic operators ######## + + def _add_(self, other): + r""" + Tensor addition. + + INPUT: + + - ``other`` -- a tensor, of the same type as ``self`` + + OUPUT: + + - the tensor resulting from the addition of ``self`` and ``other`` + + """ + if other == 0: + return +self + if not isinstance(other, FreeModuleTensor): + raise TypeError("For the addition, other must be a tensor.") + if other.tensor_type != self.tensor_type: + raise TypeError("The two tensors are not of the same type.") + basis = self.common_basis(other) + if basis is None: + raise ValueError("No common basis for the addition.") + comp_result = self.components[basis] + other.components[basis] + result = self.fmodule.tensor_from_comp(self.tensor_type, comp_result) + if self.name is not None and other.name is not None: + result.name = self.name + '+' + other.name + if self.latex_name is not None and other.latex_name is not None: + result.latex_name = self.latex_name + '+' + other.latex_name + return result + + def _sub_(self, other): + r""" + Tensor subtraction. + + INPUT: + + - ``other`` -- a tensor, of the same type as ``self`` + + OUPUT: + + - the tensor resulting from the subtraction of ``other`` from ``self`` + + """ + if other == 0: + return +self + if not isinstance(other, FreeModuleTensor): + raise TypeError("For the subtraction, other must be a tensor.") + if other.tensor_type != self.tensor_type: + raise TypeError("The two tensors are not of the same type.") + basis = self.common_basis(other) + if basis is None: + raise ValueError("No common basis for the subtraction.") + comp_result = self.components[basis] - other.components[basis] + result = self.fmodule.tensor_from_comp(self.tensor_type, comp_result) + if self.name is not None and other.name is not None: + result.name = self.name + '-' + other.name + if self.latex_name is not None and other.latex_name is not None: + result.latex_name = self.latex_name + '-' + other.latex_name + return result + + def _rmul_(self, other): + r""" + Multiplication on the left by ``other``. + + """ + if isinstance(other, FreeModuleTensor): + raise NotImplementedError("Left tensor product not implemented.") + # Left multiplication by a scalar: + result = self._new_instance() + for basis in self.components: + result.components[basis] = other * self.components[basis] + return result + + ######### End of ModuleElement arithmetic operators ######## + + def __radd__(self, other): + r""" + Addition on the left with ``other``. + + """ + return self.__add__(other) + + def __rsub__(self, other): + r""" + Subtraction from ``other``. + + """ + return (-self).__add__(other) + + def __mul__(self, other): + r""" + Tensor product. + """ + from format_utilities import format_mul_txt, format_mul_latex + if isinstance(other, FreeModuleTensor): + basis = self.common_basis(other) + if basis is None: + raise ValueError("No common basis for the tensor product.") + comp_prov = self.components[basis] * other.components[basis] + # Reordering of the contravariant and covariant indices: + k1, l1 = self.tensor_type + k2, l2 = other.tensor_type + if l1 != 0: + comp_result = comp_prov.swap_adjacent_indices(k1, + self.tensor_rank, + self.tensor_rank+k2) + else: + comp_result = comp_prov # no reordering is necessary + result = self.fmodule.tensor_from_comp((k1+k2, l1+l2), comp_result) + result.name = format_mul_txt(self.name, '*', other.name) + result.latex_name = format_mul_latex(self.latex_name, r'\otimes ', + other.latex_name) + return result + else: + # multiplication by a scalar: + result = self._new_instance() + for basis in self.components: + result.components[basis] = other * self.components[basis] + return result + + + def __div__(self, other): + r""" + Division (by a scalar) + """ + result = self._new_instance() + for basis in self.components: + result.components[basis] = self.components[basis] / other + return result + + + def __call__(self, *args): + r""" + The tensor acting on linear forms and module elements as a multilinear + map. + + INPUT: + + - ``*args`` -- list of k linear forms and l module elements, ``self`` + being a tensor of type (k,l). + + """ + from free_module_alt_form import FreeModuleLinForm + # Consistency checks: + p = len(args) + if p != self.tensor_rank: + raise TypeError(str(self.tensor_rank) + + " arguments must be provided.") + for i in range(self.tensor_type[0]): + if not isinstance(args[i], FreeModuleLinForm): + raise TypeError("The argument no. " + str(i+1) + + " must be a linear form.") + for i in range(self.tensor_type[0],p): + if not isinstance(args[i], FiniteFreeModuleElement): + raise TypeError("The argument no. " + str(i+1) + + " must be a module element.") + fmodule = self.fmodule + # Search for a common basis + basis = None + # First try with the module's default basis + def_basis = fmodule.def_basis + if def_basis in self.components: + basis = def_basis + for arg in args: + if def_basis not in arg.components: + basis = None + break + if basis is None: + # Search for another basis: + for bas in self.components: + basis = bas + for arg in args: + if bas not in arg.components: + basis = None + break + if basis is not None: # common basis found ! + break + if basis is None: + raise ValueError("No common basis for the components.") + t = self.components[basis] + v = [args[i].components[basis] for i in range(p)] + + res = 0 + for ind in t.index_generator(): + prod = t[[ind]] + for i in range(p): + prod *= v[i][[ind[i]]] + res += prod + # Name of the output: + if hasattr(res, 'name'): + res_name = None + if self.name is not None: + res_name = self.name + "(" + for i in range(p-1): + if args[i].name is not None: + res_name += args[i].name + "," + else: + res_name = None + break + if res_name is not None: + if args[p-1].name is not None: + res_name += args[p-1].name + ")" + else: + res_name = None + res.name = res_name + # LaTeX symbol of the output: + if hasattr(res, 'latex_name'): + res_latex = None + if self.latex_name is not None: + res_latex = self.latex_name + r"\left(" + for i in range(p-1): + if args[i].latex_name is not None: + res_latex += args[i].latex_name + "," + else: + res_latex = None + break + if res_latex is not None: + if args[p-1].latex_name is not None: + res_latex += args[p-1].latex_name + r"\right)" + else: + res_latex = None + res.latex_name = res_latex + return res + + def self_contract(self, pos1, pos2): + r""" + Contraction on two slots of the tensor. + + INPUT: + + - ``pos1`` -- position of the first index for the contraction, with the + convention ``pos1=0`` for the first slot + - ``pos2`` -- position of the second index for the contraction, with + the same convention as for ``pos1``. + + OUTPUT: + + - tensor resulting from the (pos1, pos2) contraction + + EXAMPLES: + + Contraction on the two slots of a type-(1,1) tensor:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: a = M.tensor((1,1), name='a') ; a + endomorphism a on the rank-3 free module M over the Integer Ring + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: a.self_contract(0,1) # contraction of slot 0 with slot 1 + 15 + sage: a.self_contract(1,0) # the order of the slots does not matter + 15 + + The contraction on two slots having the same tensor type cannot occur:: + + sage: b = M.tensor((2,0), name='b') ; b + type-(2,0) tensor b on the rank-3 free module M over the Integer Ring + sage: b[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b.self_contract(0,1) + Traceback (most recent call last): + ... + IndexError: Contraction on two contravariant indices is not allowed. + + The contraction either preserves or destroys the symmetries:: + + sage: b = M.alternating_form(2, 'b') ; b + alternating form b of degree 2 on the rank-3 free module M over the Integer Ring + sage: b[0,1], b[0,2], b[1,2] = 3, 2, 1 + sage: t = a*b ; t + type-(1,3) tensor a*b on the rank-3 free module M over the Integer Ring + sage: # by construction, t is a tensor field antisymmetric w.r.t. its last two slots: + sage: t.symmetries() + no symmetry; antisymmetry: (2, 3) + sage: s = t.self_contract(0,1) ; s # contraction on the first two slots + alternating form of degree 2 on the rank-3 free module M over the Integer Ring + sage: s.symmetries() # the antisymmetry is preserved + no symmetry; antisymmetry: (0, 1) + sage: s[:] + [ 0 45 30] + [-45 0 15] + [-30 -15 0] + sage: s == 15*b # check + True + sage: s = t.self_contract(0,2) ; s # contraction on the first and third slots + type-(0,2) tensor on the rank-3 free module M over the Integer Ring + sage: s.symmetries() # the antisymmetry has been destroyed by the above contraction: + no symmetry; no antisymmetry + sage: s[:] # indeed: + [-26 -4 6] + [-31 -2 9] + [-36 0 12] + sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) for j in M.irange()] for i in M.irange()] ) # check + True + + """ + # The indices at pos1 and pos2 must be of different types: + k_con = self.tensor_type[0] + l_cov = self.tensor_type[1] + if pos1 < k_con and pos2 < k_con: + raise IndexError("Contraction on two contravariant indices is " + + "not allowed.") + if pos1 >= k_con and pos2 >= k_con: + raise IndexError("Contraction on two covariant indices is " + + "not allowed.") + # Frame selection for the computation: + if self.fmodule.def_basis in self.components: + basis = self.fmodule.def_basis + else: # a basis is picked arbitrarily: + basis = self.pick_a_basis() + resu_comp = self.components[basis].self_contract(pos1, pos2) + if self.tensor_rank == 2: # result is a scalar + return resu_comp + else: + return self.fmodule.tensor_from_comp((k_con-1, l_cov-1), resu_comp) + + def contract(self, *args): + r""" + Contraction with another tensor. + + INPUT: + + - ``pos1`` -- position of the first index (in ``self``) for the + contraction; if not given, the last index position is assumed + - ``other`` -- the tensor to contract with + - ``pos2`` -- position of the second index (in ``other``) for the + contraction; if not given, the first index position is assumed + + OUTPUT: + + - tensor resulting from the (pos1, pos2) contraction of ``self`` with + ``other`` + + EXAMPLES: + + Contraction of a tensor of type (0,1) with a tensor of type (1,0):: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form() # tensor of type (0,1) is a linear form + sage: a[:] = [-3,2,1] + sage: b = M([2,5,-2]) # tensor of type (1,0) is a module element + sage: s = a.contract(b) ; s + 2 + sage: s in M.base_ring() + True + sage: s == a[0]*b[0] + a[1]*b[1] + a[2]*b[2] # check of the computation + True + + The positions of the contraction indices can be set explicitely:: + + sage: s == a.contract(0, b, 0) + True + sage: s == a.contract(0, b) + True + sage: s == a.contract(b, 0) + True + + In the present case, performing the contraction is identical to + applying the linear form to the module element:: + + sage: a.contract(b) == a(b) + True + + or to applying the module element, considered as a tensor of type (1,0), + to the linear form:: + + sage: a.contract(b) == b(a) + True + + We have also:: + + sage: a.contract(b) == b.contract(a) + True + + Contraction of a tensor of type (1,1) with a tensor of type (1,0):: + + sage: a = M.endomorphism() # tensor of type (1,1) + sage: a[:] = [[-1,2,3],[4,-5,6],[7,8,9]] + sage: s = a.contract(b) ; s + element of the rank-3 free module M over the Integer Ring + sage: s.view() + 2 e_0 - 29 e_1 + 36 e_2 + + Since the index positions have not been specified, the contraction + takes place on the last position of a (i.e. no. 1) and the first + position of b (i.e. no. 0):: + + sage: a.contract(b) == a.contract(1, b, 0) + True + sage: a.contract(b) == b.contract(0, a, 1) + True + sage: a.contract(b) == b.contract(a, 1) + True + + Contraction is possible only between a contravariant index and a + covariant one:: + + sage: a.contract(0, b) + Traceback (most recent call last): + ... + TypeError: Contraction not possible: the two index positions are both contravariant. + + In the present case, performing the contraction is identical to + applying the endomorphism to the module element:: + + sage: a.contract(b) == a(b) + True + + Contraction of a tensor of type (2,1) with a tensor of type (0,2):: + + sage: a = a*b ; a + type-(2,1) tensor on the rank-3 free module M over the Integer Ring + sage: b = M.tensor((0,2)) + sage: b[:] = [[-2,3,1], [0,-2,3], [4,-7,6]] + sage: s = a.contract(1, b, 1) ; s + type-(1,2) tensor on the rank-3 free module M over the Integer Ring + sage: s[:] + [[[-9, 16, 39], [18, -32, -78], [27, -48, -117]], + [[36, -64, -156], [-45, 80, 195], [54, -96, -234]], + [[63, -112, -273], [72, -128, -312], [81, -144, -351]]] + sage: # check of the computation: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == a[i,0,j]*b[k,0]+a[i,1,j]*b[k,1]+a[i,2,j]*b[k,2], + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + + The two tensors do not have to be defined on the same basis for the + contraction to take place, reflecting the fact that the contraction is + basis-independent:: + + sage: A = M.automorphism() + sage: A[:] = [[0,0,1], [1,0,0], [0,-1,0]] + sage: f = e.new_basis(A, 'f') + sage: b.comp(f)[:] # forces the computation of b's components w.r.t. basis f + [-2 -3 0] + [ 7 6 -4] + [ 3 -1 -2] + sage: b.del_other_comp(f) # deletes components w.r.t. basis e + sage: b.components.keys() # indeed: + [basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + sage: a.components.keys() # while a is known only in basis e: + [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring] + sage: s1 = a.contract(1, b, 1) ; s1 # yet the computation is possible + type-(1,2) tensor on the rank-3 free module M over the Integer Ring + sage: s1 == s # ... and yields the same result as previously: + True + + """ + nargs = len(args) + if nargs == 1: + pos1 = self.tensor_rank - 1 + other = args[0] + pos2 = 0 + elif nargs == 2: + if isinstance(args[0], FreeModuleTensor): + pos1 = self.tensor_rank - 1 + other = args[0] + pos2 = args[1] + else: + pos1 = args[0] + other = args[1] + pos2 = 0 + elif nargs == 3: + pos1 = args[0] + other = args[1] + pos2 = args[2] + else: + raise TypeError("Wrong number of arguments in contract(): " + + str(nargs) + + " arguments provided, while between 1 and 3 are expected.") + if not isinstance(other, FreeModuleTensor): + raise TypeError("For the contraction, other must be a tensor " + + "field.") + k1, l1 = self.tensor_type + k2, l2 = other.tensor_type + if pos1 < k1 and pos2 < k2: + raise TypeError("Contraction not possible: the two index " + + "positions are both contravariant.") + if pos1 >= k1 and pos2 >= k2: + raise TypeError("Contraction not possible: the two index " + + "positions are both covavariant.") + basis = self.common_basis(other) + if basis is None: + raise ValueError("No common basis for the contraction.") + cmp_res = self.components[basis].contract(pos1, + other.components[basis], pos2) + # reordering of the indices to have all contravariant indices first: + if k2 > 1: + if pos1 < k1: + cmp_res = cmp_res.swap_adjacent_indices(k1-1, k1+l1-1, k1+l1+k2-1) + else: + cmp_res = cmp_res.swap_adjacent_indices(k1, k1+l1-1, k1+l1+k2-2) + type_res = (k1+k2-1, l1+l2-1) + if type_res == (0, 0): + return cmp_res # scalar case + else: + return self.fmodule.tensor_from_comp(type_res, cmp_res) + + + def symmetrize(self, pos=None, basis=None): + r""" + Symmetrization over some arguments. + + INPUT: + + - ``pos`` -- (default: None) list of argument positions involved in the + symmetrization (with the convention position=0 for the first + argument); if none, the symmetrization is performed over all the + arguments + - ``basis`` -- (default: None) module basis with respect to which the + component computation is to be performed; if none, the module's + default basis is used if the tensor field has already components + in it; otherwise another basis w.r.t. which the tensor has + components will be picked + + OUTPUT: + + - the symmetrized tensor (instance of :class:`FreeModuleTensor`) + + EXAMPLES: + + Symmetrization of a tensor of type (2,0):: + + sage: M = FiniteFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: t = M.tensor((2,0)) + sage: t[:] = [[2,1,-3],[0,-4,5],[-1,4,2]] + sage: s = t.symmetrize() ; s + type-(2,0) tensor on the rank-3 free module M over the Rational Field + sage: t[:], s[:] + ( + [ 2 1 -3] [ 2 1/2 -2] + [ 0 -4 5] [1/2 -4 9/2] + [-1 4 2], [ -2 9/2 2] + ) + sage: s.symmetries() + symmetry: (0, 1); no antisymmetry + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: print s[i,j] == 1/2*(t[i,j]+t[j,i]), + ....: + True True True True True True True True True + + Symmetrization of a tensor of type (0,3) on the first two arguments:: + + sage: t = M.tensor((0,3)) + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: s = t.symmetrize((0,1)) ; s # (0,1) = the first two arguments + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + symmetry: (0, 1); no antisymmetry + sage: s[:] + [[[1, 2, 3], [3, -3, 9], [13, -6, -15]], + [[3, -3, 9], [13, 14, -15], [-3, 20, 21]], + [[13, -6, -15], [-3, 20, 21], [25, 26, -27]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[j,i,k]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.symmetrize((0,1)) == s # another test + True + + Symmetrization of a tensor of type (0,3) on the first and last arguments:: + + sage: s = t.symmetrize((0,2)) ; s # (0,2) = first and last arguments + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + symmetry: (0, 2); no antisymmetry + sage: s[:] + [[[1, 6, 11], [-4, 9, -8], [7, 12, 8]], + [[6, -11, -4], [9, 14, 4], [12, 17, 22]], + [[11, -4, -21], [-8, 4, 24], [8, 22, -27]]] + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[k,j,i]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.symmetrize((0,2)) == s # another test + True + + Symmetrization of a tensor of type (0,3) on the last two arguments:: + + sage: s = t.symmetrize((1,2)) ; s # (1,2) = the last two arguments + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + symmetry: (1, 2); no antisymmetry + sage: s[:] + [[[1, -1, 5], [-1, 5, 7], [5, 7, -9]], + [[10, 1, 14], [1, 14, 1], [14, 1, 18]], + [[19, -21, 2], [-21, 23, 25], [2, 25, -27]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[i,k,j]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.symmetrize((1,2)) == s # another test + True + + Full symmetrization of a tensor of type (0,3):: + + sage: s = t.symmetrize() ; s + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + symmetry: (0, 1, 2); no antisymmetry + sage: s[:] + [[[1, 8/3, 29/3], [8/3, 7/3, 0], [29/3, 0, -5/3]], + [[8/3, 7/3, 0], [7/3, 14, 25/3], [0, 25/3, 68/3]], + [[29/3, 0, -5/3], [0, 25/3, 68/3], [-5/3, 68/3, -27]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/6*(t[i,j,k]+t[i,k,j]+t[j,k,i]+t[j,i,k]+t[k,i,j]+t[k,j,i]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.symmetrize() == s # another test + True + + Symmetrization can be performed only on arguments on the same type:: + + sage: t = M.tensor((1,2)) + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: s = t.symmetrize((0,1)) + Traceback (most recent call last): + ... + TypeError: 0 is a contravariant position, while 1 is a covariant position; + symmetrization is meaningfull only on tensor arguments of the same type. + sage: s = t.symmetrize((1,2)) # OK: both 1 and 2 are covariant positions + + The order of positions does not matter:: + + sage: t.symmetrize((2,1)) == t.symmetrize((1,2)) + True + + """ + if pos is None: + pos = range(self.tensor_rank) + # check whether the symmetrization is possible: + pos_cov = self.tensor_type[0] # first covariant position + pos0 = pos[0] + if pos0 < pos_cov: # pos0 is a contravariant position + for k in range(1,len(pos)): + if pos[k] >= pos_cov: + raise TypeError( + str(pos[0]) + " is a contravariant position, while " + + str(pos[k]) + " is a covariant position; \n" + "symmetrization is meaningfull only on tensor " + + "arguments of the same type.") + else: # pos0 is a covariant position + for k in range(1,len(pos)): + if pos[k] < pos_cov: + raise TypeError( + str(pos[0]) + " is a covariant position, while " + \ + str(pos[k]) + " is a contravariant position; \n" + "symmetrization is meaningfull only on tensor " + + "arguments of the same type.") + if basis is None: + basis = self.pick_a_basis() + res_comp = self.components[basis].symmetrize(pos) + return self.fmodule.tensor_from_comp(self.tensor_type, res_comp) + + + def antisymmetrize(self, pos=None, basis=None): + r""" + Antisymmetrization over some arguments. + + INPUT: + + - ``pos`` -- (default: None) list of argument positions involved in the + antisymmetrization (with the convention position=0 for the first + argument); if none, the antisymmetrization is performed over all the + arguments + - ``basis`` -- (default: None) module basis with respect to which the + component computation is to be performed; if none, the module's + default basis is used if the tensor field has already components + in it; otherwise another basis w.r.t. which the tensor has + components will be picked + + OUTPUT: + + - the antisymmetrized tensor (instance of :class:`FreeModuleTensor`) + + EXAMPLES: + + Antisymmetrization of a tensor of type (2,0):: + + sage: M = FiniteFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: t = M.tensor((2,0)) + sage: t[:] = [[1,-2,3], [4,5,6], [7,8,-9]] + sage: s = t.antisymmetrize() ; s + type-(2,0) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + no symmetry; antisymmetry: (0, 1) + sage: t[:], s[:] + ( + [ 1 -2 3] [ 0 -3 -2] + [ 4 5 6] [ 3 0 -1] + [ 7 8 -9], [ 2 1 0] + ) + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: print s[i,j] == 1/2*(t[i,j]-t[j,i]), + ....: + True True True True True True True True True + sage: s.antisymmetrize() == s # another test + True + sage: t.antisymmetrize() == t.antisymmetrize((0,1)) + True + + Antisymmetrization of a tensor of type (0,3) over the first two + arguments:: + + sage: t = M.tensor((0,3)) + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: s = t.antisymmetrize((0,1)) ; s # (0,1) = the first two arguments + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + no symmetry; antisymmetry: (0, 1) + sage: s[:] + [[[0, 0, 0], [-7, 8, -3], [-6, 14, 6]], + [[7, -8, 3], [0, 0, 0], [19, -3, -3]], + [[6, -14, -6], [-19, 3, 3], [0, 0, 0]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[j,i,k]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.antisymmetrize((0,1)) == s # another test + True + sage: s.symmetrize((0,1)) == 0 # of course + True + + Antisymmetrization of a tensor of type (0,3) over the first and last + arguments:: + + sage: s = t.antisymmetrize((0,2)) ; s # (0,2) = first and last arguments + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + no symmetry; antisymmetry: (0, 2) + sage: s[:] + [[[0, -4, -8], [0, -4, 14], [0, -4, -17]], + [[4, 0, 16], [4, 0, -19], [4, 0, -4]], + [[8, -16, 0], [-14, 19, 0], [17, 4, 0]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[k,j,i]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.antisymmetrize((0,2)) == s # another test + True + sage: s.symmetrize((0,2)) == 0 # of course + True + sage: s.symmetrize((0,1)) == 0 # no reason for this to hold + False + + Antisymmetrization of a tensor of type (0,3) over the last two + arguments:: + + sage: s = t.antisymmetrize((1,2)) ; s # (1,2) = the last two arguments + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + no symmetry; antisymmetry: (1, 2) + sage: s[:] + [[[0, 3, -2], [-3, 0, -1], [2, 1, 0]], + [[0, -12, -2], [12, 0, -16], [2, 16, 0]], + [[0, 1, -23], [-1, 0, -1], [23, 1, 0]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[i,k,j]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.antisymmetrize((1,2)) == s # another test + True + sage: s.symmetrize((1,2)) == 0 # of course + True + + Full antisymmetrization of a tensor of type (0,3):: + + sage: s = t.antisymmetrize() ; s + alternating form of degree 3 on the rank-3 free module M over the Rational Field + sage: s.symmetries() + no symmetry; antisymmetry: (0, 1, 2) + sage: s[:] + [[[0, 0, 0], [0, 0, 2/3], [0, -2/3, 0]], + [[0, 0, -2/3], [0, 0, 0], [2/3, 0, 0]], + [[0, 2/3, 0], [-2/3, 0, 0], [0, 0, 0]]] + sage: # Check: + sage: for i in range(3): + ....: for j in range(3): + ....: for k in range(3): + ....: print s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k]+t[k,i,j]-t[k,j,i]), + ....: + True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: s.antisymmetrize() == s # another test + True + sage: s.symmetrize((0,1)) == 0 # of course + True + sage: s.symmetrize((0,2)) == 0 # of course + True + sage: s.symmetrize((1,2)) == 0 # of course + True + sage: t.antisymmetrize() == t.antisymmetrize((0,1,2)) + True + + Antisymmetrization can be performed only on arguments on the same type:: + + sage: t = M.tensor((1,2)) + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: s = t.antisymmetrize((0,1)) + Traceback (most recent call last): + ... + TypeError: 0 is a contravariant position, while 1 is a covariant position; + antisymmetrization is meaningfull only on tensor arguments of the same type. + sage: s = t.antisymmetrize((1,2)) # OK: both 1 and 2 are covariant positions + + The order of positions does not matter:: + + sage: t.antisymmetrize((2,1)) == t.antisymmetrize((1,2)) + True + + """ + if pos is None: + pos = range(self.tensor_rank) + # check whether the antisymmetrization is possible: + pos_cov = self.tensor_type[0] # first covariant position + pos0 = pos[0] + if pos0 < pos_cov: # pos0 is a contravariant position + for k in range(1,len(pos)): + if pos[k] >= pos_cov: + raise TypeError( + str(pos[0]) + " is a contravariant position, while " + + str(pos[k]) + " is a covariant position; \n" + "antisymmetrization is meaningfull only on tensor " + + "arguments of the same type.") + else: # pos0 is a covariant position + for k in range(1,len(pos)): + if pos[k] < pos_cov: + raise TypeError( + str(pos[0]) + " is a covariant position, while " + \ + str(pos[k]) + " is a contravariant position; \n" + "antisymmetrization is meaningfull only on tensor " + + "arguments of the same type.") + if basis is None: + basis = self.pick_a_basis() + res_comp = self.components[basis].antisymmetrize(pos) + return self.fmodule.tensor_from_comp(self.tensor_type, res_comp) + + + +#****************************************************************************** + +# From sage/modules/module.pyx: +#----------------------------- +### The Element should also implement _rmul_ (or _lmul_) +# +# class MyElement(sage.structure.element.ModuleElement): +# def _rmul_(self, c): +# ... + + +class FiniteFreeModuleElement(FreeModuleTensor): + r""" + Element of a free module of finite rank over a commutative ring. + + The class :class:`FiniteFreeModuleElement` inherits from + :class:`FreeModuleTensor` because the elements of a free module `M` of + finite rank over a commutative ring `R` are identified with tensors of + type (1,0) on `M` via the canonical map + + .. MATH:: + + \begin{array}{lllllll} + \Phi: & M & \longrightarrow & M^{**} & & & \\ + & v & \longmapsto & \bar v : & M^* & \longrightarrow & R \\ + & & & & a & \longmapsto & a(v) + \end{array} + + Note that for free modules of finite rank, this map is actually an + isomorphism, enabling the canonical identification: `M^{**}= M`. + + INPUT: + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an + instance of :class:`FiniteFreeModule`) + - ``name`` -- (default: None) name given to the element + - ``latex_name`` -- (default: None) LaTeX symbol to denote the element; + if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Let us consider a rank-3 free module over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + + There are four ways to construct an element of the free module M: the first + one (recommended) is via the operator __call__ acting on the free module:: + + sage: v = M([2,0,-1], basis=e, name='v') ; v + element v of the rank-3 free module M over the Integer Ring + sage: v.view() # expansion on the default basis (e) + v = 2 e_0 - e_2 + sage: v.parent() is M + True + + The second way is by a direct call to the class constructor:: + + sage: from sage.tensor.modules.free_module_tensor import FiniteFreeModuleElement + sage: v2 = FiniteFreeModuleElement(M, name='v') + sage: v2[0], v2[2] = 2, -1 # setting the nonzero components in the default basis (e) + sage: v2 + element v of the rank-3 free module M over the Integer Ring + sage: v2.view() + v = 2 e_0 - e_2 + sage: v2 == v + True + + The third way is to construct a tensor of type (1,0) on `M` (cf. the + canonical identification `M^{**}=M` recalled above):: + + sage: v3 = M.tensor((1,0), name='v') + sage: v3[0], v3[2] = 2, -1 ; v3 + element v of the rank-3 free module M over the Integer Ring + sage: v3.view() + v = 2 e_0 - e_2 + sage: v3 == v + True + + Finally, the fourth way is via some linear combination of the basis + elements:: + + sage: v4 = 2*e[0] - e[2] + sage: v4.set_name('v') ; v4 # in this case, the name has to be set separately + element v of the rank-3 free module M over the Integer Ring + sage: v4.view() + v = 2 e_0 - e_2 + sage: v4 == v + True + + The canonical identification `M^{**}=M` is implemented by letting the + module elements act on linear forms, providing the same result as the + reverse operation (cf. the map `\Phi` defined above):: + + sage: a = M.linear_form(name='a') + sage: a[:] = (2, 1, -3) ; a + linear form a on the rank-3 free module M over the Integer Ring + sage: v(a) + 7 + sage: a(v) + 7 + sage: a(v) == v(a) + True + + ARITHMETIC EXAMPLES: + + Addition:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: a = M([0,1,3], name='a') ; a + element a of the rank-3 free module M over the Integer Ring + sage: a.view() + a = e_1 + 3 e_2 + sage: b = M([2,-2,1], name='b') ; b + element b of the rank-3 free module M over the Integer Ring + sage: b.view() + b = 2 e_0 - 2 e_1 + e_2 + sage: s = a + b ; s + element a+b of the rank-3 free module M over the Integer Ring + sage: s.view() + a+b = 2 e_0 - e_1 + 4 e_2 + sage: # Test of the addition: + sage: for i in M.irange(): print s[i] == a[i] + b[i], + True True True + + Subtraction:: + + sage: s = a - b ; s + element a-b of the rank-3 free module M over the Integer Ring + sage: s.view() + a-b = -2 e_0 + 3 e_1 + 2 e_2 + sage: # Test of the substraction: + sage: for i in M.irange(): print s[i] == a[i] - b[i], + True True True + + Multiplication by a scalar:: + + sage: s = 2*a ; s + element of the rank-3 free module M over the Integer Ring + sage: s.view() + 2 e_1 + 6 e_2 + sage: a.view() + a = e_1 + 3 e_2 + + Tensor product:: + + sage: s = a*b ; s + type-(2,0) tensor a*b on the rank-3 free module M over the Integer Ring + sage: s.symmetries() + no symmetry; no antisymmetry + sage: s[:] + [ 0 0 0] + [ 2 -2 1] + [ 6 -6 3] + sage: s = a*s ; s + type-(3,0) tensor a*a*b on the rank-3 free module M over the Integer Ring + sage: s[:] + [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], + [[0, 0, 0], [2, -2, 1], [6, -6, 3]], + [[0, 0, 0], [6, -6, 3], [18, -18, 9]]] + + """ + def __init__(self, fmodule, name=None, latex_name=None): + FreeModuleTensor.__init__(self, fmodule, (1,0), name=name, + latex_name=latex_name) + + def _repr_(self): + r""" + String representation of the object. + """ + description = "element " + if self.name is not None: + description += self.name + " " + description += "of the " + str(self.fmodule) + return description + + def _new_comp(self, basis): + r""" + Create some components in the given basis. + + This method, which is already implemented in + :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency + """ + fmodule = self.fmodule # the base free module + return Components(fmodule.ring, basis, 1, start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + + + def _new_instance(self): + r""" + Create a :class:`FiniteFreeModuleElement` instance. + + """ + return FiniteFreeModuleElement(self.fmodule) + + diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py new file mode 100644 index 00000000000..9bd30507c2a --- /dev/null +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -0,0 +1,661 @@ +""" +Specific rank-2 tensors on free modules + +Four derived classes of +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` are devoted +to rank-2 tensors: + +* :class:`FreeModuleEndomorphism` for endomorphisms (type-(1,1) tensors) + + * :class:`FreeModuleAutomorphism` for invertible endomorphisms + + * :class:`FreeModuleIdentityMap` for the identity map on a free module + +* :class:`FreeModuleSymBilinForm` for symmetric bilinear forms (symmetric + type-(0,2) tensors) + +Antisymmetric bilinear forms are dealt with by the class +:class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +EXAMPLES: + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from free_module_tensor import FreeModuleTensor + +class FreeModuleEndomorphism(FreeModuleTensor): + r""" + Endomorphism (considered as a type-(1,1) tensor) on a free module. + + INPUT: + + - ``fmodule`` -- free module `M` over a commutative ring `R` + (must be an instance of :class:`FiniteFreeModule`) + - ``name`` -- (default: None) name given to the endomorphism + - ``latex_name`` -- (default: None) LaTeX symbol to denote the + endomorphism; if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Endomorphism on a rank-3 module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.endomorphism('T') ; t + endomorphism T on the rank-3 free module M over the Integer Ring + + An endomorphism is type-(1,1) tensor:: + + sage: t.parent() + free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + sage: t.tensor_type + (1, 1) + sage: t.tensor_rank + 2 + + Consequently, an endomorphism can also be created by the module method + :meth:`~sage.tensor.modules.finite_free_module.FiniteFreeModule.tensor`:: + + sage: t = M.tensor((1,1), name='T') ; t + endomorphism T on the rank-3 free module M over the Integer Ring + + Components of the endomorphism with respect to a given basis:: + + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: t[:] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] + sage: t[:] + [1 2 3] + [4 5 6] + [7 8 9] + sage: t.view() + T = e_0*e^0 + 2 e_0*e^1 + 3 e_0*e^2 + 4 e_1*e^0 + 5 e_1*e^1 + 6 e_1*e^2 + 7 e_2*e^0 + 8 e_2*e^1 + 9 e_2*e^2 + + The endomorphism acting on a module element:: + + sage: v = M([1,2,3], basis=e, name='v') ; v + element v of the rank-3 free module M over the Integer Ring + sage: w = t(v) ; w + element T(v) of the rank-3 free module M over the Integer Ring + sage: w[:] + [14, 32, 50] + sage: # check: + sage: for i in M.irange(): + ....: print sum( t[i,j]*v[j] for j in M.irange() ), + ....: + 14 32 50 + + """ + def __init__(self, fmodule, name=None, latex_name=None): + FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, + latex_name=latex_name) + + def _repr_(self): + r""" + String representation of the object. + """ + description = "endomorphism " + if self.name is not None: + description += self.name + " " + description += "on the " + str(self.fmodule) + return description + + def _new_instance(self): + r""" + Create a :class:`FreeModuleEndomorphism` instance. + + """ + return FreeModuleEndomorphism(self.fmodule) + + def __call__(self, *arg): + r""" + Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single + argument (module element). + """ + from free_module_tensor import FiniteFreeModuleElement + if len(arg) > 1: + # the endomorphism acting as a type (1,1) tensor on a pair + # (linear form, module element), returning a scalar: + return FreeModuleTensor.__call__(self, *arg) + # the endomorphism acting as such, on a module element, returning a + # module element: + vector = arg[0] + if not isinstance(vector, FiniteFreeModuleElement): + raise TypeError("The argument must be an element of a free module.") + basis = self.common_basis(vector) + t = self.components[basis] + v = vector.components[basis] + fmodule = self.fmodule + result = FiniteFreeModuleElement(fmodule) + for i in fmodule.irange(): + res = 0 + for j in fmodule.irange(): + res += t[[i,j]]*v[[j]] + result.set_comp(basis)[i] = res + # Name of the output: + result.name = None + if self.name is not None and vector.name is not None: + result.name = self.name + "(" + vector.name + ")" + # LaTeX symbol for the output: + result.latex_name = None + if self.latex_name is not None and vector.latex_name is not None: + result.latex_name = self.latex_name + r"\left(" + \ + vector.latex_name + r"\right)" + return result + +#****************************************************************************** + +class FreeModuleAutomorphism(FreeModuleEndomorphism): + r""" + Automorphism (considered as a type-(1,1) tensor) on a free module. + + INPUT: + + - ``fmodule`` -- free module `M` over a commutative ring `R` + (must be an instance of :class:`FiniteFreeModule`) + - ``name`` -- (default: None) name given to the automorphism + - ``latex_name`` -- (default: None) LaTeX symbol to denote the + automorphism; if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Automorphism on a rank-2 free module (vector space) on `\QQ`:: + + sage: M = FiniteFreeModule(QQ, 2, name='M') + sage: a = M.automorphism('A') ; a + automorphism A on the rank-2 free module M over the Rational Field + + Automorphisms are tensors of type (1,1):: + + sage: a.parent() + free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field + sage: a.tensor_type + (1, 1) + sage: a.tensor_rank + 2 + + Setting the components in a basis:: + + sage: e = M.basis('e') ; e + basis (e_0,e_1) on the rank-2 free module M over the Rational Field + sage: a[:] = [[1, 2], [-1, 3]] + sage: a[:] + [ 1 2] + [-1 3] + sage: a.view(basis=e) + A = e_0*e^0 + 2 e_0*e^1 - e_1*e^0 + 3 e_1*e^1 + + The inverse automorphism is obtained via the method :meth:`inverse`:: + + sage: b = a.inverse() ; b + automorphism A^(-1) on the rank-2 free module M over the Rational Field + sage: b.view(basis=e) + A^(-1) = 3/5 e_0*e^0 - 2/5 e_0*e^1 + 1/5 e_1*e^0 + 1/5 e_1*e^1 + sage: b[:] + [ 3/5 -2/5] + [ 1/5 1/5] + sage: a[:] * b[:] # check that b is indeed the inverse of a + [1 0] + [0 1] + + """ + def __init__(self, fmodule, name=None, latex_name=None): + FreeModuleEndomorphism.__init__(self, fmodule, name=name, + latex_name=latex_name) + self._inverse = None # inverse automorphism not set yet + + def _repr_(self): + r""" + String representation of the object. + """ + description = "automorphism " + if self.name is not None: + description += self.name + " " + description += "on the " + str(self.fmodule) + return description + + def _new_instance(self): + r""" + Create a :class:`FreeModuleAutomorphism` instance. + + """ + return FreeModuleAutomorphism(self.fmodule) + + def _del_derived(self): + r""" + Delete the derived quantities + + """ + # First delete the derived quantities pertaining to the mother class: + FreeModuleEndomorphism._del_derived(self) + # Then deletes the inverse automorphism: + self._inverse = None + + def inverse(self): + r""" + Return the inverse automorphism. + + OUTPUT: + + - instance of :class:`FreeModuleAutomorphism` representing the + automorphism that is the inverse of ``self``. + + EXAMPLES: + + Inverse of an automorphism on a rank-3 free module:: + + sage: M = FiniteFreeModule(QQ, 3, name='M') + sage: a = M.automorphism('A') + sage: e = M.basis('e') + sage: a[:] = [[1,0,-1], [0,3,0], [0,0,2]] + sage: b = a.inverse() ; b + automorphism A^(-1) on the rank-3 free module M over the Rational Field + sage: b[:] + [ 1 0 1/2] + [ 0 1/3 0] + [ 0 0 1/2] + + We may check that b is the inverse of a by performing the matrix + product of the components in the basis e:: + + sage: a[:] * b[:] + [1 0 0] + [0 1 0] + [0 0 1] + + Another check is of course:: + + sage: b.inverse() == a + True + + """ + from sage.matrix.constructor import matrix + from comp import Components + if self._inverse is None: + if self.name is None: + inv_name = None + else: + inv_name = self.name + '^(-1)' + if self.latex_name is None: + inv_latex_name = None + else: + inv_latex_name = self.latex_name + r'^{-1}' + fmodule = self.fmodule + si = fmodule.sindex ; nsi = fmodule._rank + si + self._inverse = FreeModuleAutomorphism(fmodule, inv_name, + inv_latex_name) + for basis in self.components: + try: + mat_self = matrix( + [[self.comp(basis)[i, j] + for j in range(si, nsi)] for i in range(si, nsi)]) + except (KeyError, ValueError): + continue + mat_inv = mat_self.inverse() + cinv = Components(fmodule.ring, basis, 2, start_index=si, + output_formatter=fmodule.output_formatter) + for i in range(si, nsi): + for j in range(si, nsi): + cinv[i, j] = mat_inv[i-si,j-si] + self._inverse.components[basis] = cinv + return self._inverse + + +#****************************************************************************** + +class FreeModuleIdentityMap(FreeModuleAutomorphism): + r""" + Identity map (considered as a type-(1,1) tensor) on a free module. + + INPUT: + + - ``fmodule`` -- free module `M` over a commutative ring `R` + (must be an instance of :class:`FiniteFreeModule`) + - ``name`` -- (default: 'Id') name given to the identity map. + - ``latex_name`` -- (default: None) LaTeX symbol to denote the identity + map; if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Identity map on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.identity_map() ; a + identity map on the rank-3 free module M over the Integer Ring + + The LaTeX symbol is set by default to Id, but can be changed:: + + sage: latex(a) + \mathrm{Id} + sage: a = M.identity_map(latex_name=r'\mathrm{1}') + sage: latex(a) + \mathrm{1} + + The identity map is a tensor of type (1,1) on the free module:: + + sage: a.parent() + free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + sage: a.tensor_type + (1, 1) + sage: a.tensor_rank + 2 + + Its components are Kronecker deltas in any basis:: + + sage: a[:] + [1 0 0] + [0 1 0] + [0 0 1] + sage: a.comp() # components in the module's default basis (e) + Kronecker delta of size 3x3 + sage: a.view() + Id = e_0*e^0 + e_1*e^1 + e_2*e^2 + sage: f = M.basis('f') + sage: a.comp(basis=f) + Kronecker delta of size 3x3 + sage: a.comp(f)[:] + [1 0 0] + [0 1 0] + [0 0 1] + + The components can be read, but cannot be set:: + + sage: a[1,1] + 1 + sage: a[1,1] = 2 + Traceback (most recent call last): + ... + NotImplementedError: The components of the identity map cannot be changed. + + The identity map acting on a module element:: + + sage: v = M([2,-3,1], basis=e, name='v') + sage: v.view() + v = 2 e_0 - 3 e_1 + e_2 + sage: u = a(v) ; u + element v of the rank-3 free module M over the Integer Ring + sage: u is v + True + + The identity map acting as a type-(1,1) tensor on a pair (linear form, + module element):: + + sage: w = M.tensor((0,1), name='w') ; w + linear form w on the rank-3 free module M over the Integer Ring + sage: w[:] = [0, 3, 2] + sage: s = a(w,v) ; s + -7 + sage: s == w(v) + True + + The identity map is its own inverse:: + + sage: a.inverse() == a + True + sage: a.inverse() is a + True + + """ + def __init__(self, fmodule, name='Id', latex_name=None): + if latex_name is None and name == 'Id': + latex_name = r'\mathrm{Id}' + FreeModuleAutomorphism.__init__(self, fmodule, name=name, + latex_name=latex_name) + self._inverse = self # the identity is its own inverse + + def _repr_(self): + r""" + String representation of the object. + """ + description = "identity map " + if self.name != 'Id': + description += self.name + " " + description += "on the " + str(self.fmodule) + return description + + def _del_derived(self): + r""" + Delete the derived quantities. + + """ + # FreeModuleAutomorphism._del_derived is bypassed: + FreeModuleEndomorphism._del_derived(self) + + def _new_instance(self): + r""" + Create a :class:`FreeModuleIdentityMap` instance. + + """ + return FreeModuleIdentityMap(self.fmodule) + + def _new_comp(self, basis): + r""" + Create some components in the given basis. + + """ + from comp import KroneckerDelta + fmodule = self.fmodule # the base free module + return KroneckerDelta(fmodule.ring, basis, start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + + def comp(self, basis=None): + r""" + Return the components in a given basis, as a Kronecker delta. + + INPUT: + + - ``basis`` -- (default: None) module basis in which the components + are required; if none is provided, the components are assumed to + refer to the domain's default basis + + OUTPUT: + + - components in the basis ``basis``, as an instance of the + class :class:`~sage.tensor.modules.comp.KroneckerDelta` + + EXAMPLES: + + Components of the identity map on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.identity_map() + sage: a.comp(basis=e) + Kronecker delta of size 3x3 + + For the module's default basis, the argument ``basis`` can be omitted:: + + sage: a.comp() is a.comp(basis=e) + True + sage: a.comp()[:] + [1 0 0] + [0 1 0] + [0 0 1] + + """ + if basis is None: + basis = self.fmodule.def_basis + if basis not in self.components: + self.components[basis] = self._new_comp(basis) + return self.components[basis] + + def set_comp(self, basis=None): + r""" + Redefinition of the generic tensor method + :meth:`FreeModuleTensor.set_comp`: should not be called. + + """ + raise NotImplementedError("The components of the identity map " + + "cannot be changed.") + + def add_comp(self, basis=None): + r""" + Redefinition of the generic tensor method + :meth:`FreeModuleTensor.add_comp`: should not be called. + + """ + raise NotImplementedError("The components of the identity map " + + "cannot be changed.") + + def __call__(self, *arg): + r""" + Redefinition of :meth:`FreeModuleEndomorphism.__call__`. + """ + from free_module_tensor import FiniteFreeModuleElement + from free_module_alt_form import FreeModuleLinForm + if len(arg) == 1: + # the identity map acting as such, on a module element: + vector = arg[0] + if not isinstance(vector, FiniteFreeModuleElement): + raise TypeError("The argument must be a module element.") + return vector + #!# should it be return vector.copy() instead ? + elif len(arg) == 2: + # the identity map acting as a type (1,1) tensor on a pair + # (1-form, vector), returning a scalar: + linform = arg[0] + if not isinstance(linform, FreeModuleLinForm): + raise TypeError("The first argument must be a linear form.") + vector = arg[1] + if not isinstance(vector, FiniteFreeModuleElement): + raise TypeError("The second argument must be a module element.") + return linform(vector) + else: + raise TypeError("Bad number of arguments.") + + +#****************************************************************************** + +class FreeModuleSymBilinForm(FreeModuleTensor): + r""" + Symmetric bilinear form (considered as a type-(0,2) tensor) on a free + module. + + INPUT: + + - ``fmodule`` -- free module `M` over a commutative ring `R` + (must be an instance of :class:`FiniteFreeModule`) + - ``name`` -- (default: None) name given to the symmetric bilinear form + - ``latex_name`` -- (default: None) LaTeX symbol to denote the symmetric + bilinear form; if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Symmetric bilinear form on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: a = M.sym_bilinear_form('A') ; a + symmetric bilinear form A on the rank-3 free module M over the Integer Ring + + A symmetric bilinear form is a type-(0,2) tensor that is symmetric:: + + sage: a.parent() + free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring + sage: a.tensor_type + (0, 2) + sage: a.tensor_rank + 2 + sage: a.symmetries() + symmetry: (0, 1); no antisymmetry + + Components with respect to a given basis:: + + sage: e = M.basis('e') + sage: a[0,0], a[0,1], a[0,2] = 1, 2, 3 + sage: a[1,1], a[1,2] = 4, 5 + sage: a[2,2] = 6 + + Only independent components have been set; the other ones are deduced by + symmetry:: + + sage: a[1,0], a[2,0], a[2,1] + (2, 3, 5) + sage: a[:] + [1 2 3] + [2 4 5] + [3 5 6] + + A symmetric bilinear form acts on pairs of module elements:: + + sage: u = M([2,-1,3]) ; v = M([-2,4,1]) + sage: a(u,v) + 61 + sage: a(v,u) == a(u,v) + True + + The sum of two symmetric bilinear forms is another symmetric bilinear + form:: + + sage: b = M.sym_bilinear_form('B') + sage: b[0,0], b[0,1], b[1,2] = -2, 1, -3 + sage: s = a + b ; s + symmetric bilinear form A+B on the rank-3 free module M over the Integer Ring + sage: a[:], b[:], s[:] + ( + [1 2 3] [-2 1 0] [-1 3 3] + [2 4 5] [ 1 0 -3] [ 3 4 2] + [3 5 6], [ 0 -3 0], [ 3 2 6] + ) + + Adding a symmetric bilinear from with a non-symmetric one results in a + generic type-(0,2) tensor:: + + sage: c = M.tensor((0,2), name='C') + sage: c[0,1] = 4 + sage: s = a + c ; s + type-(0,2) tensor A+C on the rank-3 free module M over the Integer Ring + sage: s.symmetries() + no symmetry; no antisymmetry + sage: s[:] + [1 6 3] + [2 4 5] + [3 5 6] + + + + """ + def __init__(self, fmodule, name=None, latex_name=None): + FreeModuleTensor.__init__(self, fmodule, (0,2), name=name, + latex_name=latex_name, sym=(0,1)) + + def _repr_(self): + r""" + String representation of the object. + """ + description = "symmetric bilinear form " + if self.name is not None: + description += self.name + " " + description += "on the " + str(self.fmodule) + return description + + def _new_instance(self): + r""" + Create a :class:`FreeModuleSymBilinForm` instance. + + """ + return FreeModuleSymBilinForm(self.fmodule) + + def _new_comp(self, basis): + r""" + Create some components in the given basis. + + """ + from comp import CompFullySym + fmodule = self.fmodule # the base free module + return CompFullySym(fmodule.ring, basis, 2, start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + + diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py new file mode 100644 index 00000000000..92b79972239 --- /dev/null +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -0,0 +1,293 @@ +r""" +Tensor free modules + + +The class :class:`TensorFreeModule` implements the tensor products of the type + +.. MATH:: + + T^{(k,l)}(M) = \underbrace{M\otimes\cdots\otimes M}_{k\ \; \mbox{times}} + \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}} + +where `M` is a free module of finite rank over a commutative ring `R` and +`M^*=\mathrm{Hom}_R(M,R)` is the dual of `M`. +`T^{(k,l)}(M)` can be canonically identified with the set of tensors of +type `(k,l)` acting as multilinear forms on `M`. +Note that `T^{(1,0)}(M) = M`. + +`T^{(k,l)}(M)` is itself a free module over `R`, of rank `n^{k+l}`, `n` +being the rank of `M`. Accordingly the class :class:`TensorFreeModule` +inherits from the class :class:`FiniteFreeModule` + +Thanks to the canonical isomorphism `M^{**}\simeq M` (which holds because `M` +is a free module of finite rank), `T^{(k,l)}(M)` can be identified with the +set of tensors of type `(k,l)` defined as multilinear maps + +.. MATH:: + + \underbrace{M^*\times\cdots\times M^*}_{k\ \; \mbox{times}} + \times \underbrace{M\times\cdots\times M}_{l\ \; \mbox{times}} + \longrightarrow R + +Accordingly, :class:`TensorFreeModule` is a Sage *parent* class, whose +*element* class is +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`. + +TODO: + +- implement more general tensor products, i.e. tensor product of the type + `M_1\otimes\cdots\otimes M_n`, where the `M_i`'s are `n` free modules of + finite rank over the same ring `R`. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.modules.module import Module +from finite_free_module import FiniteFreeModule +from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement + + +# From sage/modules/module.pyx: +# ---------------------------- +### The new Module class that should be the base of all Modules +### The derived Module class must implement the element +### constructor: +# +# class MyModule(sage.modules.module.Module): +# Element = MyElement +# def _element_constructor_(self, x): +# return self.element_class(x) +# + + +class TensorFreeModule(FiniteFreeModule): + r""" + Class for the free modules over a commutative ring `R` that are + tensor products of a given free module `M` over `R` with itself and its + dual `M^*`: + + .. MATH:: + + T^{(k,l)}(M) = \underbrace{M\otimes\cdots\otimes M}_{k\ \; \mbox{times}} + \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}} + + As recalled above, `T^{(k,l)}(M)` can be canonically identified with the + set of tensors of type `(k,l)` acting as multilinear forms on `M`. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank (must be an instance of + :class:`FiniteFreeModule`) + - ``tensor_type`` -- pair `(k,l)` with `k` being the contravariant rank and + `l` the covariant rank + - ``name`` -- (string; default: None) name given to the tensor module + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + tensor module; if none is provided, it is set to ``name`` + + EXAMPLES: + + Set of tensors of type (1,2) on a free module of rank 3 over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule + sage: T = TensorFreeModule(M, (1,2)) ; T + free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring + + Instead of importing TensorFreeModule in the global name space, it is + recommended to use the module's method + :meth:`~sage.tensor.modules.finite_free_module.FiniteFreeModule.tensor_module`:: + + sage: T = M.tensor_module(1,2) ; T + free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring + + The module `M` itself is considered as the set of tensors of type (1,0):: + + sage: M is M.tensor_module(1,0) + True + + T is a module (actually a free module) over `\ZZ`:: + + sage: T.category() + Category of modules over Integer Ring + sage: T in Modules(ZZ) + True + sage: T.rank() + 27 + sage: T.base_ring() + Integer Ring + sage: T.base_module() + rank-3 free module M over the Integer Ring + + T is a *parent* object, whose elements are instances of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`:: + + sage: t = T.an_element() ; t + type-(1,2) tensor on the rank-3 free module M over the Integer Ring + sage: from sage.tensor.modules.free_module_tensor import FreeModuleTensor + sage: isinstance(t, FreeModuleTensor) + True + sage: t in T + True + sage: T.is_parent_of(t) + True + + Elements can be constructed by means of the __call__ operator acting + on the parent; 0 yields the zero element:: + + sage: T(0) + type-(1,2) tensor zero on the rank-3 free module M over the Integer Ring + sage: T(0) is T.zero() + True + + while non-zero elements are constructed by providing their components in + a given basis:: + + sage: e = M.basis('e') ; e + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: comp = [[[i-j+k for k in range(3)] for j in range(3)] for i in range(3)] + sage: t = T(comp, basis=e, name='t') ; t + type-(1,2) tensor t on the rank-3 free module M over the Integer Ring + sage: t.comp(e)[:] + [[[0, 1, 2], [-1, 0, 1], [-2, -1, 0]], + [[1, 2, 3], [0, 1, 2], [-1, 0, 1]], + [[2, 3, 4], [1, 2, 3], [0, 1, 2]]] + sage: t.view(e) + t = e_0*e^0*e^1 + 2 e_0*e^0*e^2 - e_0*e^1*e^0 + e_0*e^1*e^2 - 2 e_0*e^2*e^0 - e_0*e^2*e^1 + e_1*e^0*e^0 + 2 e_1*e^0*e^1 + 3 e_1*e^0*e^2 + e_1*e^1*e^1 + 2 e_1*e^1*e^2 - e_1*e^2*e^0 + e_1*e^2*e^2 + 2 e_2*e^0*e^0 + 3 e_2*e^0*e^1 + 4 e_2*e^0*e^2 + e_2*e^1*e^0 + 2 e_2*e^1*e^1 + 3 e_2*e^1*e^2 + e_2*e^2*e^1 + 2 e_2*e^2*e^2 + + An alternative is to construct the tensor from an empty list of components + and to set the nonzero components afterwards:: + + sage: t = T([], name='t') + sage: t.set_comp(e)[0,1,1] = -3 + sage: t.set_comp(e)[2,0,1] = 4 + sage: t.view(e) + t = -3 e_0*e^1*e^1 + 4 e_2*e^0*e^1 + + See the documentation of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + for the full list of arguments that can be provided to the __call__ + operator. For instance, to contruct a tensor symmetric with respect to the + last two indices:: + + sage: t = T([], name='t', sym=(1,2)) + sage: t.set_comp(e)[0,1,1] = -3 + sage: t.set_comp(e)[2,0,1] = 4 + sage: t.view(e) # notice that t^2_{10} has be set equal to t^2_{01} by symmetry + t = -3 e_0*e^1*e^1 + 4 e_2*e^0*e^1 + 4 e_2*e^1*e^0 + + The tensor modules over a given module `M` are unique:: + + sage: T is M.tensor_module(1,2) + True + + """ + + Element = FreeModuleTensor + + def __init__(self, fmodule, tensor_type, name=None, latex_name=None): + self.fmodule = fmodule + self.tensor_type = tuple(tensor_type) + rank = pow(fmodule._rank, tensor_type[0] + tensor_type[1]) + self._zero_element = 0 # provisory (to avoid infinite recursion in what + # follows) + if tensor_type == (0,1): # case of the dual + if name is None and fmodule.name is not None: + name = fmodule.name + '*' + if latex_name is None and fmodule.latex_name is not None: + latex_name = fmodule.latex_name + r'^*' + FiniteFreeModule.__init__(self, fmodule.ring, rank, name=name, + latex_name=latex_name, + start_index=fmodule.sindex, + output_formatter=fmodule.output_formatter) + # Unique representation: + if self.tensor_type in self.fmodule._tensor_modules: + raise TypeError("The module of tensors of type" + + str(self.tensor_type) + + " has already been created.") + else: + self.fmodule._tensor_modules[self.tensor_type] = self + # Zero element + self._zero_element = self._element_constructor_(name='zero', + latex_name='0') + def_basis = self.fmodule.def_basis + if def_basis is not None: + self._zero_element.components[def_basis] = \ + self._zero_element._new_comp(def_basis) + # (since new components are initialized to zero) + + #### Methods required for any Parent + def _element_constructor_(self, comp=[], basis=None, name=None, + latex_name=None, sym=None, antisym=None): + r""" + Construct a tensor + """ + if comp == 0: + return self._zero_element + resu = self.element_class(self.fmodule, self.tensor_type, name=name, + latex_name=latex_name, sym=sym, + antisym=antisym) + if comp != []: + resu.set_comp(basis)[:] = comp + return resu + + def _an_element_(self): + r""" + Construct some (unamed) tensor + """ + resu = self.element_class(self.fmodule, self.tensor_type) + if self.fmodule.def_basis is not None: + sindex = self.fmodule.sindex + ind = [sindex for i in range(resu.tensor_rank)] + resu.set_comp()[ind] = self.fmodule.ring.an_element() + return resu + + #### End of methods required for any Parent + + def _repr_(self): + r""" + String representation of the object. + """ + if self.tensor_type == (0,1): + return "dual of the " + str(self.fmodule) + else: + description = "free module " + if self.name is not None: + description += self.name + " " + description += "of type-(%s,%s)" % \ + (str(self.tensor_type[0]), str(self.tensor_type[1])) + description += " tensors on the " + str(self.fmodule) + return description + + def base_module(self): + r""" + Return the free module on which ``self`` is constructed. + + OUTPUT: + + - instance of :class:`FiniteFreeModule` representing the free module + on which the tensor module is defined. + + EXAMPLE: + + Base module of a type-(1,2) tensor module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: T = M.tensor_module(1,2) + sage: T.base_module() + rank-3 free module M over the Integer Ring + + """ + return self.fmodule + From 354158f495a556c599cb6170276658762a0118c3 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Sun, 23 Mar 2014 18:10:56 -0400 Subject: [PATCH 006/129] Points are held as vectors rather than as tuples. This avoids some unwanted side effects. --- src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py | 3 +++ src/sage/geometry/hyperbolic_space/hyperbolic_point.py | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py index d0dd0d0c1f7..c6646d9dd43 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py @@ -53,6 +53,7 @@ lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') +lazy_import('sage.modules.free_module_element', 'vector') @@ -108,6 +109,8 @@ def __init__(self, coordinates, **graphics_options): "{0} is not a bounded model; boundary" " points not implemented.".format(self.model_name())) elif self.model().bdry_point_in_model(coordinates): + if type(coordinates) == tuple: + coordinates = vector(coordinates) self._coordinates = coordinates else: raise ValueError( diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index ac60d81baa9..3f8230c62b8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -54,6 +54,8 @@ lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') +lazy_import('sage.modules.free_module_element', 'vector') + class HyperbolicPoint(SageObject): r""" @@ -124,6 +126,8 @@ def __init__(self, coordinates, **graphics_options): Point in UHP I. """ if self.model().point_in_model(coordinates): + if type(coordinates) == tuple: + coordinates = vector(coordinates) self._coordinates = coordinates else: raise ValueError( From e34f29ec69a4595f52894724a5aec22a571c02ce Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Sat, 5 Apr 2014 17:46:31 +0200 Subject: [PATCH 007/129] Minor modifications in tensor/modules, including the add of method _new_instance() to classes FreeModuleAltForm and FreeModuleLinForm --- src/sage/tensor/modules/comp.py | 140 ++-- src/sage/tensor/modules/finite_free_module.py | 658 +++++++++--------- .../tensor/modules/free_module_alt_form.py | 18 +- src/sage/tensor/modules/free_module_basis.py | 138 ++-- .../tensor/modules/free_module_tensor_spec.py | 7 +- 5 files changed, 527 insertions(+), 434 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index f21d4c69d70..3391db98f97 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -693,6 +693,11 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): for i in range(si, nsi)] if self.nid == 2: try: + for i in range(self.dim): + for j in range(self.dim): + a = resu[i][j] + if hasattr(a, 'express'): + resu[i][j] = a.express resu = matrix(resu) # for a nicer output except TypeError: pass @@ -714,29 +719,42 @@ def _gen_list(self, ind, no_format=True, format_type=None): return [self._gen_list(ind + [i], no_format, format_type) for i in range(si, nsi)] - def __setitem__(self, indices, value): + def __setitem__(self, args, value): r""" Sets the component corresponding to the given indices. INPUT: - - ``indices`` -- list of indices (possibly a single integer if + - ``args`` -- list of indices (possibly a single integer if self is a 1-index object) ; if [:] is provided, all the components are set. - ``value`` -- the value to be set or a list of values if ``args`` == ``[:]`` """ + format_type = None # default value, possibly redefined below + if isinstance(args, list): # case of [[...]] syntax + if isinstance(args[0], slice): + indices = args[0] + elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] + else: + indices = tuple(args) + else: + # Determining from the input the list of indices and the format + if isinstance(args, (int, Integer)) or isinstance(args, slice): + indices = args + elif isinstance(args[0], slice): + indices = args[0] + format_type = args[1] + elif len(args) == self.nid: + indices = args + else: + format_type = args[-1] + indices = args[:-1] if isinstance(indices, slice): - self._set_list(indices, value) + self._set_list(indices, format_type, value) else: - if isinstance(indices, list): - # to ensure equivalence between [i,j,...] and [[i,j,...]] or - # [[(i,j,...)]] - if isinstance(indices[0], tuple) or isinstance(indices[0], list): - indices = indices[0] - else: - indices = tuple(indices) ind = self._check_indices(indices) if value == 0: # if the component has been set previously, it is deleted, @@ -744,9 +762,12 @@ def __setitem__(self, indices, value): if ind in self._comp: del self._comp[ind] else: - self._comp[ind] = self.ring(value) + if format_type is None: + self._comp[ind] = self.ring(value) + else: + self._comp[ind] = self.ring(value, format_type) - def _set_list(self, ind_slice, values): + def _set_list(self, ind_slice, format_type, values): r""" Set the components from a list. @@ -774,26 +795,28 @@ def _set_list(self, ind_slice, values): raise NotImplementedError("Function [start:stop:step] " + "not implemented.") for i in range(start, stop): - self[i] = values[i-start] + self[i, format_type] = values[i-start] else: if ind_slice.start is not None or ind_slice.stop is not None: raise NotImplementedError("Function [start:stop] not " + "implemented for components with " + str(self.nid) + " indices.") for i in range(si, nsi): - self._set_value_list([i], values[i-si]) + self._set_value_list([i], format_type, values[i-si]) - def _set_value_list(self, ind, val): + def _set_value_list(self, ind, format_type, val): r""" Recursive function to set a list of values to self """ if len(ind) == self.nid: + if format_type is not None: + ind = tuple(ind + [format_type]) self[ind] = val else: si = self.sindex nsi = si + self.dim for i in range(si, nsi): - self._set_value_list(ind + [i], val[i-si]) + self._set_value_list(ind + [i], format_type, val[i-si]) def swap_adjacent_indices(self, pos1, pos2, pos3): r""" @@ -2093,29 +2116,42 @@ def __getitem__(self, args): return self.output_formatter( -self._comp[ind], format_type) - def __setitem__(self, indices, value): + def __setitem__(self, args, value): r""" Sets the component corresponding to the given indices. INPUT: - - ``indices`` -- list of indices (possibly a single integer if + - ``args`` -- list of indices (possibly a single integer if self is a 1-index object) ; if [:] is provided, all the components are set. - ``value`` -- the value to be set or a list of values if ``args`` == ``[:]`` """ + format_type = None # default value, possibly redefined below + if isinstance(args, list): # case of [[...]] syntax + if isinstance(args[0], slice): + indices = args[0] + elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] + else: + indices = tuple(args) + else: + # Determining from the input the list of indices and the format + if isinstance(args, (int, Integer)) or isinstance(args, slice): + indices = args + elif isinstance(args[0], slice): + indices = args[0] + format_type = args[1] + elif len(args) == self.nid: + indices = args + else: + format_type = args[-1] + indices = args[:-1] if isinstance(indices, slice): - self._set_list(indices, value) + self._set_list(indices, format_type, value) else: - if isinstance(indices, list): - # to ensure equivalence between [i,j,...] and [[i,j,...]] or - # [[(i,j,...)]] - if isinstance(indices[0], tuple) or isinstance(indices[0], list): - indices = indices[0] - else: - indices = tuple(indices) sign, ind = self._ordered_indices(indices) if sign == 0: if value != 0: @@ -2128,11 +2164,16 @@ def __setitem__(self, indices, value): if ind in self._comp: del self._comp[ind] # zero values are not stored else: - if sign == 1: - self._comp[ind] = self.ring(value) - else: # sign = -1 - self._comp[ind] = -self.ring(value) - + if format_type is None: + if sign == 1: + self._comp[ind] = self.ring(value) + else: # sign = -1 + self._comp[ind] = -self.ring(value) + else: + if sign == 1: + self._comp[ind] = self.ring(value, format_type) + else: # sign = -1 + self._comp[ind] = -self.ring(value, format_type) def swap_adjacent_indices(self, pos1, pos2, pos3): r""" @@ -3405,7 +3446,7 @@ def __getitem__(self, args): return self.output_formatter(self.ring.zero_element(), format_type) - def __setitem__(self, indices, value): + def __setitem__(self, args, value): r""" Sets the component corresponding to the given indices. @@ -3418,23 +3459,38 @@ def __setitem__(self, indices, value): == ``[:]`` """ + format_type = None # default value, possibly redefined below + if isinstance(args, list): # case of [[...]] syntax + if isinstance(args[0], slice): + indices = args[0] + elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] + else: + indices = tuple(args) + else: + # Determining from the input the list of indices and the format + if isinstance(args, (int, Integer)) or isinstance(args, slice): + indices = args + elif isinstance(args[0], slice): + indices = args[0] + format_type = args[1] + elif len(args) == self.nid: + indices = args + else: + format_type = args[-1] + indices = args[:-1] if isinstance(indices, slice): - self._set_list(indices, value) + self._set_list(indices, format_type, value) else: - if isinstance(indices, list): - # to ensure equivalence between [i,j,...] and [[i,j,...]] or - # [[(i,j,...)]] - if isinstance(indices[0], tuple) or isinstance(indices[0], list): - indices = indices[0] - else: - indices = tuple(indices) ind = self._ordered_indices(indices)[1] # [0]=sign is not used if value == 0: if ind in self._comp: del self._comp[ind] # zero values are not stored else: - self._comp[ind] = self.ring(value) - + if format_type is None: + self._comp[ind] = self.ring(value) + else: + self._comp[ind] = self.ring(value, format_type) def __add__(self, other): r""" diff --git a/src/sage/tensor/modules/finite_free_module.py b/src/sage/tensor/modules/finite_free_module.py index e793914cd94..64898970988 100644 --- a/src/sage/tensor/modules/finite_free_module.py +++ b/src/sage/tensor/modules/finite_free_module.py @@ -409,8 +409,8 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, latex_name='0') - #### Methods required for any Parent + def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None): r""" @@ -435,6 +435,8 @@ def _an_element_(self): #### End of methods required for any Parent + #### Methods to be redefined by derived classes #### + def _repr_(self): r""" String representation of the object. @@ -444,89 +446,6 @@ def _repr_(self): description += self.name + " " description += "over the " + str(self.ring) return description - - def _latex_(self): - r""" - LaTeX representation of the object. - """ - if self.latex_name is None: - return r'\mbox{' + str(self) + r'}' - else: - return self.latex_name - - def rank(self): - r""" - Return the rank of the free module ``self``. - - Since the ring over which ``self`` is built is assumed to be - commutative (and hence has the invariant basis number property), the - rank is defined uniquely, as the cardinality of any basis of ``self``. - - EXAMPLES: - - Rank of free modules over `\ZZ`:: - - sage: M = FiniteFreeModule(ZZ, 3) - sage: M.rank() - 3 - sage: M.tensor_module(0,1).rank() - 3 - sage: M.tensor_module(0,2).rank() - 9 - sage: M.tensor_module(1,0).rank() - 3 - sage: M.tensor_module(1,1).rank() - 9 - sage: M.tensor_module(1,2).rank() - 27 - sage: M.tensor_module(2,2).rank() - 81 - - """ - return self._rank - - def zero(self): - r""" - Return the zero element. - - EXAMPLES: - - Zero elements of free modules over `\ZZ`:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M') - sage: M.zero() - element zero of the rank-3 free module M over the Integer Ring - sage: M.zero().parent() is M - True - sage: M.zero() is M(0) - True - sage: T = M.tensor_module(1,1) - sage: T.zero() - type-(1,1) tensor zero on the rank-3 free module M over the Integer Ring - sage: T.zero().parent() is T - True - sage: T.zero() is T(0) - True - - Components of the zero element with respect to some basis:: - - sage: e = M.basis('e') - sage: M.zero().comp(e)[:] - [0, 0, 0] - sage: for i in M.irange(): print M.zero().comp(e)[i] == M.base_ring().zero(), - True True True - sage: T.zero().comp(e)[:] - [0 0 0] - [0 0 0] - [0 0 0] - sage: M.tensor_module(1,2).zero().comp(e)[:] - [[[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]]] - - """ - return self._zero_element - def tensor_module(self, k, l): r""" @@ -573,91 +492,6 @@ def tensor_module(self, k, l): self._tensor_modules[(k,l)] = TensorFreeModule(self, (k,l)) return self._tensor_modules[(k,l)] - def dual(self): - r""" - Return the dual module. - - EXAMPLE: - - Dual of a free module over `\ZZ`:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M') - sage: M.dual() - dual of the rank-3 free module M over the Integer Ring - sage: latex(M.dual()) - M^* - - The dual is a free module of the same rank as M:: - - sage: isinstance(M.dual(), FiniteFreeModule) - True - sage: M.dual().rank() - 3 - - It is formed by tensors of type (0,1), i.e. linear forms:: - - sage: M.dual() is M.tensor_module(0,1) - True - sage: M.dual().an_element() - type-(0,1) tensor on the rank-3 free module M over the Integer Ring - sage: a = M.linear_form() - sage: a in M.dual() - True - - The elements of a dual basis belong of course to the dual module:: - - sage: e = M.basis('e') - sage: e.dual_basis()[0] in M.dual() - True - - """ - return self.tensor_module(0,1) - - def irange(self, start=None): - r""" - Single index generator, labelling the elements of a basis. - - INPUT: - - - ``start`` -- (integer; default: None) initial value of the index; if none is - provided, ``self.sindex`` is assumed - - OUTPUT: - - - an iterable index, starting from ``start`` and ending at - ``self.sindex + self.rank() -1`` - - EXAMPLES: - - Index range on a rank-3 module:: - - sage: M = FiniteFreeModule(ZZ, 3) - sage: for i in M.irange(): print i, - 0 1 2 - sage: for i in M.irange(start=1): print i, - 1 2 - - The default starting value corresponds to the parameter ``start_index`` - provided at the module construction (the default value being 0):: - - sage: M1 = FiniteFreeModule(ZZ, 3, start_index=1) - sage: for i in M1.irange(): print i, - 1 2 3 - sage: M2 = FiniteFreeModule(ZZ, 3, start_index=-4) - sage: for i in M2.irange(): print i, - -4 -3 -2 - - """ - si = self.sindex - imax = self._rank + si - if start is None: - i = si - else: - i = start - while i < imax: - yield i - i += 1 - def basis(self, symbol=None, latex_symbol=None): r""" Define a basis of the free module. @@ -735,171 +569,60 @@ def basis(self, symbol=None, latex_symbol=None): if symbol == other.symbol: return other return FreeModuleBasis(self, symbol, latex_symbol) - - def default_basis(self): + + + def tensor(self, tensor_type, name=None, latex_name=None, sym=None, + antisym=None): r""" - Return the default basis of the free module. + Construct a tensor on the free module. - The *default basis* is simply a basis whose name can be skipped in - methods requiring a basis as an argument. By default, it is the first - basis introduced on the module. It can be changed by the method - :meth:`set_default_basis`. + INPUT: + - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and + l the covariant rank + - ``name`` -- (string; default: None) name given to the tensor + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + tensor; if none is provided, the LaTeX symbol is set to ``name`` + - ``sym`` -- (default: None) a symmetry or a list of symmetries among + the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention position=0 for the first argument. For instance: + + * sym=(0,1) for a symmetry between the 1st and 2nd arguments + * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. + + - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries + among the arguments, with the same convention as for ``sym``. + OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + representing the tensor defined on ``self`` with the provided + characteristics. EXAMPLES: - At the module construction, no default basis is assumed:: - - sage: M = FiniteFreeModule(ZZ, 2, name='M', start_index=1) - sage: M.default_basis() - No default basis has been defined on the rank-2 free module M over the Integer Ring - - The first defined basis becomes the default one:: + Tensors on a rank-3 free module:: - sage: e = M.basis('e') ; e - basis (e_1,e_2) on the rank-2 free module M over the Integer Ring - sage: M.default_basis() - basis (e_1,e_2) on the rank-2 free module M over the Integer Ring - sage: f = M.basis('f') ; f - basis (f_1,f_2) on the rank-2 free module M over the Integer Ring - sage: M.default_basis() - basis (e_1,e_2) on the rank-2 free module M over the Integer Ring - - """ - if self.def_basis is None: - print "No default basis has been defined on the " + str(self) - return self.def_basis - - def set_default_basis(self, basis): - r""" - Sets the default basis of the free module. - - The *default basis* is simply a basis whose name can be skipped in - methods requiring a basis as an argument. By default, it is the first - basis introduced on the module. - - INPUT: - - - ``basis`` -- instance of - :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` - representing a basis on ``self`` - - EXAMPLES: - - Changing the default basis on a rank-3 free module:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) - sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring - sage: f = M.basis('f') ; f - basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring - sage: M.default_basis() - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring - sage: M.set_default_basis(f) - sage: M.default_basis() - basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring - - """ - from free_module_basis import FreeModuleBasis - if not isinstance(basis, FreeModuleBasis): - raise TypeError("The argument is not a free module basis.") - if basis.fmodule is not self: - raise ValueError("The basis is not defined on the current module.") - self.def_basis = basis - - def view_bases(self): - r""" - Display the bases that have been defined on the free module. - - EXAMPLES: - - Bases on a rank-4 free module:: - - sage: M = FiniteFreeModule(ZZ, 4, name='M', start_index=1) - sage: M.view_bases() - No basis has been defined on the rank-4 free module M over the Integer Ring - sage: e = M.basis('e') - sage: M.view_bases() - Bases defined on the rank-4 free module M over the Integer Ring: - - (e_1,e_2,e_3,e_4) (default basis) - sage: f = M.basis('f') - sage: M.view_bases() - Bases defined on the rank-4 free module M over the Integer Ring: - - (e_1,e_2,e_3,e_4) (default basis) - - (f_1,f_2,f_3,f_4) - sage: M.set_default_basis(f) - sage: M.view_bases() - Bases defined on the rank-4 free module M over the Integer Ring: - - (e_1,e_2,e_3,e_4) - - (f_1,f_2,f_3,f_4) (default basis) - - """ - if self.known_bases == []: - print "No basis has been defined on the " + str(self) - else: - print "Bases defined on the " + str(self) + ":" - for basis in self.known_bases: - item = " - " + basis.name - if basis is self.def_basis: - item += " (default basis)" - print item - - def tensor(self, tensor_type, name=None, latex_name=None, sym=None, - antisym=None): - r""" - Construct a tensor on the free module. - - INPUT: - - - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l - the covariant rank - - ``name`` -- (string; default: None) name given to the tensor - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - tensor; if none is provided, the LaTeX symbol is set to ``name`` - - ``sym`` -- (default: None) a symmetry or a list of symmetries among the - tensor arguments: each symmetry is described by a tuple containing - the positions of the involved arguments, with the convention position=0 - for the first argument. For instance: - - * sym=(0,1) for a symmetry between the 1st and 2nd arguments - * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd - arguments and a symmetry between the 2nd, 4th and 5th arguments. - - - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries - among the arguments, with the same convention as for ``sym``. - - OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` - representing the tensor defined on ``self`` with the provided - characteristics. - - EXAMPLES: - - Tensors on a rank-3 free module:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M') - sage: t = M.tensor((1,0), name='t') ; t - element t of the rank-3 free module M over the Integer Ring - sage: t = M.tensor((0,1), name='t') ; t - linear form t on the rank-3 free module M over the Integer Ring - sage: t = M.tensor((1,1), name='t') ; t - endomorphism t on the rank-3 free module M over the Integer Ring - sage: t = M.tensor((0,2), name='t', sym=(0,1)) ; t - symmetric bilinear form t on the rank-3 free module M over the Integer Ring - sage: t = M.tensor((0,2), name='t', antisym=(0,1)) ; t - alternating form t of degree 2 on the rank-3 free module M over the Integer Ring - sage: t = M.tensor((1,2), name='t') ; t - type-(1,2) tensor t on the rank-3 free module M over the Integer Ring - - See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` - for more examples and documentation. - + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((1,0), name='t') ; t + element t of the rank-3 free module M over the Integer Ring + sage: t = M.tensor((0,1), name='t') ; t + linear form t on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((1,1), name='t') ; t + endomorphism t on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((0,2), name='t', sym=(0,1)) ; t + symmetric bilinear form t on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((0,2), name='t', antisym=(0,1)) ; t + alternating form t of degree 2 on the rank-3 free module M over the Integer Ring + sage: t = M.tensor((1,2), name='t') ; t + type-(1,2) tensor t on the rank-3 free module M over the Integer Ring + + See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + for more examples and documentation. + """ from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement from free_module_tensor_spec import FreeModuleEndomorphism, \ @@ -936,13 +659,13 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): INPUT: - - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l - the covariant rank + - ``tensor_type`` -- pair (k,l) with k being the contravariant rank + and l the covariant rank - ``comp`` -- instance of :class:`~sage.tensor.modules.comp.Components` representing the tensor components in a given basis - ``name`` -- (string; default: None) name given to the tensor - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the tensor; - if none is provided, the LaTeX symbol is set to ``name`` + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + tensor; if none is provided, the LaTeX symbol is set to ``name`` OUTPUT: @@ -1328,8 +1051,287 @@ def sym_bilinear_form(self, name=None, latex_name=None): from free_module_tensor_spec import FreeModuleSymBilinForm return FreeModuleSymBilinForm(self, name=name, latex_name=latex_name) + #### End of methods to be redefined by derived classes #### + + def _latex_(self): + r""" + LaTeX representation of the object. + """ + if self.latex_name is None: + return r'\mbox{' + str(self) + r'}' + else: + return self.latex_name + + def rank(self): + r""" + Return the rank of the free module ``self``. + + Since the ring over which ``self`` is built is assumed to be + commutative (and hence has the invariant basis number property), the + rank is defined uniquely, as the cardinality of any basis of ``self``. + + EXAMPLES: + + Rank of free modules over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3) + sage: M.rank() + 3 + sage: M.tensor_module(0,1).rank() + 3 + sage: M.tensor_module(0,2).rank() + 9 + sage: M.tensor_module(1,0).rank() + 3 + sage: M.tensor_module(1,1).rank() + 9 + sage: M.tensor_module(1,2).rank() + 27 + sage: M.tensor_module(2,2).rank() + 81 + + """ + return self._rank + + def zero(self): + r""" + Return the zero element. + + EXAMPLES: + + Zero elements of free modules over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M.zero() + element zero of the rank-3 free module M over the Integer Ring + sage: M.zero().parent() is M + True + sage: M.zero() is M(0) + True + sage: T = M.tensor_module(1,1) + sage: T.zero() + type-(1,1) tensor zero on the rank-3 free module M over the Integer Ring + sage: T.zero().parent() is T + True + sage: T.zero() is T(0) + True + + Components of the zero element with respect to some basis:: + + sage: e = M.basis('e') + sage: M.zero().comp(e)[:] + [0, 0, 0] + sage: for i in M.irange(): print M.zero().comp(e)[i] == M.base_ring().zero(), + True True True + sage: T.zero().comp(e)[:] + [0 0 0] + [0 0 0] + [0 0 0] + sage: M.tensor_module(1,2).zero().comp(e)[:] + [[[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]]] + + """ + return self._zero_element + + def dual(self): + r""" + Return the dual module. + + EXAMPLE: + + Dual of a free module over `\ZZ`:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M.dual() + dual of the rank-3 free module M over the Integer Ring + sage: latex(M.dual()) + M^* + + The dual is a free module of the same rank as M:: + + sage: isinstance(M.dual(), FiniteFreeModule) + True + sage: M.dual().rank() + 3 + + It is formed by tensors of type (0,1), i.e. linear forms:: + + sage: M.dual() is M.tensor_module(0,1) + True + sage: M.dual().an_element() + type-(0,1) tensor on the rank-3 free module M over the Integer Ring + sage: a = M.linear_form() + sage: a in M.dual() + True + + The elements of a dual basis belong of course to the dual module:: + + sage: e = M.basis('e') + sage: e.dual_basis()[0] in M.dual() + True + + """ + return self.tensor_module(0,1) + + def irange(self, start=None): + r""" + Single index generator, labelling the elements of a basis. + + INPUT: + + - ``start`` -- (integer; default: None) initial value of the index; if none is + provided, ``self.sindex`` is assumed + + OUTPUT: + + - an iterable index, starting from ``start`` and ending at + ``self.sindex + self.rank() -1`` + + EXAMPLES: + + Index range on a rank-3 module:: + + sage: M = FiniteFreeModule(ZZ, 3) + sage: for i in M.irange(): print i, + 0 1 2 + sage: for i in M.irange(start=1): print i, + 1 2 + + The default starting value corresponds to the parameter ``start_index`` + provided at the module construction (the default value being 0):: + sage: M1 = FiniteFreeModule(ZZ, 3, start_index=1) + sage: for i in M1.irange(): print i, + 1 2 3 + sage: M2 = FiniteFreeModule(ZZ, 3, start_index=-4) + sage: for i in M2.irange(): print i, + -4 -3 -2 + """ + si = self.sindex + imax = self._rank + si + if start is None: + i = si + else: + i = start + while i < imax: + yield i + i += 1 + def default_basis(self): + r""" + Return the default basis of the free module. + + The *default basis* is simply a basis whose name can be skipped in + methods requiring a basis as an argument. By default, it is the first + basis introduced on the module. It can be changed by the method + :meth:`set_default_basis`. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + + EXAMPLES: + + At the module construction, no default basis is assumed:: + + sage: M = FiniteFreeModule(ZZ, 2, name='M', start_index=1) + sage: M.default_basis() + No default basis has been defined on the rank-2 free module M over the Integer Ring + + The first defined basis becomes the default one:: + + sage: e = M.basis('e') ; e + basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + sage: M.default_basis() + basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + sage: f = M.basis('f') ; f + basis (f_1,f_2) on the rank-2 free module M over the Integer Ring + sage: M.default_basis() + basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + + """ + if self.def_basis is None: + print "No default basis has been defined on the " + str(self) + return self.def_basis + + def set_default_basis(self, basis): + r""" + Sets the default basis of the free module. + + The *default basis* is simply a basis whose name can be skipped in + methods requiring a basis as an argument. By default, it is the first + basis introduced on the module. + + INPUT: + + - ``basis`` -- instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing a basis on ``self`` + + EXAMPLES: + + Changing the default basis on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: f = M.basis('f') ; f + basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + sage: M.default_basis() + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: M.set_default_basis(f) + sage: M.default_basis() + basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + + """ + from free_module_basis import FreeModuleBasis + if not isinstance(basis, FreeModuleBasis): + raise TypeError("The argument is not a free module basis.") + if basis.fmodule is not self: + raise ValueError("The basis is not defined on the current module.") + self.def_basis = basis + + + def view_bases(self): + r""" + Display the bases that have been defined on the free module. + + EXAMPLES: + + Bases on a rank-4 free module:: + + sage: M = FiniteFreeModule(ZZ, 4, name='M', start_index=1) + sage: M.view_bases() + No basis has been defined on the rank-4 free module M over the Integer Ring + sage: e = M.basis('e') + sage: M.view_bases() + Bases defined on the rank-4 free module M over the Integer Ring: + - (e_1,e_2,e_3,e_4) (default basis) + sage: f = M.basis('f') + sage: M.view_bases() + Bases defined on the rank-4 free module M over the Integer Ring: + - (e_1,e_2,e_3,e_4) (default basis) + - (f_1,f_2,f_3,f_4) + sage: M.set_default_basis(f) + sage: M.view_bases() + Bases defined on the rank-4 free module M over the Integer Ring: + - (e_1,e_2,e_3,e_4) + - (f_1,f_2,f_3,f_4) (default basis) + + """ + if self.known_bases == []: + print "No basis has been defined on the " + str(self) + else: + print "Bases defined on the " + str(self) + ":" + for basis in self.known_bases: + item = " - " + basis.name + if basis is self.def_basis: + item += " (default basis)" + print item + diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 132ff9ec777..62981de8387 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -72,6 +72,14 @@ def _del_derived(self): """ FreeModuleTensor._del_derived(self) + def _new_instance(self): + r""" + Create a :class:`FreeModuleAltForm` instance on the same module and of + the same degree. + + """ + return FreeModuleAltForm(self.fmodule, self.tensor_rank) + def _new_comp(self, basis): r""" Create some components in the given basis. @@ -339,7 +347,7 @@ def wedge(self, other): ind_r = ind_s + ind_o if len(ind_r) == len(set(ind_r)): # all indices are different cmp_r[ind_r] += val_s * val_o - result = FreeModuleAltForm(fmodule, rank_r) + result = fmodule.alternating_form(rank_r) result.components[basis] = cmp_r if self.name is not None and other.name is not None: sname = self.name @@ -439,6 +447,14 @@ def _repr_(self): description += "on the " + str(self.fmodule) return description + def _new_instance(self): + r""" + Create a :class:`FreeModuleLinForm` instance on the same module. + + """ + return FreeModuleLinForm(self.fmodule) + + def _new_comp(self, basis): r""" Create some components in the given basis. diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 3e8884f2797..5792c70ed8e 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -84,46 +84,47 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): """ def __init__(self, fmodule, symbol, latex_symbol=None): - from free_module_tensor import FiniteFreeModuleElement self.fmodule = fmodule self.name = "(" + \ - ",".join([symbol + "_" + str(i) for i in self.fmodule.irange()]) +")" + ",".join([symbol + "_" + str(i) for i in fmodule.irange()]) +")" if latex_symbol is None: latex_symbol = symbol - self.latex_name = r"\left(" + \ - ",".join([latex_symbol + "_" + str(i) - for i in self.fmodule.irange()]) + r"\right)" + self.latex_name = r"\left(" + ",".join([latex_symbol + "_" + str(i) + for i in fmodule.irange()]) + r"\right)" self.symbol = symbol + self.latex_symbol = latex_symbol # The basis is added to the module list of bases - for other in self.fmodule.known_bases: + for other in fmodule.known_bases: if symbol == other.symbol: raise ValueError("The " + str(other) + " already exist on the " + - str(self.fmodule)) - self.fmodule.known_bases.append(self) + str(fmodule)) + fmodule.known_bases.append(self) # The individual vectors: vl = list() - for i in self.fmodule.irange(): + for i in fmodule.irange(): v_name = symbol + "_" + str(i) v_symb = latex_symbol + "_" + str(i) - v = FiniteFreeModuleElement(self.fmodule, name=v_name, latex_name=v_symb) - for j in self.fmodule.irange(): - v.set_comp(self)[j] = 0 - v.set_comp(self)[i] = 1 + v = fmodule.element_class(fmodule, name=v_name, latex_name=v_symb) + for j in fmodule.irange(): + v.set_comp(self)[j] = fmodule.ring.zero() + v.set_comp(self)[i] = fmodule.ring.one() vl.append(v) self.vec = tuple(vl) - # The dual basis - self._dual_basis = FreeModuleCoBasis(self, symbol, - latex_symbol=latex_symbol) # The first defined basis is considered as the default one # and is used to initialize the components of the zero elements of # all tensor modules constructed up to now (including the base module # itself, since it is considered as a type-(1,0) tensor module) - if self.fmodule.def_basis is None: - self.fmodule.def_basis = self - for t in self.fmodule._tensor_modules.values(): + if fmodule.def_basis is None: + fmodule.def_basis = self + for t in fmodule._tensor_modules.values(): t._zero_element.components[self] = \ t._zero_element._new_comp(self) # (since new components are initialized to zero) + # The dual basis: + self._dual_basis = self._init_dual_basis() + + + ###### Methods to be redefined by derived classes of FreeModuleBasis ###### def _repr_(self): r""" @@ -131,6 +132,61 @@ def _repr_(self): """ return "basis " + self.name + " on the " + str(self.fmodule) + + def _init_dual_basis(self): + r""" + Construct the basis dual to ``self``. + + OUTPUT: + + - instance of :class:`FreeModuleCoBasis` representing the dual of + ``self`` + + """ + return FreeModuleCoBasis(self, self.symbol, + latex_symbol=self.latex_symbol) + + ###### End of methods to be redefined by derived classes ###### + + + def dual_basis(self): + r""" + Return the basis dual to ``self``. + + OUTPUT: + + - instance of :class:`FreeModuleCoBasis` representing the dual of + ``self`` + + EXAMPLES: + + Dual basis on a rank-3 free module:: + + sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') ; e + basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + sage: f = e.dual_basis() ; f + dual basis (e^1,e^2,e^3) on the rank-3 free module M over the Integer Ring + + Let us check that the elements of f are tensors of type (0,1) on M:: + + sage: f[1] in M.tensor_module(0,1) + True + sage: f[1] + linear form e^1 on the rank-3 free module M over the Integer Ring + + and that f is indeed the dual of e:: + + sage: f[1](e[1]), f[1](e[2]), f[1](e[3]) + (1, 0, 0) + sage: f[2](e[1]), f[2](e[2]), f[2](e[3]) + (0, 1, 0) + sage: f[3](e[1]), f[3](e[2]), f[3](e[3]) + (0, 0, 1) + + """ + return self._dual_basis + def _latex_(self): r""" LaTeX representation of the object. @@ -222,7 +278,9 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): raise TypeError("The argument change_of_basis must be some " + "instance of FreeModuleAutomorphism.") fmodule = self.fmodule - the_new_basis = FreeModuleBasis(fmodule, symbol, latex_symbol) + # self.__class__ is used instead of FreeModuleBasis for a correct + # construction in case of derived classes: + the_new_basis = self.__class__(fmodule, symbol, latex_symbol) transf = change_of_basis.copy() inv_transf = change_of_basis.inverse().copy() si = fmodule.sindex @@ -260,43 +318,6 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): # return the_new_basis - def dual_basis(self): - r""" - Return the basis dual to ``self``. - - OUTPUT: - - - instance of :class:`FreeModuleCoBasis` representing the dual of - ``self`` - - EXAMPLES: - - Dual basis on a rank-3 free module:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) - sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring - sage: f = e.dual_basis() ; f - dual basis (e^1,e^2,e^3) on the rank-3 free module M over the Integer Ring - - Let us check that the elements of f are tensors of type (0,1) on M:: - - sage: f[1] in M.tensor_module(0,1) - True - sage: f[1] - linear form e^1 on the rank-3 free module M over the Integer Ring - - and that f is indeed the dual of e:: - - sage: f[1](e[1]), f[1](e[2]), f[1](e[3]) - (1, 0, 0) - sage: f[2](e[1]), f[2](e[2]), f[2](e[3]) - (0, 1, 0) - sage: f[3](e[1]), f[3](e[2]), f[3](e[3]) - (0, 0, 1) - - """ - return self._dual_basis #****************************************************************************** @@ -342,7 +363,6 @@ class FreeModuleCoBasis(SageObject): """ def __init__(self, basis, symbol, latex_symbol=None): - from free_module_alt_form import FreeModuleLinForm self.basis = basis self.fmodule = basis.fmodule self.name = "(" + \ @@ -357,7 +377,7 @@ def __init__(self, basis, symbol, latex_symbol=None): for i in self.fmodule.irange(): v_name = symbol + "^" + str(i) v_symb = latex_symbol + "^" + str(i) - v = FreeModuleLinForm(self.fmodule, name=v_name, latex_name=v_symb) + v = self.fmodule.linear_form(name=v_name, latex_name=v_symb) for j in self.fmodule.irange(): v.set_comp(basis)[j] = 0 v.set_comp(basis)[i] = 1 diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py index 9bd30507c2a..6d428d4ede1 100644 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -138,7 +138,7 @@ def __call__(self, *arg): t = self.components[basis] v = vector.components[basis] fmodule = self.fmodule - result = FiniteFreeModuleElement(fmodule) + result = vector._new_instance() for i in fmodule.irange(): res = 0 for j in fmodule.irange(): @@ -229,14 +229,13 @@ def _repr_(self): def _new_instance(self): r""" Create a :class:`FreeModuleAutomorphism` instance. - """ return FreeModuleAutomorphism(self.fmodule) def _del_derived(self): r""" Delete the derived quantities - + """ # First delete the derived quantities pertaining to the mother class: FreeModuleEndomorphism._del_derived(self) @@ -299,7 +298,7 @@ def inverse(self): for basis in self.components: try: mat_self = matrix( - [[self.comp(basis)[i, j] + [[self.comp(basis)[[i, j]] for j in range(si, nsi)] for i in range(si, nsi)]) except (KeyError, ValueError): continue From 010c31309df79e4957bb7e8daca6cd47d22b2121 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Sun, 18 May 2014 23:08:34 +0200 Subject: [PATCH 008/129] Minor improvements; coincides with SageManifolds as of 18 May 2014 (commit d488f8f807 on github) --- src/doc/en/reference/index.rst | 2 +- .../en/reference/tensors_free_module/comp.rst | 16 --- .../en/reference/tensors_free_module/conf.py | 38 +------ .../finite_free_module.rst | 16 --- .../free_module_alt_form.rst | 16 --- .../tensors_free_module/free_module_basis.rst | 16 --- .../free_module_tensor.rst | 16 --- .../free_module_tensor_spec.rst | 16 --- .../reference/tensors_free_module/index.rst | 36 ++---- .../tensor_free_module.rst | 16 --- src/sage/tensor/modules/comp.py | 35 +++--- src/sage/tensor/modules/finite_free_module.py | 2 +- .../tensor/modules/free_module_alt_form.py | 2 +- src/sage/tensor/modules/free_module_basis.py | 31 +++++- src/sage/tensor/modules/free_module_tensor.py | 104 +++++++++++++----- .../tensor/modules/free_module_tensor_spec.py | 3 +- 16 files changed, 137 insertions(+), 228 deletions(-) delete mode 100644 src/doc/en/reference/tensors_free_module/comp.rst mode change 100644 => 120000 src/doc/en/reference/tensors_free_module/conf.py delete mode 100644 src/doc/en/reference/tensors_free_module/finite_free_module.rst delete mode 100644 src/doc/en/reference/tensors_free_module/free_module_alt_form.rst delete mode 100644 src/doc/en/reference/tensors_free_module/free_module_basis.rst delete mode 100644 src/doc/en/reference/tensors_free_module/free_module_tensor.rst delete mode 100644 src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst delete mode 100644 src/doc/en/reference/tensors_free_module/tensor_free_module.rst diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index b972e7235f5..ac6d13dff90 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -84,7 +84,7 @@ Groups, Monoids, Matrices, Modules * :doc:`Monoids ` * :doc:`Matrices and Spaces of Matrices ` * :doc:`Modules ` -* :doc:`Tensors on free modules of finite rank ` +* :doc:`Tensors on free modules of finite rank ` Geometry and Topology --------------------- diff --git a/src/doc/en/reference/tensors_free_module/comp.rst b/src/doc/en/reference/tensors_free_module/comp.rst deleted file mode 100644 index 5edc877457f..00000000000 --- a/src/doc/en/reference/tensors_free_module/comp.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.comp: - -Components -========== - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.comp - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/reference/tensors_free_module/conf.py b/src/doc/en/reference/tensors_free_module/conf.py deleted file mode 100644 index 26cb7636882..00000000000 --- a/src/doc/en/reference/tensors_free_module/conf.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sage documentation build configuration file, created by -# sphinx-quickstart on Thu Aug 21 20:15:55 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -sys.path.append(os.environ['SAGE_DOC']) -from common.conf import * - -# General information about the project. -project = u"Tensors on free modules" -name = 'tensors_free_modules_ref' -release = "0.1" - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = project + " v" + release -copyright = "2014, Eric Gourgoulhon and Michal Bejger" - -# Output file base name for HTML help builder. -htmlhelp_basename = name - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, document class [howto/manual]). -latex_documents = [ - ('index', name+'.tex', u'Tensors on free modules', - u'', 'manual'), -] - diff --git a/src/doc/en/reference/tensors_free_module/conf.py b/src/doc/en/reference/tensors_free_module/conf.py new file mode 120000 index 00000000000..2bdf7e68470 --- /dev/null +++ b/src/doc/en/reference/tensors_free_module/conf.py @@ -0,0 +1 @@ +../conf_sub.py \ No newline at end of file diff --git a/src/doc/en/reference/tensors_free_module/finite_free_module.rst b/src/doc/en/reference/tensors_free_module/finite_free_module.rst deleted file mode 100644 index 9c41e1ef8be..00000000000 --- a/src/doc/en/reference/tensors_free_module/finite_free_module.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.finite_free_module: - -Free modules of finite rank -=========================== - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.finite_free_module - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/reference/tensors_free_module/free_module_alt_form.rst b/src/doc/en/reference/tensors_free_module/free_module_alt_form.rst deleted file mode 100644 index 8c481f2dd81..00000000000 --- a/src/doc/en/reference/tensors_free_module/free_module_alt_form.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_alt_form: - -Alternating forms on free modules -================================= - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.free_module_alt_form - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/reference/tensors_free_module/free_module_basis.rst b/src/doc/en/reference/tensors_free_module/free_module_basis.rst deleted file mode 100644 index 0fdc17da7a5..00000000000 --- a/src/doc/en/reference/tensors_free_module/free_module_basis.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_basis: - -Bases of free modules -===================== - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.free_module_basis - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/reference/tensors_free_module/free_module_tensor.rst b/src/doc/en/reference/tensors_free_module/free_module_tensor.rst deleted file mode 100644 index 128eaf03085..00000000000 --- a/src/doc/en/reference/tensors_free_module/free_module_tensor.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_tensor: - -Tensors on free modules -======================= - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.free_module_tensor - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst b/src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst deleted file mode 100644 index e9c7512966d..00000000000 --- a/src/doc/en/reference/tensors_free_module/free_module_tensor_spec.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_tensor_spec: - -Specific tensors on free modules -================================ - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.free_module_tensor_spec - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/reference/tensors_free_module/index.rst b/src/doc/en/reference/tensors_free_module/index.rst index a08660c8f71..80aa0b33c56 100644 --- a/src/doc/en/reference/tensors_free_module/index.rst +++ b/src/doc/en/reference/tensors_free_module/index.rst @@ -1,41 +1,21 @@ Tensors on free modules of finite rank ====================================== -This is the reference manual for tensors on free modules of finite rank over -a commutative ring. - -This work is part of the `SageManifolds project `_ -but it does not depend upon other SageManifolds classes. In other words, it -constitutes a self-consistent subset that can be used independently of -SageManifolds. - - -This document is licensed under a `Creative Commons Attribution-Share Alike -3.0 License`__. - -__ http://creativecommons.org/licenses/by-sa/3.0/ - - .. toctree:: :maxdepth: 2 - finite_free_module + sage/tensor/modules/finite_free_module - free_module_basis + sage/tensor/modules/free_module_basis - tensor_free_module + sage/tensor/modules/tensor_free_module - free_module_tensor + sage/tensor/modules/free_module_tensor - free_module_tensor_spec + sage/tensor/modules/free_module_tensor_spec - free_module_alt_form - - comp + sage/tensor/modules/free_module_alt_form -Indices and Tables ------------------- + sage/tensor/modules/comp -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` +.. include:: ../footer.txt diff --git a/src/doc/en/reference/tensors_free_module/tensor_free_module.rst b/src/doc/en/reference/tensors_free_module/tensor_free_module.rst deleted file mode 100644 index eb3dc6994a3..00000000000 --- a/src/doc/en/reference/tensors_free_module/tensor_free_module.rst +++ /dev/null @@ -1,16 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.tensor_free_module: - -Tensor products of free modules -=============================== - -.. This file has been autogenerated. - - -.. automodule:: sage.tensor.modules.tensor_free_module - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 3391db98f97..3d3642b7f4c 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -614,13 +614,13 @@ def __getitem__(self, args): no_format = True if isinstance(args[0], slice): indices = args[0] - elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + elif isinstance(args[0], (tuple, list)): # to ensure equivalence between indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] else: indices = tuple(args) else: # Determining from the input the list of indices and the format - if isinstance(args, (int, Integer)) or isinstance(args, slice): + if isinstance(args, (int, Integer, slice)): indices = args elif isinstance(args[0], slice): indices = args[0] @@ -736,13 +736,13 @@ def __setitem__(self, args, value): if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], slice): indices = args[0] - elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + elif isinstance(args[0], (tuple, list)): # to ensure equivalence between indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] else: indices = tuple(args) else: # Determining from the input the list of indices and the format - if isinstance(args, (int, Integer)) or isinstance(args, slice): + if isinstance(args, (int, Integer, slice)): indices = args elif isinstance(args[0], slice): indices = args[0] @@ -967,7 +967,7 @@ def __eq__(self, other): def __ne__(self, other): r""" - Inequality operator. + Non-equality operator. INPUT: @@ -1312,7 +1312,10 @@ def contract(self, pos1, other, pos2): if pos2 < 0 or pos2 > other.nid - 1: raise IndexError("pos2 out of range.") return (self*other).self_contract(pos1, - pos2+self.nid) #!# correct but not optimal + pos2+self.nid) + #!# the above is correct (in particular the symmetries are delt by + # self_contract()), but it is not optimal (unnecessary terms are + # evaluated when performing the tensor product self*other) def index_generator(self): r""" @@ -2069,13 +2072,13 @@ def __getitem__(self, args): no_format = True if isinstance(args[0], slice): indices = args[0] - elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + elif isinstance(args[0], (tuple, list)): # to ensure equivalence between indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] else: indices = tuple(args) else: # Determining from the input the list of indices and the format - if isinstance(args, (int, Integer)) or isinstance(args, slice): + if isinstance(args, (int, Integer, slice)): indices = args elif isinstance(args[0], slice): indices = args[0] @@ -2133,13 +2136,13 @@ def __setitem__(self, args, value): if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], slice): indices = args[0] - elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + elif isinstance(args[0], (tuple, list)): # to ensure equivalence between indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] else: indices = tuple(args) else: # Determining from the input the list of indices and the format - if isinstance(args, (int, Integer)) or isinstance(args, slice): + if isinstance(args, (int, Integer, slice)): indices = args elif isinstance(args[0], slice): indices = args[0] @@ -2359,7 +2362,7 @@ def __mul__(self, other): def self_contract(self, pos1, pos2): r""" - Index contraction, , taking care of the symmetries. + Index contraction, taking care of the symmetries. INPUT: @@ -2556,7 +2559,7 @@ def self_contract(self, pos1, pos2): self.sindex, self.output_formatter, sym=sym_res, antisym=antisym_res) # The contraction itself: - for ind_res in result.index_generator(): + for ind_res in result.non_redundant_index_generator(): ind = list(ind_res) ind.insert(pos1, 0) ind.insert(pos2, 0) @@ -3410,13 +3413,13 @@ def __getitem__(self, args): no_format = True if isinstance(args[0], slice): indices = args[0] - elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + elif isinstance(args[0], (tuple, list)): # to ensure equivalence between indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] else: indices = tuple(args) else: # Determining from the input the list of indices and the format - if isinstance(args, (int, Integer)) or isinstance(args, slice): + if isinstance(args, (int, Integer, slice)): indices = args elif isinstance(args[0], slice): indices = args[0] @@ -3463,13 +3466,13 @@ def __setitem__(self, args, value): if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], slice): indices = args[0] - elif isinstance(args[0], tuple) or isinstance(args[0], list): # to ensure equivalence between + elif isinstance(args[0], (tuple, list)): # to ensure equivalence between indices = args[0] # [[(i,j,...)]] or [[[i,j,...]]] and [[i,j,...]] else: indices = tuple(args) else: # Determining from the input the list of indices and the format - if isinstance(args, (int, Integer)) or isinstance(args, slice): + if isinstance(args, (int, Integer, slice)): indices = args elif isinstance(args[0], slice): indices = args[0] diff --git a/src/sage/tensor/modules/finite_free_module.py b/src/sage/tensor/modules/finite_free_module.py index 64898970988..1a6ae244c53 100644 --- a/src/sage/tensor/modules/finite_free_module.py +++ b/src/sage/tensor/modules/finite_free_module.py @@ -398,7 +398,7 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, self.output_formatter = output_formatter # Dictionary of the tensor modules built on self # (dict. keys = (k,l) --the tensor type) - self._tensor_modules = {(1,0): self} # self is considered as the sets of + self._tensor_modules = {(1,0): self} # self is considered as the set of # tensors of type (1,0) self.known_bases = [] # List of known bases on the free module self.def_basis = None # default basis diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 62981de8387..352ce9614ed 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -346,7 +346,7 @@ def wedge(self, other): for ind_o, val_o in cmp_o._comp.items(): ind_r = ind_s + ind_o if len(ind_r) == len(set(ind_r)): # all indices are different - cmp_r[ind_r] += val_s * val_o + cmp_r[[ind_r]] += val_s * val_o result = fmodule.alternating_form(rank_r) result.components[basis] = cmp_r if self.name is not None and other.name is not None: diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 5792c70ed8e..82dee3954c8 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -146,6 +146,25 @@ def _init_dual_basis(self): return FreeModuleCoBasis(self, self.symbol, latex_symbol=self.latex_symbol) + def _new_instance(self, symbol, latex_symbol=None): + r""" + Construct a new basis on the same module as ``self``. + + INPUT: + + - ``symbol`` -- (string) a letter (of a few letters) to denote a + generic element of the basis + - ``latex_symbol`` -- (string; default: None) symbol to denote a + generic element of the basis; if None, the value of ``symbol`` is + used. + + OUTPUT: + + - instance of :class:`FreeModuleBasis` + + """ + return FreeModuleBasis(self.fmodule, symbol, latex_symbol=latex_symbol) + ###### End of methods to be redefined by derived classes ###### @@ -202,9 +221,15 @@ def __hash__(self): def __eq__(self, other): r""" - Comparison operator + Equality (comparison) operator """ return other is self + + def __ne__(self, other): + r""" + Non-equality operator. + """ + return not self.__eq__(other) def __getitem__(self, index): r""" @@ -278,9 +303,9 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): raise TypeError("The argument change_of_basis must be some " + "instance of FreeModuleAutomorphism.") fmodule = self.fmodule - # self.__class__ is used instead of FreeModuleBasis for a correct + # self._new_instance used instead of FreeModuleBasis for a correct # construction in case of derived classes: - the_new_basis = self.__class__(fmodule, symbol, latex_symbol) + the_new_basis = self._new_instance(symbol, latex_symbol=latex_symbol) transf = change_of_basis.copy() inv_transf = change_of_basis.inverse().copy() si = fmodule.sindex diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index d1e6beb7e44..c47d6c1fa3a 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -79,7 +79,16 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. sage: t.view(e) # expansion of t on the basis e_i*e^j of T^(1,1)(M) t = -3 e_0*e^0 - Since e is M's default basis, shorcuts for the above writings are:: + The commands t.set_comp(e) and t.comp(e) can be abridged by providing + the basis as the first argument in the square brackets:: + + sage: t[e,0,0] = -3 + sage: t[e,:] + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + + Actually, since e is M's default basis, the mention of e can be omitted:: sage: t[0,0] = -3 sage: t[:] @@ -159,9 +168,6 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. from sage.rings.integer import Integer from sage.structure.element import ModuleElement -#!# or from sage.structure.element import Element -# to avoid arithmetics defined in ModuleElement ?? - from comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym class FreeModuleTensor(ModuleElement): @@ -851,32 +857,73 @@ def del_other_comp(self, basis=None): for other_basis in to_be_deleted: del self.components[other_basis] - def __getitem__(self, indices): + def __getitem__(self, args): r""" - Return a component w.r.t. the free module's default basis. + Return a component w.r.t. some basis. INPUT: - - ``indices`` -- list of indices defining the component + - ``args`` -- list of indices defining the component; if [:] is + provided, all the components are returned. The basis can be passed + as the first item of ``args``; if not, the free module's default + basis is assumed. """ - return self.comp()[indices] - - def __setitem__(self, indices, value): + if isinstance(args, list): # case of [[...]] syntax + if isinstance(args[0], (int, Integer, slice)): + basis = self.fmodule.def_basis + else: + basis = args[0] + args = args[1:] + else: + if isinstance(args, (int, Integer, slice)): + basis = self.fmodule.def_basis + elif not isinstance(args[0], (int, Integer, slice)): + basis = args[0] + args = args[1:] + if len(args)==1: + args = args[0] # to accommodate for [e,:] syntax + else: + basis = self.fmodule.def_basis + return self.comp(basis)[args] + + + def __setitem__(self, args, value): r""" - Set a component w.r.t. the free module's default basis. + Set a component w.r.t. some basis. INPUT: - - - ``indices`` -- list of indices defining the component - + + - ``args`` -- list of indices defining the component; if [:] is + provided, all the components are set. The basis can be passed + as the first item of ``args``; if not, the free module's default + basis is assumed. + - ``value`` -- the value to be set or a list of values if ``args`` + == ``[:]`` + """ - self.set_comp()[indices] = value + if isinstance(args, list): # case of [[...]] syntax + if isinstance(args[0], (int, Integer, slice, tuple)): + basis = self.fmodule.def_basis + else: + basis = args[0] + args = args[1:] + else: + if isinstance(args, (int, Integer, slice)): + basis = self.fmodule.def_basis + elif not isinstance(args[0], (int, Integer, slice)): + basis = args[0] + args = args[1:] + if len(args)==1: + args = args[0] # to accommodate for [e,:] syntax + else: + basis = self.fmodule.def_basis + self.set_comp(basis)[args] = value def copy(self): r""" - Returns an exact copy of ``self``. + Return an exact copy of ``self``. The name and the derived quantities are not copied. @@ -1152,12 +1199,10 @@ def _add_(self, other): - the tensor resulting from the addition of ``self`` and ``other`` """ + # No need for consistency check since self and other are guaranted + # to belong to the same tensor module if other == 0: return +self - if not isinstance(other, FreeModuleTensor): - raise TypeError("For the addition, other must be a tensor.") - if other.tensor_type != self.tensor_type: - raise TypeError("The two tensors are not of the same type.") basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the addition.") @@ -1182,12 +1227,10 @@ def _sub_(self, other): - the tensor resulting from the subtraction of ``other`` from ``self`` """ + # No need for consistency check since self and other are guaranted + # to belong to the same tensor module if other == 0: return +self - if not isinstance(other, FreeModuleTensor): - raise TypeError("For the subtraction, other must be a tensor.") - if other.tensor_type != self.tensor_type: - raise TypeError("The two tensors are not of the same type.") basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the subtraction.") @@ -1204,6 +1247,7 @@ def _rmul_(self, other): Multiplication on the left by ``other``. """ + #!# The following test is probably not necessary: if isinstance(other, FreeModuleTensor): raise NotImplementedError("Left tensor product not implemented.") # Left multiplication by a scalar: @@ -1218,12 +1262,16 @@ def __radd__(self, other): r""" Addition on the left with ``other``. + This allows to write "0 + t", where "t" is a tensor + """ return self.__add__(other) def __rsub__(self, other): r""" Subtraction from ``other``. + + This allows to write "0 - t", where "t" is a tensor """ return (-self).__add__(other) @@ -1574,14 +1622,14 @@ def contract(self, *args): sage: A = M.automorphism() sage: A[:] = [[0,0,1], [1,0,0], [0,-1,0]] - sage: f = e.new_basis(A, 'f') - sage: b.comp(f)[:] # forces the computation of b's components w.r.t. basis f + sage: h = e.new_basis(A, 'h') + sage: b.comp(h)[:] # forces the computation of b's components w.r.t. basis h [-2 -3 0] [ 7 6 -4] [ 3 -1 -2] - sage: b.del_other_comp(f) # deletes components w.r.t. basis e + sage: b.del_other_comp(h) # deletes components w.r.t. basis e sage: b.components.keys() # indeed: - [basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + [basis (h_0,h_1,h_2) on the rank-3 free module M over the Integer Ring] sage: a.components.keys() # while a is known only in basis e: [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring] sage: s1 = a.contract(1, b, 1) ; s1 # yet the computation is possible diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py index 6d428d4ede1..548973fc0e9 100644 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -414,6 +414,7 @@ def __init__(self, fmodule, name='Id', latex_name=None): FreeModuleAutomorphism.__init__(self, fmodule, name=name, latex_name=latex_name) self._inverse = self # the identity is its own inverse + self.comp() # Initializing the components in the module's default basis def _repr_(self): r""" @@ -458,7 +459,7 @@ def comp(self, basis=None): - ``basis`` -- (default: None) module basis in which the components are required; if none is provided, the components are assumed to - refer to the domain's default basis + refer to the module's default basis OUTPUT: From 24fc77c947722ac90a530c4e0984ab41b5063513 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 18 May 2014 21:56:21 -0700 Subject: [PATCH 009/129] First pass of review changes. --- src/doc/en/reference/geometry/index.rst | 17 +- .../hyperbolic_space/hyperbolic_bdry_point.py | 100 +++--- .../hyperbolic_space/hyperbolic_constants.py | 1 + .../hyperbolic_space/hyperbolic_factory.py | 148 ++++---- .../hyperbolic_space/hyperbolic_geodesic.py | 325 +++++++++--------- .../hyperbolic_space/hyperbolic_interface.py | 152 ++++---- .../hyperbolic_space/hyperbolic_isometry.py | 90 ++--- .../hyperbolic_space/hyperbolic_methods.py | 149 ++++---- .../hyperbolic_space/hyperbolic_model.py | 181 +++++----- .../hyperbolic_space/hyperbolic_point.py | 185 +++++----- .../hyperbolic_space/model_factory.py | 42 +-- 11 files changed, 668 insertions(+), 722 deletions(-) diff --git a/src/doc/en/reference/geometry/index.rst b/src/doc/en/reference/geometry/index.rst index 1f0efe9ed2f..c0e78aea85a 100644 --- a/src/doc/en/reference/geometry/index.rst +++ b/src/doc/en/reference/geometry/index.rst @@ -40,7 +40,13 @@ polytopes and polyhedra (with rational or numerical coordinates). sage/geometry/triangulation/base sage/geometry/triangulation/element -<<<<<<< HEAD + sage/geometry/hyperplane_arrangement/arrangement + sage/geometry/hyperplane_arrangement/library + sage/geometry/hyperplane_arrangement/hyperplane + sage/geometry/hyperplane_arrangement/affine_subspace + + sage/geometry/linear_expression + Hyperbolic Geometry ------------------- .. toctree:: @@ -53,14 +59,5 @@ Hyperbolic Geometry sage/geometry/hyperbolic_space/hyperbolic_model sage/geometry/hyperbolic_space/hyperbolic_interface sage/geometry/hyperbolic_space/hyperbolic_methods -======= - sage/geometry/hyperplane_arrangement/arrangement - sage/geometry/hyperplane_arrangement/library - sage/geometry/hyperplane_arrangement/hyperplane - sage/geometry/hyperplane_arrangement/affine_subspace - - sage/geometry/linear_expression - ->>>>>>> 8be52e678f5af0e62794959b70b7cb8babe0a488 .. include:: ../footer.txt diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py index c6646d9dd43..a117326e145 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py @@ -20,7 +20,7 @@ abbreviated UHP for convenience:: sage: UHP.point(3) - Boundary point in UHP 3. + Boundary point in UHP 3 Points on the boundary are infinitely far from interior points:: @@ -41,22 +41,19 @@ from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint from sage.symbolic.pynac import I from sage.rings.infinity import infinity +from sage.rings.all import RR from sage.misc.lazy_import import lazy_import lazy_import('sage.plot.point', 'point') -lazy_import('sage.rings.all', 'RR') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', + ['HyperbolicAbstractFactory', 'HyperbolicFactoryHM', + 'HyperbolicFactoryUHP', 'HyperbolicFactoryPD', + 'HyperbolicFactoryKM']) +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', + ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) lazy_import('sage.modules.free_module_element', 'vector') - class HyperbolicBdryPoint(HyperbolicPoint): r""" Abstract base class for points on the ideal boundary of hyperbolic @@ -64,11 +61,7 @@ class HyperbolicBdryPoint(HyperbolicPoint): INPUT: - - The coordinates of a hyperbolic boundary point in the appropriate model. - - OUTPUT: - - - A hyperbolic boundary point. + - the coordinates of a hyperbolic boundary point in the appropriate model EXAMPLES: @@ -76,10 +69,10 @@ class HyperbolicBdryPoint(HyperbolicPoint): sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * sage: p = HyperbolicBdryPointUHP(1); p - Boundary point in UHP 1. + Boundary point in UHP 1 sage: q = HyperbolicBdryPointPD(1); q - Boundary point in PD 1. + Boundary point in PD 1 sage: p == q False @@ -92,7 +85,7 @@ class HyperbolicBdryPoint(HyperbolicPoint): sage: HyperbolicBdryPointUHP(0.2 + 0.3*I) Traceback (most recent call last): ... - ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model. + ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model """ def __init__(self, coordinates, **graphics_options): r""" @@ -102,68 +95,64 @@ def __init__(self, coordinates, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * sage: HyperbolicBdryPointUHP(1) - Boundary point in UHP 1. + Boundary point in UHP 1 """ if not self.model().bounded: raise NotImplementedError( "{0} is not a bounded model; boundary" - " points not implemented.".format(self.model_name())) + " points not implemented".format(self.model_name())) elif self.model().bdry_point_in_model(coordinates): if type(coordinates) == tuple: coordinates = vector(coordinates) self._coordinates = coordinates else: raise ValueError( - "{0} is not a valid".format(coordinates) + " boundary point" - " in the {0} model.".format(self.model_name())) + "{0} is not a valid".format(coordinates) + + " boundary point in the {0} model".format(self.model_name())) self._graphics_options = graphics_options - def __repr__(self): + def _repr_(self): r""" Return a string representation of ``self``. OUTPUT: - - string. + - string EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: HyperbolicBdryPointUHP(infinity).__repr__() - 'Boundary point in UHP +Infinity.' + sage: HyperbolicBdryPointUHP(infinity) + Boundary point in UHP +Infinity - sage: HyperbolicBdryPointPD(-1).__repr__() - 'Boundary point in PD -1.' + sage: HyperbolicBdryPointPD(-1) + Boundary point in PD -1 - sage: HyperbolicBdryPointKM((0, -1)).__repr__() - 'Boundary point in KM (0, -1).' + sage: HyperbolicBdryPointKM((0, -1)) + Boundary point in KM (0, -1) """ - return "Boundary point in {0} {1}.".format(self.model_name(), + return "Boundary point in {0} {1}".format(self.model_name(), self.coordinates()) -class HyperbolicBdryPointUHP (HyperbolicBdryPoint): +class HyperbolicBdryPointUHP(HyperbolicBdryPoint): r""" Create a boundary point for the UHP model. INPUT: - - The coordinates of a hyperbolic boundary point in the upper half plane. - - OUTPUT: - - - A hyperbolic boundary point. + - the coordinates of a hyperbolic boundary point in the upper half plane EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointUHP sage: q = HyperbolicBdryPointUHP(1); q - Boundary point in UHP 1. + Boundary point in UHP 1 """ HFactory = HyperbolicFactoryUHP HMethods = HyperbolicMethodsUHP - def show(self, boundary = True, **options): + def show(self, boundary=True, **options): r""" EXAMPLES:: @@ -172,15 +161,15 @@ def show(self, boundary = True, **options): sage: HyperbolicBdryPointUHP(infinity).show() Traceback (most recent call last): ... - NotImplementedError: Can't draw the point infinity. + NotImplementedError: can't draw the point infinity """ - opts = dict([('axes', False),('aspect_ratio',1)]) + opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) opts.update(options) from sage.misc.functional import numerical_approx p = self.coordinates() if p == infinity: - raise NotImplementedError("Can't draw the point infinity.") + raise NotImplementedError("can't draw the point infinity") p = numerical_approx(p) pic = point((p, 0), **opts) if boundary: @@ -190,50 +179,42 @@ def show(self, boundary = True, **options): return pic -class HyperbolicBdryPointPD (HyperbolicBdryPoint): +class HyperbolicBdryPointPD(HyperbolicBdryPoint): r""" Create a boundary point for the PD model. INPUT: - - The coordinates of a hyperbolic boundary point in the Poincare disk. - - OUTPUT: - - - A hyperbolic boundary point. + - the coordinates of a hyperbolic boundary point in the Poincare disk EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointPD sage: q = HyperbolicBdryPointPD(1); q - Boundary point in PD 1. + Boundary point in PD 1 """ HFactory = HyperbolicFactoryPD HMethods = HyperbolicMethodsUHP -class HyperbolicBdryPointKM (HyperbolicBdryPoint): +class HyperbolicBdryPointKM(HyperbolicBdryPoint): r""" Create a boundary point for the KM model. INPUT: - - The coordinates of a hyperbolic boundary point in the Klein disk. - - OUTPUT: - - - A hyperbolic boundary point. + - the coordinates of a hyperbolic boundary point in the Klein disk EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointKM sage: q = HyperbolicBdryPointKM((1,0)); q - Boundary point in KM (1, 0). + Boundary point in KM (1, 0) """ HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP -class HyperbolicBdryPointHM (HyperbolicBdryPoint): +class HyperbolicBdryPointHM(HyperbolicBdryPoint): r""" A dummy class for the boundary points of the hyperboloid model. The model is not bounded, so there are no boundary points. The class is needed for @@ -245,7 +226,8 @@ class HyperbolicBdryPointHM (HyperbolicBdryPoint): sage: q = HyperbolicBdryPointHM((1,0,0)); q Traceback (most recent call last): ... - NotImplementedError: HM is not a bounded model; boundary points not implemented. + NotImplementedError: HM is not a bounded model; boundary points not implemented """ HFactory = HyperbolicFactoryHM HMethods = HyperbolicMethodsUHP + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py b/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py index 955ed3535b8..f41d0543ca6 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py @@ -2,3 +2,4 @@ EPSILON = 10**-9 LORENTZ_GRAM = matrix(3,[1,0,0,0,1,0,0,0,-1]) + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py index a865afd10b9..da882372f5b 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py @@ -1,7 +1,10 @@ r""" +Hyerbolic Factory + AUTHORS: -- Greg Laun (2013): initial version +- Greg Laun (2013): initial version +""" #*********************************************************************** # Copyright (C) 2013 Greg Laun @@ -11,42 +14,37 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #*********************************************************************** -""" + from sage.structure.unique_representation import UniqueRepresentation from sage.misc.lazy_import import lazy_import lazy_import('sage.functions.other','sqrt') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModel') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPoint') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPoint') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometry') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicUHP') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicPD') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicKM') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicHM') - -class HyperbolicAbstractFactory (UniqueRepresentation): +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', + ['HyperbolicModel', 'HyperbolicModelUHP', 'HyperbolicModelPD', + 'HyperbolicModelKM', 'HyperbolicModelHM', 'HyperbolicBdryPointHM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', + ['HyperbolicPoint', 'HyperbolicPointUHP', 'HyperbolicPointPD', + 'HyperbolicPointKM', 'HyperbolicPointHM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', + ['HyperbolicBdryPoint', 'HyperbolicBdryPointUHP', + 'HyperbolicBdryPointPD', 'HyperbolicBdryPointKM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', + ['HyperbolicIsometry', 'HyperbolicIsometryUHP', + 'HyperbolicIsometryPD', 'HyperbolicIsometryKM', + 'HyperbolicIsometryHM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', + ['HyperbolicGeodesic', 'HyperbolicGeodesicUHP', + 'HyperbolicGeodesicPD', 'HyperbolicGeodesicKM', + 'HyperbolicGeodesicHM']) + +class HyperbolicAbstractFactory(UniqueRepresentation): + """ + Abstract factory for creating the hyperbolic models. + """ HModel = HyperbolicModel HPoint = HyperbolicPoint HBdryPoint = HyperbolicBdryPoint @@ -72,7 +70,6 @@ def get_model(cls): sage: HyperbolicFactoryHM.get_model() - """ return cls.HModel @@ -85,18 +82,19 @@ def get_interior_point(cls, coordinates, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * sage: HyperbolicFactoryUHP.get_interior_point(2 + 3*I) - Point in UHP 3*I + 2. + Point in UHP 3*I + 2 sage: HyperbolicFactoryPD.get_interior_point(0) - Point in PD 0. + Point in PD 0 sage: HyperbolicFactoryKM.get_interior_point((0,0)) - Point in KM (0, 0). + Point in KM (0, 0) sage: HyperbolicFactoryHM.get_interior_point((0,0,1)) - Point in HM (0, 0, 1). + Point in HM (0, 0, 1) - sage: p = HyperbolicFactoryUHP.get_interior_point(I, color="red"); p.graphics_options() + sage: p = HyperbolicFactoryUHP.get_interior_point(I, color="red") + sage: p.graphics_options() {'color': 'red'} """ return cls.HPoint(coordinates, **graphics_options) @@ -111,16 +109,16 @@ def get_bdry_point(cls, coordinates, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * sage: HyperbolicFactoryUHP.get_bdry_point(12) - Boundary point in UHP 12. + Boundary point in UHP 12 sage: HyperbolicFactoryUHP.get_bdry_point(infinity) - Boundary point in UHP +Infinity. + Boundary point in UHP +Infinity sage: HyperbolicFactoryPD.get_bdry_point(I) - Boundary point in PD I. + Boundary point in PD I sage: HyperbolicFactoryKM.get_bdry_point((0,-1)) - Boundary point in KM (0, -1). + Boundary point in KM (0, -1) """ return cls.HBdryPoint(coordinates, **graphics_options) @@ -129,43 +127,43 @@ def get_point(cls, coordinates, **graphics_options): r""" Automatically determine the type of point to return given either (1) the coordinates of a point in the interior or ideal boundary - of hyperbolic space or (2) a HyperbolicPoint or - HyperbolicBdryPoint object. + of hyperbolic space or (2) a :class:`HyperbolicPoint` or + :class:`HyperbolicBdryPoint` object. INPUT: - - a point in hyperbolic space or on the ideal boundary. + - a point in hyperbolic space or on the ideal boundary OUTPUT: - - A HyperbolicPoint or HyperbolicBdryPoint. + - a :class:`HyperbolicPoint` or :class:`HyperbolicBdryPoint` - EXAMPLES:: + EXAMPLES: We can create an interior point via the coordinates:: sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * sage: p = HyperbolicFactoryUHP.get_point(2*I); p - Point in UHP 2*I. + Point in UHP 2*I Or we can create a boundary point via the coordinates:: sage: q = HyperbolicFactoryUHP.get_point(23); q - Boundary point in UHP 23. + Boundary point in UHP 23 Or we can create both types of points by passing the HyperbolicPoint or HyperbolicBdryPoint object:: sage: HyperbolicFactoryUHP.get_point(p) - Point in UHP 2*I. + Point in UHP 2*I sage: HyperbolicFactoryUHP.get_point(q) - Boundary point in UHP 23. + Boundary point in UHP 23 sage: HyperbolicFactoryUHP.get_point(12 - I) Traceback (most recent call last): ... - ValueError: -I + 12 is neither an interior nor boundary point in the UHP model. + ValueError: -I + 12 is neither an interior nor boundary point in the UHP model """ from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint if isinstance(coordinates, HyperbolicPoint): @@ -176,9 +174,8 @@ def get_point(cls, coordinates, **graphics_options): elif cls.HModel.bdry_point_in_model(coordinates): return cls.HBdryPoint(coordinates, **graphics_options) else: - e_1 = "{0} is neither an interior nor boundary".format(coordinates) - e_2 = " point in the {0} model.".format(cls.get_model().short_name) - raise ValueError(e_1 + e_2) + raise ValueError("{0} is neither an interior nor boundary".format(coordinates) + + " point in the {0} model".format(cls.get_model().short_name)) @classmethod def get_geodesic(cls, start, end, **graphics_options): @@ -189,16 +186,16 @@ def get_geodesic(cls, start, end, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * sage: HyperbolicFactoryUHP.get_geodesic(I, 2*I) - Geodesic in UHP from I to 2*I. + Geodesic in UHP from I to 2*I sage: HyperbolicFactoryPD.get_geodesic(0, I/2) - Geodesic in PD from 0 to 1/2*I. + Geodesic in PD from 0 to 1/2*I sage: HyperbolicFactoryKM.get_geodesic((1/2, 1/2), (0,0)) - Geodesic in KM from (1/2, 1/2) to (0, 0). + Geodesic in KM from (1/2, 1/2) to (0, 0) sage: HyperbolicFactoryHM.get_geodesic((0,0,1), (1,0, sqrt(2))) - Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)). + Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)) """ return cls.HGeodesic(start, end, **graphics_options) @@ -213,24 +210,24 @@ def get_isometry(cls, A): sage: HyperbolicFactoryUHP.get_isometry(identity_matrix(2)) Isometry in UHP [1 0] - [0 1]. + [0 1] sage: HyperbolicFactoryPD.get_isometry(identity_matrix(2)) Isometry in PD [1 0] - [0 1]. + [0 1] sage: HyperbolicFactoryKM.get_isometry(identity_matrix(3)) Isometry in KM [1 0 0] [0 1 0] - [0 0 1]. + [0 0 1] sage: HyperbolicFactoryHM.get_isometry(identity_matrix(3)) Isometry in HM [1 0 0] [0 1 0] - [0 0 1]. + [0 0 1] """ return cls.HIsometry(A) @@ -250,7 +247,10 @@ def get_background_graphic(cls, **bdry_options): """ return None -class HyperbolicFactoryUHP (HyperbolicAbstractFactory, UniqueRepresentation): +class HyperbolicFactoryUHP(HyperbolicAbstractFactory, UniqueRepresentation): + """ + Factory for creating the UHP model. + """ HModel = HyperbolicModelUHP HPoint = HyperbolicPointUHP HBdryPoint = HyperbolicBdryPointUHP @@ -276,7 +276,10 @@ def get_background_graphic(cls, **bdry_options): bd_max = bdry_options.get('bd_max', 5) return line(((bd_min, 0), (bd_max, 0)), color='black') -class HyperbolicFactoryPD (HyperbolicAbstractFactory, UniqueRepresentation): +class HyperbolicFactoryPD(HyperbolicAbstractFactory, UniqueRepresentation): + """ + Factory for creating the PD model. + """ from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD HModel = HyperbolicModelPD HPoint = HyperbolicPointPD @@ -299,10 +302,13 @@ def get_background_graphic(cls, **bdry_options): sage: circ = HyperbolicFactoryPD.get_background_graphic() """ from sage.plot.circle import circle - return circle((0,0),1, axes=False, color = 'black') + return circle((0,0), 1, axes=False, color='black') -class HyperbolicFactoryKM (HyperbolicAbstractFactory, UniqueRepresentation): +class HyperbolicFactoryKM(HyperbolicAbstractFactory, UniqueRepresentation): + """ + Factory for creating the KM model. + """ from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelKM HModel = HyperbolicModelKM HPoint = HyperbolicPointKM @@ -325,9 +331,12 @@ def get_background_graphic(cls, **bdry_options): sage: circ = HyperbolicFactoryKM.get_background_graphic() """ from sage.plot.circle import circle - return circle((0,0),1, axes=False, color = 'black') + return circle((0,0), 1, axes=False, color='black') class HyperbolicFactoryHM (HyperbolicAbstractFactory, UniqueRepresentation): + """ + Factory for creating the HM model. + """ HModel = HyperbolicModelHM HPoint = HyperbolicPointHM HIsometry = HyperbolicIsometryHM @@ -355,3 +364,4 @@ def get_background_graphic(cls, **bdry_options): (x,y) = var('x,y') return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max),\ (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 7b9f48c1d15..a74c82f2059 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -15,7 +15,7 @@ UHP for convenience:: sage: UHP.geodesic(2, 3) - Geodesic in UHP from 2 to 3. + Geodesic in UHP from 2 to 3 sage: g = UHP.geodesic(I, 3 + I) sage: g.length() arccosh(11/2) @@ -44,18 +44,15 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.rings.infinity import infinity +from sage.rings.all import CC, RR from sage.symbolic.constants import pi -lazy_import('sage.rings.all', 'CC') lazy_import('sage.functions.other', 'real') lazy_import('sage.functions.other', 'imag') -lazy_import('sage.all', 'var') -lazy_import('sage.plot.plot', 'parametric_plot') -lazy_import('sage.plot.plot3d.all', 'parametric_plot3d') + lazy_import('sage.functions.trig', 'cos') lazy_import('sage.functions.trig', 'sin') lazy_import('sage.plot.line', 'line') -lazy_import('sage.rings.all', 'RR') lazy_import('sage.modules.free_module_element', 'vector') lazy_import('sage.functions.other','sqrt') lazy_import('sage.functions.hyperbolic', 'cosh') @@ -65,10 +62,9 @@ lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', + ['HyperbolicFactoryUHP', 'HyperbolicFactoryPD', + 'HyperbolicFactoryKM', 'HyperbolicFactoryHM']) class HyperbolicGeodesic(SageObject): r""" @@ -77,13 +73,11 @@ class HyperbolicGeodesic(SageObject): INPUT: - - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic. - - - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + - ``start`` -- a HyperbolicPoint or coordinates of a point in + hyperbolic space representing the start of the geodesic - OUTPUT: - - A hyperbolic geodesic. + - ``end`` -- a HyperbolicPoint or coordinates of a point in + hyperbolic space representing the end of the geodesic EXAMPLES:: @@ -94,20 +88,19 @@ class HyperbolicGeodesic(SageObject): HFactory = HyperbolicAbstractFactory HMethods = HyperbolicAbstractMethods - ##################### - # "Private" Methods # - ##################### - + ##################### + # "Private" Methods # + ##################### def __init__(self, start, end, **graphics_options): r""" - See `HyperbolicGeodesic` for full documentation. + See :class:`HyperbolicGeodesic` for full documentation. EXAMPLES :: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HyperbolicGeodesicUHP(I, 2 + I) - Geodesic in UHP from I to I + 2. + Geodesic in UHP from I to I + 2 """ self._model = self.HFactory.get_model() self._start = self.HFactory.get_point(start) @@ -118,9 +111,10 @@ def __init__(self, start, end, **graphics_options): def _cached_start(self): r""" The representation of the start point used for calculations. + For example, if the current model uses the HyperbolicMethodsUHP - class, then _cached_start will hold the upper half plane - representation of self.start(). + class, then :meth:`_cached_start` will hold the upper half plane + representation of ``self.start()``. EXAMPLES:: @@ -134,10 +128,11 @@ def _cached_start(self): @lazy_attribute def _cached_end(self): r""" - The representation of the end point used for calculations. For - example, if the current model uses the HyperbolicMethodsUHP - class, then _cached_end will hold the upper half plane - representation of self.end(). + The representation of the end point used for calculations. + + For example, if the current model uses the + :class:`HyperbolicMethodsUHP` class, then :meth:`_cached_end` will + hold the upper half plane representation of ``self.end()``. EXAMPLES:: @@ -151,10 +146,7 @@ def _cached_end(self): @lazy_attribute def _cached_endpoints(self): r""" - The representation of the end point used for calculations. For - example, if the current model uses the HyperbolicMethodsUHP - class, then _cached_end will hold the upper half plane - representation of self.end(). + The representation of the endpoints used for calculations. EXAMPLES:: @@ -170,7 +162,7 @@ def _complete(self): r""" Return whether the geodesic is complete. This is used for geodesics in non-bounded models. For thse models, - self.complete() simply sets _complete to True. + ``self.complete()`` simply sets ``_complete`` to ``True``. EXAMPLES:: @@ -191,36 +183,32 @@ def _complete(self): else: return False #All non-bounded geodesics start life incomplete. - def __repr__(self): + def _repr_(self): r""" Return a string representation of ``self``. - OUTPUT: - - - string. - EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(3 + 4*I, I).__repr__() - 'Geodesic in UHP from 4*I + 3 to I.' + sage: HyperbolicGeodesicUHP(3 + 4*I, I) + Geodesic in UHP from 4*I + 3 to I - sage: HyperbolicGeodesicPD(1/2 + I/2, 0).__repr__() - 'Geodesic in PD from 1/2*I + 1/2 to 0.' + sage: HyperbolicGeodesicPD(1/2 + I/2, 0) + Geodesic in PD from 1/2*I + 1/2 to 0 - sage: HyperbolicGeodesicKM((1/2, 1/2), (0, 0)).__repr__() - 'Geodesic in KM from (1/2, 1/2) to (0, 0).' + sage: HyperbolicGeodesicKM((1/2, 1/2), (0, 0)) + Geodesic in KM from (1/2, 1/2) to (0, 0) - sage: HyperbolicGeodesicHM((0,0,1), (0, 1, sqrt(Integer(2)))).__repr__() - 'Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)).' + sage: HyperbolicGeodesicHM((0,0,1), (0, 1, sqrt(Integer(2)))) + Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ - return "Geodesic in {0} from {1} to {2}.".format( \ + return "Geodesic in {0} from {1} to {2}".format( \ self.model_name(), self.start().coordinates(), \ self.end().coordinates()) def __eq__(self, other): r""" - Return rue if self is equal to other as an oriented geodesic. + Return ``True`` if ``self`` is equal to other as an oriented geodesic. EXAMPLES:: @@ -232,13 +220,13 @@ def __eq__(self, other): sage: g1 == g1 True """ - return (self.model_name() == other.model_name() and - self.start() == other.start() + return (self.model_name() == other.model_name() + and self.start() == other.start() and self.end() == other.end()) -####################### -# Setters and Getters # -####################### + ####################### + # Setters and Getters # + ####################### def start(self): r""" @@ -249,7 +237,7 @@ def start(self): sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicGeodesicUHP(I, 3*I) sage: g.start() - Point in UHP I. + Point in UHP I """ return self._start @@ -262,7 +250,7 @@ def end(self): sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicGeodesicUHP(I, 3*I) sage: g.end() - Point in UHP 3*I. + Point in UHP 3*I """ return self._end @@ -275,14 +263,14 @@ def endpoints(self): sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicGeodesicUHP(I, 3*I) sage: g.endpoints() - [Point in UHP I., Point in UHP 3*I.] + [Point in UHP I, Point in UHP 3*I] """ return [self.start(), self.end()] @classmethod def model(cls): r""" - Return the model to which the HyperbolicPoint belongs. + Return the model to which the :class:`HyperbolicPoint` belongs. EXAMPLES:: @@ -329,13 +317,13 @@ def to_model(self, model_name): INPUT: - - ``model_name`` -- a string representing the image model. + - ``model_name`` -- a string representing the image model EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HyperbolicGeodesicUHP(I, 2*I).to_model('PD') - Geodesic in PD from 0 to 1/3*I. + Geodesic in PD from 0 to 1/3*I """ from sage.geometry.hyperbolic_space.model_factory import ModelFactory factory = ModelFactory.find_factory(model_name) @@ -365,10 +353,14 @@ def graphics_options(self): """ return self._graphics_options - def update_graphics(self, update = False, **options): + def update_graphics(self, update=False, **options): r""" - Update the graphics options of a HyperbolicPoint. If update is - True, the original option are updated rather than overwritten. + Update the graphics options of a HyperbolicPoint. + + INPUT: + + - ``update`` -- if ``True``, the original option are updated + rather than overwritten EXAMPLES:: @@ -384,20 +376,19 @@ def update_graphics(self, update = False, **options): sage: g.update_graphics(True, size = 20); g.graphics_options() {'color': 'blue', 'size': 20} - """ if not update: self._graphics_options = {} self._graphics_options.update(**options) -################### -# Boolean Methods # -################### + ################### + # Boolean Methods # + ################### def is_complete(self): r""" - Return True if ``self`` is a complete geodesic (that is, both - endpoints are on the ideal boundary) and false otherwise. + Return ``True`` if ``self`` is a complete geodesic (that is, both + endpoints are on the ideal boundary) and ``False`` otherwise. EXAMPLES:: @@ -415,16 +406,12 @@ def is_complete(self): def is_asymptotically_parallel(self, other): r""" - Return True if ``self`` and ``other`` are asymptotically - parallel, False otherwise. + Return ``True`` if ``self`` and ``other`` are asymptotically + parallel, ``False`` otherwise. INPUT: - - ``other`` -- a hyperbolic geodesic. - - OUTPUT: - - - boolean. + - ``other`` -- a hyperbolic geodesic EXAMPLES:: @@ -454,16 +441,12 @@ def is_asymptotically_parallel(self, other): def is_ultra_parallel(self,other): r""" - Return True if ``self`` and ``other`` are asymptotically - parallel, False otherwise. + Return ``True`` if ``self`` and ``other`` are asymptotically + parallel, ``False`` otherwise. INPUT: - - ``other`` -- a hyperbolic geodesic. - - OUTPUT: - - - boolean. + - ``other`` -- a hyperbolic geodesic EXAMPLES:: @@ -486,23 +469,23 @@ def is_ultra_parallel(self,other): sage: g.is_ultra_parallel(g) False """ - [R_self, R_other]= [k.reflection_in() for k in [self,other]] + [R_self, R_other] = [k.reflection_in() for k in [self,other]] return (R_self*R_other).classification() == 'hyperbolic' - def is_parallel(self,other): + def is_parallel(self, other): r""" - Return True if the two given hyperbolic + Return ``True`` if the two given hyperbolic geodesics are either ultraparallel or asymptotically parallel, - False otherwise + ``False`` otherwise. INPUT: - Two hyperbolic geodesics in any model + - ``other`` -- a hyperbolic geodesic in any model OUTPUT: - True if the given geodesics are either ultraparallel or - asymptotically parallel, False if not. + ``True`` if the given geodesics are either ultraparallel or + asymptotically parallel, ``False`` if not. EXAMPLES:: @@ -526,39 +509,38 @@ def is_parallel(self,other): sage: g.is_parallel(g) False """ - [R_self, R_other]= [k.reflection_in() for k in [self,other]] + [R_self, R_other] = [k.reflection_in() for k in [self,other]] return (R_self*R_other).classification() in ['parabolic', 'hyperbolic'] -################################### -# Methods implemented in HMethods # -################################### + ################################### + # Methods implemented in HMethods # + ################################### def ideal_endpoints(self): r""" Return the ideal endpoints in bounded models. Raise a - NotImplementedError in models that are not bounded. + ``NotImplementedError`` in models that are not bounded. EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HyperbolicGeodesicUHP(1 + I, 1 + 3*I).ideal_endpoints() - [Boundary point in UHP 1., Boundary point in UHP +Infinity.] + [Boundary point in UHP 1, Boundary point in UHP +Infinity] sage: HyperbolicGeodesicPD(0, I/2).ideal_endpoints() - [Boundary point in PD -I., Boundary point in PD I.] + [Boundary point in PD -I, Boundary point in PD I] sage: HyperbolicGeodesicKM((0,0), (0, 1/2)).ideal_endpoints() - [Boundary point in KM (0, -1)., Boundary point in KM (0, 1).] + [Boundary point in KM (0, -1), Boundary point in KM (0, 1)] sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).ideal_endpoints() Traceback (most recent call last): ... - NotImplementedError: Boundary points are not implemented in the HM model. - + NotImplementedError: boundary points are not implemented in the HM model """ if not self.model().bounded: - raise NotImplementedError("Boundary points are not implemented in the "\ - + "{0} model.".format(self.model_name())) + raise NotImplementedError("boundary points are not implemented in the "\ + + "{0} model".format(self.model_name())) if self.is_complete(): return self.endpoints() ends = self.HMethods.boundary_points(*self._cached_endpoints) @@ -569,33 +551,33 @@ def ideal_endpoints(self): def complete(self): r""" Return the ideal endpoints in bounded models. Raise a - NotImplementedError in models that are not bounded. + ``NotImplementedError`` in models that are not bounded. EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HyperbolicGeodesicUHP(1 + I, 1 + 3*I).complete() - Geodesic in UHP from 1 to +Infinity. + Geodesic in UHP from 1 to +Infinity sage: HyperbolicGeodesicPD(0, I/2).complete() - Geodesic in PD from -I to I. + Geodesic in PD from -I to I sage: HyperbolicGeodesicKM((0,0), (0, 1/2)).complete() - Geodesic in KM from (0, -1) to (0, 1). + Geodesic in KM from (0, -1) to (0, 1) sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).complete() - Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)). + Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)) sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).complete().is_complete() True """ if self.model().bounded: return self.HFactory.get_geodesic(*self.ideal_endpoints()) - else: - from copy import copy - g = copy(self) - g._complete = True - return g + + from copy import copy + g = copy(self) + g._complete = True + return g def uncomplete(self): r""" @@ -607,14 +589,14 @@ def uncomplete(self): sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicGeodesicUHP(I, 2 + 3*I) sage: g.uncomplete() - Geodesic in UHP from I to 3*I + 2. + Geodesic in UHP from I to 3*I + 2 sage: g.uncomplete().complete() == g.complete() True sage: h = HyperbolicGeodesicUHP(2, 3) sage: h.uncomplete().complete() - Geodesic in UHP from 2 to 3. + Geodesic in UHP from 2 to 3 """ if not self.is_complete(): return self @@ -633,18 +615,18 @@ def reflection_in(self): sage: HyperbolicGeodesicUHP(2,4).reflection_in() Isometry in UHP [ 3 -8] - [ 1 -3]. + [ 1 -3] sage: HyperbolicGeodesicPD(0, I).reflection_in() Isometry in PD [ 0 -1] - [ 1 0]. + [ 1 0] sage: HyperbolicGeodesicKM((0,0), (0,1)).reflection_in() Isometry in KM [-1 0 0] [ 0 1 0] - [ 0 0 1]. + [ 0 0 1] sage: A = HyperbolicGeodesicHM((0,0,1), (1,0, n(sqrt(2)))).reflection_in() sage: B = diagonal_matrix([1, -1, 1]) @@ -659,15 +641,15 @@ def common_perpendicular(self, other, **graphics_options): r""" Return the unique hyperbolic geodesic perpendicular to two given geodesics, if such a geodesic exists. If none exists, raise a - ValueError. + ``ValueError``. INPUT: - - ``other`` -- a hyperbolic geodesic in the same model as ``self``. + - ``other`` -- a hyperbolic geodesic in the same model as ``self`` OUTPUT: - - a hyperbolic geodesic. + - a hyperbolic geodesic EXAMPLES:: @@ -675,7 +657,7 @@ def common_perpendicular(self, other, **graphics_options): sage: g = HyperbolicGeodesicUHP(2,3) sage: h = HyperbolicGeodesicUHP(4,5) sage: g.common_perpendicular(h) - Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2. + Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2 It is an error to ask for the common perpendicular of two intersecting geodesics:: @@ -685,11 +667,10 @@ def common_perpendicular(self, other, **graphics_options): sage: g.common_perpendicular(h) Traceback (most recent call last): ... - ValueError: Geodesics intersect. No common perpendicular exists. + ValueError: geodesics intersect, no common perpendicular exists """ if not self.is_parallel(other): - raise ValueError('Geodesics intersect. ' \ - 'No common perpendicular exists.') + raise ValueError('geodesics intersect, no common perpendicular exists') perp_ends = self.HMethods.common_perpendicular( *(self._cached_endpoints + other._cached_endpoints)) M = self.HMethods.model() @@ -702,16 +683,16 @@ def intersection(self, other, **graphics_options): Return the point of intersection of two geodesics (if such a point exists). - The option `as_complete` determines whether we test for the + The option ``as_complete`` determines whether we test for the completed geodesics to intersect, or just the segments. INPUT: - - ``other`` -- a hyperbolic geodesic in the same model as ``self``. + - ``other`` -- a hyperbolic geodesic in the same model as ``self`` OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -719,7 +700,7 @@ def intersection(self, other, **graphics_options): sage: g = HyperbolicGeodesicUHP(3,5) sage: h = HyperbolicGeodesicUHP(4,7) sage: g.intersection(h) - Point in UHP 2/3*sqrt(-2) + 13/3. + Point in UHP 2/3*sqrt(-2) + 13/3 If the given geodesics do not intersect, raise an error:: @@ -728,19 +709,19 @@ def intersection(self, other, **graphics_options): sage: g.intersection(h) Traceback (most recent call last): ... - ValueError: Geodesics don't intersect. + ValueError: geodesics don't intersect If the given geodesics are identical, return that geodesic:: sage: g = HyperbolicGeodesicUHP(4+I,18*I) sage: h = HyperbolicGeodesicUHP(4+I,18*I) sage: g.intersection(h) - Geodesic in UHP from I + 4 to 18*I. + Geodesic in UHP from I + 4 to 18*I """ if self == other: return self elif self.is_parallel(other): - raise ValueError("Geodesics don't intersect.") + raise ValueError("geodesics don't intersect") inters = self.HMethods.intersection(*(self._cached_endpoints + other._cached_endpoints)) if len(inters) == 2: @@ -750,7 +731,7 @@ def intersection(self, other, **graphics_options): self.model_name()) return self.HFactory.get_point(inters, **graphics_options) else: - raise ValueError("Can't calculate the intersection of" + raise ValueError("can't calculate the intersection of" "{1} and {2}".format(self, other)) @@ -773,11 +754,11 @@ def perpendicular_bisector(self, **graphics_options): sage: g.perpendicular_bisector() Traceback (most recent call last): ... - ValueError: Perpendicular bisector is not defined for complete geodesics. + ValueError: perpendicular bisector is not defined for complete geodesics """ if self.is_complete(): - raise ValueError("Perpendicular bisector is not defined for " \ - "complete geodesics.") + raise ValueError("perpendicular bisector is not defined for " \ + "complete geodesics") bisect_ends = self.HMethods.perpendicular_bisector( *self._cached_endpoints) M = self.HMethods.model() @@ -822,10 +803,10 @@ def midpoint(self, **graphics_options): sage: HyperbolicGeodesicUHP(0,2).midpoint() Traceback (most recent call last): ... - ValueError: Midpoint not defined for complete geodesics. + ValueError: midpoint not defined for complete geodesics """ if self.is_complete(): - raise ValueError("Midpoint not defined for complete geodesics.") + raise ValueError("midpoint not defined for complete geodesics") mid = self.HMethods.midpoint(*self._cached_endpoints, **graphics_options) mid = self.HMethods.model().point_to_model(mid, self.model_name()) @@ -838,11 +819,12 @@ def dist(self, other): INPUT: - - ``other`` -- a hyperbolic geodesic or hyperbolic point in the same model. + - ``other`` -- a hyperbolic geodesic or hyperbolic point in + the same model OUTPUT: - - the hyperbolic distance. + - the hyperbolic distance EXAMPLES:: @@ -881,12 +863,11 @@ def angle(self, other): Return the angle between any two given geodesics if they intersect. - -``other`` -- a hyperbolic geodesic in the same model as - ``self``. + - ``other`` -- a hyperbolic geodesic in the same model as ``self`` OUTPUT: - - The angle in radians between the two given geodesics. + - the angle in radians between the two given geodesics EXAMPLES:: @@ -904,7 +885,7 @@ def angle(self, other): sage: g.angle(h) Traceback (most recent call last): ... - ValueError: Geodesics do not intersect. + ValueError: geodesics do not intersect If the geodesics are identical, return angle 0:: @@ -913,7 +894,7 @@ def angle(self, other): 0 """ if self.is_parallel(other): - raise ValueError("Geodesics do not intersect.") + raise ValueError("geodesics do not intersect") if not (self.is_complete() and other.is_complete()): try: # Make sure the segments intersect. @@ -947,9 +928,11 @@ class HyperbolicGeodesicUHP (HyperbolicGeodesic): INPUT: - - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; + - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a point + in hyperbolic space representing the start of the geodesic - - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point + in hyperbolic space representing the end of the geodesic OUTPUT: @@ -965,14 +948,14 @@ class HyperbolicGeodesicUHP (HyperbolicGeodesic): HFactory = HyperbolicFactoryUHP HMethods = HyperbolicMethodsUHP - def show(self, boundary = True, **options): + def show(self, boundary=True, **options): r""" EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HyperbolicGeodesicUHP(0, 1).show() """ - opts = dict([('axes', False),('aspect_ratio',1)]) + opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) opts.update(options) end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] @@ -1002,6 +985,8 @@ def show(self, boundary = True, **options): if abs(theta1 - theta2) < EPSILON: theta2 += pi [theta1, theta2] = sorted([theta1,theta2]) + from sage.calculus.var import var + from sage.plot.plot import parametric_plot x = var('x') pic= parametric_plot((radius*cos(x) + real(center),radius*sin(x) + imag(center)), (x, theta1, theta2), **opts) @@ -1020,19 +1005,17 @@ def show(self, boundary = True, **options): return pic -class HyperbolicGeodesicPD (HyperbolicGeodesic): +class HyperbolicGeodesicPD(HyperbolicGeodesic): r""" Create a geodesic in the Poincare disk model. INPUT: - - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; - - - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a + point in hyperbolic space representing the start of the geodesic - OUTPUT: - - A hyperbolic geodesic. + - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point + in hyperbolic space representing the end of the geodesic EXAMPLES:: @@ -1043,14 +1026,14 @@ class HyperbolicGeodesicPD (HyperbolicGeodesic): HFactory = HyperbolicFactoryPD HMethods = HyperbolicMethodsUHP - def show(self, boundary = True, **options): + def show(self, boundary=True, **options): r""" EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HyperbolicGeodesicPD(0, 1).show() """ - opts = dict([('axes', False),('aspect_ratio',1)]) + opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) opts.update(options) end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] @@ -1070,6 +1053,8 @@ def show(self, boundary = True, **options): theta2 = CC(end_2 - center).arg() if theta2 < theta1: theta1, theta2 = theta2, theta1 + from sage.calculus.var import var + from sage.plot.plot import parametric_plot x = var('x') mid = (theta1 + theta2)/2.0 if (radius*cos(mid) + real(center))**2 + \ @@ -1098,13 +1083,11 @@ class HyperbolicGeodesicKM (HyperbolicGeodesic): INPUT: - - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; - - - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. + - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a + point in hyperbolic space representing the start of the geodesic - OUTPUT: - - A hyperbolic geodesic. + - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point + in hyperbolic space representing the end of the geodesic EXAMPLES:: @@ -1133,19 +1116,17 @@ def show(self, boundary = True, **options): return pic -class HyperbolicGeodesicHM (HyperbolicGeodesic): +class HyperbolicGeodesicHM(HyperbolicGeodesic): r""" Create a geodesic in the hyperboloid model. INPUT: - - ``start`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the start of the geodesic; + - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a + point in hyperbolic space representing the start of the geodesic - - ``end`` -- a HyperbolicPoint or coordinates of a point in hyperbolic space representing the end of the geodesic. - - OUTPUT: - - A hyperbolic geodesic. + - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point + in hyperbolic space representing the end of the geodesic EXAMPLES:: @@ -1156,7 +1137,7 @@ class HyperbolicGeodesicHM (HyperbolicGeodesic): HFactory = HyperbolicFactoryHM HMethods = HyperbolicMethodsUHP - def show(self, show_hyperboloid = True, **graphics_options): + def show(self, show_hyperboloid=True, **graphics_options): r""" EXAMPLES:: @@ -1164,7 +1145,7 @@ def show(self, show_hyperboloid = True, **graphics_options): sage: g = HyperbolicGeodesicHM.random_element() sage: g.show() """ - from sage.all import var + from sage.calculus.var import var (x,y,z) = var('x,y,z') opts = self.graphics_options() opts.update(graphics_options) @@ -1184,8 +1165,10 @@ def show(self, show_hyperboloid = True, **graphics_options): # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike. hyperbola = cosh(x)*v1 + sinh(x)*v2 endtime = arcsinh(v2_ldot_u2) + from sage.plot.plot3d.all import parametric_plot3d pic = parametric_plot3d(hyperbola,(x,0, endtime),**graphics_options) if show_hyperboloid: bd_pic = self.HFactory.get_background_graphic() pic= bd_pic + pic return pic + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index d0a1f6583f8..d16dae67d37 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -19,11 +19,10 @@ EXAMPLES:: sage: UHP.point(2 + I) - Point in UHP I + 2. + Point in UHP I + 2 sage: PD.point(1/2 + I/2) - Point in PD 1/2*I + 1/2. - + Point in PD 1/2*I + 1/2 """ #*********************************************************************** @@ -39,46 +38,38 @@ #*********************************************************************** from sage.structure.unique_representation import UniqueRepresentation from sage.misc.lazy_import import lazy_import -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModel') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelKM') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPoint') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', 'HyperbolicPointKM') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', 'HyperbolicBdryPointKM') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesicKM') - - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometry') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'HyperbolicIsometryKM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', + ['HyperbolicModel','HyperbolicModelUHP', + 'HyperbolicModelPD', 'HyperbolicModelHM', 'HyperbolicModelKM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', + ['HyperbolicFactory', 'HyperbolicFactoryUHP', + 'HyperbolicFactoryPD', 'HyperbolicFactoryHM', + 'HyperbolicFactoryKM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', + ['HyperbolicPoint', 'HyperbolicPointUHP', 'HyperbolicPointPD', + 'HyperbolicPointHM', 'HyperbolicPointKM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', + ['HyperbolicBdryPointUHP', 'HyperbolicBdryPointPD', + 'HyperbolicBdryPointHM', 'HyperbolicBdryPointKM']) + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', + ['HyperbolicGeodesic', 'HyperbolicGeodesicUHP', + 'HyperbolicGeodesicPD', 'HyperbolicGeodesicHM', + 'HyperbolicGeodesicKM']) + + +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', + ['HyperbolicIsometry', 'HyperbolicIsometryUHP', + 'HyperbolicIsometryPD', 'HyperbolicIsometryHM', + 'HyperbolicIsometryKM']) class HyperbolicUserInterface(UniqueRepresentation): r""" - Abstract base class for Hyperbolic Interfaces. These provide a user + Abstract base class for hyperbolic interfaces. These provide a user interface for interacting with models of hyperbolic geometry without having the interface dictate the class structure. """ @@ -145,29 +136,29 @@ def is_bounded(cls): @classmethod def point(cls, p, **kwargs): r""" - Return a HyperbolicPoint object in the current model with - coordinates ``p``. + Return a :class:`HyperbolicPoint` object in the current + model with coordinates ``p``. EXAMPLES:: sage: UHP.point(0) - Boundary point in UHP 0. + Boundary point in UHP 0 sage: PD.point(I/2) - Point in PD 1/2*I. + Point in PD 1/2*I sage: KM.point((0,1)) - Boundary point in KM (0, 1). + Boundary point in KM (0, 1) sage: HM.point((0,0,1)) - Point in HM (0, 0, 1). + Point in HM (0, 0, 1) """ return cls.HFactory.get_point(p, **kwargs) @classmethod def point_in_model(cls, p): r""" - Return True if ``p`` gives the coordinates of a point in the + Return ``True`` if ``p`` gives the coordinates of a point in the interior of hyperbolic space in the model. EXAMPLES:: @@ -184,7 +175,7 @@ def point_in_model(cls, p): @classmethod def bdry_point_in_model(cls, p): r""" - Return True if ``p`` gives the coordinates of a point on the + Return ``True`` if ``p`` gives the coordinates of a point on the ideal boundary of hyperbolic space in the current model. EXAMPLES:: @@ -199,8 +190,8 @@ def bdry_point_in_model(cls, p): @classmethod def isometry_in_model(cls, A): r""" - Return True if the matrix ``A`` acts isometrically on hyperbolic - space in the current model. + Return ``True`` if the matrix ``A`` acts isometrically on + hyperbolic space in the current model. EXAMPLES:: @@ -213,30 +204,30 @@ def isometry_in_model(cls, A): @classmethod def geodesic(cls, start, end, **kwargs): r""" - Return an oriented HyperbolicGeodesic object in the current - model that starts at ``start`` and ends at ``end``. + Return an oriented :class:`HyperbolicGeodesic` object in the + current model that starts at ``start`` and ends at ``end``. EXAMPLES:: sage: UHP.geodesic(1, 0) - Geodesic in UHP from 1 to 0. + Geodesic in UHP from 1 to 0 sage: PD.geodesic(1, 0) - Geodesic in PD from 1 to 0. + Geodesic in PD from 1 to 0 sage: KM.geodesic((0,1/2), (1/2, 0)) - Geodesic in KM from (0, 1/2) to (1/2, 0). + Geodesic in KM from (0, 1/2) to (1/2, 0) sage: HM.geodesic((0,0,1), (0,1, sqrt(2))) - Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)). + Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ return cls.HFactory.get_geodesic(start, end, **kwargs) @classmethod def isometry(cls, A): r""" - Return an HyperbolicIsometry object in the current model that - coincides with (in the case of linear isometry groups) or lifts + Return an :class:`HyperbolicIsometry` object in the current model + that coincides with (in the case of linear isometry groups) or lifts to (in the case of projective isometry groups) the matrix ``A``. EXAMPLES:: @@ -244,24 +235,24 @@ def isometry(cls, A): sage: UHP.isometry(identity_matrix(2)) Isometry in UHP [1 0] - [0 1]. + [0 1] sage: PD.isometry(identity_matrix(2)) Isometry in PD [1 0] - [0 1]. + [0 1] sage: KM.isometry(identity_matrix(3)) Isometry in KM [1 0 0] [0 1 0] - [0 0 1]. + [0 0 1] sage: HM.isometry(identity_matrix(3)) Isometry in HM [1 0 0] [0 1 0] - [0 0 1]. + [0 0 1] """ return cls.HFactory.get_isometry(A) @@ -306,22 +297,24 @@ def isometry_from_fixed_points(cls, p1, p2): INPUT: - - ``p1``, ``p2`` -- points in the ideal boundary of hyperbolic space either as coordinates or as HyperbolicPoints. + - ``p1``, ``p2`` -- points in the ideal boundary of hyperbolic + space either as coordinates or as :class:`HyperbolicPoint`. OUTPUT: - - A HyperbolicIsometry in the current model whose classification is hyperbolic that fixes ``p1`` and ``p2``. + - a :class:`HyperbolicIsometry` in the current model whose + classification is hyperbolic that fixes ``p1`` and ``p2`` EXAMPLES:: sage: UHP.isometry_from_fixed_points(0, 4) Isometry in UHP [ -1 0] - [-1/5 -1/5]. + [-1/5 -1/5] sage: UHP.isometry_from_fixed_points(UHP.point(0), UHP.point(4)) Isometry in UHP [ -1 0] - [-1/5 -1/5]. + [-1/5 -1/5] """ return cls.HIsometry.isometry_from_fixed_points(cls.point(p1), cls.point(p2)) @@ -332,8 +325,8 @@ def dist(cls, a, b): INPUT: - - ``a`` -- a hyperbolic point. - - ``b`` -- a hyperbolic point. + - ``a`` -- a hyperbolic point + - ``b`` -- a hyperbolic point EXAMPLES:: @@ -354,9 +347,11 @@ def point_to_model(cls, p, model): INPUT: - - ``p`` -- a point in the current model of hyperbolic space either as coordinates or as a HyperbolicPoint. + - ``p`` -- a point in the current model of hyperbolic space + either as coordinates or as a :class:`HyperbolicPoint` - - ``model`` -- the name of an implemented model of hyperbolic space of the same dimension. + - ``model`` -- the name of an implemented model of hyperbolic + space of the same dimension EXAMPLES:: @@ -366,7 +361,6 @@ def point_to_model(cls, p, model): +Infinity sage: UHP.point_to_model(UHP.point(I), 'HM') (0, 0, 1) - """ if isinstance(p, HyperbolicPoint): p = p.coordinates() @@ -380,8 +374,9 @@ def isometry_to_model(cls, A, model): INPUT: - - ``A`` -- a matrix acting isometrically on the current model. - - ``model`` -- the name of an implemented model of hyperbolic space of the same dimension. + - ``A`` -- a matrix acting isometrically on the current model + - ``model`` -- the name of an implemented model of hyperbolic + space of the same dimension EXAMPLES:: @@ -404,8 +399,9 @@ class UHP(HyperbolicUserInterface): Hyperbolic interface for the UHP model. EXAMPLES:: + sage: UHP.point(I) - Point in UHP I. + Point in UHP I """ HModel = HyperbolicModelUHP HFactory = HyperbolicFactoryUHP @@ -419,8 +415,9 @@ class PD(HyperbolicUserInterface): Hyperbolic interface for the PD model. EXAMPLES:: + sage: PD.point(I) - Boundary point in PD I. + Boundary point in PD I """ HModel = HyperbolicModelPD HFactory = HyperbolicFactoryPD @@ -434,8 +431,9 @@ class KM(HyperbolicUserInterface): Hyperbolic interface for the KM model. EXAMPLES:: + sage: KM.point((0,0)) - Point in KM (0, 0). + Point in KM (0, 0) """ HModel = HyperbolicModelKM HFactory = HyperbolicFactoryKM @@ -449,11 +447,13 @@ class HM(HyperbolicUserInterface): Hyperbolic interface for the HM model. EXAMPLES:: + sage: HM.point((0,0,1)) - Point in HM (0, 0, 1). + Point in HM (0, 0, 1) """ HModel = HyperbolicModelHM HFactory = HyperbolicFactoryHM HPoint = HyperbolicPointHM HIsometry = HyperbolicIsometryHM HGeodesic = HyperbolicGeodesicHM + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 226c098d7a0..b1f68512d45 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -23,12 +23,12 @@ sage: UHP.isometry(matrix(2,[1,2,3,4])) Isometry in UHP [1 2] - [3 4]. + [3 4] sage: A = UHP.isometry(matrix(2,[0,1,1,0])) sage: A.inverse() Isometry in UHP [0 1] - [1 0]. + [1 0] """ #*********************************************************************** @@ -51,13 +51,12 @@ from sage.functions.other import real, imag lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', + ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', + ['HyperbolicFactoryUHP', 'HyperbolicFactoryPD', + 'HyperbolicFactoryKM', 'HyperbolicFactoryHM']) class HyperbolicIsometry(SageObject): r""" @@ -66,11 +65,12 @@ class HyperbolicIsometry(SageObject): INPUT: - - A matrix representing a hyperbolic isometry in the appropriate model. + - ``A`` -- a matrix representing a hyperbolic isometry in the + appropriate model OUTPUT: - - A hyperbolic isometry. + - a hyperbolic isometry EXAMPLES:: @@ -81,9 +81,9 @@ class HyperbolicIsometry(SageObject): HFactory = HyperbolicAbstractFactory HMethods = HyperbolicAbstractMethods - ##################### - # "Private" Methods # - ##################### + ##################### + # "Private" Methods # + ##################### def __init__(self, A): r""" @@ -95,7 +95,7 @@ def __init__(self, A): sage: HyperbolicIsometryUHP(matrix(2, [0,1,-1,0])) Isometry in UHP [ 0 1] - [-1 0]. + [-1 0] """ self._model = self.HFactory.get_model() self._model.isometry_test(A) @@ -120,21 +120,23 @@ def _cached_matrix(self): return self.model().isometry_to_model(self.matrix(), self.HMethods.model().short_name) - def __repr__(self): + def _repr_(self): r""" Return a string representation of ``self``. OUTPUT: - - a string. + - a string EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(identity_matrix(2)).__repr__() - 'Isometry in UHP\n[1 0]\n[0 1].' + sage: HyperbolicIsometryUHP(identity_matrix(2)) + Isometry in UHP + [1 0] + [0 1] """ - return "Isometry in {0}\n{1}.".format(self.model_name(), self.matrix()) + return "Isometry in {0}\n{1}".format(self.model_name(), self.matrix()) def _latex_(self): r""" @@ -163,7 +165,7 @@ def _latex_(self): def __eq__(self, other): r""" - Return True if the isometries are the same and False otherwise. + Return ``True`` if the isometries are the same and ``False`` otherwise. EXAMPLES:: @@ -180,7 +182,7 @@ def __eq__(self, other): else: return self.model_name() == other.model_name() and pos_matrix - def __pow__ (self, n): + def __pow__(self, n): r""" EXAMPLES:: @@ -189,13 +191,12 @@ def __pow__ (self, n): sage: A**3 Isometry in UHP [41 15] - [30 11]. + [30 11] """ return type (self) (self.matrix()**n) - def __mul__ (self, other): + def __mul__(self, other): r""" - EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * @@ -204,15 +205,15 @@ def __mul__ (self, other): sage: B*A Isometry in UHP [16 8] - [ 7 6]. + [ 7 6] sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) sage: p = UHP.point(2 + I) sage: A*p - Point in UHP 8/17*I + 53/17. + Point in UHP 8/17*I + 53/17 sage: g = UHP.geodesic(2 + I,4 + I) sage: A*g - Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. + Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37 sage: A = diagonal_matrix([1, -1, 1]) sage: A = HyperbolicIsometryHM(A) @@ -220,8 +221,7 @@ def __mul__ (self, other): False sage: p = HM.point((0, 1, sqrt(2))) sage: A*p - Point in HM (0, -1, sqrt(2)). - + Point in HM (0, -1, sqrt(2)) """ from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint @@ -359,7 +359,7 @@ def to_model(self, model_name): Isometry in HM [1 0 0] [0 1 0] - [0 0 1]. + [0 0 1] """ from sage.geometry.hyperbolic_space.model_factory import ModelFactory factory = ModelFactory.find_factory(model_name) @@ -451,7 +451,7 @@ def axis(self, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) sage: H.axis() - Geodesic in UHP from 0 to +Infinity. + Geodesic in UHP from 0 to +Infinity It is an error to call this function on an isometry that is not hyperbolic:: @@ -460,11 +460,11 @@ def axis(self, **graphics_options): sage: P.axis() Traceback (most recent call last): ... - ValueError: The isometry is not hyperbolic. Axis is undefined. + ValueError: the isometry is not hyperbolic: axis is undefined. """ if self.classification() not in ( ['hyperbolic', 'orientation-reversing hyperbolic']): - raise ValueError("The isometry is not hyperbolic. Axis is" + raise ValueError("the isometry is not hyperbolic: axis is" " undefined.") return self.HFactory.get_geodesic(*self.fixed_point_set()) @@ -475,7 +475,7 @@ def fixed_point_set(self, **graphics_options): OUTPUT: - - a list of hyperbolic points. + - a list of hyperbolic points EXAMPLES: @@ -490,7 +490,7 @@ def fixed_point_set(self, **graphics_options): sage: A.orientation_preserving() False sage: A.fixed_point_set() - [Boundary point in UHP 1., Boundary point in UHP -1.] + [Boundary point in UHP 1, Boundary point in UHP -1] :: @@ -499,7 +499,7 @@ def fixed_point_set(self, **graphics_options): sage: B.fixed_point_set() Traceback (most recent call last): ... - ValueError: The identity transformation fixes the entire hyperbolic plane. + ValueError: the identity transformation fixes the entire hyperbolic plane """ pts = self.HMethods.fixed_point_set(self._cached_matrix) pts = [self.HMethods.model().point_to_model(k, self.model_name()) for\ @@ -514,7 +514,7 @@ def fixed_geodesic(self, **graphics_options): sage: A = UHP.isometry(matrix(2, [0, 1, 1, 0])) sage: A.fixed_geodesic() - Geodesic in UHP from 1 to -1. + Geodesic in UHP from 1 to -1 """ fps = self.HMethods.fixed_point_set(self._cached_matrix) if len(fps) < 2: @@ -529,18 +529,18 @@ def fixed_geodesic(self, **graphics_options): def repelling_fixed_point(self, **graphics_options): r""" For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ValueError. + Otherwise raise a ``ValueError``. OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) sage: A.repelling_fixed_point() - Boundary point in UHP 0. + Boundary point in UHP 0 """ fp = self.HMethods.repelling_fixed_point(self._cached_matrix) fp = self.HMethods.model().point_to_model(fp, self.model_name()) @@ -549,18 +549,18 @@ def repelling_fixed_point(self, **graphics_options): def attracting_fixed_point(self, **graphics_options): r""" For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ValueError. + Otherwise raise a `ValueError``. OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) sage: A.attracting_fixed_point() - Boundary point in UHP +Infinity. + Boundary point in UHP +Infinity """ fp = self.HMethods.attracting_fixed_point(self._cached_matrix) fp = self.HMethods.model().point_to_model(fp, self.model_name()) @@ -580,13 +580,13 @@ def isometry_from_fixed_points(cls, repel, attract): sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) Traceback (most recent call last): ... - ValueError: Fixed points of hyperbolic elements must be ideal. + ValueError: fixed points of hyperbolic elements must be ideal sage: p, q = [UHP.point(k) for k in [2, 0]] sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) Isometry in UHP [ -1 0] - [-1/3 -1/3]. + [-1/3 -1/3] """ try: A = cls.HMethods.isometry_from_fixed_points(repel._cached_coordinates, diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index 1670c757e46..f483f51d1a3 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -2,56 +2,49 @@ Hyperbolic Methods This module should not be used directly by users. It is provided for -developers of sage. +developers of Sage. This module implements computational methods for some models of hyperbolic space. The methods may operate on points, geodesics, or isometries of hyperbolic space. However, instead of taking -HyperbolicPoint, HyperbolicGeodesic, or HyperbolicIsometry objects as +:class:`HyperbolicPoint`, :class:`HyperbolicGeodesic`, or +:class:`HyperbolicIsometry` objects as input, they instead take the coordinates of points, the endpoints of geodesics, or matrices. Similarly, they output coordinates or matrices -rather than Hyperbolic objects. - -The methods are factored out of the HyperbolicPoint, HyperbolicGeodesic, -and HyperbolicIsometry classes to allow the implementation of additional -models of hyperbolic space with minimal work. For example, to implement -a model of 2-dimensional hyperbolic space, as long as one provides an -isometry of that model with the upper half plane, one can use the upper -half plane methods to do computations. This prevents, for example, -having to work out an efficient algorithm for computing the midpoint of -a geodesic in every model. Isometries are implemented in the -HyperbolicModel module, and it is primarily in that model that new code -must be added to implement a new model of hyperbolic space. +rather than hyperbolic objects. + +The methods are factored out of the :class:`HyperbolicPoint`, +:class:`HyperbolicGeodesic`, and :class:`HyperbolicIsometry` classes +to allow the implementation of additional models of hyperbolic space +with minimal work. For example, to implement a model of 2-dimensional +hyperbolic space, as long as one provides an isometry of that model with +the upper half plane, one can use the upper half plane methods to do +computations. This prevents, for example, having to work out an +efficient algorithm for computing the midpoint of a geodesic in every model. +Isometries are implemented in the :class:`HyperbolicModel` module, and it +is primarily in that model that new code must be added to implement +a new model of hyperbolic space. In practice, all of the current models of 2 dimensional hyperbolic space use the upper half plane model for their computations. This can lead to some problems, such as long coordinate strings for symbolic points. For -example, the vector (1, 0, sqrt(2)) defines a point in the hyperboloid +example, the vector ``(1, 0, sqrt(2))`` defines a point in the hyperboloid model. Performing mapping this point to the upper half plane and performing computations there may return with vector whose components -are unsimplified strings have several sqrt(2)'s. Presently, this +are unsimplified strings have several ``sqrt(2)``'s. Presently, this drawback is outweighed by the rapidity with which new models can be implemented. AUTHORS: -- Greg Laun (2013): Refactoring, rewrites, all docstrings. -- Rania Amer (2011): some UHP and PD methods. -- Jean-Philippe Burelle (2011): some UHP and PD methods. -- Zach Groton (2011): some UHP and PD methods. -- Greg Laun (2011): some UHP and PD methods. -- Jeremy Lent (2011): some UHP and PD methods. -- Leila Vaden (2011): some UHP and PD methods. -- Derrick Wigglesworth (2011): some UHP and PD methods. -- Bill Goldman (2011): many UHP and PD methods, implemented in Mathematica. +- Bill Goldman, Derrick Wigglesworth, Leila Vaden, Jeremy Lent, Zach Groton, + Jean-Philippe Burelle, Rania Amer, Greg Laun (2011): initial algorithms """ #*********************************************************************** # # Copyright (C) 2013 Greg Laun # -# -# # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. @@ -104,15 +97,15 @@ def model_name(cls): return cls.HModel.short_name -class HyperbolicMethodsUHP (HyperbolicAbstractMethods): +class HyperbolicMethodsUHP(HyperbolicAbstractMethods): r""" Hyperbolic methods for the UHP model of hyperbolic space. """ HModel = HyperbolicModelUHP -################# -# Point Methods # -################# + ################# + # Point Methods # + ################# @classmethod def point_dist(cls, p1, p2): @@ -157,7 +150,7 @@ def random_point(cls, **kwargs): r""" Return a random point in the upper half plane. The points are uniformly distributed over the rectangle - `[-10,10]x[-10i,10i]`. + `[-10,10] \times [-10i,10i]`. EXAMPLES:: @@ -174,9 +167,9 @@ def random_point(cls, **kwargs): return RR.random_element(min = real_min ,max=real_max) +\ I*RR.random_element(min = imag_min,max = imag_max) -#################### -# Geodesic Methods # -#################### + #################### + # Geodesic Methods # + #################### @classmethod def boundary_points(cls, p1, p2): @@ -187,11 +180,11 @@ def boundary_points(cls, p1, p2): INPUT: - - ``p1``, ``p2`` -- points in the hyperbolic plane. + - ``p1``, ``p2`` -- points in the hyperbolic plane OUTPUT: - - a list of boundary points. + - a list of boundary points EXAMPLES:: @@ -256,11 +249,11 @@ def common_perpendicular(cls, start_1, end_1, start_2, end_2): INPUT: - - ``other`` -- a hyperbolic geodesic in current model. + - ``other`` -- a hyperbolic geodesic in current model OUTPUT: - - a hyperbolic geodesic. + - a hyperbolic geodesic EXAMPLES:: @@ -292,11 +285,11 @@ def intersection(cls, start_1, end_1, start_2, end_2): INPUT: - - ``other`` -- a hyperbolic geodesic in the current model. + - ``other`` -- a hyperbolic geodesic in the current model OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -411,11 +404,11 @@ def geod_dist_from_point(cls, start, end, p): INPUT: - - ``other`` -- a hyperbolic point in the same model. + - ``other`` -- a hyperbolic point in the same model OUTPUT: - - the hyperbolic distance. + - the hyperbolic distance EXAMPLES:: @@ -457,7 +450,7 @@ def uncomplete(cls, start, end): INPUT: - - ``start`` -- a real number or infinity. + - ``start`` -- a real number or infinity - ``end`` -- a real number or infinity EXAMPLES:: @@ -485,11 +478,11 @@ def angle(cls, start_1, end_1, start_2, end_2): INPUT: - -``other`` -- a hyperbolic geodesic in the UHP model. + -``other`` -- a hyperbolic geodesic in the UHP model OUTPUT: - - The angle in radians between the two given geodesics + - the angle in radians between the two given geodesics EXAMPLES:: @@ -525,13 +518,14 @@ def angle(cls, start_1, end_1, start_2, end_2): return real(arccos((b_1+b_2)/abs(b_2-b_1))) -#################### -# Isometry Methods # -#################### + #################### + # Isometry Methods # + #################### + @classmethod def orientation_preserving(cls, M): r""" - Return `True` if self is orientation preserving and `False` + Return ``True`` if ``self`` is orientation preserving and ``False`` otherwise. EXAMPLES:: @@ -608,7 +602,7 @@ def classification(cls, M): def translation_length(cls, M): r""" For hyperbolic elements, return the translation length. - Otherwise, raise a ValueError. + Otherwise, raise a ``ValueError``. EXAMPLES:: @@ -630,8 +624,8 @@ def translation_length(cls, M): if cls.classification(M) in ['hyperbolic', 'oriention-reversing hyperbolic']: return 2*arccosh(tau/2) - raise TypeError("Translation length is only defined for hyperbolic" - " transformations.") + raise TypeError("translation length is only defined for hyperbolic" + " transformations") @classmethod def isometry_from_fixed_points(cls, repel, attract): @@ -646,15 +640,14 @@ def isometry_from_fixed_points(cls, repel, attract): sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2 + I, 3 + I) Traceback (most recent call last): ... - ValueError: Fixed points of hyperbolic elements must be ideal. + ValueError: fixed points of hyperbolic elements must be ideal sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2, 0) [ -1 0] [-1/3 -1/3] """ if imag(repel) + imag(attract) > EPSILON: - raise ValueError("Fixed points of hyperbolic elements must be" - " ideal.") + raise ValueError("fixed points of hyperbolic elements must be ideal") repel = real(repel) attract = real(attract) if repel == infinity: @@ -677,7 +670,7 @@ def fixed_point_set(cls, M): OUTPUT: - - a list of hyperbolic points. + - a list of hyperbolic points EXAMPLES:: @@ -693,15 +686,15 @@ def fixed_point_set(cls, M): sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) Traceback (most recent call last): ... - ValueError: The identity transformation fixes the entire hyperbolic plane. + ValueError: the identity transformation fixes the entire hyperbolic plane """ d = sqrt(M.det()**2) M = M/sqrt(d) tau = M.trace()**2 M_cls = cls.classification(M) if M_cls == 'identity': - raise ValueError("The identity transformation fixes the entire " - "hyperbolic plane.") + raise ValueError("the identity transformation fixes the entire " + "hyperbolic plane") if M_cls == 'parabolic': if abs(M[1,0]) < EPSILON: return [infinity] @@ -745,11 +738,11 @@ def fixed_point_set(cls, M): def repelling_fixed_point(cls, M): r""" For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ValueError. + Otherwise raise a ``ValueError``. OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -760,8 +753,8 @@ def repelling_fixed_point(cls, M): """ if cls.classification(M) not in ['hyperbolic', 'orientation-reversing hyperbolic']: - raise ValueError("Repelling fixed point is defined only" + - "for hyperbolic isometries.") + raise ValueError("repelling fixed point is defined only" + "for hyperbolic isometries") v = M.eigenmatrix_right()[1].column(1) if v[1] == 0: return infinity @@ -771,11 +764,11 @@ def repelling_fixed_point(cls, M): def attracting_fixed_point(cls, M): r""" For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ValueError. + Otherwise raise a ``ValueError``. OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -786,8 +779,8 @@ def attracting_fixed_point(cls, M): """ if cls.classification(M) not in \ ['hyperbolic', 'orientation-reversing hyperbolic']: - raise ValueError("Attracting fixed point is defined only" + - "for hyperbolic isometries.") + raise ValueError("attracting fixed point is defined only" + + "for hyperbolic isometries") v = M.eigenmatrix_right()[1].column(0) if v[1] == 0: return infinity @@ -800,11 +793,12 @@ def random_isometry(cls, preserve_orientation = True, **kwargs): INPUT: - - ``preserve_orientation`` -- if ``True`` return an orientation-preserving isometry. + - ``preserve_orientation`` -- if ``True`` return an + orientation-preserving isometry OUTPUT: - - a hyperbolic isometry. + - a hyperbolic isometry EXAMPLES:: @@ -830,10 +824,9 @@ def random_isometry(cls, preserve_orientation = True, **kwargs): else: return M -################### -# Helping Methods # -################### - + ################### + # Helping Methods # + ################### @classmethod def _to_std_geod(cls, start, p, end): @@ -867,12 +860,13 @@ def _crossratio_matrix(cls, p_0, p_1, p_2): INPUT: - - A list of three distinct elements of three distinct elements of `\mathbb{CP}^1` in affine coordinates. That is, each element must be a complex number, `\infty`, or symbolic. + - A list of three distinct elements of three distinct elements + of `\mathbb{CP}^1` in affine coordinates. That is, each element + must be a complex number, `\infty`, or symbolic. OUTPUT: - - An element of '\GL(2,\CC)`. - + - an element of '\GL(2,\CC)` EXAMPLES:: @@ -928,3 +922,4 @@ def _mobius_sending(cls, list1, list2): A = cls._crossratio_matrix(z[0],z[1],z[2]) B = cls._crossratio_matrix(w[0],w[1],w[2]) return B.inverse()*A + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 9407f080187..3bfdb953d86 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -14,7 +14,8 @@ manifold equipped with a complete Riemannian metric of constant curvature `-1`. This module records information sufficient to enable computations in hyperbolic space without explicitly specifying the underlying set or -its Riemannian metric. Although, see the `SageManifolds `_ project if +its Riemannian metric. Although, see the +`SageManifolds `_ project if you would like to take this approach. This module implements the abstract base class for a model of hyperbolic @@ -70,34 +71,27 @@ # # Copyright (C) 2013 Greg Laun # -# -# # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #*********************************************************************** + from sage.structure.unique_representation import UniqueRepresentation from sage.misc.lazy_import import lazy_import from sage.functions.other import imag, real from sage.rings.all import CC, RR +from sage.rings.integer import Integer from sage.symbolic.pynac import I from sage.rings.infinity import infinity from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.matrix.all import matrix lazy_import('sage.misc.misc', 'attrcall') -lazy_import('sage.matrix.all', 'matrix') -lazy_import('sage.rings.integer', 'Integer') lazy_import('sage.modules.free_module_element', 'vector') - lazy_import('sage.functions.other','sqrt') - lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', '_SL2R_to_SO21') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', '_SO21_to_SL2R') - - class HyperbolicModel(UniqueRepresentation): @@ -117,16 +111,16 @@ class HyperbolicModel(UniqueRepresentation): @classmethod def point_in_model(cls, p): #Abstract r""" - Return true if the point is in the given model and false + Return ``True`` if the point is in the given model and ``False`` otherwise. INPUT: - - Any object that can converted into a complex number. + - any object that can converted into a complex number OUTPUT: - - Boolean. + - boolean EXAMPLES:: @@ -141,7 +135,7 @@ def point_in_model(cls, p): #Abstract def point_test(cls, p): #Abstract r""" Test whether a point is in the model. If the point is in the - model, do nothing. Otherwise, raise a ValueError. + model, do nothing. Otherwise, raise a ``ValueError``. EXAMPLES:: @@ -150,25 +144,25 @@ def point_test(cls, p): #Abstract sage: HyperbolicModelUHP.point_test(2 - I) Traceback (most recent call last): ... - ValueError: -I + 2 is not a valid point in the UHP model. + ValueError: -I + 2 is not a valid point in the UHP model """ if not (cls.point_in_model(p) or cls.bdry_point_in_model(p)): - error_string = "{0} is not a valid point in the {1} model." + error_string = "{0} is not a valid point in the {1} model" raise ValueError(error_string.format(p, cls.short_name)) @classmethod - def bdry_point_in_model(cls,p): #Abstract + def bdry_point_in_model(cls, p): #Abstract r""" - Return true if the point is on the ideal boundary of hyperbolic - space and false otherwise. + Return ``True`` if the point is on the ideal boundary of hyperbolic + space and ``False`` otherwise. INPUT: - - Any object that can converted into a complex number. + - any object that can converted into a complex number OUTPUT: - - Boolean. + - boolean EXAMPLES:: @@ -181,7 +175,7 @@ def bdry_point_in_model(cls,p): #Abstract def bdry_point_test(cls, p): #Abstract r""" Test whether a point is in the model. If the point is in the - model, do nothing. Otherwise, raise a ValueError. + model, do nothing. Otherwise, raise a ``ValueError``. EXAMPLES:: @@ -190,10 +184,10 @@ def bdry_point_test(cls, p): #Abstract sage: HyperbolicModelUHP.bdry_point_test(1 + I) Traceback (most recent call last): ... - ValueError: I + 1 is not a valid boundary point in the UHP model. + ValueError: I + 1 is not a valid boundary point in the UHP model """ if not cls.bounded or not cls.bdry_point_in_model(p): - error_string = "{0} is not a valid boundary point in the {1} model." + error_string = "{0} is not a valid boundary point in the {1} model" raise ValueError(error_string.format(p, cls.short_name)) @classmethod @@ -204,11 +198,11 @@ def isometry_in_model(cls, A): #Abstract INPUT: - - A matrix that represents an isometry in the appropriate model. + - a matrix that represents an isometry in the appropriate model OUTPUT: - - Boolean. + - boolean EXAMPLES:: @@ -224,7 +218,7 @@ def isometry_in_model(cls, A): #Abstract def isometry_act_on_point(cls, A, p): #Abtsract r""" Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action ``A \cdot p``. + return image of ``p`` unduer the action `A \cdot p`. EXAMPLES:: @@ -234,13 +228,13 @@ def isometry_act_on_point(cls, A, p): #Abtsract sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) True """ - return A*vector(p) + return A * vector(p) @classmethod def isometry_test(cls, A): #Abstract r""" Test whether an isometry is in the model. If the isometry is in - the model, do nothing. Otherwise, raise a ValueError. + the model, do nothing. Otherwise, raise a ``ValueError``. EXAMPLES:: @@ -265,12 +259,13 @@ def point_to_model(cls, coordinates, model_name): #Abstract INPUT: - - ``coordinates`` -- the coordinates of a valid point in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``coordinates`` -- the coordinates of a valid point in the + current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES:: @@ -303,13 +298,13 @@ def point_to_model(cls, coordinates, model_name): #Abstract sage: UHP.point_to_model(infinity, 'HM') Traceback (most recent call last): ... - NotImplementedError: Boundary points are not implemented for the HM model. + NotImplementedError: boundary points are not implemented for the HM model """ cls.point_test(coordinates) model = ModelFactory.find_model(model_name) if (not model.bounded) and cls.bdry_point_in_model(coordinates): - raise NotImplementedError("Boundary points are not implemented for" - " the {0} model.".format(model_name)) + raise NotImplementedError("boundary points are not implemented for" + " the {0} model".format(model_name)) return cls.pt_conversion_dict[model_name](coordinates) @classmethod @@ -320,12 +315,12 @@ def isometry_to_model(cls, A, model_name): #Abstract INPUT: - - ``A`` -- a matrix in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``A`` -- a matrix in the current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES:: @@ -361,7 +356,7 @@ def isometry_to_model(cls, A, model_name): #Abstract cls.isometry_test(A) return cls.isom_conversion_dict[model_name](A) -class HyperbolicModelUHP (HyperbolicModel, UniqueRepresentation): +class HyperbolicModelUHP(HyperbolicModel, UniqueRepresentation): r""" Upper Half Plane model. """ @@ -421,7 +416,7 @@ def point_in_model(cls, p): #UHP def bdry_point_in_model(cls,p): #UHP r""" Check whether a complex number is a real number or ``\infty``. - In the UHP.model_name_name, this is the ideal boundary of + In the ``UHP.model_name_name``, this is the ideal boundary of hyperbolic space. EXAMPLES:: @@ -450,7 +445,7 @@ def bdry_point_in_model(cls,p): #UHP def isometry_act_on_point(cls, A, p): #UHP r""" Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action ``A \cdot p``. + return image of ``p`` unduer the action `A \cdot p`. EXAMPLES:: @@ -466,7 +461,7 @@ def isometry_act_on_point(cls, A, p): #UHP def isometry_in_model(cls,A): #UHP r""" Check that ``A`` acts as an isometry on the upper half plane. - That is, ``A`` must be an invertible ``2 x 2`` matrix with real + That is, ``A`` must be an invertible `2 \times 2` matrix with real entries. EXAMPLES:: @@ -490,12 +485,13 @@ def point_to_model(cls, coordinates, model_name): #UHP INPUT: - - ``coordinates`` -- the coordinates of a valid point in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``coordinates`` -- the coordinates of a valid point in the + current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES:: @@ -514,13 +510,13 @@ def point_to_model(cls, coordinates, model_name): #UHP sage: UHP.point_to_model(infinity, 'HM') Traceback (most recent call last): ... - NotImplementedError: Boundary points are not implemented for the HM model. + NotImplementedError: boundary points are not implemented for the HM model """ p = coordinates if (cls.bdry_point_in_model(p) and not ModelFactory.find_model(model_name).bounded): - raise NotImplementedError("Boundary points are not implemented for" - " the {0} model.".format(model_name)) + raise NotImplementedError("boundary points are not implemented for" + " the {0} model".format(model_name)) if p == infinity: return { 'UHP' : p, @@ -537,12 +533,12 @@ def isometry_to_model(cls, A, model_name): # UHP INPUT: - - ``A`` -- a matrix in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``A`` -- a matrix in the current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP @@ -556,7 +552,7 @@ def isometry_to_model(cls, A, model_name): # UHP return cls.isom_conversion_dict[model_name](A) -class HyperbolicModelPD (HyperbolicModel, UniqueRepresentation): +class HyperbolicModelPD(HyperbolicModel, UniqueRepresentation): r""" Poincare Disk Model. """ @@ -593,7 +589,7 @@ class HyperbolicModelPD (HyperbolicModel, UniqueRepresentation): @classmethod def point_in_model(cls, p): #PD r""" - Check whether a complex number lies in the open unit disk. + Check whether a complex number lies in the open unit disk. EXAMPLES:: @@ -612,7 +608,7 @@ def point_in_model(cls, p): #PD @classmethod def bdry_point_in_model(cls,p): #PD r""" - Check whether a complex number lies in the open unit disk. + Check whether a complex number lies in the open unit disk. EXAMPLES:: @@ -632,7 +628,7 @@ def bdry_point_in_model(cls,p): #PD def isometry_act_on_point(cls, A, p): #PD r""" Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action ``A \cdot p``. + return image of ``p`` unduer the action `A \cdot p`. EXAMPLES:: @@ -651,7 +647,7 @@ def isometry_act_on_point(cls, A, p): #PD @classmethod def isometry_in_model(cls, A): #PD r""" - Check if the given matrix A is in the group U(1,1). + Check if the given matrix ``A`` is in the group `U(1,1)`. EXAMPLES:: @@ -673,12 +669,13 @@ def point_to_model(cls, coordinates, model_name): #PD INPUT: - - ``coordinates`` -- the coordinates of a valid point in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``coordinates`` -- the coordinates of a valid point in the + current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES:: @@ -704,17 +701,18 @@ def isometry_to_model(cls, A, model_name): # PD INPUT: - - ``A`` -- a matrix in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``A`` -- a matrix in the current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES: We check that orientation-reversing isometries behave as they should:: + sage: PD.isometry_to_model(matrix(2,[0,I,I,0]),'UHP') [ 0 -1] [-1 0] @@ -725,7 +723,7 @@ def isometry_to_model(cls, A, model_name): # PD return cls.isom_conversion_dict[model_name](I*A) return cls.isom_conversion_dict[model_name](A) -class HyperbolicModelKM (HyperbolicModel, UniqueRepresentation): +class HyperbolicModelKM(HyperbolicModel, UniqueRepresentation): r""" Klein Model. """ @@ -789,14 +787,14 @@ def bdry_point_in_model(cls,p): #KM sage: KM.bdry_point_in_model((1 , .2)) False """ - return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) + return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) @classmethod #KM def isometry_act_on_point(cls, A, p): #KM r""" Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action ``A \cdot p``. + return image of ``p`` unduer the action `A \cdot p`. EXAMPLES:: @@ -814,7 +812,7 @@ def isometry_act_on_point(cls, A, p): #KM @classmethod def isometry_in_model(cls, A): #KM r""" - Check if the given matrix A is in the group SO(2,1). + Check if the given matrix ``A`` is in the group `SO(2,1)`. EXAMPLES:: @@ -834,12 +832,13 @@ def point_to_model(cls, coordinates, model_name): #KM INPUT: - - ``coordinates`` -- the coordinates of a valid point in the current model. - - ``model_name`` -- a string denoting the model to be converted to. + - ``coordinates`` -- the coordinates of a valid point in the + current model + - ``model_name`` -- a string denoting the model to be converted to OUTPUT: - - the coordinates of a point in the ``short_name`` model. + - the coordinates of a point in the ``short_name`` model EXAMPLES:: @@ -858,7 +857,7 @@ def point_to_model(cls, coordinates, model_name): #KM model_name) -class HyperbolicModelHM (HyperbolicModel, UniqueRepresentation): +class HyperbolicModelHM(HyperbolicModel, UniqueRepresentation): r""" Hyperboloid Model. """ @@ -905,7 +904,7 @@ def point_in_model(cls, p): #HM @classmethod def bdry_point_in_model(cls,p): #HM r""" - Return False since the Hyperboloid model has no boundary points. + Return ``False`` since the Hyperboloid model has no boundary points. EXAMPLES:: @@ -924,7 +923,7 @@ def bdry_point_in_model(cls,p): #HM @classmethod def isometry_in_model(cls, A): #HM r""" - Test that the matrix ``A`` is in the group ``SO (2,1)^+``. + Test that the matrix ``A`` is in the group `SO (2,1)^+`. EXAMPLES:: @@ -937,8 +936,8 @@ def isometry_in_model(cls, A): #HM def _SL2R_to_SO21 (A): r""" - Given a matrix in SL(2,R) return its irreducible representation in - O(2,1). + Given a matrix in `SL(2, \RR)` return its irreducible representation in + `O(2,1)`. Note that this is not the only homomorphism, but it is the only one that works in the context of the implemented 2D hyperbolic geometry @@ -973,7 +972,7 @@ def _SL2R_to_SO21 (A): def _SO21_to_SL2R (M): r""" - A homomorphism from ``SO(2 ,1)``to ``SL (2, \Bold{R})``. + A homomorphism from `SO(2 ,1)` to `SL (2, \RR)`. Note that this is not the only homomorphism, but it is the only one that works in the context of the implemented 2D hyperbolic geometry @@ -1024,18 +1023,17 @@ def _SO21_to_SL2R (M): def _mobius_transform(A, z): r""" - Given a matrix `A' in `GL(2,\mathbb{C})' and a point `z' in - the complex plan return the mobius transformation action of `A' - on `z'. + Given a matrix ``A`` in `GL(2,\CC)` and a point ``z`` in the complex + plane return the mobius transformation action of ``A`` on ``z``. INPUT: - - ``A`` -- a `2`x`2` invertible matrix over the complex numbers. - - `z` -- a complex number or infinity. + - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers + - ``z`` -- a complex number or infinity OUTPUT: - - A complex number or infinity. + - a complex number or infinity EXAMPLES:: @@ -1046,20 +1044,20 @@ def _mobius_transform(A, z): sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) x + I*y - The matrix must be square and `2`x`2`:: + The matrix must be square and `2`x`2`:: sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) Traceback (most recent call last): ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring. + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring sage: mobius_transform(identity_matrix(3),I) Traceback (most recent call last): ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring. + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - The matrix can be symbolic or can be a matrix over the real - or complex numbers, but must be invertible:: + The matrix can be symbolic or can be a matrix over the real + or complex numbers, but must be invertible:: sage: (a,b,c,d) = var('a,b,c,d'); sage: mobius_transform(matrix(2,[a,b,c,d]),I) @@ -1067,8 +1065,8 @@ def _mobius_transform(A, z): sage: mobius_transform(matrix(2,[0,0,0,0]),I) Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring. + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring """ if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: (a,b,c,d) = A.list() @@ -1086,7 +1084,8 @@ def _mobius_transform(A, z): return (a*w + b)/(c*w + d) else: raise TypeError("A must be an invertible 2x2 matrix over the" - " complex numbers or a symbolic ring.") + " complex numbers or a symbolic ring") + def _PD_preserve_orientation(A): r""" For a PD isometry, determine if it preserves orientation. @@ -1094,6 +1093,7 @@ def _PD_preserve_orientation(A): of the determinant, and it is used a few times in this file. EXAMPLES:: + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _PD_preserve_orientation as orient sage: orient(matrix(2, [-I, 0, 0, I])) True @@ -1102,3 +1102,4 @@ def _PD_preserve_orientation(A): """ return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() and abs(A[0][0]) - abs(A[0][1]) != 0) + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 3f8230c62b8..b678b1dc167 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -15,7 +15,7 @@ UHP for convenience:: sage: UHP.point(2 + I) - Point in UHP I + 2. + Point in UHP I + 2 sage: g = UHP.point(3 + I) sage: g.dist(UHP.point(I)) arccosh(11/2) @@ -36,25 +36,18 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex -lazy_import('sage.plot.point', 'point') -lazy_import('sage.rings.all', 'RR') -lazy_import('sage.rings.all', 'CC') +lazy_import('sage.rings.all', ['RR', 'CC']) lazy_import('sage.functions.other', 'real') +lazy_import('sage.modules.free_module_element', 'vector') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', + ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') - +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', + ['HyperbolicAbstractFactory', 'HyperbolicFactoryUHP', + 'HyperbolicFactoryPD', 'HyperbolicFactoryKM', + 'HyperbolicFactoryHM']) lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') -lazy_import('sage.modules.free_module_element', 'vector') class HyperbolicPoint(SageObject): @@ -64,11 +57,7 @@ class HyperbolicPoint(SageObject): INPUT: - - The coordinates of a hyperbolic point in the appropriate model. - - OUTPUT: - - - A hyperbolic point. + - the coordinates of a hyperbolic point in the appropriate model EXAMPLES: @@ -77,10 +66,10 @@ class HyperbolicPoint(SageObject): sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * sage: p = HyperbolicPointUHP(.2 + .3*I); p - Point in UHP 0.200000000000000 + 0.300000000000000*I. + Point in UHP 0.200000000000000 + 0.300000000000000*I sage: q = HyperbolicPointPD(0.2 + 0.3*I); q - Point in PD 0.200000000000000 + 0.300000000000000*I. + Point in PD 0.200000000000000 + 0.300000000000000*I sage: p == q False @@ -95,25 +84,26 @@ class HyperbolicPoint(SageObject): sage: HyperbolicPointUHP(0.2 - 0.3*I) Traceback (most recent call last): ... - ValueError: 0.200000000000000 - 0.300000000000000*I is not a valid point in the UHP model. + ValueError: 0.200000000000000 - 0.300000000000000*I is not a valid point in the UHP model sage: HyperbolicPointPD(1.2) Traceback (most recent call last): ... - ValueError: 1.20000000000000 is not a valid point in the PD model. + ValueError: 1.20000000000000 is not a valid point in the PD model sage: HyperbolicPointKM((1,1)) Traceback (most recent call last): ... - ValueError: (1, 1) is not a valid point in the KM model. + ValueError: (1, 1) is not a valid point in the KM model sage: HyperbolicPointHM((1, 1, 1)) Traceback (most recent call last): ... - ValueError: (1, 1, 1) is not a valid point in the HM model. + ValueError: (1, 1, 1) is not a valid point in the HM model """ HFactory = HyperbolicAbstractFactory HMethods = HyperbolicAbstractMethods + def __init__(self, coordinates, **graphics_options): r""" See ``HyperbolicPoint`` for full documentation. @@ -123,22 +113,21 @@ def __init__(self, coordinates, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * sage: p = HyperbolicPointUHP(I) sage: p - Point in UHP I. + Point in UHP I """ if self.model().point_in_model(coordinates): - if type(coordinates) == tuple: + if isinstance(coordinates, tuple): coordinates = vector(coordinates) self._coordinates = coordinates else: raise ValueError( "{0} is not a valid".format(coordinates) + - " point in the {0} model.".format(self.model().short_name) - ) + " point in the {0} model".format(self.model().short_name)) self._graphics_options = graphics_options - ##################### - # "Private" Methods # - ##################### + ##################### + # "Private" Methods # + ##################### @lazy_attribute def _cached_coordinates(self): @@ -157,34 +146,33 @@ def _cached_coordinates(self): """ return self.model().point_to_model(self.coordinates(), self.HMethods.model_name()) - def __repr__(self): + def _repr_(self): r""" Return a string representation of ``self``. OUTPUT: - - string. + - string EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(3 + 4*I).__repr__() - 'Point in UHP 4*I + 3.' + sage: HyperbolicPointUHP(3 + 4*I) + Point in UHP 4*I + 3 - sage: HyperbolicPointPD(1/2 + I/2).__repr__() - 'Point in PD 1/2*I + 1/2.' + sage: HyperbolicPointPD(1/2 + I/2) + Point in PD 1/2*I + 1/2 - sage: HyperbolicPointKM((1/2, 1/2)).__repr__() - 'Point in KM (1/2, 1/2).' + sage: HyperbolicPointKM((1/2, 1/2)) + Point in KM (1/2, 1/2) - sage: HyperbolicPointHM((0,0,1)).__repr__() - 'Point in HM (0, 0, 1).' + sage: HyperbolicPointHM((0,0,1)) + Point in HM (0, 0, 1) """ - return "Point in {0} {1}.".format(self.model_name(), - self.coordinates()) + return "Point in {0} {1}".format(self.model_name(), self.coordinates()) def _latex_(self): - """ + r""" EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * @@ -193,13 +181,13 @@ def _latex_(self): 0 sage: q = HM.point((0,0,1)) sage: latex(q) - \left(0, 0, 1\right) + \left(0,\,0,\,1\right) """ return latex(self.coordinates()) def __eq__(self, other): r""" - Return True if self is equal to other. + Return ``True`` if ``self`` is equal to ``other``. EXAMPLES:: @@ -245,10 +233,10 @@ def __rmul__(self, other): sage: A = matrix(2, [0, 1, 1, 0]) sage: A*UHP.point(2 + I) - Point in UHP 1/5*I + 2/5. + Point in UHP 1/5*I + 2/5 sage: B = diagonal_matrix([-1, -1, 1]) sage: B*HM.point((0, 1, sqrt(2))) - Point in HM (0, -1, sqrt(2)). + Point in HM (0, -1, sqrt(2)) """ from sage.matrix.matrix import is_Matrix if is_Matrix(other): @@ -258,9 +246,9 @@ def __rmul__(self, other): raise TypeError("unsupported operand type(s) for *:" "{0} and {1}".format(self, other)) -####################### -# Setters and Getters # -####################### + ####################### + # Setters and Getters # + ####################### def coordinates(self): r""" @@ -286,7 +274,7 @@ def coordinates(self): @classmethod def model(cls): r""" - Return the model to which the HyperbolicPoint belongs. + Return the model to which the :class:`HyperbolicPoint` belongs. EXAMPLES:: @@ -333,23 +321,23 @@ def to_model(self, model_name): INPUT: - - ``model_name`` -- a string representing the image model. + - ``model_name`` -- a string representing the image model EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * sage: HyperbolicPointUHP(I).to_model('PD') - Point in PD 0. + Point in PD 0 """ factory = ModelFactory.find_factory(model_name) coordinates = self.model().point_to_model(self.coordinates(), model_name) return factory.get_point(coordinates) - def update_graphics(self, update = False, **options): + def update_graphics(self, update=False, **options): r""" - Update the graphics options of a HyperbolicPoint. If update is - True, update rather than overwrite. + Update the graphics options of a HyperbolicPoint. If ``update`` is + ``True``, update rather than overwrite. EXAMPLES:: @@ -365,7 +353,6 @@ def update_graphics(self, update = False, **options): sage: p.update_graphics(True, size = 20); p.graphics_options() {'color': 'blue', 'size': 20} - """ if not update: self._graphics_options = {} @@ -384,22 +371,22 @@ def graphics_options(self): """ return self._graphics_options -################################### -# Methods implemented in HMethods # -################################### + ################################### + # Methods implemented in HMethods # + ################################### def dist(self, other): r""" Calculate the hyperbolic distance between two points in the same model. - INPUTS: + INPUT: - - ``other`` -- a hyperbolic point in the same model as ``self``. + - ``other`` -- a hyperbolic point in the same model as ``self`` - OUTPUTS: + OUTPUT: - - the hyperbolic distance. + - the hyperbolic distance EXAMPLES:: @@ -452,29 +439,29 @@ def symmetry_in (self): sage: z.symmetry_in() Isometry in UHP [ 3/2 -13/2] - [ 1/2 -3/2]. + [ 1/2 -3/2] sage: HyperbolicPointUHP(I).symmetry_in() Isometry in UHP [ 0 -1] - [ 1 0]. + [ 1 0] sage: HyperbolicPointPD(0).symmetry_in() Isometry in PD [-I 0] - [ 0 I]. + [ 0 I] sage: HyperbolicPointKM((0, 0)).symmetry_in() Isometry in KM [-1 0 0] [ 0 -1 0] - [ 0 0 1]. + [ 0 0 1] sage: HyperbolicPointHM((0,0,1)).symmetry_in() Isometry in HM [-1 0 0] [ 0 -1 0] - [ 0 0 1]. + [ 0 0 1] sage: p = HyperbolicPointUHP.random_element() sage: A = p.symmetry_in() @@ -521,11 +508,11 @@ def random_element(cls, **kwargs): p = cls.HMethods.model().point_to_model(p, cls.model().short_name) return cls.HFactory.get_point(p) -########### -# Display # -########### + ########### + # Display # + ########### - def show(self, boundary = True, **options): + def show(self, boundary=True, **options): r""" EXAMPLES:: @@ -546,34 +533,31 @@ def show(self, boundary = True, **options): p = map(numerical_approx, p) else: p = numerical_approx(p) + from sage.plot.point import point pic = point(p, **opts) if boundary: bd_pic = self.HFactory.get_background_graphic() pic = bd_pic + pic return pic -class HyperbolicPointUHP (HyperbolicPoint): +class HyperbolicPointUHP(HyperbolicPoint): r""" - Create a point in the UHP model. + A point in the UHP model. INPUT: - - The coordinates of a point in the unit disk in the complex plane `\CC`. - - OUTPUT: - - - A hyperbolic point. + - the coordinates of a point in the unit disk in the complex plane `\CC` EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointUHP sage: HyperbolicPointUHP(2*I) - Point in UHP 2*I. + Point in UHP 2*I """ HFactory = HyperbolicFactoryUHP HMethods = HyperbolicMethodsUHP - def show(self, boundary = True, **options): + def show(self, boundary=True, **options): r""" EXAMPLES:: @@ -589,6 +573,7 @@ def show(self, boundary = True, **options): from sage.misc.functional import numerical_approx p = self.coordinates() + 0*I p = numerical_approx(p) + from sage.plot.point import point pic = point(p, **opts) if boundary: cent = real(p) @@ -603,38 +588,30 @@ class HyperbolicPointPD (HyperbolicPoint): INPUT: - - The coordinates of a point in the unit disk in the complex plane `\CC`. - - OUTPUT: - - - A hyperbolic point. + - the coordinates of a point in the unit disk in the complex plane `\CC` EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointPD sage: HyperbolicPointPD(0) - Point in PD 0. + Point in PD 0 """ HFactory = HyperbolicFactoryPD HMethods = HyperbolicMethodsUHP -class HyperbolicPointKM (HyperbolicPoint): +class HyperbolicPointKM(HyperbolicPoint): r""" Create a point in the KM model. INPUT: - - The coordinates of a point in the unit disk in the real plane `\RR^2`. - - OUTPUT: - - - A hyperbolic point. + - the coordinates of a point in the unit disk in the real plane `\RR^2` EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointKM sage: HyperbolicPointKM((0,0)) - Point in KM (0, 0). + Point in KM (0, 0) """ HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP @@ -645,17 +622,15 @@ class HyperbolicPointHM (HyperbolicPoint): INPUT: - - The coordinates of a point in the hyperboloid given by `x^2 + y^2 - z^2 = -1`. - - OUTPUT: - - - A hyperbolic point. + - the coordinates of a point in the hyperboloid given + by `x^2 + y^2 - z^2 = -1` EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointHM sage: HyperbolicPointHM((0,0,1)) - Point in HM (0, 0, 1). + Point in HM (0, 0, 1) """ HFactory = HyperbolicFactoryHM HMethods = HyperbolicMethodsUHP + diff --git a/src/sage/geometry/hyperbolic_space/model_factory.py b/src/sage/geometry/hyperbolic_space/model_factory.py index b053415fdf2..b846c323370 100644 --- a/src/sage/geometry/hyperbolic_space/model_factory.py +++ b/src/sage/geometry/hyperbolic_space/model_factory.py @@ -1,7 +1,10 @@ r""" +Factory for Hyperbolic Models + AUTHORS: -- Greg Laun (2013): initial version +- Greg Laun (2013): initial version +""" #***************************************************************************** # Copyright (C) 2013 Greg Laun @@ -11,24 +14,22 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #***************************************************************************** -""" + from sage.structure.unique_representation import UniqueRepresentation from sage.misc.lazy_import import lazy_import +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', + ['HyperbolicModelUHP', 'HyperbolicModelPD', + 'HyperbolicModelHM', 'HyperbolicModelKM']) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', 'HyperbolicModelKM') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryPD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryHM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicFactoryKM') - +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', + ['HyperbolicFactoryUHP', 'HyperbolicFactoryPD', + 'HyperbolicFactoryHM', 'HyperbolicFactoryKM']) - -class ModelFactory (UniqueRepresentation): +class ModelFactory(UniqueRepresentation): + """ + Factory for creating the hyperbolic models. + """ @classmethod def find_model(cls, model_name): r""" @@ -36,9 +37,9 @@ def find_model(cls, model_name): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory - sage: ModelFactory.find_model('UHP') - + sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory + sage: ModelFactory.find_model('UHP') + """ return { 'UHP': HyperbolicModelUHP, @@ -54,9 +55,9 @@ def find_factory(cls, model_name): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory - sage: ModelFactory.find_factory('UHP') - + sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory + sage: ModelFactory.find_factory('UHP') + """ return { 'UHP' : HyperbolicFactoryUHP, @@ -64,3 +65,4 @@ def find_factory(cls, model_name): 'HM' : HyperbolicFactoryHM, 'KM' : HyperbolicFactoryKM }[model_name] + From 2a64983a7d7b70d708719bc5cf3013f9e05e4719 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 20 May 2014 09:19:08 -0700 Subject: [PATCH 010/129] Some additional review changes. --- .../hyperbolic_space/hyperbolic_geodesic.py | 41 +- .../hyperbolic_space/hyperbolic_isometry.py | 85 ++-- .../hyperbolic_space/hyperbolic_methods.py | 49 +-- .../hyperbolic_space/hyperbolic_model.py | 397 +++++++++--------- .../hyperbolic_space/hyperbolic_point.py | 7 +- 5 files changed, 282 insertions(+), 297 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index a74c82f2059..bbaf4f466db 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -178,8 +178,8 @@ def _complete(self): True """ if self.model().bounded: - return (self.model().bdry_point_in_model(self.start().coordinates()) and - self.model().bdry_point_in_model(self.end().coordinates())) + return (self.model().bdry_point_in_model(self.start().coordinates()) + and self.model().bdry_point_in_model(self.end().coordinates())) else: return False #All non-bounded geodesics start life incomplete. @@ -202,9 +202,8 @@ def _repr_(self): sage: HyperbolicGeodesicHM((0,0,1), (0, 1, sqrt(Integer(2)))) Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ - return "Geodesic in {0} from {1} to {2}".format( \ - self.model_name(), self.start().coordinates(), \ - self.end().coordinates()) + return "Geodesic in {0} from {1} to {2}".format(self.model_name(), + self.start().coordinates(), self.end().coordinates()) def __eq__(self, other): r""" @@ -436,8 +435,8 @@ def is_asymptotically_parallel(self, other): """ p1, p2 = self.complete().endpoints() q1, q2 = other.complete().endpoints() - return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1,q2])) and - self.model() == other.model()) + return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1,q2])) + and self.model() == other.model()) def is_ultra_parallel(self,other): r""" @@ -539,7 +538,7 @@ def ideal_endpoints(self): NotImplementedError: boundary points are not implemented in the HM model """ if not self.model().bounded: - raise NotImplementedError("boundary points are not implemented in the "\ + raise NotImplementedError("boundary points are not implemented in the " + "{0} model".format(self.model_name())) if self.is_complete(): return self.endpoints() @@ -601,8 +600,8 @@ def uncomplete(self): if not self.is_complete(): return self ends = self.HMethods.uncomplete(*self._cached_endpoints) - ends = [self.HMethods.model().point_to_model(k, self.model_name()) for - k in ends] + ends = [self.HMethods.model().point_to_model(k, self.model_name()) + for k in ends] return self.HFactory.get_geodesic(*ends) def reflection_in(self): @@ -674,8 +673,8 @@ def common_perpendicular(self, other, **graphics_options): perp_ends = self.HMethods.common_perpendicular( *(self._cached_endpoints + other._cached_endpoints)) M = self.HMethods.model() - perp_ends = [M.point_to_model(k, self.model_name()) for - k in perp_ends] + perp_ends = [M.point_to_model(k, self.model_name()) + for k in perp_ends] return self.HFactory.get_geodesic(*perp_ends, **graphics_options) def intersection(self, other, **graphics_options): @@ -762,8 +761,8 @@ def perpendicular_bisector(self, **graphics_options): bisect_ends = self.HMethods.perpendicular_bisector( *self._cached_endpoints) M = self.HMethods.model() - bisect_ends = [M.point_to_model(k, self.model_name()) for - k in bisect_ends] + bisect_ends = [M.point_to_model(k, self.model_name()) + for k in bisect_ends] return self.HFactory.get_geodesic(*bisect_ends, **graphics_options) @classmethod @@ -781,8 +780,8 @@ def random_element(cls, **kwargs): # Some kwargs are for parametrizing the random geodesic # others are for graphics options. Is there a better way to do this? g_ends = [cls.HMethods.random_point(**kwargs) for k in range(2)] - g_ends = [cls.HMethods.model().point_to_model(k, cls.model_name())\ - for k in g_ends] + g_ends = [cls.HMethods.model().point_to_model(k, cls.model_name()) + for k in g_ends] return cls.HFactory.get_geodesic(*g_ends, **kwargs) def midpoint(self, **graphics_options): @@ -808,7 +807,7 @@ def midpoint(self, **graphics_options): if self.is_complete(): raise ValueError("midpoint not defined for complete geodesics") mid = self.HMethods.midpoint(*self._cached_endpoints, - **graphics_options) + **graphics_options) mid = self.HMethods.model().point_to_model(mid, self.model_name()) return self.HFactory.get_point(mid) @@ -901,7 +900,7 @@ def angle(self, other): self.intersection (other) except ValueError: print("Warning: Geodesic segments do not intersect. " - "The angle between them is not defined. \n" + "The angle between them is not defined.\n" "Returning the angle between their completions.") return self.complete().angle(other.complete()) return self.HMethods.angle(*(self._cached_endpoints + @@ -922,7 +921,7 @@ def length(self): return end1.dist(end2) -class HyperbolicGeodesicUHP (HyperbolicGeodesic): +class HyperbolicGeodesicUHP(HyperbolicGeodesic): r""" Create a geodesic in the upper half plane model. @@ -1077,7 +1076,7 @@ def show(self, boundary=True, **options): return pic -class HyperbolicGeodesicKM (HyperbolicGeodesic): +class HyperbolicGeodesicKM(HyperbolicGeodesic): r""" Create a geodesic in the Klein disk model. @@ -1098,7 +1097,7 @@ class HyperbolicGeodesicKM (HyperbolicGeodesic): HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP - def show(self, boundary = True, **options): + def show(self, boundary=True, **options): r""" EXAMPLES:: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index b1f68512d45..52573cb2ce8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -68,10 +68,6 @@ class HyperbolicIsometry(SageObject): - ``A`` -- a matrix representing a hyperbolic isometry in the appropriate model - OUTPUT: - - - a hyperbolic isometry - EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * @@ -193,7 +189,7 @@ def __pow__(self, n): [41 15] [30 11] """ - return type (self) (self.matrix()**n) + return self.__class__(self.matrix()**n) def __mul__(self, other): r""" @@ -242,17 +238,17 @@ def __mul__(self, other): # def __call__ (self, other): # r""" # EXAMPLES:: - + # # sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * # sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) # sage: p = UHP.point(2 + I) # sage: A(p) # Point in UHP 8/17*I + 53/17. - + # # sage: g = UHP.geodesic(2 + I,4 + I) # sage: A (g) # Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. - + # # sage: A = diagonal_matrix([1, -1, 1]) # sage: A = HyperbolicIsometryHM(A) # sage: A.orientation_preserving() @@ -269,9 +265,9 @@ def __mul__(self, other): # return self.HFactory.get_point(self.model().isometry_act_on_point( # self.matrix(), other.coordinates())) -####################### -# Setters and Getters # -####################### + ####################### + # Setters and Getters # + ####################### def matrix(self): r""" @@ -366,13 +362,13 @@ def to_model(self, model_name): matrix = self.model().isometry_to_model(self.matrix(), model_name) return factory.get_isometry(matrix) -################### -# Boolean Methods # -################### + ################### + # Boolean Methods # + ################### def orientation_preserving(self): r""" - Return `True` if self is orientation preserving and `False` + Return ``True`` if ``self`` is orientation preserving and ``False`` otherwise. EXAMPLES:: @@ -387,9 +383,9 @@ def orientation_preserving(self): """ return self.HMethods.orientation_preserving(self._cached_matrix) -################################### -# Methods implemented in HMethods # -################################### + ################################### + # Methods implemented in HMethods # + ################################### def classification(self): r""" @@ -420,8 +416,8 @@ def classification(self): def translation_length(self): r""" - For hyperbolic elements, return the translation length. - Otherwise, raise a ValueError. + For hyperbolic elements, return the translation length; + otherwise, raise a ``ValueError``. EXAMPLES:: @@ -444,7 +440,7 @@ def translation_length(self): def axis(self, **graphics_options): r""" For a hyperbolic isometry, return the axis of the - transformation. Otherwise raise a ValueError. + transformation; otherwise raise a ``ValueError``. EXAMPLES:: @@ -492,7 +488,6 @@ def fixed_point_set(self, **graphics_options): sage: A.fixed_point_set() [Boundary point in UHP 1, Boundary point in UHP -1] - :: sage: B = HyperbolicIsometryUHP(identity_matrix(2)) @@ -528,8 +523,8 @@ def fixed_geodesic(self, **graphics_options): def repelling_fixed_point(self, **graphics_options): r""" - For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ``ValueError``. + For a hyperbolic isometry, return the attracting fixed point; + otherwise raise a ``ValueError``. OUTPUT: @@ -548,8 +543,8 @@ def repelling_fixed_point(self, **graphics_options): def attracting_fixed_point(self, **graphics_options): r""" - For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a `ValueError``. + For a hyperbolic isometry, return the attracting fixed point; + otherwise raise a `ValueError``. OUTPUT: @@ -605,11 +600,12 @@ def random_element(cls, preserve_orientation = True, **kwargs): INPUT: - - ``preserve_orientation`` -- if ``True`` return an orientation- preserving isometry. + - ``preserve_orientation`` -- if ``True`` return an + orientation-preserving isometry OUTPUT: - - a hyperbolic isometry. + - a hyperbolic isometry EXAMPLES:: @@ -624,17 +620,13 @@ def random_element(cls, preserve_orientation = True, **kwargs): return cls.HFactory.get_isometry(A) -class HyperbolicIsometryUHP (HyperbolicIsometry): +class HyperbolicIsometryUHP(HyperbolicIsometry): r""" Create a hyperbolic isometry in the UHP model. INPUT: - - A matrix in `GL(2,\RR)`. - - OUTPUT: - - - A hyperbolic isometry. + - a matrix in `GL(2, \RR)` EXAMPLES:: @@ -645,17 +637,13 @@ class HyperbolicIsometryUHP (HyperbolicIsometry): HFactory = HyperbolicFactoryUHP HMethods = HyperbolicMethodsUHP -class HyperbolicIsometryPD (HyperbolicIsometry): +class HyperbolicIsometryPD(HyperbolicIsometry): r""" Create a hyperbolic isometry in the PD model. INPUT: - - A matrix in `PU(1,1)`. - - OUTPUT: - - - A hyperbolic isometry. + - a matrix in `PU(1,1)` EXAMPLES:: @@ -665,17 +653,13 @@ class HyperbolicIsometryPD (HyperbolicIsometry): HFactory = HyperbolicFactoryPD HMethods = HyperbolicMethodsUHP -class HyperbolicIsometryKM (HyperbolicIsometry): +class HyperbolicIsometryKM(HyperbolicIsometry): r""" Create a hyperbolic isometry in the KM model. INPUT: - - A matrix in `SO(2,1)`. - - OUTPUT: - - - A hyperbolic isometry. + - a matrix in `SO(2,1)` EXAMPLES:: @@ -685,17 +669,13 @@ class HyperbolicIsometryKM (HyperbolicIsometry): HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP -class HyperbolicIsometryHM (HyperbolicIsometry): +class HyperbolicIsometryHM(HyperbolicIsometry): r""" Create a hyperbolic isometry in the HM model. INPUT: - - A matrix in `SO(2,1)`. - - OUTPUT: - - - A hyperbolic isometry. + - a matrix in `SO(2,1)` EXAMPLES:: @@ -704,3 +684,4 @@ class HyperbolicIsometryHM (HyperbolicIsometry): """ HFactory = HyperbolicFactoryHM HMethods = HyperbolicMethodsUHP + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index f483f51d1a3..c1565e0a4b8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -50,6 +50,7 @@ # the License, or (at your option) any later version. # http://www.gnu.org/licenses/ #*********************************************************************** + from sage.structure.unique_representation import UniqueRepresentation from sage.misc.lazy_import import lazy_import from sage.symbolic.pynac import I @@ -60,7 +61,7 @@ from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModel from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP -from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform +from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform class HyperbolicAbstractMethods(UniqueRepresentation): @@ -357,7 +358,7 @@ def perpendicular_bisector(cls, start, end): matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)])) S= cls._to_std_geod(end_1, start, end_2) H = S.inverse()*T*S - return [_mobius_transform(H ,k) for k in [end_1, end_2]] + return [mobius_transform(H ,k) for k in [end_1, end_2]] @classmethod def midpoint(cls, start, end): @@ -393,7 +394,7 @@ def midpoint(cls, start, end): end_p = start else: end_p = end - end_p = _mobius_transform (M, end_p) + end_p = mobius_transform (M, end_p) return end_p @classmethod @@ -439,7 +440,7 @@ def geod_dist_from_point(cls, start, end, p): # Map the endpoints to 0 and infinity and another endpoint # to 1 T = cls._crossratio_matrix(bd_1, bd_1 + 1, bd_2) - x = _mobius_transform(T, p) + x = mobius_transform(T, p) return cls.point_dist(x, abs(x)*I) @classmethod @@ -466,8 +467,8 @@ def uncomplete(cls, start, end): radius = abs(real(end) - center) p = center + radius*I A = cls._to_std_geod(start, p, end).inverse() - p1 = _mobius_transform(A, I/Integer(3)) - p2 = _mobius_transform(A, 3*I) + p1 = mobius_transform(A, I/Integer(3)) + p2 = mobius_transform(A, 3*I) return [p1, p2] @classmethod @@ -509,7 +510,7 @@ def angle(cls, start_1, end_1, start_2, end_2): # with endpoints [0,oo] T = cls._crossratio_matrix(p_1, p_1 +1, p_2) # b_1 and b_2 are the endpoints of the image of other - b_1, b_2 = [_mobius_transform(T, k) for k in [q_1, q_2]] + b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] # If other is now a straight line... if (b_1 == infinity or b_2 == infinity): # then since they intersect, they are equal @@ -614,8 +615,8 @@ def translation_length(cls, M): sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) sage: p = exp(i*7*pi/8) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform - sage: Hp = _mobius_transform(H, p) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: Hp = mobius_transform(H, p) sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) True """ @@ -677,10 +678,10 @@ def fixed_point_set(cls, M): sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform - sage: bool(_mobius_transform(H, p1) == p1) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(mobius_transform(H, p1) == p1) True - sage: bool(_mobius_transform(H, p2) == p2) + sage: bool(mobius_transform(H, p2) == p2) True sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) @@ -838,14 +839,14 @@ def _to_std_geod(cls, start, p, end): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] sage: A = HyperbolicMethodsUHP._to_std_geod(p_1, p_2, p_3) - sage: bool(abs(_mobius_transform(A, p_1)) < 10**-9) + sage: bool(abs(mobius_transform(A, p_1)) < 10**-9) True - sage: bool(abs(_mobius_transform(A, p_2) - I) < 10**-9) + sage: bool(abs(mobius_transform(A, p_2) - I) < 10**-9) True - sage: bool(_mobius_transform(A, p_3) == infinity) + sage: bool(mobius_transform(A, p_3) == infinity) True """ B = matrix(2, [1, 0, 0, -I]) @@ -871,14 +872,14 @@ def _crossratio_matrix(cls, p_0, p_1, p_2): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] sage: A = HyperbolicMethodsUHP._crossratio_matrix(p_1, p_2, p_3) - sage: bool(abs(_mobius_transform(A, p_1) < 10**-9)) + sage: bool(abs(mobius_transform(A, p_1) < 10**-9)) True - sage: bool(abs(_mobius_transform(A, p_2) - 1) < 10**-9) + sage: bool(abs(mobius_transform(A, p_2) - 1) < 10**-9) True - sage: bool(_mobius_transform(A, p_3) == infinity) + sage: bool(mobius_transform(A, p_3) == infinity) True sage: (x,y,z) = var('x,y,z'); HyperbolicMethodsUHP._crossratio_matrix(x,y,z) [ y - z -x*(y - z)] @@ -905,12 +906,12 @@ def _mobius_sending(cls, list1, list2): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform - sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) True - sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) True - sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) True """ if len(list1) != 3 or len(list2) != 3: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 3bfdb953d86..6ac89a8c649 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -93,6 +93,181 @@ lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') +##################################################################### +## Some helper functions + +def SL2R_to_SO21(A): + r""" + Given a matrix in `SL(2, \RR)` return its irreducible representation in + `O(2,1)`. + + Note that this is not the only homomorphism, but it is the only one + that works in the context of the implemented 2D hyperbolic geometry + models. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import SL2R_to_SO21 + sage: A = SL2R_to_SO21(identity_matrix(2)) + sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix + sage: norm(A.transpose()*J*A - J) < 10**-4 + True + """ + a,b,c,d = (A/A.det().sqrt()).list() + B = matrix(3, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, + Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, + Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, + a*b + c*d, Integer(1)/Integer(2)*a**2 - + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - + Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + + Integer(1)/Integer(2)*d**2]) + B = B.apply_map(attrcall('real')) # Kill ~0 imaginary parts + if A.det() > 0: + return B + else: + # Orientation-reversing isometries swap the nappes of + # the lightcone. This fixes that issue. + return -B + +def SO21_to_SL2R(M): + r""" + A homomorphism from `SO(2, 1)` to `SL(2, \RR)`. + + Note that this is not the only homomorphism, but it is the only one + that works in the context of the implemented 2D hyperbolic geometry + models. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import SO21_to_SL2R + sage: (SO21_to_SL2R(identity_matrix(3)) - identity_matrix(2)).norm() < 10**-4 + True + """ + #################################################################### + # SL(2,R) is the double cover of SO (2,1)^+, so we need to choose # + # a lift. I have formulas for the absolute values of each entry # + # a,b ,c,d of the lift matrix(2,[a,b,c,d]), but we need to choose # + # one entry to be positive. I choose d for no particular reason, # + # unless d = 0, then we choose c > 0. The basic strategy for this # + # function is to find the linear map induced by the SO(2,1) # + # element on the Lie algebra sl(2, R). This corresponds to the # + # Adjoint action by a matrix A or -A in SL(2,R). To find which # + # matrix let X,Y,Z be a basis for sl(2,R) and look at the images # + # of X,Y,Z as well as the second and third standard basis vectors # + # for 2x2 matrices (these are traceless, so are in the Lie # + # algebra). These corresponds to AXA^-1 etc and give formulas # + # for the entries of A. # + #################################################################### + (m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8,m_9) = M.list() + d = sqrt(Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 - + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + if M.det() > 0: #EPSILON? + det_sign = 1 + elif M.det() < 0: #EPSILON? + det_sign = -1 + if d > 0: #EPSILON? + c = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/d + b = (-Integer(1)/Integer(2)*m_2 + Integer(1)/Integer(2)*m_3)/d + ad = det_sign*1 + b*c # ad - bc = pm 1 + a = ad/d + else: # d is 0, so we make c > 0 + c = sqrt(-Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 + + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + d = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/c + #d = 0, so ad - bc = -bc = pm 1. + b = - (det_sign*1)/c + a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b + A = matrix(2,[a,b,c,d]) + return A + +def mobius_transform(A, z): + r""" + Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex + plane return the mobius transformation action of ``A`` on ``z``. + + INPUT: + + - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers + - ``z`` -- a complex number or infinity + + OUTPUT: + + - a complex number or infinity + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform as mobius_transform + sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) + 2/109*I + 43/109 + sage: y = var('y') + sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) + x + I*y + + The matrix must be square and `2`x`2`:: + + sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + sage: mobius_transform(identity_matrix(3),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + The matrix can be symbolic or can be a matrix over the real + or complex numbers, but must be invertible:: + + sage: (a,b,c,d) = var('a,b,c,d'); + sage: mobius_transform(matrix(2,[a,b,c,d]),I) + (I*a + b)/(I*c + d) + + sage: mobius_transform(matrix(2,[0,0,0,0]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + """ + if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: + (a,b,c,d) = A.list() + if z == infinity: + if c == 0: + return infinity + return a/c + if a*d - b*c < 0: + w = z.conjugate() # Reverses orientation + else: + w = z + if c*z + d == 0: + return infinity + else: + return (a*w + b)/(c*w + d) + else: + raise TypeError("A must be an invertible 2x2 matrix over the" + " complex numbers or a symbolic ring") + +def PD_preserve_orientation(A): + r""" + For a PD isometry, determine if it preserves orientation. + This test is more more involved than just checking the sign + of the determinant, and it is used a few times in this file. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import PD_preserve_orientation as orient + sage: orient(matrix(2, [-I, 0, 0, I])) + True + sage: orient(matrix(2, [0, I, I, 0])) + False + """ + return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() + and abs(A[0][0]) - abs(A[0][1]) != 0) + + +##################################################################### +## The actual classes class HyperbolicModel(UniqueRepresentation): r""" @@ -175,7 +350,7 @@ def bdry_point_in_model(cls, p): #Abstract def bdry_point_test(cls, p): #Abstract r""" Test whether a point is in the model. If the point is in the - model, do nothing. Otherwise, raise a ``ValueError``. + model, do nothing; otherwise raise a ``ValueError``. EXAMPLES:: @@ -193,8 +368,8 @@ def bdry_point_test(cls, p): #Abstract @classmethod def isometry_in_model(cls, A): #Abstract r""" - Return true if the input matrix represents an isometry of the - given model and false otherwise. + Return ``True`` if the input matrix represents an isometry of the + given model and ``False`` otherwise. INPUT: @@ -234,7 +409,7 @@ def isometry_act_on_point(cls, A, p): #Abtsract def isometry_test(cls, A): #Abstract r""" Test whether an isometry is in the model. If the isometry is in - the model, do nothing. Otherwise, raise a ``ValueError``. + the model, do nothing; otherwise, raise a ``ValueError``. EXAMPLES:: @@ -365,7 +540,7 @@ class HyperbolicModelUHP(HyperbolicModel, UniqueRepresentation): bounded = True conformal = True dimension = 2 - isometry_group = "PSL (2,\\Bold{R})" + isometry_group = "PSL(2, \\Bold{R})" isometry_group_is_projective = True pt_conversion_dict = { 'UHP' : lambda p : p, @@ -378,12 +553,11 @@ class HyperbolicModelUHP(HyperbolicModel, UniqueRepresentation): imag(p)**2 + 1)) } isom_conversion_dict = { - 'UHP': lambda A : A, - 'PD' : lambda A : matrix(2,[1,-I,-I,1])*A*\ - matrix(2,[1,I,I,1])/Integer(2), - 'HM' : lambda A : _SL2R_to_SO21(A), - 'KM' : lambda A : _SL2R_to_SO21(A) - } + 'UHP': lambda A : A, + 'PD' : lambda A : matrix(2,[1,-I,-I,1]) * A * matrix(2,[1,I,I,1])/Integer(2), + 'HM' : SL2R_to_SO21, + 'KM' : SL2R_to_SO21 + } @classmethod def point_in_model(cls, p): #UHP @@ -439,7 +613,7 @@ def bdry_point_in_model(cls,p): #UHP False """ im = abs(imag(CC(p)).n()) - return bool( (im < EPSILON) or (p == infinity)) + return bool( (im < EPSILON) or (p == infinity) ) @classmethod #UHP def isometry_act_on_point(cls, A, p): #UHP @@ -455,7 +629,7 @@ def isometry_act_on_point(cls, A, p): #UHP sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) True """ - return _mobius_transform(A, p) + return mobius_transform(A, p) @classmethod def isometry_in_model(cls,A): #UHP @@ -580,9 +754,9 @@ class HyperbolicModelPD(HyperbolicModel, UniqueRepresentation): 'PD' : lambda A : A, 'UHP': lambda A : (matrix(2,[1,I,I,1])*A* matrix(2,[1,-I,-I,1])/Integer(2)), - 'KM' : lambda A : _SL2R_to_SO21( matrix(2,[1,I,I,1])*A* + 'KM' : lambda A : SL2R_to_SO21( matrix(2,[1,I,I,1]) * A * matrix(2,[1,-I,-I,1])/Integer(2)), - 'HM' : lambda A : _SL2R_to_SO21( matrix(2,[1,I,I,1])*A* + 'HM' : lambda A : SL2R_to_SO21( matrix(2,[1,I,I,1]) * A * matrix(2,[1,-I,-I,1])/Integer(2)) } @@ -638,9 +812,9 @@ def isometry_act_on_point(cls, A, p): #PD sage: bool(norm(HyperbolicModelPD.isometry_act_on_point(I2, q) - q) < 10**-9) True """ - _image = _mobius_transform(A, p) - if not _PD_preserve_orientation(A): - return _mobius_transform(I*matrix(2,[0,1,1,0]), _image) + _image = mobius_transform(A, p) + if not PD_preserve_orientation(A): + return mobius_transform(I*matrix(2,[0,1,1,0]), _image) return _image @@ -659,7 +833,7 @@ def isometry_in_model(cls, A): #PD # alpha = A[0][0] # beta = A[0][1] # Orientation preserving and reversing - return _PD_preserve_orientation(A) or _PD_preserve_orientation(I*A) + return PD_preserve_orientation(A) or PD_preserve_orientation(I*A) @classmethod def point_to_model(cls, coordinates, model_name): #PD @@ -719,7 +893,7 @@ def isometry_to_model(cls, A, model_name): # PD """ cls.isometry_test(A) # Check for orientation-reversing isometries. - if (not _PD_preserve_orientation(A) and model_name == 'UHP'): + if (not PD_preserve_orientation(A) and model_name == 'UHP'): return cls.isom_conversion_dict[model_name](I*A) return cls.isom_conversion_dict[model_name](A) @@ -733,7 +907,7 @@ class HyperbolicModelKM(HyperbolicModel, UniqueRepresentation): conformal = False dimension = 2 isometry_group_is_projective = True - isometry_group = "PSO (2, 1)" + isometry_group = "PSO(2, 1)" pt_conversion_dict = { 'UHP' : lambda p: -p[0]/(p[1] - 1) +\ I*(-(sqrt(-p[0]**2 -p[1]**2 + 1) - p[0]**2 - p[1]**2 + @@ -745,8 +919,8 @@ class HyperbolicModelKM(HyperbolicModel, UniqueRepresentation): p[1]**2))/(1 - p[0]**2 - p[1]**2) } isom_conversion_dict = { - 'UHP' : lambda A : _SO21_to_SL2R(A) , - 'PD' : lambda A : matrix(2,[1,-I,-I,1])*_SO21_to_SL2R(A)*\ + 'UHP' : SO21_to_SL2R, + 'PD' : lambda A : matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(A) *\ matrix(2,[1,I,I,1])/Integer(2), 'KM' : lambda A : A, 'HM' : lambda A : A @@ -866,7 +1040,7 @@ class HyperbolicModelHM(HyperbolicModel, UniqueRepresentation): bounded = False conformal = True dimension = 2 - isometry_group = "SO (2, 1)" + isometry_group = "SO(2, 1)" pt_conversion_dict = { 'UHP' : lambda p : -((p[0]*p[2] + p[0]) + I*(p[2] +1))/((p[1] - 1)*p[2] - p[0]**2 - @@ -876,8 +1050,8 @@ class HyperbolicModelHM(HyperbolicModel, UniqueRepresentation): 'HM' : lambda p : p } isom_conversion_dict = { - 'UHP' : lambda A : _SO21_to_SL2R(A) , - 'PD' : lambda A : matrix(2,[1,-I,-I,1])*_SO21_to_SL2R(A)*\ + 'UHP' : SO21_to_SL2R, + 'PD' : lambda A : matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(A) *\ matrix(2,[1,I,I,1])/Integer(2), 'KM' : lambda A : A, 'HM' : lambda A : A @@ -923,7 +1097,7 @@ def bdry_point_in_model(cls,p): #HM @classmethod def isometry_in_model(cls, A): #HM r""" - Test that the matrix ``A`` is in the group `SO (2,1)^+`. + Test that the matrix ``A`` is in the group `SO(2,1)^+`. EXAMPLES:: @@ -934,172 +1108,3 @@ def isometry_in_model(cls, A): #HM from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON) -def _SL2R_to_SO21 (A): - r""" - Given a matrix in `SL(2, \RR)` return its irreducible representation in - `O(2,1)`. - - Note that this is not the only homomorphism, but it is the only one - that works in the context of the implemented 2D hyperbolic geometry - models. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _SL2R_to_SO21 - sage: A = _SL2R_to_SO21(identity_matrix(2)) - sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix - sage: norm(A.transpose()*J*A - J) < 10**-4 - True - """ - a,b,c,d = (A/A.det().sqrt()).list() - B = matrix(3, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, - Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - - Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, - Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - - Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, - a*b + c*d, Integer(1)/Integer(2)*a**2 - - Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - - Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + - Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + - Integer(1)/Integer(2)*d**2]) - B = B.apply_map(attrcall('real')) # Kill ~0 imaginary parts - if A.det() > 0: - return B - else: - # Orientation-reversing isometries swap the nappes of - # the lightcone. This fixes that issue. - return -B - -def _SO21_to_SL2R (M): - r""" - A homomorphism from `SO(2 ,1)` to `SL (2, \RR)`. - - Note that this is not the only homomorphism, but it is the only one - that works in the context of the implemented 2D hyperbolic geometry - models. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _SO21_to_SL2R - sage: (_SO21_to_SL2R(identity_matrix(3)) - identity_matrix(2)).norm() < 10**-4 - True - """ - #################################################################### - # SL (2,R) is the double cover of SO (2,1)^+, so we need to choose # - # a lift. I have formulas for the absolute values of each entry # - # a,b ,c,d of the lift matrix(2,[a,b,c,d]), but we need to choose # - # one entry to be positive. I choose d for no particular reason, # - # unless d = 0, then we choose c > 0. The basic strategy for this # - # function is to find the linear map induced by the SO (2,1) # - # element on the Lie algebra sl (2, R). This corresponds to the # - # Adjoint action by a matrix A or -A in SL (2,R). To find which # - # matrix let X,Y,Z be a basis for sl(2,R) and look at the images # - # of X,Y,Z as well as the second and third standard basis vectors # - # for 2x2 matrices (these are traceless, so are in the Lie # - # algebra). These corresponds to AXA^-1 etc and give formulas # - # for the entries of A. # - #################################################################### - (m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8,m_9) = M.list() - d = sqrt(Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 - - Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) - if M.det() > 0: #EPSILON? - det_sign = 1 - elif M.det() < 0: #EPSILON? - det_sign = -1 - if d > 0: #EPSILON? - c = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/d - b = (-Integer(1)/Integer(2)*m_2 + Integer(1)/Integer(2)*m_3)/d - ad = det_sign*1 + b*c # ad - bc = pm 1 - a = ad/d - else: # d is 0, so we make c > 0 - c = sqrt(-Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 + - Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) - d = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/c - #d = 0, so ad - bc = -bc = pm 1. - b = - (det_sign*1)/c - a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b - A = matrix(2,[a,b,c,d]) - return A - -def _mobius_transform(A, z): - r""" - Given a matrix ``A`` in `GL(2,\CC)` and a point ``z`` in the complex - plane return the mobius transformation action of ``A`` on ``z``. - - INPUT: - - - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers - - ``z`` -- a complex number or infinity - - OUTPUT: - - - a complex number or infinity - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform as mobius_transform - sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) - 2/109*I + 43/109 - sage: y = var('y') - sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) - x + I*y - - The matrix must be square and `2`x`2`:: - - sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - - sage: mobius_transform(identity_matrix(3),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - - The matrix can be symbolic or can be a matrix over the real - or complex numbers, but must be invertible:: - - sage: (a,b,c,d) = var('a,b,c,d'); - sage: mobius_transform(matrix(2,[a,b,c,d]),I) - (I*a + b)/(I*c + d) - - sage: mobius_transform(matrix(2,[0,0,0,0]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - """ - if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: - (a,b,c,d) = A.list() - if z == infinity: - if c == 0: - return infinity - return a/c - if a*d - b*c < 0: - w = z.conjugate() # Reverses orientation - else: - w = z - if c*z + d == 0: - return infinity - else: - return (a*w + b)/(c*w + d) - else: - raise TypeError("A must be an invertible 2x2 matrix over the" - " complex numbers or a symbolic ring") - -def _PD_preserve_orientation(A): - r""" - For a PD isometry, determine if it preserves orientation. - This test is more more involved than just checking the sign - of the determinant, and it is used a few times in this file. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _PD_preserve_orientation as orient - sage: orient(matrix(2, [-I, 0, 0, I])) - True - sage: orient(matrix(2, [0, I, I, 0])) - False - """ - return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() - and abs(A[0][0]) - abs(A[0][1]) != 0) - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index b678b1dc167..f7dc70aa5e8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -222,8 +222,8 @@ def __eq__(self, other): sage: p1 == p2 True """ - return (self.model_name() == other.model_name() and - bool(self.coordinates() == other.coordinates())) + return (self.model_name() == other.model_name() + and bool(self.coordinates() == other.coordinates())) def __rmul__(self, other): r""" @@ -330,8 +330,7 @@ def to_model(self, model_name): Point in PD 0 """ factory = ModelFactory.find_factory(model_name) - coordinates = self.model().point_to_model(self.coordinates(), - model_name) + coordinates = self.model().point_to_model(self.coordinates(), model_name) return factory.get_point(coordinates) def update_graphics(self, update=False, **options): From 7371810de8f1fc99cfa9cc230a2892a695ab9d99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 25 Jun 2014 21:17:42 +0200 Subject: [PATCH 011/129] trac #9439 work on doc --- src/doc/en/reference/geometry/index.rst | 17 ++++---- .../hyperbolic_space/hyperbolic_interface.py | 28 ++++++++----- .../hyperbolic_space/hyperbolic_isometry.py | 5 ++- .../hyperbolic_space/hyperbolic_methods.py | 8 ++-- .../hyperbolic_space/hyperbolic_model.py | 42 +++++++++++-------- .../hyperbolic_space/hyperbolic_point.py | 2 +- 6 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/doc/en/reference/geometry/index.rst b/src/doc/en/reference/geometry/index.rst index 0f05380db43..bffcdc2e7bb 100644 --- a/src/doc/en/reference/geometry/index.rst +++ b/src/doc/en/reference/geometry/index.rst @@ -34,7 +34,13 @@ polytopes and polyhedra (with rational or numerical coordinates). sage/geometry/triangulation/base sage/geometry/triangulation/element -<<<<<<< HEAD + sage/geometry/hyperplane_arrangement/arrangement + sage/geometry/hyperplane_arrangement/library + sage/geometry/hyperplane_arrangement/hyperplane + sage/geometry/hyperplane_arrangement/affine_subspace + + sage/geometry/linear_expression + Hyperbolic Geometry ------------------- .. toctree:: @@ -47,15 +53,6 @@ Hyperbolic Geometry sage/geometry/hyperbolic_space/hyperbolic_model sage/geometry/hyperbolic_space/hyperbolic_interface sage/geometry/hyperbolic_space/hyperbolic_methods -======= - sage/geometry/hyperplane_arrangement/arrangement - sage/geometry/hyperplane_arrangement/library - sage/geometry/hyperplane_arrangement/hyperplane - sage/geometry/hyperplane_arrangement/affine_subspace - - sage/geometry/linear_expression - ->>>>>>> 8be52e678f5af0e62794959b70b7cb8babe0a488 Backends for Polyhedral Computations ------------------------------------ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index d0a1f6583f8..61bcf0d463b 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Interface to Hyperbolic Models @@ -9,7 +10,7 @@ convenient user experience. The interfaces are by default given abbreviated names. For example, -UHP (upper half plane model), PD (Poincare disk model), KM (Klein disk +UHP (upper half plane model), PD (Poincaré disk model), KM (Klein disk model), and HM (hyperboloid model). AUTHORS: @@ -23,7 +24,6 @@ sage: PD.point(1/2 + I/2) Point in PD 1/2*I + 1/2. - """ #*********************************************************************** @@ -98,7 +98,7 @@ def model_name(cls): sage: UHP.model_name() 'Upper Half Plane Model' sage: PD.model_name() - 'Poincare Disk Model' + 'Poincaré Disk Model' sage: KM.model_name() 'Klein Disk Model' sage: HM.model_name() @@ -167,7 +167,7 @@ def point(cls, p, **kwargs): @classmethod def point_in_model(cls, p): r""" - Return True if ``p`` gives the coordinates of a point in the + Return ``True`` if ``p`` gives the coordinates of a point in the interior of hyperbolic space in the model. EXAMPLES:: @@ -184,7 +184,7 @@ def point_in_model(cls, p): @classmethod def bdry_point_in_model(cls, p): r""" - Return True if ``p`` gives the coordinates of a point on the + Return ``True`` if ``p`` gives the coordinates of a point on the ideal boundary of hyperbolic space in the current model. EXAMPLES:: @@ -199,7 +199,7 @@ def bdry_point_in_model(cls, p): @classmethod def isometry_in_model(cls, A): r""" - Return True if the matrix ``A`` acts isometrically on hyperbolic + Return ``True`` if the matrix ``A`` acts isometrically on hyperbolic space in the current model. EXAMPLES:: @@ -306,11 +306,13 @@ def isometry_from_fixed_points(cls, p1, p2): INPUT: - - ``p1``, ``p2`` -- points in the ideal boundary of hyperbolic space either as coordinates or as HyperbolicPoints. + - ``p1``, ``p2`` -- points in the ideal boundary of hyperbolic + space either as coordinates or as HyperbolicPoints. OUTPUT: - - A HyperbolicIsometry in the current model whose classification is hyperbolic that fixes ``p1`` and ``p2``. + - A HyperbolicIsometry in the current model whose + classification is hyperbolic that fixes ``p1`` and ``p2``. EXAMPLES:: @@ -354,9 +356,11 @@ def point_to_model(cls, p, model): INPUT: - - ``p`` -- a point in the current model of hyperbolic space either as coordinates or as a HyperbolicPoint. + - ``p`` -- a point in the current model of hyperbolic space + either as coordinates or as a HyperbolicPoint. - - ``model`` -- the name of an implemented model of hyperbolic space of the same dimension. + - ``model`` -- the name of an implemented model of hyperbolic + space of the same dimension. EXAMPLES:: @@ -404,6 +408,7 @@ class UHP(HyperbolicUserInterface): Hyperbolic interface for the UHP model. EXAMPLES:: + sage: UHP.point(I) Point in UHP I. """ @@ -419,6 +424,7 @@ class PD(HyperbolicUserInterface): Hyperbolic interface for the PD model. EXAMPLES:: + sage: PD.point(I) Boundary point in PD I. """ @@ -434,6 +440,7 @@ class KM(HyperbolicUserInterface): Hyperbolic interface for the KM model. EXAMPLES:: + sage: KM.point((0,0)) Point in KM (0, 0). """ @@ -449,6 +456,7 @@ class HM(HyperbolicUserInterface): Hyperbolic interface for the HM model. EXAMPLES:: + sage: HM.point((0,0,1)) Point in HM (0, 0, 1). """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 226c098d7a0..f3759eb6733 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -477,7 +477,7 @@ def fixed_point_set(self, **graphics_options): - a list of hyperbolic points. - EXAMPLES: + EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * sage: H = HyperbolicIsometryUHP(matrix(2, [-2/3,-1/3,-1/3,-2/3])) @@ -535,7 +535,7 @@ def repelling_fixed_point(self, **graphics_options): - a hyperbolic point. - EXAMPLES: + EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) @@ -685,6 +685,7 @@ class HyperbolicIsometryKM (HyperbolicIsometry): HFactory = HyperbolicFactoryKM HMethods = HyperbolicMethodsUHP + class HyperbolicIsometryHM (HyperbolicIsometry): r""" Create a hyperbolic isometry in the HM model. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index 1670c757e46..b3adcc655ba 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -148,7 +148,7 @@ def symmetry_in(cls, p): [ 1/2 -3/2] """ cls.model().point_test(p) - x,y = real(p), imag(p) + x, y = real(p), imag(p) if y > 0: return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) @@ -157,7 +157,7 @@ def random_point(cls, **kwargs): r""" Return a random point in the upper half plane. The points are uniformly distributed over the rectangle - `[-10,10]x[-10i,10i]`. + `[-10,10]\times[-10i,10i]`. EXAMPLES:: @@ -485,7 +485,7 @@ def angle(cls, start_1, end_1, start_2, end_2): INPUT: - -``other`` -- a hyperbolic geodesic in the UHP model. + - ``other`` -- a hyperbolic geodesic in the UHP model. OUTPUT: @@ -531,7 +531,7 @@ def angle(cls, start_1, end_1, start_2, end_2): @classmethod def orientation_preserving(cls, M): r""" - Return `True` if self is orientation preserving and `False` + Return ``True`` if self is orientation preserving and ``False`` otherwise. EXAMPLES:: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 9407f080187..3e8c92f8dda 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Hyperbolic Models @@ -28,7 +29,7 @@ EXAMPLES: We illustrate how the classes in this module encode data by comparing -the upper half plane (UHP), Poincare disk (PD) and hyperboloid (HM) +the upper half plane (UHP), Poincaré disk (PD) and hyperboloid (HM) models. First we import:: sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP as U @@ -117,7 +118,7 @@ class HyperbolicModel(UniqueRepresentation): @classmethod def point_in_model(cls, p): #Abstract r""" - Return true if the point is in the given model and false + Return ``True`` if the point is in the given model and ``False`` otherwise. INPUT: @@ -157,7 +158,7 @@ def point_test(cls, p): #Abstract raise ValueError(error_string.format(p, cls.short_name)) @classmethod - def bdry_point_in_model(cls,p): #Abstract + def bdry_point_in_model(cls, p): #Abstract r""" Return true if the point is on the ideal boundary of hyperbolic space and false otherwise. @@ -199,8 +200,8 @@ def bdry_point_test(cls, p): #Abstract @classmethod def isometry_in_model(cls, A): #Abstract r""" - Return true if the input matrix represents an isometry of the - given model and false otherwise. + Return ``True`` if the input matrix represents an isometry of the + given model and ``False`` otherwise. INPUT: @@ -234,13 +235,15 @@ def isometry_act_on_point(cls, A, p): #Abtsract sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) True """ - return A*vector(p) + return A * vector(p) @classmethod def isometry_test(cls, A): #Abstract r""" - Test whether an isometry is in the model. If the isometry is in - the model, do nothing. Otherwise, raise a ValueError. + Test whether an isometry is in the model. + + If the isometry is in the model, do nothing. Otherwise, raise + a ValueError. EXAMPLES:: @@ -361,6 +364,7 @@ def isometry_to_model(cls, A, model_name): #Abstract cls.isometry_test(A) return cls.isom_conversion_dict[model_name](A) + class HyperbolicModelUHP (HyperbolicModel, UniqueRepresentation): r""" Upper Half Plane model. @@ -418,7 +422,7 @@ def point_in_model(cls, p): #UHP return bool(imag(CC(p)) > 0) @classmethod - def bdry_point_in_model(cls,p): #UHP + def bdry_point_in_model(cls, p): #UHP r""" Check whether a complex number is a real number or ``\infty``. In the UHP.model_name_name, this is the ideal boundary of @@ -463,7 +467,7 @@ def isometry_act_on_point(cls, A, p): #UHP return _mobius_transform(A, p) @classmethod - def isometry_in_model(cls,A): #UHP + def isometry_in_model(cls, A): #UHP r""" Check that ``A`` acts as an isometry on the upper half plane. That is, ``A`` must be an invertible ``2 x 2`` matrix with real @@ -545,6 +549,7 @@ def isometry_to_model(cls, A, model_name): # UHP - the coordinates of a point in the ``short_name`` model. EXAMPLES:: + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP sage: HyperbolicModelUHP.isometry_to_model(matrix(2,[0, 1, 1, 0]),'PD') [0 I] @@ -552,15 +557,15 @@ def isometry_to_model(cls, A, model_name): # UHP """ cls.isometry_test(A) if A.det() < 0 and model_name == 'PD': - return cls.isom_conversion_dict[model_name](I*A) + return cls.isom_conversion_dict[model_name](I * A) return cls.isom_conversion_dict[model_name](A) class HyperbolicModelPD (HyperbolicModel, UniqueRepresentation): r""" - Poincare Disk Model. + Poincaré Disk Model. """ - name = "Poincare Disk Model" + name = "Poincaré Disk Model" short_name = "PD" bounded = True conformal = True @@ -610,7 +615,7 @@ def point_in_model(cls, p): #PD @classmethod - def bdry_point_in_model(cls,p): #PD + def bdry_point_in_model(cls, p): #PD r""" Check whether a complex number lies in the open unit disk. @@ -715,6 +720,7 @@ def isometry_to_model(cls, A, model_name): # PD We check that orientation-reversing isometries behave as they should:: + sage: PD.isometry_to_model(matrix(2,[0,I,I,0]),'UHP') [ 0 -1] [-1 0] @@ -773,7 +779,7 @@ def point_in_model(cls, p): #KM return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) @classmethod - def bdry_point_in_model(cls,p): #KM + def bdry_point_in_model(cls, p): #KM r""" Check whether a point lies in the unit circle, which corresponds to the ideal boundary of the hyperbolic plane in the Klein model. @@ -900,12 +906,12 @@ def point_in_model(cls, p): #HM sage: HM.point_in_model((1,2,1)) False """ - return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 +1 < EPSILON) + return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 + 1 < EPSILON) @classmethod - def bdry_point_in_model(cls,p): #HM + def bdry_point_in_model(cls, p): #HM r""" - Return False since the Hyperboloid model has no boundary points. + Return ``False`` since the Hyperboloid model has no boundary points. EXAMPLES:: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 3f8230c62b8..7570278e553 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -193,7 +193,7 @@ def _latex_(self): 0 sage: q = HM.point((0,0,1)) sage: latex(q) - \left(0, 0, 1\right) + \left(0,\,0,\,1\right) """ return latex(self.coordinates()) From fe22a753e12f7779c4ab70dd733a917915fa84f4 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 26 Jun 2014 00:23:11 -0400 Subject: [PATCH 012/129] Some tweaks from the merge. --- .../hyperbolic_space/hyperbolic_methods.py | 68 +++++++++---------- .../hyperbolic_space/hyperbolic_model.py | 2 +- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index b895078be0e..7d6a82cb215 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -12,8 +12,9 @@ geodesics, or matrices. Similarly, they output coordinates or matrices rather than Hyperbolic objects. -The methods are factored out of the HyperbolicPoint, HyperbolicGeodesic, -and HyperbolicIsometry classes to allow the implementation of additional +The methods are factored out of the :class:`HyperbolicPoint`, +:class:`HyperbolicGeodesic`, and :class:`HyperbolicIsometry` classes +to allow the implementation of additional models of hyperbolic space with minimal work. For example, to implement a model of 2-dimensional hyperbolic space, as long as one provides an isometry of that model with the upper half plane, one can use the upper @@ -26,10 +27,10 @@ In practice, all of the current models of 2 dimensional hyperbolic space use the upper half plane model for their computations. This can lead to some problems, such as long coordinate strings for symbolic points. For -example, the vector (1, 0, sqrt(2)) defines a point in the hyperboloid +example, the vector ``(1, 0, sqrt(2))`` defines a point in the hyperboloid model. Performing mapping this point to the upper half plane and performing computations there may return with vector whose components -are unsimplified strings have several sqrt(2)'s. Presently, this +are unsimplified strings have several ``sqrt(2)``'s. Presently, this drawback is outweighed by the rapidity with which new models can be implemented. @@ -67,7 +68,7 @@ from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModel from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP -from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform +from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform class HyperbolicAbstractMethods(UniqueRepresentation): @@ -364,7 +365,7 @@ def perpendicular_bisector(cls, start, end): matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)])) S= cls._to_std_geod(end_1, start, end_2) H = S.inverse()*T*S - return [_mobius_transform(H ,k) for k in [end_1, end_2]] + return [mobius_transform(H ,k) for k in [end_1, end_2]] @classmethod def midpoint(cls, start, end): @@ -400,7 +401,7 @@ def midpoint(cls, start, end): end_p = start else: end_p = end - end_p = _mobius_transform (M, end_p) + end_p = mobius_transform (M, end_p) return end_p @classmethod @@ -446,7 +447,7 @@ def geod_dist_from_point(cls, start, end, p): # Map the endpoints to 0 and infinity and another endpoint # to 1 T = cls._crossratio_matrix(bd_1, bd_1 + 1, bd_2) - x = _mobius_transform(T, p) + x = mobius_transform(T, p) return cls.point_dist(x, abs(x)*I) @classmethod @@ -473,8 +474,8 @@ def uncomplete(cls, start, end): radius = abs(real(end) - center) p = center + radius*I A = cls._to_std_geod(start, p, end).inverse() - p1 = _mobius_transform(A, I/Integer(3)) - p2 = _mobius_transform(A, 3*I) + p1 = mobius_transform(A, I/Integer(3)) + p2 = mobius_transform(A, 3*I) return [p1, p2] @classmethod @@ -516,7 +517,7 @@ def angle(cls, start_1, end_1, start_2, end_2): # with endpoints [0,oo] T = cls._crossratio_matrix(p_1, p_1 +1, p_2) # b_1 and b_2 are the endpoints of the image of other - b_1, b_2 = [_mobius_transform(T, k) for k in [q_1, q_2]] + b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] # If other is now a straight line... if (b_1 == infinity or b_2 == infinity): # then since they intersect, they are equal @@ -620,8 +621,8 @@ def translation_length(cls, M): sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) sage: p = exp(i*7*pi/8) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform - sage: Hp = _mobius_transform(H, p) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: Hp = mobius_transform(H, p) sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) True """ @@ -646,15 +647,14 @@ def isometry_from_fixed_points(cls, repel, attract): sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2 + I, 3 + I) Traceback (most recent call last): ... - ValueError: Fixed points of hyperbolic elements must be ideal. + ValueError: fixed points of hyperbolic elements must be ideal sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2, 0) [ -1 0] [-1/3 -1/3] """ if imag(repel) + imag(attract) > EPSILON: - raise ValueError("Fixed points of hyperbolic elements must be" - " ideal.") + raise ValueError("fixed points of hyperbolic elements must be ideal") repel = real(repel) attract = real(attract) if repel == infinity: @@ -684,24 +684,24 @@ def fixed_point_set(cls, M): sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform - sage: bool(_mobius_transform(H, p1) == p1) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(mobius_transform(H, p1) == p1) True - sage: bool(_mobius_transform(H, p2) == p2) + sage: bool(mobius_transform(H, p2) == p2) True sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) Traceback (most recent call last): ... - ValueError: The identity transformation fixes the entire hyperbolic plane. + ValueError: the identity transformation fixes the entire hyperbolic plane """ d = sqrt(M.det()**2) M = M/sqrt(d) tau = M.trace()**2 M_cls = cls.classification(M) if M_cls == 'identity': - raise ValueError("The identity transformation fixes the entire " - "hyperbolic plane.") + raise ValueError("the identity transformation fixes the entire " + "hyperbolic plane") if M_cls == 'parabolic': if abs(M[1,0]) < EPSILON: return [infinity] @@ -845,14 +845,14 @@ def _to_std_geod(cls, start, p, end): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] sage: A = HyperbolicMethodsUHP._to_std_geod(p_1, p_2, p_3) - sage: bool(abs(_mobius_transform(A, p_1)) < 10**-9) + sage: bool(abs(mobius_transform(A, p_1)) < 10**-9) True - sage: bool(abs(_mobius_transform(A, p_2) - I) < 10**-9) + sage: bool(abs(mobius_transform(A, p_2) - I) < 10**-9) True - sage: bool(_mobius_transform(A, p_3) == infinity) + sage: bool(mobius_transform(A, p_3) == infinity) True """ B = matrix(2, [1, 0, 0, -I]) @@ -877,14 +877,14 @@ def _crossratio_matrix(cls, p_0, p_1, p_2): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] sage: A = HyperbolicMethodsUHP._crossratio_matrix(p_1, p_2, p_3) - sage: bool(abs(_mobius_transform(A, p_1) < 10**-9)) + sage: bool(abs(mobius_transform(A, p_1) < 10**-9)) True - sage: bool(abs(_mobius_transform(A, p_2) - 1) < 10**-9) + sage: bool(abs(mobius_transform(A, p_2) - 1) < 10**-9) True - sage: bool(_mobius_transform(A, p_3) == infinity) + sage: bool(mobius_transform(A, p_3) == infinity) True sage: (x,y,z) = var('x,y,z'); HyperbolicMethodsUHP._crossratio_matrix(x,y,z) [ y - z -x*(y - z)] @@ -911,12 +911,12 @@ def _mobius_sending(cls, list1, list2): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import _mobius_transform - sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) True - sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) True - sage: bool(abs(_mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) True """ if len(list1) != 3 or len(list2) != 3: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 69f0fc23b9f..b794a012a96 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -735,7 +735,7 @@ class HyperbolicModelPD(HyperbolicModel, UniqueRepresentation): r""" Poincaré Disk Model. """ - name = "Poincaré Disk Model" + name = "Poincare Disk Model" # u"Poincaré Disk Model" short_name = "PD" bounded = True conformal = True From 267b6b937226357d87669f7c5c8c699d99188b5a Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Wed, 16 Jul 2014 18:28:02 +0200 Subject: [PATCH 013/129] Update to SageManifolds v0.5 --- src/doc/en/reference/modules/index.rst | 2 +- .../conf.py | 0 .../index.rst | 2 +- src/doc/en/tensors_free_module/conf.py | 2 +- ...module.rst => finite_rank_free_module.rst} | 4 +- .../free_module_tensor_spec.rst | 4 +- src/doc/en/tensors_free_module/index.rst | 2 +- src/sage/tensor/modules/all.py | 2 +- src/sage/tensor/modules/comp.py | 543 ++++++------- ...e_module.py => finite_rank_free_module.py} | 440 +++++++---- .../tensor/modules/free_module_alt_form.py | 151 ++-- src/sage/tensor/modules/free_module_basis.py | 120 +-- src/sage/tensor/modules/free_module_tensor.py | 735 +++++++++--------- .../tensor/modules/free_module_tensor_spec.py | 280 ++----- src/sage/tensor/modules/tensor_free_module.py | 90 +-- 15 files changed, 1229 insertions(+), 1148 deletions(-) rename src/doc/en/reference/{tensors_free_module => tensor_free_modules}/conf.py (100%) rename src/doc/en/reference/{tensors_free_module => tensor_free_modules}/index.rst (89%) rename src/doc/en/tensors_free_module/{finite_free_module.rst => finite_rank_free_module.rst} (60%) rename src/sage/tensor/modules/{finite_free_module.py => finite_rank_free_module.py} (76%) diff --git a/src/doc/en/reference/modules/index.rst b/src/doc/en/reference/modules/index.rst index cda83d9224a..102d3768602 100644 --- a/src/doc/en/reference/modules/index.rst +++ b/src/doc/en/reference/modules/index.rst @@ -7,7 +7,7 @@ Modules sage/modules/module sage/modules/free_module sage/modules/free_module_element - sage/tensor/modules/finite_free_module + sage/tensor/modules/finite_rank_free_module sage/modules/complex_double_vector sage/modules/real_double_vector diff --git a/src/doc/en/reference/tensors_free_module/conf.py b/src/doc/en/reference/tensor_free_modules/conf.py similarity index 100% rename from src/doc/en/reference/tensors_free_module/conf.py rename to src/doc/en/reference/tensor_free_modules/conf.py diff --git a/src/doc/en/reference/tensors_free_module/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst similarity index 89% rename from src/doc/en/reference/tensors_free_module/index.rst rename to src/doc/en/reference/tensor_free_modules/index.rst index 80aa0b33c56..6e137827636 100644 --- a/src/doc/en/reference/tensors_free_module/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -4,7 +4,7 @@ Tensors on free modules of finite rank .. toctree:: :maxdepth: 2 - sage/tensor/modules/finite_free_module + sage/tensor/modules/finite_rank_free_module sage/tensor/modules/free_module_basis diff --git a/src/doc/en/tensors_free_module/conf.py b/src/doc/en/tensors_free_module/conf.py index 26cb7636882..cc2c42da15d 100644 --- a/src/doc/en/tensors_free_module/conf.py +++ b/src/doc/en/tensors_free_module/conf.py @@ -18,7 +18,7 @@ # General information about the project. project = u"Tensors on free modules" name = 'tensors_free_modules_ref' -release = "0.1" +release = "0.2" # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". diff --git a/src/doc/en/tensors_free_module/finite_free_module.rst b/src/doc/en/tensors_free_module/finite_rank_free_module.rst similarity index 60% rename from src/doc/en/tensors_free_module/finite_free_module.rst rename to src/doc/en/tensors_free_module/finite_rank_free_module.rst index 9c41e1ef8be..c327216f0fd 100644 --- a/src/doc/en/tensors_free_module/finite_free_module.rst +++ b/src/doc/en/tensors_free_module/finite_rank_free_module.rst @@ -1,6 +1,6 @@ .. nodoctest -.. _sage.tensor.modules.finite_free_module: +.. _sage.tensor.modules.finite_rank_free_module: Free modules of finite rank =========================== @@ -8,7 +8,7 @@ Free modules of finite rank .. This file has been autogenerated. -.. automodule:: sage.tensor.modules.finite_free_module +.. automodule:: sage.tensor.modules.finite_rank_free_module :members: :undoc-members: :show-inheritance: diff --git a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst index e9c7512966d..a11fd6cb08e 100644 --- a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst +++ b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst @@ -2,8 +2,8 @@ .. _sage.tensor.modules.free_module_tensor_spec: -Specific tensors on free modules -================================ +Tensors of type (1,1) on free modules +===================================== .. This file has been autogenerated. diff --git a/src/doc/en/tensors_free_module/index.rst b/src/doc/en/tensors_free_module/index.rst index a08660c8f71..77482e593be 100644 --- a/src/doc/en/tensors_free_module/index.rst +++ b/src/doc/en/tensors_free_module/index.rst @@ -19,7 +19,7 @@ __ http://creativecommons.org/licenses/by-sa/3.0/ .. toctree:: :maxdepth: 2 - finite_free_module + finite_rank_free_module free_module_basis diff --git a/src/sage/tensor/modules/all.py b/src/sage/tensor/modules/all.py index 177dce6e060..a6d8750ae90 100644 --- a/src/sage/tensor/modules/all.py +++ b/src/sage/tensor/modules/all.py @@ -1 +1 @@ -from finite_free_module import FiniteFreeModule +from finite_rank_free_module import FiniteRankFreeModule diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 3d3642b7f4c..57482896d60 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -479,24 +479,24 @@ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): # For efficiency, no test is performed regarding the type and range of # the arguments: - self.ring = ring - self.frame = frame - self.nid = nb_indices - self.dim = len(frame) - self.sindex = start_index - self.output_formatter = output_formatter + self._ring = ring + self._frame = frame + self._nid = nb_indices + self._dim = len(frame) + self._sindex = start_index + self._output_formatter = output_formatter self._comp = {} # the dictionary of components, with the indices as keys def _repr_(self): r""" String representation of the object. """ - description = str(self.nid) - if self.nid == 1: + description = str(self._nid) + if self._nid == 1: description += "-index" else: description += "-indices" - description += " components w.r.t. " + str(self.frame) + description += " components w.r.t. " + str(self._frame) return description def _new_instance(self): @@ -508,8 +508,8 @@ def _new_instance(self): :class:`Components`. """ - return Components(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter) + return Components(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter) def copy(self): r""" @@ -538,7 +538,7 @@ def copy(self): """ result = self._new_instance() - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): if hasattr(val, 'copy'): result._comp[ind] = val.copy() else: @@ -553,7 +553,7 @@ def _del_zeros(self): # The zeros are first searched; they are deleted in a second stage, to # avoid changing the dictionary while it is read zeros = [] - for ind, value in self._comp.items(): + for ind, value in self._comp.iteritems(): if value == 0: zeros.append(ind) for ind in zeros: @@ -577,13 +577,13 @@ def _check_indices(self, indices): ind = (indices,) else: ind = tuple(indices) - if len(ind) != self.nid: - raise TypeError("Wrong number of indices: " + str(self.nid) + + if len(ind) != self._nid: + raise TypeError("Wrong number of indices: " + str(self._nid) + " expected, while " + str(len(ind)) + " are provided.") - si = self.sindex - imax = self.dim - 1 + si - for k in range(self.nid): + si = self._sindex + imax = self._dim - 1 + si + for k in range(self._nid): i = ind[k] if i < si or i > imax: raise IndexError("Index out of range: " @@ -608,7 +608,7 @@ def __getitem__(self, args): `T_{ij...}` (for a 2-indices object, a matrix is returned). """ - no_format = self.output_formatter is None + no_format = self._output_formatter is None format_type = None # default value, possibly redefined below if isinstance(args, list): # case of [[...]] syntax no_format = True @@ -624,8 +624,9 @@ def __getitem__(self, args): indices = args elif isinstance(args[0], slice): indices = args[0] - format_type = args[1] - elif len(args) == self.nid: + if len(args) == 2: + format_type = args[1] + elif len(args) == self._nid: indices = args else: format_type = args[-1] @@ -638,16 +639,16 @@ def __getitem__(self, args): if no_format: return self._comp[ind] elif format_type is None: - return self.output_formatter(self._comp[ind]) + return self._output_formatter(self._comp[ind]) else: - return self.output_formatter(self._comp[ind], format_type) + return self._output_formatter(self._comp[ind], format_type) else: # if the value is not stored in self._comp, it is zero: if no_format: - return self.ring.zero_element() + return self._ring.zero_element() elif format_type is None: - return self.output_formatter(self.ring.zero_element()) + return self._output_formatter(self._ring.zero_element()) else: - return self.output_formatter(self.ring.zero_element(), + return self._output_formatter(self._ring.zero_element(), format_type) def _get_list(self, ind_slice, no_format=True, format_type=None): @@ -667,9 +668,9 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): """ from sage.matrix.constructor import matrix - si = self.sindex - nsi = si + self.dim - if self.nid == 1: + si = self._sindex + nsi = si + self._dim + if self._nid == 1: if ind_slice.start is None: start = si else: @@ -687,17 +688,17 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): return [self[i, format_type] for i in range(start, stop)] if ind_slice.start is not None or ind_slice.stop is not None: raise NotImplementedError("Function [start:stop] not " + - "implemented for components with " + str(self.nid) + + "implemented for components with " + str(self._nid) + " indices.") resu = [self._gen_list([i], no_format, format_type) for i in range(si, nsi)] - if self.nid == 2: + if self._nid == 2: try: - for i in range(self.dim): - for j in range(self.dim): + for i in range(self._dim): + for j in range(self._dim): a = resu[i][j] - if hasattr(a, 'express'): - resu[i][j] = a.express + if hasattr(a, '_express'): + resu[i][j] = a._express resu = matrix(resu) # for a nicer output except TypeError: pass @@ -707,15 +708,15 @@ def _gen_list(self, ind, no_format=True, format_type=None): r""" Recursive function to generate the list of values """ - if len(ind) == self.nid: + if len(ind) == self._nid: if no_format: return self[ind] else: args = tuple(ind + [format_type]) return self.__getitem__(args) else: - si = self.sindex - nsi = si + self.dim + si = self._sindex + nsi = si + self._dim return [self._gen_list(ind + [i], no_format, format_type) for i in range(si, nsi)] @@ -746,8 +747,9 @@ def __setitem__(self, args, value): indices = args elif isinstance(args[0], slice): indices = args[0] - format_type = args[1] - elif len(args) == self.nid: + if len(args) == 2: + format_type = args[1] + elif len(args) == self._nid: indices = args else: format_type = args[-1] @@ -763,9 +765,14 @@ def __setitem__(self, args, value): del self._comp[ind] else: if format_type is None: - self._comp[ind] = self.ring(value) + self._comp[ind] = self._ring(value) else: - self._comp[ind] = self.ring(value, format_type) + self._comp[ind] = self._ring({format_type: value}) + # NB: the writing + # self._comp[ind] = self._ring(value, format_type) + # is not allowed when ring is an algebra and value some + # element of the algebra's base ring, cf. the discussion at + # http://trac.sagemath.org/ticket/16054 def _set_list(self, ind_slice, format_type, values): r""" @@ -780,9 +787,9 @@ def _set_list(self, ind_slice, format_type, values): a slice of the full list, in the form ``[a:b]`` """ - si = self.sindex - nsi = si + self.dim - if self.nid == 1: + si = self._sindex + nsi = si + self._dim + if self._nid == 1: if ind_slice.start is None: start = si else: @@ -799,7 +806,7 @@ def _set_list(self, ind_slice, format_type, values): else: if ind_slice.start is not None or ind_slice.stop is not None: raise NotImplementedError("Function [start:stop] not " + - "implemented for components with " + str(self.nid) + + "implemented for components with " + str(self._nid) + " indices.") for i in range(si, nsi): self._set_value_list([i], format_type, values[i-si]) @@ -808,13 +815,13 @@ def _set_value_list(self, ind, format_type, val): r""" Recursive function to set a list of values to self """ - if len(ind) == self.nid: + if len(ind) == self._nid: if format_type is not None: ind = tuple(ind + [format_type]) self[ind] = val else: - si = self.sindex - nsi = si + self.dim + si = self._sindex + nsi = si + self._dim for i in range(si, nsi): self._set_value_list(ind + [i], format_type, val[i-si]) @@ -873,7 +880,7 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): """ result = self._new_instance() - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] result._comp[new_ind] = val # the above writing is more efficient than result[new_ind] = val @@ -928,7 +935,7 @@ def is_zero(self): # any zero value # In other words, the full method should be # return self.comp == {} - for val in self._comp.values(): + for val in self._comp.itervalues(): if val != 0: return False return True @@ -955,13 +962,13 @@ def __eq__(self, other): else: # other is another Components if not isinstance(other, Components): raise TypeError("An instance of Components is expected.") - if other.frame != self.frame: + if other._frame != self._frame: return False - if other.nid != self.nid: + if other._nid != self._nid: return False - if other.sindex != self.sindex: + if other._sindex != self._sindex: return False - if other.output_formatter != self.output_formatter: + if other._output_formatter != self._output_formatter: return False return (self - other).is_zero() @@ -1001,7 +1008,7 @@ def __neg__(self): """ result = self._new_instance() - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): result._comp[ind] = - val return result @@ -1026,17 +1033,17 @@ def __add__(self, other): "an instance of Components.") if isinstance(other, CompWithSym): return other + self # to deal properly with symmetries - if other.frame != self.frame: + if other._frame != self._frame: raise TypeError("The two sets of components are not defined on " + "the same frame.") - if other.nid != self.nid: + if other._nid != self._nid: raise TypeError("The two sets of components do not have the " + "same number of indices.") - if other.sindex != self.sindex: + if other._sindex != self._sindex: raise TypeError("The two sets of components do not have the " + "same starting index.") result = self.copy() - for ind, val in other._comp.items(): + for ind, val in other._comp.iteritems(): result[[ind]] += val return result @@ -1090,39 +1097,39 @@ def __mul__(self, other): if not isinstance(other, Components): raise TypeError("The second argument for the tensor product " + "must be an instance of Components.") - if other.frame != self.frame: + if other._frame != self._frame: raise TypeError("The two sets of components are not defined on " + "the same frame.") - if other.sindex != self.sindex: + if other._sindex != self._sindex: raise TypeError("The two sets of components do not have the " + "same starting index.") if isinstance(other, CompWithSym): sym = [] - if other.sym != []: - for s in other.sym: - ns = tuple(s[i]+self.nid for i in range(len(s))) + if other._sym != []: + for s in other._sym: + ns = tuple(s[i]+self._nid for i in range(len(s))) sym.append(ns) antisym = [] - if other.antisym != []: - for s in other.antisym: - ns = tuple(s[i]+self.nid for i in range(len(s))) + if other._antisym != []: + for s in other._antisym: + ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) - result = CompWithSym(self.ring, self.frame, self.nid + other.nid, - self.sindex, self.output_formatter, sym, + result = CompWithSym(self._ring, self._frame, self._nid + other._nid, + self._sindex, self._output_formatter, sym, antisym) - elif self.nid == 1 and other.nid == 1: + elif self._nid == 1 and other._nid == 1: if self is other: # == would be dangerous here # the result is symmetric: - result = CompFullySym(self.ring, self.frame, 2, self.sindex, - self.output_formatter) + result = CompFullySym(self._ring, self._frame, 2, self._sindex, + self._output_formatter) else: - result = Components(self.ring, self.frame, 2, self.sindex, - self.output_formatter) + result = Components(self._ring, self._frame, 2, self._sindex, + self._output_formatter) else: - result = Components(self.ring, self.frame, self.nid + other.nid, - self.sindex, self.output_formatter) - for ind_s, val_s in self._comp.items(): - for ind_o, val_o in other._comp.items(): + result = Components(self._ring, self._frame, self._nid + other._nid, + self._sindex, self._output_formatter) + for ind_s, val_s in self._comp.iteritems(): + for ind_o, val_o in other._comp.iteritems(): result._comp[ind_s + ind_o] = val_s * val_o return result @@ -1138,7 +1145,7 @@ def __rmul__(self, other): result = self._new_instance() if other == 0: return result # because a just created Components is zero - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): result._comp[ind] = other * val return result @@ -1152,7 +1159,7 @@ def __div__(self, other): raise NotImplementedError("Division by an object of type " + "Components not implemented.") result = self._new_instance() - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): result._comp[ind] = val / other return result @@ -1213,30 +1220,30 @@ def self_contract(self, pos1, pos2): [12, 24, 36] """ - if self.nid < 2: + if self._nid < 2: raise TypeError("Contraction can be perfomed only on " + "components with at least 2 indices.") - if pos1 < 0 or pos1 > self.nid - 1: + if pos1 < 0 or pos1 > self._nid - 1: raise IndexError("pos1 out of range.") - if pos2 < 0 or pos2 > self.nid - 1: + if pos2 < 0 or pos2 > self._nid - 1: raise IndexError("pos2 out of range.") if pos1 == pos2: raise IndexError("The two positions must differ for the " + "contraction to be meaningful.") - si = self.sindex - nsi = si + self.dim - if self.nid == 2: + si = self._sindex + nsi = si + self._dim + if self._nid == 2: res = 0 for i in range(si, nsi): res += self[[i,i]] return res else: # More than 2 indices - result = Components(self.ring, self.frame, self.nid - 2, - self.sindex, self.output_formatter) + result = Components(self._ring, self._frame, self._nid - 2, + self._sindex, self._output_formatter) if pos1 > pos2: pos1, pos2 = (pos2, pos1) - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): if ind[pos1] == ind[pos2]: # there is a contribution to the contraction ind_res = ind[:pos1] + ind[pos1+1:pos2] + ind[pos2+1:] @@ -1307,12 +1314,12 @@ def contract(self, pos1, other, pos2): if not isinstance(other, Components): raise TypeError("For the contraction, other must be an instance " + "of Components.") - if pos1 < 0 or pos1 > self.nid - 1: + if pos1 < 0 or pos1 > self._nid - 1: raise IndexError("pos1 out of range.") - if pos2 < 0 or pos2 > other.nid - 1: + if pos2 < 0 or pos2 > other._nid - 1: raise IndexError("pos2 out of range.") return (self*other).self_contract(pos1, - pos2+self.nid) + pos2+self._nid) #!# the above is correct (in particular the symmetries are delt by # self_contract()), but it is not optimal (unnecessary terms are # evaluated when performing the tensor product self*other) @@ -1342,15 +1349,15 @@ def index_generator(self): (0, 0) (0, 1) (0, 2) (1, 0) (1, 1) (1, 2) (2, 0) (2, 1) (2, 2) """ - si = self.sindex - imax = self.dim - 1 + si - ind = [si for k in range(self.nid)] - ind_end = [si for k in range(self.nid)] + si = self._sindex + imax = self._dim - 1 + si + ind = [si for k in range(self._nid)] + ind_end = [si for k in range(self._nid)] ind_end[0] = imax+1 while ind != ind_end: yield tuple(ind) ret = 1 - for pos in range(self.nid-1,-1,-1): + for pos in range(self._nid-1,-1,-1): if ind[pos] != imax: ind[pos] += ret ret = 0 @@ -1523,20 +1530,20 @@ def symmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup if pos is None: - pos = range(self.nid) + pos = range(self._nid) else: if len(pos) < 2: raise TypeError("At least two index positions must be given.") - if len(pos) > self.nid: + if len(pos) > self._nid: raise TypeError("Number of index positions larger than the " \ "total number of indices.") n_sym = len(pos) # number of indices involved in the symmetry - if n_sym == self.nid: - result = CompFullySym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter) + if n_sym == self._nid: + result = CompFullySym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter) else: - result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter, sym=pos) + result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter, sym=pos) sym_group = SymmetricGroup(n_sym) for ind in result.non_redundant_index_generator(): sum = 0 @@ -1691,20 +1698,20 @@ def antisymmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup if pos is None: - pos = range(self.nid) + pos = range(self._nid) else: if len(pos) < 2: raise TypeError("At least two index positions must be given.") - if len(pos) > self.nid: + if len(pos) > self._nid: raise TypeError("Number of index positions larger than the " \ "total number of indices.") n_sym = len(pos) # number of indices involved in the antisymmetry - if n_sym == self.nid: - result = CompFullyAntiSym(self.ring, self.frame, self.nid, - self.sindex, self.output_formatter) + if n_sym == self._nid: + result = CompFullyAntiSym(self._ring, self._frame, self._nid, + self._sindex, self._output_formatter) else: - result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter, antisym=pos) + result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter, antisym=pos) sym_group = SymmetricGroup(n_sym) for ind in result.non_redundant_index_generator(): sum = 0 @@ -1925,7 +1932,7 @@ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None, sym=None, antisym=None): Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) - self.sym = [] + self._sym = [] if sym is not None and sym != []: if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple -> 1-item list: @@ -1935,11 +1942,11 @@ def __init__(self, ring, frame, nb_indices, start_index=0, raise IndexError("At least two index positions must be " + "provided to define a symmetry.") for i in isym: - if i<0 or i>self.nid-1: + if i<0 or i>self._nid-1: raise IndexError("Invalid index position: " + str(i) + - " not in [0," + str(self.nid-1) + "]") - self.sym.append(tuple(isym)) - self.antisym = [] + " not in [0," + str(self._nid-1) + "]") + self._sym.append(tuple(isym)) + self._antisym = [] if antisym is not None and antisym != []: if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple -> 1-item list: @@ -1949,15 +1956,15 @@ def __init__(self, ring, frame, nb_indices, start_index=0, raise IndexError("At least two index positions must be " + "provided to define an antisymmetry.") for i in isym: - if i<0 or i>self.nid-1: + if i<0 or i>self._nid-1: raise IndexError("Invalid index position: " + str(i) + - " not in [0," + str(self.nid-1) + "]") - self.antisym.append(tuple(isym)) + " not in [0," + str(self._nid-1) + "]") + self._antisym.append(tuple(isym)) # Final consistency check: index_list = [] - for isym in self.sym: + for isym in self._sym: index_list += isym - for isym in self.antisym: + for isym in self._antisym: index_list += isym if len(index_list) != len(set(index_list)): # There is a repeated index position: @@ -1968,16 +1975,16 @@ def _repr_(self): r""" String representation of the object. """ - description = str(self.nid) - if self.nid == 1: + description = str(self._nid) + if self._nid == 1: description += "-index" else: description += "-indices" - description += " components w.r.t. " + str(self.frame) - for isym in self.sym: + description += " components w.r.t. " + str(self._frame) + for isym in self._sym: description += ", with symmetry on the index positions " + \ str(tuple(isym)) - for isym in self.antisym: + for isym in self._antisym: description += ", with antisymmetry on the index positions " + \ str(tuple(isym)) return description @@ -1988,8 +1995,8 @@ def _new_instance(self): and with the same number of indices and the same symmetries """ - return CompWithSym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter, self.sym, self.antisym) + return CompWithSym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter, self._sym, self._antisym) def _ordered_indices(self, indices): r""" @@ -2017,7 +2024,7 @@ def _ordered_indices(self, indices): """ from sage.combinat.permutation import Permutation ind = list(self._check_indices(indices)) - for isym in self.sym: + for isym in self._sym: indsym = [] for pos in isym: indsym.append(ind[pos]) @@ -2025,7 +2032,7 @@ def _ordered_indices(self, indices): for k, pos in enumerate(isym): ind[pos] = indsym_ordered[k] sign = 1 - for isym in self.antisym: + for isym in self._antisym: indsym = [] for pos in isym: indsym.append(ind[pos]) @@ -2066,7 +2073,7 @@ def __getitem__(self, args): `T_{ij...}` (for a 2-indices object, a matrix is returned). """ - no_format = self.output_formatter is None + no_format = self._output_formatter is None format_type = None # default value, possibly redefined below if isinstance(args, list): # case of [[...]] syntax no_format = True @@ -2082,8 +2089,9 @@ def __getitem__(self, args): indices = args elif isinstance(args[0], slice): indices = args[0] - format_type = args[1] - elif len(args) == self.nid: + if len(args) == 2: + format_type = args[1] + elif len(args) == self._nid: indices = args else: format_type = args[-1] @@ -2094,11 +2102,11 @@ def __getitem__(self, args): sign, ind = self._ordered_indices(indices) if (sign == 0) or (ind not in self._comp): # the value is zero: if no_format: - return self.ring.zero_element() + return self._ring.zero_element() elif format_type is None: - return self.output_formatter(self.ring.zero_element()) + return self._output_formatter(self._ring.zero_element()) else: - return self.output_formatter(self.ring.zero_element(), + return self._output_formatter(self._ring.zero_element(), format_type) else: # non zero value if no_format: @@ -2108,15 +2116,15 @@ def __getitem__(self, args): return -self._comp[ind] elif format_type is None: if sign == 1: - return self.output_formatter(self._comp[ind]) + return self._output_formatter(self._comp[ind]) else: # sign = -1 - return self.output_formatter(-self._comp[ind]) + return self._output_formatter(-self._comp[ind]) else: if sign == 1: - return self.output_formatter( + return self._output_formatter( self._comp[ind], format_type) else: # sign = -1 - return self.output_formatter( + return self._output_formatter( -self._comp[ind], format_type) def __setitem__(self, args, value): @@ -2146,8 +2154,9 @@ def __setitem__(self, args, value): indices = args elif isinstance(args[0], slice): indices = args[0] - format_type = args[1] - elif len(args) == self.nid: + if len(args) == 2: + format_type = args[1] + elif len(args) == self._nid: indices = args else: format_type = args[-1] @@ -2169,14 +2178,14 @@ def __setitem__(self, args, value): else: if format_type is None: if sign == 1: - self._comp[ind] = self.ring(value) + self._comp[ind] = self._ring(value) else: # sign = -1 - self._comp[ind] = -self.ring(value) + self._comp[ind] = -self._ring(value) else: if sign == 1: - self._comp[ind] = self.ring(value, format_type) + self._comp[ind] = self._ring({format_type: value}) else: # sign = -1 - self._comp[ind] = -self.ring(value, format_type) + self._comp[ind] = -self._ring({format_type: value}) def swap_adjacent_indices(self, pos1, pos2, pos3): r""" @@ -2217,9 +2226,9 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): [[0, 4, 5], [-4, 0, 6], [-5, -6, 0]], [[0, 7, 8], [-7, 0, 9], [-8, -9, 0]]] sage: c1 = c.swap_adjacent_indices(0,1,3) - sage: c.antisym # c is antisymmetric with respect to the last pair of indices... + sage: c._antisym # c is antisymmetric with respect to the last pair of indices... [(1, 2)] - sage: c1.antisym #...while c1 is antisymmetric with respect to the first pair of indices + sage: c1._antisym #...while c1 is antisymmetric with respect to the first pair of indices [(0, 1)] sage: c[0,1,2] 3 @@ -2232,18 +2241,18 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): """ result = self._new_instance() # The symmetries: - lpos = range(self.nid) + lpos = range(self._nid) new_lpos = lpos[:pos1] + lpos[pos2:pos3] + lpos[pos1:pos2] + lpos[pos3:] - result.sym = [] - for s in self.sym: + result._sym = [] + for s in self._sym: new_s = [new_lpos.index(pos) for pos in s] - result.sym.append(tuple(sorted(new_s))) - result.antisym = [] - for s in self.antisym: + result._sym.append(tuple(sorted(new_s))) + result._antisym = [] + for s in self._antisym: new_s = [new_lpos.index(pos) for pos in s] - result.antisym.append(tuple(sorted(new_s))) + result._antisym.append(tuple(sorted(new_s))) # The values: - for ind, val in self._comp.items(): + for ind, val in self._comp.iteritems(): new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] result[new_ind] = val return result @@ -2267,53 +2276,53 @@ def __add__(self, other): if not isinstance(other, Components): raise TypeError("The second argument for the addition must be a " + "an instance of Components.") - if other.frame != self.frame: + if other._frame != self._frame: raise TypeError("The two sets of components are not defined on " + "the same frame.") - if other.nid != self.nid: + if other._nid != self._nid: raise TypeError("The two sets of components do not have the " + "same number of indices.") - if other.sindex != self.sindex: + if other._sindex != self._sindex: raise TypeError("The two sets of components do not have the " + "same starting index.") if isinstance(other, CompWithSym): # Are the symmetries of the same type ? - diff_sym = set(self.sym).symmetric_difference(set(other.sym)) + diff_sym = set(self._sym).symmetric_difference(set(other._sym)) diff_antisym = \ - set(self.antisym).symmetric_difference(set(other.antisym)) + set(self._antisym).symmetric_difference(set(other._antisym)) if diff_sym == set() and diff_antisym == set(): # The symmetries/antisymmetries are identical: result = self.copy() - for ind, val in other._comp.items(): + for ind, val in other._comp.iteritems(): result[[ind]] += val return result else: # The symmetries/antisymmetries are different: only the # common ones are kept common_sym = [] - for isym in self.sym: - for osym in other.sym: + for isym in self._sym: + for osym in other._sym: com = tuple(set(isym).intersection(set(osym))) if len(com) > 1: common_sym.append(com) common_antisym = [] - for isym in self.antisym: - for osym in other.antisym: + for isym in self._antisym: + for osym in other._antisym: com = tuple(set(isym).intersection(set(osym))) if len(com) > 1: common_antisym.append(com) if common_sym != [] or common_antisym != []: - result = CompWithSym(self.ring, self.frame, self.nid, - self.sindex, self.output_formatter, + result = CompWithSym(self._ring, self._frame, self._nid, + self._sindex, self._output_formatter, common_sym, common_antisym) else: # no common symmetry -> the result is a generic Components: - result = Components(self.ring, self.frame, self.nid, - self.sindex, self.output_formatter) + result = Components(self._ring, self._frame, self._nid, + self._sindex, self._output_formatter) else: # other has no symmetry at all: - result = Components(self.ring, self.frame, self.nid, - self.sindex, self.output_formatter) + result = Components(self._ring, self._frame, self._nid, + self._sindex, self._output_formatter) for ind in result.non_redundant_index_generator(): result[[ind]] = self[[ind]] + other[[ind]] return result @@ -2335,27 +2344,27 @@ def __mul__(self, other): if not isinstance(other, Components): raise TypeError("The second argument for the tensor product " + "be an instance of Components.") - if other.frame != self.frame: + if other._frame != self._frame: raise TypeError("The two sets of components are not defined on " + "the same frame.") - if other.sindex != self.sindex: + if other._sindex != self._sindex: raise TypeError("The two sets of components do not have the " + "same starting index.") - sym = list(self.sym) - antisym = list(self.antisym) + sym = list(self._sym) + antisym = list(self._antisym) if isinstance(other, CompWithSym): - if other.sym != []: - for s in other.sym: - ns = tuple(s[i]+self.nid for i in range(len(s))) + if other._sym != []: + for s in other._sym: + ns = tuple(s[i]+self._nid for i in range(len(s))) sym.append(ns) - if other.antisym != []: - for s in other.antisym: - ns = tuple(s[i]+self.nid for i in range(len(s))) + if other._antisym != []: + for s in other._antisym: + ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) - result = CompWithSym(self.ring, self.frame, self.nid + other.nid, - self.sindex, self.output_formatter, sym, antisym) - for ind_s, val_s in self._comp.items(): - for ind_o, val_o in other._comp.items(): + result = CompWithSym(self._ring, self._frame, self._nid + other._nid, + self._sindex, self._output_formatter, sym, antisym) + for ind_s, val_s in self._comp.iteritems(): + for ind_o, val_o in other._comp.iteritems(): result._comp[ind_s + ind_o] = val_s * val_o return result @@ -2472,19 +2481,19 @@ def self_contract(self, pos1, pos2): [[0, 0, 0], [-2, 1, 0], [-3, 3, -1]] """ - if self.nid < 2: + if self._nid < 2: raise TypeError("Contraction can be perfomed only on " + "components with at least 2 indices.") - if pos1 < 0 or pos1 > self.nid - 1: + if pos1 < 0 or pos1 > self._nid - 1: raise IndexError("pos1 out of range.") - if pos2 < 0 or pos2 > self.nid - 1: + if pos2 < 0 or pos2 > self._nid - 1: raise IndexError("pos2 out of range.") if pos1 == pos2: raise IndexError("The two positions must differ for the " + "contraction to take place.") - si = self.sindex - nsi = si + self.dim - if self.nid == 2: + si = self._sindex + nsi = si + self._dim + if self._nid == 2: res = 0 for i in range(si, nsi): res += self[[i,i]] @@ -2494,8 +2503,8 @@ def self_contract(self, pos1, pos2): if pos1 > pos2: pos1, pos2 = (pos2, pos1) # Determination of the remaining symmetries: - sym_res = list(self.sym) - for isym in self.sym: + sym_res = list(self._sym) + for isym in self._sym: isym_res = list(isym) if pos1 in isym: isym_res.remove(pos1) @@ -2505,8 +2514,8 @@ def self_contract(self, pos1, pos2): sym_res.remove(isym) else: sym_res[sym_res.index(isym)] = tuple(isym_res) - antisym_res = list(self.antisym) - for isym in self.antisym: + antisym_res = list(self._antisym) + for isym in self._antisym: isym_res = list(isym) if pos1 in isym: isym_res.remove(pos1) @@ -2544,19 +2553,19 @@ def self_contract(self, pos1, pos2): antisym_res[k] = tuple(isym_res) # Construction of the appropriate object in view of the # remaining symmetries: - nid_res = self.nid - 2 + nid_res = self._nid - 2 if max_sym == 0 and max_antisym == 0: - result = Components(self.ring, self.frame, nid_res, self.sindex, - self.output_formatter) + result = Components(self._ring, self._frame, nid_res, self._sindex, + self._output_formatter) elif max_sym == nid_res: - result = CompFullySym(self.ring, self.frame, nid_res, - self.sindex, self.output_formatter) + result = CompFullySym(self._ring, self._frame, nid_res, + self._sindex, self._output_formatter) elif max_antisym == nid_res: - result = CompFullyAntiSym(self.ring, self.frame, nid_res, - self.sindex, self.output_formatter) + result = CompFullyAntiSym(self._ring, self._frame, nid_res, + self._sindex, self._output_formatter) else: - result = CompWithSym(self.ring, self.frame, nid_res, - self.sindex, self.output_formatter, + result = CompWithSym(self._ring, self._frame, nid_res, + self._sindex, self._output_formatter, sym=sym_res, antisym=antisym_res) # The contraction itself: for ind_res in result.non_redundant_index_generator(): @@ -2642,19 +2651,19 @@ def non_redundant_index_generator(self): sage: for ind in c.non_redundant_index_generator(): print ind, # nothing since c is identically zero in this case (for 5 > 4) """ - si = self.sindex - imax = self.dim - 1 + si - ind = [si for k in range(self.nid)] - ind_end = [si for k in range(self.nid)] + si = self._sindex + imax = self._dim - 1 + si + ind = [si for k in range(self._nid)] + ind_end = [si for k in range(self._nid)] ind_end[0] = imax+1 while ind != ind_end: ordered = True - for isym in self.sym: + for isym in self._sym: for k in range(len(isym)-1): if ind[isym[k+1]] < ind[isym[k]]: ordered = False break - for isym in self.antisym: + for isym in self._antisym: for k in range(len(isym)-1): if ind[isym[k+1]] <= ind[isym[k]]: ordered = False @@ -2662,7 +2671,7 @@ def non_redundant_index_generator(self): if ordered: yield tuple(ind) ret = 1 - for pos in range(self.nid-1,-1,-1): + for pos in range(self._nid-1,-1,-1): if ind[pos] != imax: ind[pos] += ret ret = 0 @@ -2791,7 +2800,7 @@ def symmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) - sage: a1.sym # a1 has two distinct symmetries: + sage: a1._sym # a1 has two distinct symmetries: [(0, 1), (2, 3)] sage: a[0,1,2,0] == a[0,0,2,1] # a is symmetric w.r.t. positions 1 and 3 True @@ -2880,24 +2889,24 @@ def symmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup if pos is None: - pos = range(self.nid) + pos = range(self._nid) else: if len(pos) < 2: raise TypeError("At least two index positions must be given.") - if len(pos) > self.nid: + if len(pos) > self._nid: raise TypeError("Number of index positions larger than the " \ "total number of indices.") pos = tuple(pos) pos_set = set(pos) # If the symmetry is already present, there is nothing to do: - for isym in self.sym: + for isym in self._sym: if pos_set.issubset(set(isym)): return self.copy() # # Interference of the new symmetry with existing ones: # sym_res = [pos] # starting the list of symmetries of the result - for isym in self.sym: + for isym in self._sym: inter = pos_set.intersection(set(isym)) # if len(inter) == len(isym), isym is included in the new symmetry # and therefore has not to be included in sym_res @@ -2920,7 +2929,7 @@ def symmetrize(self, pos=None): # antisym_res = [] # starting the list of antisymmetries of the result zero_result = False - for iasym in self.antisym: + for iasym in self._antisym: inter = pos_set.intersection(set(iasym)) if len(inter) > 1: # If at least two of the symmetry indices are already involved @@ -2947,12 +2956,12 @@ def symmetrize(self, pos=None): max_sym = 0 for isym in sym_res: max_sym = max(max_sym, len(isym)) - if max_sym == self.nid: - result = CompFullySym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter) + if max_sym == self._nid: + result = CompFullySym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter) else: - result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter, sym=sym_res, + result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter, sym=sym_res, antisym=antisym_res) if zero_result: return result # since a just created instance is zero @@ -3068,7 +3077,7 @@ def antisymmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (1, 3), with antisymmetry on the index positions (0, 2) - sage: s.antisym # the antisymmetry (0,1,2) has been reduced to (0,2), since 1 is involved in the new antisymmetry (1,3): + sage: s._antisym # the antisymmetry (0,1,2) has been reduced to (0,2), since 1 is involved in the new antisymmetry (1,3): [(1, 3), (0, 2)] Partial antisymmetrization of 4-indices components with a symmetry on @@ -3140,24 +3149,24 @@ def antisymmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup if pos is None: - pos = range(self.nid) + pos = range(self._nid) else: if len(pos) < 2: raise TypeError("At least two index positions must be given.") - if len(pos) > self.nid: + if len(pos) > self._nid: raise TypeError("Number of index positions larger than the " \ "total number of indices.") pos = tuple(pos) pos_set = set(pos) # If the antisymmetry is already present, there is nothing to do: - for iasym in self.antisym: + for iasym in self._antisym: if pos_set.issubset(set(iasym)): return self.copy() # # Interference of the new antisymmetry with existing ones # antisym_res = [pos] # starting the list of symmetries of the result - for iasym in self.antisym: + for iasym in self._antisym: inter = pos_set.intersection(set(iasym)) # if len(inter) == len(iasym), iasym is included in the new # antisymmetry and therefore has not to be included in antisym_res @@ -3182,7 +3191,7 @@ def antisymmetrize(self, pos=None): # sym_res = [] # starting the list of symmetries of the result zero_result = False - for isym in self.sym: + for isym in self._sym: inter = pos_set.intersection(set(isym)) if len(inter) > 1: # If at least two of the antisymmetry indices are already @@ -3209,12 +3218,12 @@ def antisymmetrize(self, pos=None): max_sym = 0 for isym in antisym_res: max_sym = max(max_sym, len(isym)) - if max_sym == self.nid: - result = CompFullyAntiSym(self.ring, self.frame, self.nid, - self.sindex, self.output_formatter) + if max_sym == self._nid: + result = CompFullyAntiSym(self._ring, self._frame, self._nid, + self._sindex, self._output_formatter) else: - result = CompWithSym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter, sym=sym_res, + result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter, sym=sym_res, antisym=antisym_res) if zero_result: return result # since a just created instance is zero @@ -3378,8 +3387,8 @@ def _repr_(self): r""" String representation of the object. """ - return "fully symmetric " + str(self.nid) + "-indices" + \ - " components w.r.t. " + str(self.frame) + return "fully symmetric " + str(self._nid) + "-indices" + \ + " components w.r.t. " + str(self._frame) def _new_instance(self): r""" @@ -3387,8 +3396,8 @@ def _new_instance(self): and with the same number of indices. """ - return CompFullySym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter) + return CompFullySym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter) def __getitem__(self, args): r""" @@ -3407,7 +3416,7 @@ def __getitem__(self, args): `T_{ij...}` (for a 2-indices object, a matrix is returned). """ - no_format = self.output_formatter is None + no_format = self._output_formatter is None format_type = None # default value, possibly redefined below if isinstance(args, list): # case of [[...]] syntax no_format = True @@ -3423,8 +3432,9 @@ def __getitem__(self, args): indices = args elif isinstance(args[0], slice): indices = args[0] - format_type = args[1] - elif len(args) == self.nid: + if len(args) == 2: + format_type = args[1] + elif len(args) == self._nid: indices = args else: format_type = args[-1] @@ -3437,16 +3447,16 @@ def __getitem__(self, args): if no_format: return self._comp[ind] elif format_type is None: - return self.output_formatter(self._comp[ind]) + return self._output_formatter(self._comp[ind]) else: - return self.output_formatter(self._comp[ind], format_type) + return self._output_formatter(self._comp[ind], format_type) else: # the value is zero if no_format: - return self.ring.zero_element() + return self._ring.zero_element() elif format_type is None: - return self.output_formatter(self.ring.zero_element()) + return self._output_formatter(self._ring.zero_element()) else: - return self.output_formatter(self.ring.zero_element(), + return self._output_formatter(self._ring.zero_element(), format_type) def __setitem__(self, args, value): @@ -3476,8 +3486,9 @@ def __setitem__(self, args, value): indices = args elif isinstance(args[0], slice): indices = args[0] - format_type = args[1] - elif len(args) == self.nid: + if len(args) == 2: + format_type = args[1] + elif len(args) == self._nid: indices = args else: format_type = args[-1] @@ -3491,9 +3502,9 @@ def __setitem__(self, args, value): del self._comp[ind] # zero values are not stored else: if format_type is None: - self._comp[ind] = self.ring(value) + self._comp[ind] = self._ring(value) else: - self._comp[ind] = self.ring(value, format_type) + self._comp[ind] = self._ring({format_type: value}) def __add__(self, other): r""" @@ -3515,17 +3526,17 @@ def __add__(self, other): raise TypeError("The second argument for the addition must be a " + "an instance of Components.") if isinstance(other, CompFullySym): - if other.frame != self.frame: + if other._frame != self._frame: raise TypeError("The two sets of components are not defined " + "on the same frame.") - if other.nid != self.nid: + if other._nid != self._nid: raise TypeError("The two sets of components do not have the " + "same number of indices.") - if other.sindex != self.sindex: + if other._sindex != self._sindex: raise TypeError("The two sets of components do not have the " + "same starting index.") result = self.copy() - for ind, val in other._comp.items(): + for ind, val in other._comp.iteritems(): result[[ind]] += val return result else: @@ -3676,8 +3687,8 @@ def _repr_(self): r""" String representation of the object. """ - return "fully antisymmetric " + str(self.nid) + "-indices" + \ - " components w.r.t. " + str(self.frame) + return "fully antisymmetric " + str(self._nid) + "-indices" + \ + " components w.r.t. " + str(self._frame) def _new_instance(self): r""" @@ -3685,8 +3696,8 @@ def _new_instance(self): and with the same number of indices. """ - return CompFullyAntiSym(self.ring, self.frame, self.nid, self.sindex, - self.output_formatter) + return CompFullyAntiSym(self._ring, self._frame, self._nid, self._sindex, + self._output_formatter) def __add__(self, other): @@ -3709,17 +3720,17 @@ def __add__(self, other): raise TypeError("The second argument for the addition must be a " + "an instance of Components.") if isinstance(other, CompFullyAntiSym): - if other.frame != self.frame: + if other._frame != self._frame: raise TypeError("The two sets of components are not defined " + "on the same frame.") - if other.nid != self.nid: + if other._nid != self._nid: raise TypeError("The two sets of components do not have the " + "same number of indices.") - if other.sindex != self.sindex: + if other._sindex != self._sindex: raise TypeError("The two sets of components do not have the " + "same starting index.") result = self.copy() - for ind, val in other._comp.items(): + for ind, val in other._comp.iteritems(): result[[ind]] += val return result else: @@ -3790,14 +3801,14 @@ class KroneckerDelta(CompFullySym): def __init__(self, ring, frame, start_index=0, output_formatter=None): CompFullySym.__init__(self, ring, frame, 2, start_index, output_formatter) - for i in range(self.sindex, self.dim + self.sindex): - self._comp[(i,i)] = self.ring(1) + for i in range(self._sindex, self._dim + self._sindex): + self._comp[(i,i)] = self._ring(1) def _repr_(self): r""" String representation of the object. """ - n = str(self.dim) + n = str(self._dim) return "Kronecker delta of size " + n + "x" + n def __setitem__(self, args, value): diff --git a/src/sage/tensor/modules/finite_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py similarity index 76% rename from src/sage/tensor/modules/finite_free_module.py rename to src/sage/tensor/modules/finite_rank_free_module.py index 1a6ae244c53..42d02c461a8 100644 --- a/src/sage/tensor/modules/finite_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -1,7 +1,7 @@ r""" Free modules of finite rank -The class :class:`FiniteFreeModule` implements free modules of finite rank +The class :class:`FiniteRankFreeModule` implements free modules of finite rank over a commutative ring. A *free module of finite rank* over a commutative ring `R` is a module `M` over @@ -17,24 +17,23 @@ .. NOTE:: - The class :class:`FiniteFreeModule` does not inherit from + The class :class:`FiniteRankFreeModule` does not inherit from :class:`~sage.modules.free_module.FreeModule_generic` since the latter is a derived class of :class:`~sage.modules.module.Module_old`, which does not conform to the new coercion model. Moreover, the class :class:`~sage.modules.free_module.FreeModule_generic` seems to assume a distinguished basis (cf. its method :meth:`~sage.modules.free_module.FreeModule_generic.basis`). - Besides, the class :class:`FiniteFreeModule` does not inherit + Besides, the class :class:`FiniteRankFreeModule` does not inherit from the class - :class:`~sage.combinat.free_module.CombinatorialFreeModule`, which conforms - to the new coercion model, since this class is devoted to modules with a + :class:`~sage.combinat.free_module.CombinatorialFreeModule` (which conforms + to the new coercion model) since this class is devoted to modules with a distinguished basis. -For the above reasons, the class :class:`FiniteFreeModule` inherits directly from -the generic class :class:`~sage.modules.module.Module` and each instance of -:class:`FiniteFreeModule` belongs to the category -:class:`~sage.categories.modules.Modules` -and not to the category +For the above reasons, the class :class:`FiniteRankFreeModule` inherits +directly from the generic class :class:`~sage.structure.parent.Parent` +with the category set to :class:`~sage.categories.modules.Modules` +and not to :class:`~sage.categories.modules_with_basis.ModulesWithBasis`. TODO: @@ -57,7 +56,7 @@ Let us define a free module of rank 2 over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 2, name='M') ; M + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') ; M rank-2 free module M over the Integer Ring We introduce a first basis on M:: @@ -174,43 +173,36 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.modules.module import Module -from free_module_tensor import FiniteFreeModuleElement from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.categories.modules import Modules +from free_module_tensor import FiniteRankFreeModuleElement -# From sage/modules/module.pyx: -# ---------------------------- -### The new Module class that should be the base of all Modules -### The derived Module class must implement the element -### constructor: -# -# class MyModule(sage.modules.module.Module): -# Element = MyElement -# def _element_constructor_(self, x): -# return self.element_class(x) -# - - -class FiniteFreeModule(UniqueRepresentation, Module): +class FiniteRankFreeModule(UniqueRepresentation, Parent): r""" Free module of finite rank over a commutative ring `R`. - - This class inherits from the generic class - :class:`~sage.modules.module.Module`. .. NOTE:: - The class :class:`FiniteFreeModule` does not inherit from + The class :class:`FiniteRankFreeModule` does not inherit from :class:`~sage.modules.free_module.FreeModule_generic` since the latter is a derived class of :class:`~sage.modules.module.Module_old`, which does not conform to the new coercion model. - Besides, the class :class:`FiniteFreeModule` does not inherit + Besides, the class :class:`FiniteRankFreeModule` does not inherit from the class :class:`CombinatorialFreeModule` since the latter is devoted to modules *with a basis*. - The class :class:`FiniteFreeModule` is a Sage *Parent* class whose elements - belong to the class - :class:`~sage.tensor.modules.free_module_tensor.FiniteFreeModuleElement`. + .. NOTE:: + + Following the recommendation exposed in + `trac ticket 16427 `_ + the class :class:`FiniteRankFreeModule` inherits directly from + :class:`~sage.structure.parent.Parent` and not from the Cython class + :class:`~sage.modules.module.Module`. + + The class :class:`FiniteRankFreeModule` is a Sage *Parent* class whose + elements belong to the class + :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`. INPUT: @@ -232,9 +224,9 @@ class FiniteFreeModule(UniqueRepresentation, Module): Free module of rank 3 over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3) ; M + sage: M = FiniteRankFreeModule(ZZ, 3) ; M rank-3 free module over the Integer Ring - sage: M = FiniteFreeModule(ZZ, 3, name='M') ; M # declaration with a name + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') ; M # declaration with a name rank-3 free module M over the Integer Ring sage: M.category() Category of modules over Integer Ring @@ -246,7 +238,7 @@ class FiniteFreeModule(UniqueRepresentation, Module): If the base ring is a field, the free module is in the category of vector spaces:: - sage: V = FiniteFreeModule(QQ, 3, name='V') ; V + sage: V = FiniteRankFreeModule(QQ, 3, name='V') ; V rank-3 free module V over the Rational Field sage: V.category() Category of vector spaces over Rational Field @@ -255,17 +247,17 @@ class FiniteFreeModule(UniqueRepresentation, Module): sage: latex(M) # the default is the symbol provided in the string ``name`` M - sage: M = FiniteFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') sage: latex(M) \mathcal{M} M is a *parent* object, whose elements are instances of - :class:`~sage.tensor.modules.free_module_tensor.FiniteFreeModuleElement`:: + :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`:: sage: v = M.an_element() ; v element of the rank-3 free module M over the Integer Ring - sage: from sage.tensor.modules.free_module_tensor import FiniteFreeModuleElement - sage: isinstance(v, FiniteFreeModuleElement) + sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement + sage: isinstance(v, FiniteRankFreeModuleElement) True sage: v in M True @@ -334,14 +326,14 @@ class FiniteFreeModule(UniqueRepresentation, Module): This can be changed via the parameter ``start_index`` in the module construction:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: for i in M.irange(): print i, 1 2 3 The parameter ``output_formatter`` in the constructor of the free module is used to set the output format of tensor components:: - sage: M = FiniteFreeModule(QQ, 3, output_formatter=Rational.numerical_approx) + sage: M = FiniteRankFreeModule(QQ, 3, output_formatter=Rational.numerical_approx) sage: e = M.basis('e') sage: v = M([1/3, 0, -2], basis=e) sage: v.comp(e)[:] @@ -354,7 +346,7 @@ class FiniteFreeModule(UniqueRepresentation, Module): All the tests from the suite for the category :class:`~sage.categories.modules.Modules` are passed:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: TestSuite(M).run(verbose=True) running ._test_additive_associativity() . . . pass @@ -380,29 +372,29 @@ class FiniteFreeModule(UniqueRepresentation, Module): """ - Element = FiniteFreeModuleElement + Element = FiniteRankFreeModuleElement def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, output_formatter=None): if not ring.is_commutative(): raise TypeError("The module base ring must be commutative.") - Module.__init__(self, ring) - self.ring = ring # for efficiency (to avoid calls to self.base_ring()) + Parent.__init__(self, base=ring, category=Modules(ring)) + self._ring = ring # same as self._base self._rank = rank - self.name = name + self._name = name if latex_name is None: - self.latex_name = self.name + self._latex_name = self._name else: - self.latex_name = latex_name - self.sindex = start_index - self.output_formatter = output_formatter + self._latex_name = latex_name + self._sindex = start_index + self._output_formatter = output_formatter # Dictionary of the tensor modules built on self # (dict. keys = (k,l) --the tensor type) self._tensor_modules = {(1,0): self} # self is considered as the set of # tensors of type (1,0) - self.known_bases = [] # List of known bases on the free module - self.def_basis = None # default basis - self.basis_changes = {} # Dictionary of the changes of bases + self._known_bases = [] # List of known bases on the free module + self._def_basis = None # default basis + self._basis_changes = {} # Dictionary of the changes of bases # Zero element: if not hasattr(self, '_zero_element'): self._zero_element = self._element_constructor_(name='zero', @@ -428,8 +420,8 @@ def _an_element_(self): Construct some (unamed) element of the module """ resu = self.element_class(self) - if self.def_basis is not None: - resu.set_comp()[:] = [self.ring.an_element() for i in + if self._def_basis is not None: + resu.set_comp()[:] = [self._ring.an_element() for i in range(self._rank)] return resu @@ -442,11 +434,11 @@ def _repr_(self): String representation of the object. """ description = "rank-" + str(self._rank) + " free module " - if self.name is not None: - description += self.name + " " - description += "over the " + str(self.ring) + if self._name is not None: + description += self._name + " " + description += "over the " + str(self._ring) return description - + def tensor_module(self, k, l): r""" Return the free module of all tensors of type (k,l) defined on @@ -470,7 +462,7 @@ def tensor_module(self, k, l): Tensor modules over a free module over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: T = M.tensor_module(1,2) ; T free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring sage: T.an_element() @@ -518,7 +510,7 @@ def basis(self, symbol=None, latex_symbol=None): Bases on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring sage: e[0] @@ -554,7 +546,7 @@ def basis(self, symbol=None, latex_symbol=None): The individual elements of the basis are labelled according the parameter ``start_index`` provided at the free module construction:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring sage: e[1] @@ -565,8 +557,8 @@ def basis(self, symbol=None, latex_symbol=None): if symbol is None: return self.default_basis() else: - for other in self.known_bases: - if symbol == other.symbol: + for other in self._known_bases: + if symbol == other._symbol: return other return FreeModuleBasis(self, symbol, latex_symbol) @@ -606,7 +598,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, Tensors on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,0), name='t') ; t element t of the rank-3 free module M over the Integer Ring sage: t = M.tensor((0,1), name='t') ; t @@ -624,32 +616,34 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, for more examples and documentation. """ - from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement - from free_module_tensor_spec import FreeModuleEndomorphism, \ - FreeModuleSymBilinForm + from free_module_tensor_spec import FreeModuleEndomorphism from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm if tensor_type==(1,0): - return FiniteFreeModuleElement(self, name=name, latex_name=latex_name) + return self.element_class(self, name=name, latex_name=latex_name) + #!# the above is preferable to + # return FiniteRankFreeModuleElement(self, name=name, latex_name=latex_name) + # because self.element_class is a (dynamically created) derived + # class of FiniteRankFreeModuleElement elif tensor_type==(0,1): return FreeModuleLinForm(self, name=name, latex_name=latex_name) elif tensor_type==(1,1): return FreeModuleEndomorphism(self, name=name, latex_name=latex_name) - elif tensor_type==(0,2) and sym==(0,1): - return FreeModuleSymBilinForm(self, name=name, - latex_name=latex_name) elif tensor_type[0]==0 and tensor_type[1]>1 and antisym is not None: if len(antisym)==tensor_type[1]: return FreeModuleAltForm(self, tensor_type[1], name=name, latex_name=latex_name) else: - return FreeModuleTensor(self, tensor_type, name=name, + return self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, latex_name=latex_name, sym=sym, antisym=antisym) else: - return FreeModuleTensor(self, tensor_type, name=name, + return self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, latex_name=latex_name, sym=sym, antisym=antisym) + def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): r""" @@ -679,7 +673,7 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): Construction of a tensor of rank 1:: sage: from sage.tensor.modules.comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring sage: c = Components(ZZ, e, 1) @@ -714,48 +708,47 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): 4 e^0/\e^1 + 5 e^1/\e^2 """ - from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement - from free_module_tensor_spec import FreeModuleEndomorphism, \ - FreeModuleSymBilinForm + from free_module_tensor_spec import FreeModuleEndomorphism from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm from comp import CompWithSym, CompFullySym, CompFullyAntiSym # # 0/ Compatibility checks: - if comp.ring is not self.ring: + if comp._ring is not self._ring: raise TypeError("The components are not defined on the same" + " ring as the module.") - if comp.frame not in self.known_bases: + if comp._frame not in self._known_bases: raise TypeError("The components are not defined on a basis of" + " the module.") - if comp.nid != tensor_type[0] + tensor_type[1]: + if comp._nid != tensor_type[0] + tensor_type[1]: raise TypeError("Number of component indices not compatible with "+ " the tensor type.") # # 1/ Construction of the tensor: if tensor_type == (1,0): - resu = FiniteFreeModuleElement(self, name=name, latex_name=latex_name) + resu = self.element_class(self, name=name, latex_name=latex_name) + #!# the above is preferable to + # resu = FiniteRankFreeModuleElement(self, name=name, latex_name=latex_name) + # because self.element_class is a (dynamically created) derived + # class of FiniteRankFreeModuleElement elif tensor_type == (0,1): resu = FreeModuleLinForm(self, name=name, latex_name=latex_name) elif tensor_type == (1,1): resu = FreeModuleEndomorphism(self, name=name, latex_name=latex_name) - elif tensor_type == (0,2) and isinstance(comp, CompFullySym): - resu = FreeModuleSymBilinForm(self, name=name, - latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and \ isinstance(comp, CompFullyAntiSym): resu = FreeModuleAltForm(self, tensor_type[1], name=name, latex_name=latex_name) else: - resu = FreeModuleTensor(self, tensor_type, name=name, - latex_name=latex_name) + resu = self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, latex_name=latex_name) # Tensor symmetries deduced from those of comp: if isinstance(comp, CompWithSym): - resu.sym = comp.sym - resu.antisym = comp.antisym + resu._sym = comp._sym + resu._antisym = comp._antisym # # 2/ Tensor components set to comp: - resu.components[comp.frame] = comp + resu._components[comp._frame] = comp # return resu @@ -784,7 +777,7 @@ def alternating_form(self, degree, name=None, latex_name=None): Alternating forms on a rank-3 module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.alternating_form(2, 'a') ; a alternating form a of degree 2 on the rank-3 free module M over the Integer Ring @@ -846,7 +839,7 @@ def linear_form(self, name=None, latex_name=None): Linear form on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') ; a linear form A on the rank-3 free module M over the Integer Ring @@ -894,7 +887,7 @@ def endomorphism(self, name=None, latex_name=None): Endomorphism on a rank-3 module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.endomorphism('T') ; t endomorphism T on the rank-3 free module M over the Integer Ring @@ -902,7 +895,7 @@ def endomorphism(self, name=None, latex_name=None): sage: t.parent() free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring - sage: t.tensor_type + sage: t.tensor_type() (1, 1) Consequently, an endomorphism can also be created by the method @@ -940,7 +933,7 @@ def automorphism(self, name=None, latex_name=None): Automorphism on a rank-2 free module (vector space) on `\QQ`:: - sage: M = FiniteFreeModule(QQ, 2, name='M') + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: a = M.automorphism('A') ; a automorphism A on the rank-2 free module M over the Rational Field @@ -948,7 +941,7 @@ def automorphism(self, name=None, latex_name=None): sage: a.parent() free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field - sage: a.tensor_type + sage: a.tensor_type() (1, 1) See @@ -980,7 +973,7 @@ def identity_map(self, name='Id', latex_name=None): Identity map on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() ; a identity map on the rank-3 free module M over the Integer Ring @@ -997,7 +990,7 @@ def identity_map(self, name='Id', latex_name=None): sage: a.parent() free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring - sage: a.tensor_type + sage: a.tensor_type() (1, 1) See @@ -1024,13 +1017,14 @@ def sym_bilinear_form(self, name=None, latex_name=None): OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleSymBilinForm` + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + of tensor type (0,2) and symmetric EXAMPLES: Symmetric bilinear form on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.sym_bilinear_form('A') ; a symmetric bilinear form A on the rank-3 free module M over the Integer Ring @@ -1038,18 +1032,72 @@ def sym_bilinear_form(self, name=None, latex_name=None): sage: a.parent() free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring - sage: a.tensor_type + sage: a.tensor_type() (0, 2) + sage: a.tensor_rank() + 2 sage: a.symmetries() symmetry: (0, 1); no antisymmetry + + Components with respect to a given basis:: + + sage: e = M.basis('e') + sage: a[0,0], a[0,1], a[0,2] = 1, 2, 3 + sage: a[1,1], a[1,2] = 4, 5 + sage: a[2,2] = 6 + + Only independent components have been set; the other ones are deduced by + symmetry:: + + sage: a[1,0], a[2,0], a[2,1] + (2, 3, 5) + sage: a[:] + [1 2 3] + [2 4 5] + [3 5 6] + + A symmetric bilinear form acts on pairs of module elements:: + + sage: u = M([2,-1,3]) ; v = M([-2,4,1]) + sage: a(u,v) + 61 + sage: a(v,u) == a(u,v) + True + + The sum of two symmetric bilinear forms is another symmetric bilinear + form:: + + sage: b = M.sym_bilinear_form('B') + sage: b[0,0], b[0,1], b[1,2] = -2, 1, -3 + sage: s = a + b ; s + symmetric bilinear form A+B on the rank-3 free module M over the Integer Ring + sage: a[:], b[:], s[:] + ( + [1 2 3] [-2 1 0] [-1 3 3] + [2 4 5] [ 1 0 -3] [ 3 4 2] + [3 5 6], [ 0 -3 0], [ 3 2 6] + ) + + Adding a symmetric bilinear from with a non-symmetric one results in a + generic type-(0,2) tensor:: + + sage: c = M.tensor((0,2), name='C') + sage: c[0,1] = 4 + sage: s = a + c ; s + type-(0,2) tensor A+C on the rank-3 free module M over the Integer Ring + sage: s.symmetries() + no symmetry; no antisymmetry + sage: s[:] + [1 6 3] + [2 4 5] + [3 5 6] - See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleSymBilinForm` - for further documentation. + See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + for more documentation. """ - from free_module_tensor_spec import FreeModuleSymBilinForm - return FreeModuleSymBilinForm(self, name=name, latex_name=latex_name) + return self.tensor_module(0,2).element_class(self, (0,2), name=name, + latex_name=latex_name, sym=(0,1)) #### End of methods to be redefined by derived classes #### @@ -1057,10 +1105,10 @@ def _latex_(self): r""" LaTeX representation of the object. """ - if self.latex_name is None: + if self._latex_name is None: return r'\mbox{' + str(self) + r'}' else: - return self.latex_name + return self._latex_name def rank(self): r""" @@ -1074,7 +1122,7 @@ def rank(self): Rank of free modules over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3) + sage: M = FiniteRankFreeModule(ZZ, 3) sage: M.rank() 3 sage: M.tensor_module(0,1).rank() @@ -1101,7 +1149,7 @@ def zero(self): Zero elements of free modules over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.zero() element zero of the rank-3 free module M over the Integer Ring sage: M.zero().parent() is M @@ -1143,7 +1191,7 @@ def dual(self): Dual of a free module over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.dual() dual of the rank-3 free module M over the Integer Ring sage: latex(M.dual()) @@ -1151,7 +1199,7 @@ def dual(self): The dual is a free module of the same rank as M:: - sage: isinstance(M.dual(), FiniteFreeModule) + sage: isinstance(M.dual(), FiniteRankFreeModule) True sage: M.dual().rank() 3 @@ -1182,18 +1230,18 @@ def irange(self, start=None): INPUT: - ``start`` -- (integer; default: None) initial value of the index; if none is - provided, ``self.sindex`` is assumed + provided, ``self._sindex`` is assumed OUTPUT: - an iterable index, starting from ``start`` and ending at - ``self.sindex + self.rank() -1`` + ``self._sindex + self.rank() -1`` EXAMPLES: Index range on a rank-3 module:: - sage: M = FiniteFreeModule(ZZ, 3) + sage: M = FiniteRankFreeModule(ZZ, 3) sage: for i in M.irange(): print i, 0 1 2 sage: for i in M.irange(start=1): print i, @@ -1202,15 +1250,15 @@ def irange(self, start=None): The default starting value corresponds to the parameter ``start_index`` provided at the module construction (the default value being 0):: - sage: M1 = FiniteFreeModule(ZZ, 3, start_index=1) + sage: M1 = FiniteRankFreeModule(ZZ, 3, start_index=1) sage: for i in M1.irange(): print i, 1 2 3 - sage: M2 = FiniteFreeModule(ZZ, 3, start_index=-4) + sage: M2 = FiniteRankFreeModule(ZZ, 3, start_index=-4) sage: for i in M2.irange(): print i, -4 -3 -2 """ - si = self.sindex + si = self._sindex imax = self._rank + si if start is None: i = si @@ -1238,7 +1286,7 @@ def default_basis(self): At the module construction, no default basis is assumed:: - sage: M = FiniteFreeModule(ZZ, 2, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1) sage: M.default_basis() No default basis has been defined on the rank-2 free module M over the Integer Ring @@ -1254,9 +1302,9 @@ def default_basis(self): basis (e_1,e_2) on the rank-2 free module M over the Integer Ring """ - if self.def_basis is None: + if self._def_basis is None: print "No default basis has been defined on the " + str(self) - return self.def_basis + return self._def_basis def set_default_basis(self, basis): r""" @@ -1276,7 +1324,7 @@ def set_default_basis(self, basis): Changing the default basis on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring sage: f = M.basis('f') ; f @@ -1291,20 +1339,21 @@ def set_default_basis(self, basis): from free_module_basis import FreeModuleBasis if not isinstance(basis, FreeModuleBasis): raise TypeError("The argument is not a free module basis.") - if basis.fmodule is not self: + if basis._fmodule is not self: raise ValueError("The basis is not defined on the current module.") - self.def_basis = basis + self._def_basis = basis - def view_bases(self): r""" Display the bases that have been defined on the free module. + + Use the method :meth:`bases` to get the raw list of bases. EXAMPLES: Bases on a rank-4 free module:: - sage: M = FiniteFreeModule(ZZ, 4, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 4, name='M', start_index=1) sage: M.view_bases() No basis has been defined on the rank-4 free module M over the Integer Ring sage: e = M.basis('e') @@ -1323,15 +1372,140 @@ def view_bases(self): - (f_1,f_2,f_3,f_4) (default basis) """ - if self.known_bases == []: + if self._known_bases == []: print "No basis has been defined on the " + str(self) else: print "Bases defined on the " + str(self) + ":" - for basis in self.known_bases: - item = " - " + basis.name - if basis is self.def_basis: + for basis in self._known_bases: + item = " - " + basis._name + if basis is self._def_basis: item += " (default basis)" print item + def bases(self): + r""" + Return the list of bases that have been defined on the free module. + + Use the method :meth:`view_bases` to get a formatted output with more + information. + + OUTPUT: + + - list of instances of class + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + + EXAMPLES: + + Bases on a rank-3 free module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M_3', start_index=1) + sage: M.bases() + [] + sage: e = M.basis('e') + sage: M.bases() + [basis (e_1,e_2,e_3) on the rank-3 free module M_3 over the Integer Ring] + sage: f = M.basis('f') + sage: M.bases() + [basis (e_1,e_2,e_3) on the rank-3 free module M_3 over the Integer Ring, + basis (f_1,f_2,f_3) on the rank-3 free module M_3 over the Integer Ring] + + """ + return self._known_bases + + def basis_change(self, basis1, basis2): + r""" + Return a change of basis previously defined on the free module. + + INPUT: + + - ``basis1`` -- basis 1, denoted `(e_i)` below + - ``basis2`` -- basis 2, denoted `(f_i)` below + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + describing the automorphism `P` that relates the basis `(e_i)` to the + basis `(f_i)` according to `f_i = P(e_i)` + + EXAMPLES: + + Changes of basis on a rank-2 free module:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.automorphism() + sage: a[:] = [[1, 2], [-1, 3]] + sage: f = e.new_basis(a, 'f') + sage: M.basis_change(e,f) + automorphism on the rank-2 free module M over the Rational Field + sage: M.basis_change(e,f)[:] + [ 1 2] + [-1 3] + sage: M.basis_change(f,e)[:] + [ 3/5 -2/5] + [ 1/5 1/5] + """ + if (basis1, basis2) not in self._basis_changes: + raise TypeError("The change of basis from '" + repr(basis1) + + "' to '" + repr(basis2) + "' has not been " + + "defined on the " + repr(self)) + return self._basis_changes[(basis1, basis2)] + + def set_basis_change(self, basis1, basis2, change_of_basis, + compute_inverse=True): + r""" + Relates two bases by an automorphism. + + This updates the internal dictionary ``self._basis_changes``. + + INPUT: + + - ``basis1`` -- basis 1, denoted `(e_i)` below + - ``basis2`` -- basis 2, denoted `(f_i)` below + - ``change_of_basis`` -- instance of class + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + describing the automorphism `P` that relates the basis `(e_i)` to + the basis `(f_i)` according to `f_i = P(e_i)` + - ``compute_inverse`` (default: True) -- if set to True, the inverse + automorphism is computed and the change from basis `(f_i)` to `(e_i)` + is set to it in the internal dictionary ``self._basis_changes`` + + EXAMPLES: + + Defining a change of basis on a rank-2 free module:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: e = M.basis('e') + sage: f = M.basis('f') + sage: a = M.automorphism() + sage: a[:] = [[1, 2], [-1, 3]] + sage: M.set_basis_change(e, f, a) + + The change of basis and its inverse have been recorded:: + + sage: M.basis_change(e,f)[:] + [ 1 2] + [-1 3] + sage: M.basis_change(f,e)[:] + [ 3/5 -2/5] + [ 1/5 1/5] + + and are effective:: + + sage: f[0].view(e) + f_0 = e_0 - e_1 + sage: e[0].view(f) + e_0 = 3/5 f_0 + 1/5 f_1 + + """ + from free_module_tensor_spec import FreeModuleAutomorphism + if not isinstance(change_of_basis, FreeModuleAutomorphism): + raise TypeError("The argument change_of_basis must be some " + + "instance of FreeModuleAutomorphism.") + self._basis_changes[(basis1, basis2)] = change_of_basis + if compute_inverse: + self._basis_changes[(basis2, basis1)] = change_of_basis.inverse() + diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 352ce9614ed..82e39961aea 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -15,6 +15,13 @@ - Eric Gourgoulhon, Michal Bejger (2014): initial version +TODO: + +* Implement a specific parent for alternating forms of a fixed degree p>1, with + element :class:`FreeModuleAltForm` and with coercion to tensor modules of + type (0,p). +* Implement a specific parent for linear forms, with element + :class:`FreeModuleLinForm` and with coercion to tensor modules of type (0,1). """ #****************************************************************************** @@ -27,7 +34,7 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement +from free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement from comp import Components, CompFullyAntiSym class FreeModuleAltForm(FreeModuleTensor): @@ -37,7 +44,7 @@ class FreeModuleAltForm(FreeModuleTensor): INPUT: - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` - (must be an instance of :class:`FiniteFreeModule`) + (must be an instance of :class:`FiniteRankFreeModule`) - ``degree`` -- the degree of the alternating form (i.e. its tensor rank) - ``name`` -- (default: None) name given to the alternating form - ``latex_name`` -- (default: None) LaTeX symbol to denote the alternating @@ -54,10 +61,10 @@ def _repr_(self): String representation of the object. """ description = "alternating form " - if self.name is not None: - description += self.name + " " - description += "of degree " + str(self.tensor_rank) + " on the " + \ - str(self.fmodule) + if self._name is not None: + description += self._name + " " + description += "of degree " + str(self._tensor_rank) + " on the " + \ + str(self._fmodule) return description def _init_derived(self): @@ -74,11 +81,11 @@ def _del_derived(self): def _new_instance(self): r""" - Create a :class:`FreeModuleAltForm` instance on the same module and of - the same degree. + Create an instance of the same class as ``self``, on the same module + and of the same degree. """ - return FreeModuleAltForm(self.fmodule, self.tensor_rank) + return self.__class__(self._fmodule, self._tensor_rank) def _new_comp(self, basis): r""" @@ -87,21 +94,21 @@ def _new_comp(self, basis): This method, which is already implemented in :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency """ - fmodule = self.fmodule # the base free module - if self.tensor_rank == 1: - return Components(fmodule.ring, basis, 1, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) + fmodule = self._fmodule # the base free module + if self._tensor_rank == 1: + return Components(fmodule._ring, basis, 1, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) else: - return CompFullyAntiSym(fmodule.ring, basis, self.tensor_rank, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) + return CompFullyAntiSym(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) def degree(self): r""" Return the degree of the alternating form. """ - return self.tensor_rank + return self._tensor_rank def view(self, basis=None, format_spec=None): @@ -118,14 +125,14 @@ def view(self, basis=None, format_spec=None): which the alternating form is expanded; if none is provided, the module's default basis is assumed - ``format_spec`` -- (default: None) format specification passed to - ``self.fmodule.output_formatter`` to format the output. + ``self._fmodule._output_formatter`` to format the output. EXAMPLES: Display of an alternating form of degree 1 (linear form) on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('a', latex_name=r'\alpha') sage: a[:] = [1,-3,4] @@ -178,7 +185,7 @@ def view(self, basis=None, format_spec=None): The output format can be set via the argument ``output_formatter`` passed at the module construction:: - sage: N = FiniteFreeModule(QQ, 3, name='N', start_index=1, output_formatter=Rational.numerical_approx) + sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: b = N.alternating_form(2, 'b') sage: b[1,2], b[1,3], b[2,3] = 1/3, 5/2, 4 @@ -195,7 +202,7 @@ def view(self, basis=None, format_spec=None): from sage.misc.latex import latex from format_utilities import is_atomic, FormattedExpansion if basis is None: - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis cobasis = basis.dual_basis() comp = self.comp(basis) terms_txt = [] @@ -206,8 +213,8 @@ def view(self, basis=None, format_spec=None): if coef != 0: bases_txt = [] bases_latex = [] - for k in range(self.tensor_rank): - bases_txt.append(cobasis[ind[k]].name) + for k in range(self._tensor_rank): + bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) @@ -249,11 +256,11 @@ def view(self, basis=None, format_spec=None): else: expansion_latex += "+" + term result = FormattedExpansion(self) - if self.name is None: + if self._name is None: result.txt = expansion_txt else: - result.txt = self.name + " = " + expansion_txt - if self.latex_name is None: + result.txt = self._name + " = " + expansion_txt + if self._latex_name is None: result.latex = expansion_latex else: result.latex = latex(self) + " = " + expansion_latex @@ -277,7 +284,7 @@ def wedge(self, other): Exterior product of two linear forms:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') sage: a[:] = [1,-3,4] @@ -328,43 +335,43 @@ def wedge(self, other): if not isinstance(other, FreeModuleAltForm): raise TypeError("The second argument for the exterior product " + "must be an alternating form.") - if other.tensor_rank == 0: + if other._tensor_rank == 0: return other*self - if self.tensor_rank == 0: + if self._tensor_rank == 0: return self*other - fmodule = self.fmodule + fmodule = self._fmodule basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the exterior product.") - rank_r = self.tensor_rank + other.tensor_rank - cmp_s = self.components[basis] - cmp_o = other.components[basis] - cmp_r = CompFullyAntiSym(fmodule.ring, basis, rank_r, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) - for ind_s, val_s in cmp_s._comp.items(): - for ind_o, val_o in cmp_o._comp.items(): + rank_r = self._tensor_rank + other._tensor_rank + cmp_s = self._components[basis] + cmp_o = other._components[basis] + cmp_r = CompFullyAntiSym(fmodule._ring, basis, rank_r, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) + for ind_s, val_s in cmp_s._comp.iteritems(): + for ind_o, val_o in cmp_o._comp.iteritems(): ind_r = ind_s + ind_o if len(ind_r) == len(set(ind_r)): # all indices are different cmp_r[[ind_r]] += val_s * val_o result = fmodule.alternating_form(rank_r) - result.components[basis] = cmp_r - if self.name is not None and other.name is not None: - sname = self.name - oname = other.name + result._components[basis] = cmp_r + if self._name is not None and other._name is not None: + sname = self._name + oname = other._name if not is_atomic(sname): sname = '(' + sname + ')' if not is_atomic(oname): oname = '(' + oname + ')' - result.name = sname + '/\\' + oname - if self.latex_name is not None and other.latex_name is not None: - slname = self.latex_name - olname = other.latex_name + result._name = sname + '/\\' + oname + if self._latex_name is not None and other._latex_name is not None: + slname = self._latex_name + olname = other._latex_name if not is_atomic(slname): slname = '(' + slname + ')' if not is_atomic(olname): olname = '(' + olname + ')' - result.latex_name = slname + r'\wedge ' + olname + result._latex_name = slname + r'\wedge ' + olname return result @@ -379,7 +386,7 @@ class FreeModuleLinForm(FreeModuleAltForm): INPUT: - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` - (must be an instance of :class:`FiniteFreeModule`) + (must be an instance of :class:`FiniteRankFreeModule`) - ``name`` -- (default: None) name given to the linear form - ``latex_name`` -- (default: None) LaTeX symbol to denote the linear form; if none is provided, the LaTeX symbol is set to ``name`` @@ -388,7 +395,7 @@ class FreeModuleLinForm(FreeModuleAltForm): Linear form on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') ; a linear form A on the rank-3 free module M over the Integer Ring @@ -429,7 +436,7 @@ class FreeModuleLinForm(FreeModuleAltForm): As such, it is a tensor of type (0,1):: - sage: a.tensor_type + sage: a.tensor_type() (0, 1) """ @@ -442,18 +449,18 @@ def _repr_(self): String representation of the object. """ description = "linear form " - if self.name is not None: - description += self.name + " " - description += "on the " + str(self.fmodule) + if self._name is not None: + description += self._name + " " + description += "on the " + str(self._fmodule) return description def _new_instance(self): r""" - Create a :class:`FreeModuleLinForm` instance on the same module. + Create an instance of the same class as ``self`` and on the same + module. """ - return FreeModuleLinForm(self.fmodule) - + return self.__class__(self._fmodule) def _new_comp(self, basis): r""" @@ -462,9 +469,9 @@ def _new_comp(self, basis): This method, which is already implemented in :meth:`FreeModuleAltForm._new_comp`, is redefined here for efficiency """ - fmodule = self.fmodule # the base free module - return Components(fmodule.ring, basis, 1, start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) + fmodule = self._fmodule # the base free module + return Components(fmodule._ring, basis, 1, start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) def __call__(self, vector): r""" @@ -473,31 +480,31 @@ def __call__(self, vector): INPUT: - ``vector`` -- an element of the module (instance of - :class:`FiniteFreeModuleElement`) + :class:`FiniteRankFreeModuleElement`) OUTPUT: - ring element `\langle \omega, v \rangle` """ - if not isinstance(vector, FiniteFreeModuleElement): + if not isinstance(vector, FiniteRankFreeModuleElement): raise TypeError("The argument must be a free module element.") basis = self.common_basis(vector) if basis is None: raise ValueError("No common basis for the components.") - omega = self.components[basis] - vv = vector.components[basis] + omega = self._components[basis] + vv = vector._components[basis] resu = 0 - for i in self.fmodule.irange(): + for i in self._fmodule.irange(): resu += omega[[i]]*vv[[i]] # Name and LaTeX symbol of the output: - if hasattr(resu, 'name'): - if self.name is not None and vector.name is not None: - resu.name = self.name + "(" + vector.name + ")" - if hasattr(resu, 'latex_name'): - if self.latex_name is not None and vector.latex_name is not None: - resu.latex_name = self.latex_name + r"\left(" + \ - vector.latex_name + r"\right)" + if hasattr(resu, '_name'): + if self._name is not None and vector._name is not None: + resu._name = self._name + "(" + vector._name + ")" + if hasattr(resu, '_latex_name'): + if self._latex_name is not None and vector._latex_name is not None: + resu._latex_name = self._latex_name + r"\left(" + \ + vector._latex_name + r"\right)" return resu diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 82dee3954c8..169c2cb01cd 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -33,7 +33,7 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): INPUT: - ``fmodule`` -- free module `M` (must be an instance of - :class:`FiniteFreeModule`) + :class:`FiniteRankFreeModule`) - ``symbol`` -- (string) a letter (of a few letters) to denote a generic element of the basis - ``latex_symbol`` -- (string; default: None) symbol to denote a generic @@ -43,7 +43,7 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): A basis on a rank-3 free module over `\ZZ`:: - sage: M0 = FiniteFreeModule(ZZ, 3, name='M_0') + sage: M0 = FiniteRankFreeModule(ZZ, 3, name='M_0') sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis sage: e = FreeModuleBasis(M0, 'e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M_0 over the Integer Ring @@ -51,7 +51,7 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): Instead of importing FreeModuleBasis in the global name space, one can use the module's method :meth:`basis`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring @@ -76,7 +76,7 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): The individual elements of the basis are labelled according the parameter ``start_index`` provided at the free module construction:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring sage: e[1] @@ -84,21 +84,21 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): """ def __init__(self, fmodule, symbol, latex_symbol=None): - self.fmodule = fmodule - self.name = "(" + \ + self._fmodule = fmodule + self._name = "(" + \ ",".join([symbol + "_" + str(i) for i in fmodule.irange()]) +")" if latex_symbol is None: latex_symbol = symbol - self.latex_name = r"\left(" + ",".join([latex_symbol + "_" + str(i) + self._latex_name = r"\left(" + ",".join([latex_symbol + "_" + str(i) for i in fmodule.irange()]) + r"\right)" - self.symbol = symbol - self.latex_symbol = latex_symbol + self._symbol = symbol + self._latex_symbol = latex_symbol # The basis is added to the module list of bases - for other in fmodule.known_bases: - if symbol == other.symbol: + for other in fmodule._known_bases: + if symbol == other._symbol: raise ValueError("The " + str(other) + " already exist on the " + str(fmodule)) - fmodule.known_bases.append(self) + fmodule._known_bases.append(self) # The individual vectors: vl = list() for i in fmodule.irange(): @@ -106,20 +106,20 @@ def __init__(self, fmodule, symbol, latex_symbol=None): v_symb = latex_symbol + "_" + str(i) v = fmodule.element_class(fmodule, name=v_name, latex_name=v_symb) for j in fmodule.irange(): - v.set_comp(self)[j] = fmodule.ring.zero() - v.set_comp(self)[i] = fmodule.ring.one() + v.set_comp(self)[j] = fmodule._ring.zero() + v.set_comp(self)[i] = fmodule._ring.one() vl.append(v) - self.vec = tuple(vl) - # The first defined basis is considered as the default one - # and is used to initialize the components of the zero elements of - # all tensor modules constructed up to now (including the base module - # itself, since it is considered as a type-(1,0) tensor module) - if fmodule.def_basis is None: - fmodule.def_basis = self - for t in fmodule._tensor_modules.values(): - t._zero_element.components[self] = \ - t._zero_element._new_comp(self) - # (since new components are initialized to zero) + self._vec = tuple(vl) + # The first defined basis is considered as the default one: + if fmodule._def_basis is None: + fmodule._def_basis = self + # Initialization of the components w.r.t the current basis of the zero + # elements of all tensor modules constructed up to now (including the + # base module itself, since it is considered as a type-(1,0) tensor + # module) + for t in fmodule._tensor_modules.itervalues(): + t._zero_element._components[self] = t._zero_element._new_comp(self) + # (since new components are initialized to zero) # The dual basis: self._dual_basis = self._init_dual_basis() @@ -130,7 +130,7 @@ def _repr_(self): r""" String representation of the object. """ - return "basis " + self.name + " on the " + str(self.fmodule) + return "basis " + self._name + " on the " + str(self._fmodule) def _init_dual_basis(self): @@ -143,8 +143,8 @@ def _init_dual_basis(self): ``self`` """ - return FreeModuleCoBasis(self, self.symbol, - latex_symbol=self.latex_symbol) + return FreeModuleCoBasis(self, self._symbol, + latex_symbol=self._latex_symbol) def _new_instance(self, symbol, latex_symbol=None): r""" @@ -163,7 +163,7 @@ def _new_instance(self, symbol, latex_symbol=None): - instance of :class:`FreeModuleBasis` """ - return FreeModuleBasis(self.fmodule, symbol, latex_symbol=latex_symbol) + return FreeModuleBasis(self._fmodule, symbol, latex_symbol=latex_symbol) ###### End of methods to be redefined by derived classes ###### @@ -181,7 +181,7 @@ def dual_basis(self): Dual basis on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring sage: f = e.dual_basis() ; f @@ -210,7 +210,7 @@ def _latex_(self): r""" LaTeX representation of the object. """ - return self.latex_name + return self._latex_name def __hash__(self): r""" @@ -240,20 +240,20 @@ def __getitem__(self, index): - ``index`` -- the index of the basis element """ - n = self.fmodule._rank - si = self.fmodule.sindex + n = self._fmodule._rank + si = self._fmodule._sindex i = index - si if i < 0 or i > n-1: raise ValueError("Index out of range: " + str(i+si) + " not in [" + str(si) + "," + str(n-1+si) + "]") - return self.vec[i] + return self._vec[i] def __len__(self): r""" Return the basis length, i.e. the rank of the free module. """ - return self.fmodule._rank + return self._fmodule._rank def new_basis(self, change_of_basis, symbol, latex_symbol=None): r""" @@ -282,7 +282,7 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): Change of basis on a rank-2 free module:: - sage: M = FiniteFreeModule(QQ, 2, name='M', start_index=1) + sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') sage: a = M.automorphism() sage: a[:] = [[1, 2], [-1, 3]] @@ -302,22 +302,22 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): if not isinstance(change_of_basis, FreeModuleAutomorphism): raise TypeError("The argument change_of_basis must be some " + "instance of FreeModuleAutomorphism.") - fmodule = self.fmodule + fmodule = self._fmodule # self._new_instance used instead of FreeModuleBasis for a correct # construction in case of derived classes: the_new_basis = self._new_instance(symbol, latex_symbol=latex_symbol) transf = change_of_basis.copy() inv_transf = change_of_basis.inverse().copy() - si = fmodule.sindex + si = fmodule._sindex # Components of the new basis vectors in the old basis: for i in fmodule.irange(): for j in fmodule.irange(): - the_new_basis.vec[i-si].add_comp(self)[[j]] = \ + the_new_basis._vec[i-si].add_comp(self)[[j]] = \ transf.comp(self)[[j,i]] # Components of the new dual-basis elements in the old dual basis: for i in fmodule.irange(): for j in fmodule.irange(): - the_new_basis._dual_basis.form[i-si].add_comp(self)[[j]] = \ + the_new_basis._dual_basis._form[i-si].add_comp(self)[[j]] = \ inv_transf.comp(self)[[i,j]] # The components of the transformation and its inverse are the same in # the two bases: @@ -329,17 +329,17 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): # Components of the old basis vectors in the new basis: for i in fmodule.irange(): for j in fmodule.irange(): - self.vec[i-si].add_comp(the_new_basis)[[j]] = \ + self._vec[i-si].add_comp(the_new_basis)[[j]] = \ inv_transf.comp(self)[[j,i]] # Components of the old dual-basis elements in the new cobasis: for i in fmodule.irange(): for j in fmodule.irange(): - self._dual_basis.form[i-si].add_comp(the_new_basis)[[j]] = \ + self._dual_basis._form[i-si].add_comp(the_new_basis)[[j]] = \ transf.comp(self)[[i,j]] # The automorphism and its inverse are added to the module's dictionary # of changes of bases: - fmodule.basis_changes[(self, the_new_basis)] = transf - fmodule.basis_changes[(the_new_basis, self)] = inv_transf + fmodule._basis_changes[(self, the_new_basis)] = transf + fmodule._basis_changes[(the_new_basis, self)] = inv_transf # return the_new_basis @@ -363,7 +363,7 @@ class FreeModuleCoBasis(SageObject): Dual basis on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring sage: from sage.tensor.modules.free_module_basis import FreeModuleCoBasis @@ -388,38 +388,38 @@ class FreeModuleCoBasis(SageObject): """ def __init__(self, basis, symbol, latex_symbol=None): - self.basis = basis - self.fmodule = basis.fmodule - self.name = "(" + \ - ",".join([symbol + "^" + str(i) for i in self.fmodule.irange()]) +")" + self._basis = basis + self._fmodule = basis._fmodule + self._name = "(" + \ + ",".join([symbol + "^" + str(i) for i in self._fmodule.irange()]) +")" if latex_symbol is None: latex_symbol = symbol - self.latex_name = r"\left(" + \ + self._latex_name = r"\left(" + \ ",".join([latex_symbol + "^" + str(i) - for i in self.fmodule.irange()]) + r"\right)" + for i in self._fmodule.irange()]) + r"\right)" # The individual linear forms: vl = list() - for i in self.fmodule.irange(): + for i in self._fmodule.irange(): v_name = symbol + "^" + str(i) v_symb = latex_symbol + "^" + str(i) - v = self.fmodule.linear_form(name=v_name, latex_name=v_symb) - for j in self.fmodule.irange(): + v = self._fmodule.linear_form(name=v_name, latex_name=v_symb) + for j in self._fmodule.irange(): v.set_comp(basis)[j] = 0 v.set_comp(basis)[i] = 1 vl.append(v) - self.form = tuple(vl) + self._form = tuple(vl) def _repr_(self): r""" String representation of the object. """ - return "dual basis " + self.name + " on the " + str(self.fmodule) + return "dual basis " + self._name + " on the " + str(self._fmodule) def _latex_(self): r""" LaTeX representation of the object. """ - return self.latex_name + return self._latex_name def __getitem__(self, index): r""" @@ -430,11 +430,11 @@ def __getitem__(self, index): - ``index`` -- the index of the linear form """ - n = self.fmodule._rank - si = self.fmodule.sindex + n = self._fmodule._rank + si = self._fmodule._sindex i = index - si if i < 0 or i > n-1: raise ValueError("Index out of range: " + str(i+si) + " not in [" + str(si) + "," + str(n-1+si) + "]") - return self.form[i] + return self._form[i] diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index c47d6c1fa3a..dd9e196aa63 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -20,7 +20,7 @@ Various derived classes of :class:`FreeModuleTensor` are devoted to specific tensors: -* :class:`FiniteFreeModuleElement` for elements of `M`, considered as +* :class:`FiniteRankFreeModuleElement` for elements of `M`, considered as type-(1,0) tensors thanks to the canonical identification `M^{**}=M`, which holds since `M` is a free module of finite rank @@ -39,9 +39,6 @@ * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` for the identity map on a free module -* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleSymBilinForm` - for symmetric type-(0,2) tensors (symmetric bilinear forms) - :class:`FreeModuleTensor` is a Sage *element* class, the corresponding *parent* class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. @@ -54,7 +51,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. A tensor of type (1,1) on a rank-3 free module over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t endomorphism t on the rank-3 free module M over the Integer Ring sage: t.parent() @@ -177,7 +174,7 @@ class FreeModuleTensor(ModuleElement): INPUT: - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an - instance of :class:`FiniteFreeModule`) + instance of :class:`FiniteRankFreeModule`) - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l the covariant rank - ``name`` -- (default: None) name given to the tensor @@ -199,7 +196,7 @@ class FreeModuleTensor(ModuleElement): A tensor of type (1,1) on a rank-3 free module over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t endomorphism t on the rank-3 free module M over the Integer Ring @@ -214,17 +211,18 @@ class FreeModuleTensor(ModuleElement): def __init__(self, fmodule, tensor_type, name=None, latex_name=None, sym=None, antisym=None): ModuleElement.__init__(self, fmodule.tensor_module(*tensor_type)) - self.fmodule = fmodule - self.tensor_type = tuple(tensor_type) - self.tensor_rank = self.tensor_type[0] + self.tensor_type[1] - self.name = name + self._fmodule = fmodule + self._tensor_type = tuple(tensor_type) + self._tensor_rank = self._tensor_type[0] + self._tensor_type[1] + self._name = name if latex_name is None: - self.latex_name = self.name + self._latex_name = self._name else: - self.latex_name = latex_name - self.components = {} # components on various bases (not set yet) + self._latex_name = latex_name + self._components = {} # dict. of the sets of components on various + # bases, with the bases as keys (initially empty) # Treatment of symmetry declarations: - self.sym = [] + self._sym = [] if sym is not None and sym != []: if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple -> 1-item list: @@ -232,11 +230,11 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, for isym in sym: if len(isym) > 1: for i in isym: - if i<0 or i>self.tensor_rank-1: + if i<0 or i>self._tensor_rank-1: raise IndexError("Invalid position: " + str(i) + - " not in [0," + str(self.tensor_rank-1) + "]") - self.sym.append(tuple(isym)) - self.antisym = [] + " not in [0," + str(self._tensor_rank-1) + "]") + self._sym.append(tuple(isym)) + self._antisym = [] if antisym is not None and antisym != []: if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple -> 1-item list: @@ -244,15 +242,15 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, for isym in antisym: if len(isym) > 1: for i in isym: - if i<0 or i>self.tensor_rank-1: + if i<0 or i>self._tensor_rank-1: raise IndexError("Invalid position: " + str(i) + - " not in [0," + str(self.tensor_rank-1) + "]") - self.antisym.append(tuple(isym)) + " not in [0," + str(self._tensor_rank-1) + "]") + self._antisym.append(tuple(isym)) # Final consistency check: index_list = [] - for isym in self.sym: + for isym in self._sym: index_list += isym - for isym in self.antisym: + for isym in self._antisym: index_list += isym if len(index_list) != len(set(index_list)): # There is a repeated index position: @@ -270,7 +268,7 @@ def __nonzero__(self): This method is called by self.is_zero(). """ basis = self.pick_a_basis() - return not self.components[basis].is_zero() + return not self._components[basis].is_zero() ####### End of required methods for ModuleElement (beside arithmetic) ####### @@ -278,21 +276,26 @@ def _repr_(self): r""" String representation of the object. """ - description = "type-(%s,%s) tensor" % \ - (str(self.tensor_type[0]), str(self.tensor_type[1])) - if self.name is not None: - description += " " + self.name - description += " on the " + str(self.fmodule) + # Special cases + if self._tensor_type == (0,2) and self._sym == [(0,1)]: + description = "symmetric bilinear form " + else: + # Generic case + description = "type-(%s,%s) tensor" % \ + (str(self._tensor_type[0]), str(self._tensor_type[1])) + if self._name is not None: + description += " " + self._name + description += " on the " + str(self._fmodule) return description def _latex_(self): r""" LaTeX representation of the object. """ - if self.latex_name is None: + if self._latex_name is None: return r'\mbox{' + str(self) + r'}' else: - return self.latex_name + return self._latex_name def _init_derived(self): r""" @@ -306,6 +309,89 @@ def _del_derived(self): """ pass # no derived quantities + #### Simple accessors #### + + def tensor_type(self): + r""" + Return the tensor type of ``self``. + + OUTPUT: + + - pair (k,l), where k is the contravariant rank and l is the covariant + rank + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.an_element().tensor_type() + (1, 0) + sage: t = M.tensor((2,1)) + sage: t.tensor_type() + (2, 1) + + """ + return self._tensor_type + + def tensor_rank(self): + r""" + Return the tensor rank of ``self``. + + OUTPUT: + + - integer k+l, where k is the contravariant rank and l is the covariant + rank + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: M.an_element().tensor_rank() + 1 + sage: t = M.tensor((2,1)) + sage: t.tensor_rank() + 3 + + """ + return self._tensor_rank + + def symmetries(self): + r""" + Print the list of symmetries and antisymmetries. + + EXAMPLES: + + Various symmetries / antisymmetries for a rank-4 tensor:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((4,0), name='T') # no symmetry declared + sage: t.symmetries() + no symmetry; no antisymmetry + sage: t = M.tensor((4,0), name='T', sym=(0,1)) + sage: t.symmetries() + symmetry: (0, 1); no antisymmetry + sage: t = M.tensor((4,0), name='T', sym=[(0,1), (2,3)]) + sage: t.symmetries() + symmetries: [(0, 1), (2, 3)]; no antisymmetry + sage: t = M.tensor((4,0), name='T', sym=(0,1), antisym=(2,3)) + sage: t.symmetries() + symmetry: (0, 1); antisymmetry: (2, 3) + + """ + if len(self._sym) == 0: + s = "no symmetry; " + elif len(self._sym) == 1: + s = "symmetry: " + str(self._sym[0]) + "; " + else: + s = "symmetries: " + str(self._sym) + "; " + if len(self._antisym) == 0: + a = "no antisymmetry" + elif len(self._antisym) == 1: + a = "antisymmetry: " + str(self._antisym[0]) + else: + a = "antisymmetries: " + str(self._antisym) + print s, a + + #### End of simple accessors ##### + def view(self, basis=None, format_spec=None): r""" Displays the tensor in terms of its expansion onto a given basis. @@ -319,13 +405,13 @@ def view(self, basis=None, format_spec=None): which the tensor is expanded; if none is provided, the module's default basis is assumed - ``format_spec`` -- (default: None) format specification passed to - ``self.fmodule.output_formatter`` to format the output. + ``self._fmodule._output_formatter`` to format the output. EXAMPLES: Display of a module element (type-(1,0) tensor):: - sage: M = FiniteFreeModule(QQ, 2, name='M', start_index=1) + sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') sage: v = M([1/3,-2], name='v') sage: v.view() @@ -368,7 +454,7 @@ def view(self, basis=None, format_spec=None): The output format can be set via the argument ``output_formatter`` passed at the module construction:: - sage: N = FiniteFreeModule(QQ, 2, name='N', start_index=1, output_formatter=Rational.numerical_approx) + sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1, output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: v = N([1/3,-2], name='v') sage: v.view() # default format (53 bits of precision) @@ -386,12 +472,12 @@ def view(self, basis=None, format_spec=None): from sage.misc.latex import latex from format_utilities import is_atomic, FormattedExpansion if basis is None: - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis cobasis = basis.dual_basis() comp = self.comp(basis) terms_txt = [] terms_latex = [] - n_con = self.tensor_type[0] + n_con = self._tensor_type[0] for ind in comp.index_generator(): ind_arg = ind + (format_spec,) coef = comp[ind_arg] @@ -399,10 +485,10 @@ def view(self, basis=None, format_spec=None): bases_txt = [] bases_latex = [] for k in range(n_con): - bases_txt.append(basis[ind[k]].name) + bases_txt.append(basis[ind[k]]._name) bases_latex.append(latex(basis[ind[k]])) - for k in range(n_con, self.tensor_rank): - bases_txt.append(cobasis[ind[k]].name) + for k in range(n_con, self._tensor_rank): + bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "*".join(bases_txt) basis_term_latex = r"\otimes ".join(bases_latex) @@ -444,81 +530,43 @@ def view(self, basis=None, format_spec=None): else: expansion_latex += "+" + term result = FormattedExpansion(self) - if self.name is None: + if self._name is None: result.txt = expansion_txt else: - result.txt = self.name + " = " + expansion_txt - if self.latex_name is None: + result.txt = self._name + " = " + expansion_txt + if self._latex_name is None: result.latex = expansion_latex else: result.latex = latex(self) + " = " + expansion_latex return result - - def symmetries(self): - r""" - Print the list of symmetries and antisymmetries. - - EXAMPLES: - - Various symmetries / antisymmetries for a rank-4 tensor:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M') - sage: t = M.tensor((4,0), name='T') # no symmetry declared - sage: t.symmetries() - no symmetry; no antisymmetry - sage: t = M.tensor((4,0), name='T', sym=(0,1)) - sage: t.symmetries() - symmetry: (0, 1); no antisymmetry - sage: t = M.tensor((4,0), name='T', sym=[(0,1), (2,3)]) - sage: t.symmetries() - symmetries: [(0, 1), (2, 3)]; no antisymmetry - sage: t = M.tensor((4,0), name='T', sym=(0,1), antisym=(2,3)) - sage: t.symmetries() - symmetry: (0, 1); antisymmetry: (2, 3) - - """ - if len(self.sym) == 0: - s = "no symmetry; " - elif len(self.sym) == 1: - s = "symmetry: " + str(self.sym[0]) + "; " - else: - s = "symmetries: " + str(self.sym) + "; " - if len(self.antisym) == 0: - a = "no antisymmetry" - elif len(self.antisym) == 1: - a = "antisymmetry: " + str(self.antisym[0]) - else: - a = "antisymmetries: " + str(self.antisym) - print s, a - - def set_name(self, name, latex_name=None): + + def set_name(self, name=None, latex_name=None): r""" Set (or change) the text name and LaTeX name of the tensor. INPUT: - - ``name`` -- name given to the tensor - - ``latex_name`` -- (default: None) LaTeX symbol to denote the tensor; - if none is provided, the LaTeX symbol is set to ``name`` + - ``name`` -- (string; default: None) name given to the tensor + - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + tensor; if None while ``name`` is provided, the LaTeX symbol is set + to ``name``. """ - self.name = name - if latex_name is None: - self.latex_name = self.name - else: - self.latex_name = latex_name + if name is not None: + self._name = name + if latex_name is None: + self._latex_name = self._name + if latex_name is not None: + self._latex_name = latex_name def _new_instance(self): r""" - Create a :class:`FreeModuleTensor` instance of the same tensor type and - with the same symmetries. + Create a tensor of the same tensor type and with the same symmetries + as ``self``. - This method must be redefined by derived classes of - :class:`FreeModuleTensor`. - """ - return FreeModuleTensor(self.fmodule, self.tensor_type, sym=self.sym, - antisym=self.antisym) + return self.__class__(self._fmodule, self._tensor_type, sym=self._sym, + antisym=self._antisym) def _new_comp(self, basis): r""" @@ -533,25 +581,25 @@ def _new_comp(self, basis): - an instance of :class:`~sage.tensor.modules.comp.Components` (or of one of its subclass) """ - fmodule = self.fmodule # the base free module - if self.sym == [] and self.antisym == []: - return Components(fmodule.ring, basis, self.tensor_rank, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) - for isym in self.sym: - if len(isym) == self.tensor_rank: - return CompFullySym(fmodule.ring, basis, self.tensor_rank, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) - for isym in self.antisym: - if len(isym) == self.tensor_rank: - return CompFullyAntiSym(fmodule.ring, basis, self.tensor_rank, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) - return CompWithSym(fmodule.ring, basis, self.tensor_rank, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter, - sym=self.sym, antisym=self.antisym) + fmodule = self._fmodule # the base free module + if self._sym == [] and self._antisym == []: + return Components(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) + for isym in self._sym: + if len(isym) == self._tensor_rank: + return CompFullySym(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) + for isym in self._antisym: + if len(isym) == self._tensor_rank: + return CompFullyAntiSym(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) + return CompWithSym(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter, + sym=self._sym, antisym=self._antisym) def comp(self, basis=None, from_basis=None): r""" @@ -568,7 +616,7 @@ def comp(self, basis=None, from_basis=None): - ``from_basis`` -- (default: None) basis from which the required components are computed, via the tensor change-of-basis formula, if they are not known already in the basis ``basis``; - if none, a basis is picked in ``self.components``. + if none, a basis is picked in ``self._components``. OUTPUT: @@ -579,7 +627,7 @@ class :class:`~sage.tensor.modules.comp.Components` Components of a tensor of type-(1,1):: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 @@ -615,44 +663,44 @@ class :class:`~sage.tensor.modules.comp.Components` [-3 0 0] """ - fmodule = self.fmodule + fmodule = self._fmodule if basis is None: - basis = fmodule.def_basis - if basis not in self.components: + basis = fmodule._def_basis + if basis not in self._components: # The components must be computed from # those in the basis from_basis if from_basis is None: - for known_basis in self.components: - if (known_basis, basis) in self.fmodule.basis_changes \ - and (basis, known_basis) in self.fmodule.basis_changes: + for known_basis in self._components: + if (known_basis, basis) in self._fmodule._basis_changes \ + and (basis, known_basis) in self._fmodule._basis_changes: from_basis = known_basis break if from_basis is None: raise ValueError("No basis could be found for computing " + "the components in the " + str(basis)) - elif from_basis not in self.components: + elif from_basis not in self._components: raise ValueError("The tensor components are not known in the " + "basis "+ str(from_basis)) - (n_con, n_cov) = self.tensor_type + (n_con, n_cov) = self._tensor_type if n_cov > 0: - if (from_basis, basis) not in fmodule.basis_changes: + if (from_basis, basis) not in fmodule._basis_changes: raise ValueError("The change-of-basis matrix from the " + str(from_basis) + " to the " + str(basis) + " has not been set.") pp = \ - fmodule.basis_changes[(from_basis, basis)].comp(from_basis) + fmodule._basis_changes[(from_basis, basis)].comp(from_basis) # pp not used if n_cov = 0 (pure contravariant tensor) if n_con > 0: - if (basis, from_basis) not in fmodule.basis_changes: + if (basis, from_basis) not in fmodule._basis_changes: raise ValueError("The change-of-basis matrix from the " + str(basis) + " to the " + str(from_basis) + " has not been set.") ppinv = \ - fmodule.basis_changes[(basis, from_basis)].comp(from_basis) + fmodule._basis_changes[(basis, from_basis)].comp(from_basis) # ppinv not used if n_con = 0 (pure covariant tensor) - old_comp = self.components[from_basis] + old_comp = self._components[from_basis] new_comp = self._new_comp(basis) - rank = self.tensor_rank + rank = self._tensor_rank # loop on the new components: for ind_new in new_comp.non_redundant_index_generator(): # Summation on the old components multiplied by the proper @@ -666,9 +714,9 @@ class :class:`~sage.tensor.modules.comp.Components` t *= pp[[ind_old[i], ind_new[i]]] res += t new_comp[ind_new] = res - self.components[basis] = new_comp + self._components[basis] = new_comp # end of case where the computation was necessary - return self.components[basis] + return self._components[basis] def set_comp(self, basis=None): r""" @@ -694,7 +742,7 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not Setting components of a type-(1,1) tensor:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t.set_comp()[0,1] = -3 @@ -710,7 +758,7 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not sage: f = M.basis('f') sage: t.set_comp(f)[0,1] = 4 - sage: t.components.keys() # the components w.r.t. basis e have been deleted + sage: t._components.keys() # the components w.r.t. basis e have been deleted [basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] sage: t.view(f) t = 4 f_0*f^1 @@ -720,27 +768,24 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] - sage: M.basis_changes[(e,f)] = a - sage: M.basis_changes[(f,e)] = a.inverse() + sage: M.set_basis_change(e, f, a) sage: t.view(e) t = -4 e_1*e^2 - sage: t.components.keys() # random output (dictionary keys) + sage: t._components.keys() # random output (dictionary keys) [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] """ - if self is self.parent()._zero_element: #!# this is maybe not very efficient - raise ValueError("The zero element cannot be changed.") if basis is None: - basis = self.fmodule.def_basis - if basis not in self.components: - if basis not in self.fmodule.known_bases: + basis = self._fmodule._def_basis + if basis not in self._components: + if basis not in self._fmodule._known_bases: raise ValueError("The " + str(basis) + " has not been " + - "defined on the " + str(self.fmodule)) - self.components[basis] = self._new_comp(basis) + "defined on the " + str(self._fmodule)) + self._components[basis] = self._new_comp(basis) self._del_derived() # deletes the derived quantities self.del_other_comp(basis) - return self.components[basis] + return self._components[basis] def add_comp(self, basis=None): r""" @@ -772,7 +817,7 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not Setting components of a type-(1,1) tensor:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t.add_comp()[0,1] = -3 @@ -791,7 +836,7 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not The components w.r.t. basis e have been kept:: - sage: t.components.keys() # # random output (dictionary keys) + sage: t._components.keys() # # random output (dictionary keys) [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] sage: t.view(f) @@ -800,14 +845,14 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not t = -3 e_0*e^1 + 2 e_1*e^2 """ - if basis is None: basis = self.fmodule.def_basis - if basis not in self.components: - if basis not in self.fmodule.known_bases: + if basis is None: basis = self._fmodule._def_basis + if basis not in self._components: + if basis not in self._fmodule._known_bases: raise ValueError("The " + str(basis) + " has not been " + - "defined on the " + str(self.fmodule)) - self.components[basis] = self._new_comp(basis) + "defined on the " + str(self._fmodule)) + self._components[basis] = self._new_comp(basis) self._del_derived() # deletes the derived quantities - return self.components[basis] + return self._components[basis] def del_other_comp(self, basis=None): @@ -823,39 +868,39 @@ def del_other_comp(self, basis=None): Deleting components of a module element:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: u = M([2,1,-5]) sage: f = M.basis('f') sage: u.add_comp(f)[:] = [0,4,2] - sage: u.components.keys() # random output (dictionary keys) + sage: u._components.keys() # random output (dictionary keys) [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring, basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] sage: u.del_other_comp(f) - sage: u.components.keys() + sage: u._components.keys() [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] Let us restore the components w.r.t. e and delete those w.r.t. f:: sage: u.add_comp(e)[:] = [2,1,-5] - sage: u.components.keys() # random output (dictionary keys) + sage: u._components.keys() # random output (dictionary keys) [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring, basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] sage: u.del_other_comp() # default argument: basis = e - sage: u.components.keys() + sage: u._components.keys() [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] """ - if basis is None: basis = self.fmodule.def_basis - if basis not in self.components: + if basis is None: basis = self._fmodule._def_basis + if basis not in self._components: raise ValueError("The components w.r.t. the " + str(basis) + " have not been defined.") to_be_deleted = [] - for other_basis in self.components: + for other_basis in self._components: if other_basis != basis: to_be_deleted.append(other_basis) for other_basis in to_be_deleted: - del self.components[other_basis] + del self._components[other_basis] def __getitem__(self, args): r""" @@ -871,20 +916,20 @@ def __getitem__(self, args): """ if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], (int, Integer, slice)): - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis else: basis = args[0] args = args[1:] else: if isinstance(args, (int, Integer, slice)): - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis elif not isinstance(args[0], (int, Integer, slice)): basis = args[0] args = args[1:] if len(args)==1: args = args[0] # to accommodate for [e,:] syntax else: - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis return self.comp(basis)[args] @@ -904,20 +949,20 @@ def __setitem__(self, args, value): """ if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], (int, Integer, slice, tuple)): - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis else: basis = args[0] args = args[1:] else: if isinstance(args, (int, Integer, slice)): - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis elif not isinstance(args[0], (int, Integer, slice)): basis = args[0] args = args[1:] if len(args)==1: args = args[0] # to accommodate for [e,:] syntax else: - basis = self.fmodule.def_basis + basis = self._fmodule._def_basis self.set_comp(basis)[args] = value @@ -931,7 +976,7 @@ def copy(self): Copy of a tensor of type (1,1):: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 @@ -955,8 +1000,8 @@ def copy(self): """ resu = self._new_instance() - for basis, comp in self.components.items(): - resu.components[basis] = comp.copy() + for basis, comp in self._components.iteritems(): + resu._components[basis] = comp.copy() return resu def common_basis(self, other): @@ -968,7 +1013,7 @@ def common_basis(self, other): If the current components of ``self`` and ``other`` are all relative to different bases, a common basis is searched by performing a component transformation, via the transformations listed in - ``self.fmodule.basis_changes``, still privileging transformations to + ``self._fmodule._basis_changes``, still privileging transformations to the free module's default basis. INPUT: @@ -986,34 +1031,33 @@ def common_basis(self, other): Common basis for the components of two module elements:: - sage: M = FiniteFreeModule(ZZ, 3, name='M', start_index=1) + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: u = M([2,1,-5]) sage: f = M.basis('f') - sage: M.basis_changes.clear() # to ensure that bases e and f are unrelated at this stage + sage: M._basis_changes.clear() # to ensure that bases e and f are unrelated at this stage sage: v = M([0,4,2], basis=f) sage: u.common_basis(v) The above result is None since u and v have been defined on different bases and no connection between these bases have been set:: - sage: u.components.keys() + sage: u._components.keys() [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] - sage: v.components.keys() + sage: v._components.keys() [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] Linking bases e and f changes the result:: sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] - sage: M.basis_changes[(e,f)] = a - sage: M.basis_changes[(f,e)] = a.inverse() + sage: M.set_basis_change(e, f, a) sage: u.common_basis(v) basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring Indeed, v is now known in basis e:: - sage: v.components.keys() # random output (dictionary keys) + sage: v._components.keys() # random output (dictionary keys) [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring, basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] @@ -1021,42 +1065,42 @@ def common_basis(self, other): # Compatibility checks: if not isinstance(other, FreeModuleTensor): raise TypeError("The argument must be a tensor on a free module.") - fmodule = self.fmodule - if other.fmodule != fmodule: + fmodule = self._fmodule + if other._fmodule != fmodule: raise TypeError("The two tensors are not defined on the same " + "free module.") - def_basis = fmodule.def_basis + def_basis = fmodule._def_basis # # 1/ Search for a common basis among the existing components, i.e. # without performing any component transformation. # ------------------------------------------------------------- - if def_basis in self.components and def_basis in other.components: + if def_basis in self._components and def_basis in other._components: return def_basis # the module's default basis is privileged - for basis1 in self.components: - if basis1 in other.components: + for basis1 in self._components: + if basis1 in other._components: return basis1 # 2/ Search for a common basis via one component transformation # ---------------------------------------------------------- # If this point is reached, it is indeed necessary to perform at least # one component transformation to get a common basis - if def_basis in self.components: - for obasis in other.components: - if (obasis, def_basis) in fmodule.basis_changes: + if def_basis in self._components: + for obasis in other._components: + if (obasis, def_basis) in fmodule._basis_changes: other.comp(def_basis, from_basis=obasis) return def_basis - if def_basis in other.components: - for sbasis in self.components: - if (sbasis, def_basis) in fmodule.basis_changes: + if def_basis in other._components: + for sbasis in self._components: + if (sbasis, def_basis) in fmodule._basis_changes: self.comp(def_basis, from_basis=sbasis) return def_basis # If this point is reached, then def_basis cannot be a common basis # via a single component transformation - for sbasis in self.components: - for obasis in other.components: - if (obasis, sbasis) in fmodule.basis_changes: + for sbasis in self._components: + for obasis in other._components: + if (obasis, sbasis) in fmodule._basis_changes: other.comp(sbasis, from_basis=obasis) return sbasis - if (sbasis, obasis) in fmodule.basis_changes: + if (sbasis, obasis) in fmodule._basis_changes: self.comp(obasis, from_basis=sbasis) return obasis # @@ -1064,16 +1108,16 @@ def common_basis(self, other): # ----------------------------------------------------------- # If this point is reached, it is indeed necessary to perform at two # component transformation to get a common basis - for sbasis in self.components: - for obasis in other.components: - if (sbasis, def_basis) in fmodule.basis_changes and \ - (obasis, def_basis) in fmodule.basis_changes: + for sbasis in self._components: + for obasis in other._components: + if (sbasis, def_basis) in fmodule._basis_changes and \ + (obasis, def_basis) in fmodule._basis_changes: self.comp(def_basis, from_basis=sbasis) other.comp(def_basis, from_basis=obasis) return def_basis - for basis in fmodule.known_bases: - if (sbasis, basis) in fmodule.basis_changes and \ - (obasis, basis) in fmodule.basis_changes: + for basis in fmodule._known_bases: + if (sbasis, basis) in fmodule._basis_changes and \ + (obasis, basis) in fmodule._basis_changes: self.comp(basis, from_basis=sbasis) other.comp(basis, from_basis=obasis) return basis @@ -1095,11 +1139,11 @@ def pick_a_basis(self): representing the basis """ - if self.fmodule.def_basis in self.components: - return self.fmodule.def_basis # the default basis is privileged + if self._fmodule._def_basis in self._components: + return self._fmodule._def_basis # the default basis is privileged else: # a basis is picked arbitrarily: - return self.components.items()[0][0] + return self._components.items()[0][0] def __eq__(self, other): r""" @@ -1114,7 +1158,7 @@ def __eq__(self, other): - True if ``self`` is equal to ``other`` and False otherwise """ - if self.tensor_rank == 0: + if self._tensor_rank == 0: raise NotImplementedError("Scalar comparison not implemented.") if isinstance(other, (int, Integer)): # other should be 0 if other == 0: @@ -1124,14 +1168,14 @@ def __eq__(self, other): elif not isinstance(other, FreeModuleTensor): return False else: # other is another tensor - if other.fmodule != self.fmodule: + if other._fmodule != self._fmodule: return False - if other.tensor_type != self.tensor_type: + if other._tensor_type != self._tensor_type: return False basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the comparison.") - return bool(self.components[basis] == other.components[basis]) + return bool(self._components[basis] == other._components[basis]) def __ne__(self, other): r""" @@ -1158,12 +1202,12 @@ def __pos__(self): """ result = self._new_instance() - for basis in self.components: - result.components[basis] = + self.components[basis] - if self.name is not None: - result.name = '+' + self.name - if self.latex_name is not None: - result.latex_name = '+' + self.latex_name + for basis in self._components: + result._components[basis] = + self._components[basis] + if self._name is not None: + result._name = '+' + self._name + if self._latex_name is not None: + result._latex_name = '+' + self._latex_name return result def __neg__(self): @@ -1176,12 +1220,12 @@ def __neg__(self): """ result = self._new_instance() - for basis in self.components: - result.components[basis] = - self.components[basis] - if self.name is not None: - result.name = '-' + self.name - if self.latex_name is not None: - result.latex_name = '-' + self.latex_name + for basis in self._components: + result._components[basis] = - self._components[basis] + if self._name is not None: + result._name = '-' + self._name + if self._latex_name is not None: + result._latex_name = '-' + self._latex_name return result ######### ModuleElement arithmetic operators ######## @@ -1206,12 +1250,12 @@ def _add_(self, other): basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the addition.") - comp_result = self.components[basis] + other.components[basis] - result = self.fmodule.tensor_from_comp(self.tensor_type, comp_result) - if self.name is not None and other.name is not None: - result.name = self.name + '+' + other.name - if self.latex_name is not None and other.latex_name is not None: - result.latex_name = self.latex_name + '+' + other.latex_name + comp_result = self._components[basis] + other._components[basis] + result = self._fmodule.tensor_from_comp(self._tensor_type, comp_result) + if self._name is not None and other._name is not None: + result._name = self._name + '+' + other._name + if self._latex_name is not None and other._latex_name is not None: + result._latex_name = self._latex_name + '+' + other._latex_name return result def _sub_(self, other): @@ -1234,12 +1278,12 @@ def _sub_(self, other): basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the subtraction.") - comp_result = self.components[basis] - other.components[basis] - result = self.fmodule.tensor_from_comp(self.tensor_type, comp_result) - if self.name is not None and other.name is not None: - result.name = self.name + '-' + other.name - if self.latex_name is not None and other.latex_name is not None: - result.latex_name = self.latex_name + '-' + other.latex_name + comp_result = self._components[basis] - other._components[basis] + result = self._fmodule.tensor_from_comp(self._tensor_type, comp_result) + if self._name is not None and other._name is not None: + result._name = self._name + '-' + other._name + if self._latex_name is not None and other._latex_name is not None: + result._latex_name = self._latex_name + '-' + other._latex_name return result def _rmul_(self, other): @@ -1252,8 +1296,8 @@ def _rmul_(self, other): raise NotImplementedError("Left tensor product not implemented.") # Left multiplication by a scalar: result = self._new_instance() - for basis in self.components: - result.components[basis] = other * self.components[basis] + for basis in self._components: + result._components[basis] = other * self._components[basis] return result ######### End of ModuleElement arithmetic operators ######## @@ -1285,26 +1329,26 @@ def __mul__(self, other): basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the tensor product.") - comp_prov = self.components[basis] * other.components[basis] + comp_prov = self._components[basis] * other._components[basis] # Reordering of the contravariant and covariant indices: - k1, l1 = self.tensor_type - k2, l2 = other.tensor_type + k1, l1 = self._tensor_type + k2, l2 = other._tensor_type if l1 != 0: comp_result = comp_prov.swap_adjacent_indices(k1, - self.tensor_rank, - self.tensor_rank+k2) + self._tensor_rank, + self._tensor_rank+k2) else: comp_result = comp_prov # no reordering is necessary - result = self.fmodule.tensor_from_comp((k1+k2, l1+l2), comp_result) - result.name = format_mul_txt(self.name, '*', other.name) - result.latex_name = format_mul_latex(self.latex_name, r'\otimes ', - other.latex_name) + result = self._fmodule.tensor_from_comp((k1+k2, l1+l2), comp_result) + result._name = format_mul_txt(self._name, '*', other._name) + result._latex_name = format_mul_latex(self._latex_name, r'\otimes ', + other._latex_name) return result else: # multiplication by a scalar: result = self._new_instance() - for basis in self.components: - result.components[basis] = other * self.components[basis] + for basis in self._components: + result._components[basis] = other * self._components[basis] return result @@ -1313,8 +1357,8 @@ def __div__(self, other): Division (by a scalar) """ result = self._new_instance() - for basis in self.components: - result.components[basis] = self.components[basis] / other + for basis in self._components: + result._components[basis] = self._components[basis] / other return result @@ -1332,43 +1376,55 @@ def __call__(self, *args): from free_module_alt_form import FreeModuleLinForm # Consistency checks: p = len(args) - if p != self.tensor_rank: - raise TypeError(str(self.tensor_rank) + + if p != self._tensor_rank: + raise TypeError(str(self._tensor_rank) + " arguments must be provided.") - for i in range(self.tensor_type[0]): + for i in range(self._tensor_type[0]): if not isinstance(args[i], FreeModuleLinForm): raise TypeError("The argument no. " + str(i+1) + " must be a linear form.") - for i in range(self.tensor_type[0],p): - if not isinstance(args[i], FiniteFreeModuleElement): + for i in range(self._tensor_type[0],p): + if not isinstance(args[i], FiniteRankFreeModuleElement): raise TypeError("The argument no. " + str(i+1) + " must be a module element.") - fmodule = self.fmodule + fmodule = self._fmodule # Search for a common basis basis = None # First try with the module's default basis - def_basis = fmodule.def_basis - if def_basis in self.components: + def_basis = fmodule._def_basis + if def_basis in self._components: basis = def_basis for arg in args: - if def_basis not in arg.components: + if def_basis not in arg._components: basis = None break if basis is None: # Search for another basis: - for bas in self.components: + for bas in self._components: basis = bas for arg in args: - if bas not in arg.components: + if bas not in arg._components: + basis = None + break + if basis is not None: # common basis found ! + break + if basis is None: + # A last attempt to find a common basis, possibly via a + # change-of-components transformation + for arg in args: + self.common_basis(arg) # to trigger some change of components + for bas in self._components: + basis = bas + for arg in args: + if bas not in arg._components: basis = None break if basis is not None: # common basis found ! break if basis is None: raise ValueError("No common basis for the components.") - t = self.components[basis] - v = [args[i].components[basis] for i in range(p)] - + t = self._components[basis] + v = [args[i]._components[basis] for i in range(p)] res = 0 for ind in t.index_generator(): prod = t[[ind]] @@ -1376,39 +1432,39 @@ def __call__(self, *args): prod *= v[i][[ind[i]]] res += prod # Name of the output: - if hasattr(res, 'name'): + if hasattr(res, '_name'): res_name = None - if self.name is not None: - res_name = self.name + "(" + if self._name is not None: + res_name = self._name + "(" for i in range(p-1): - if args[i].name is not None: - res_name += args[i].name + "," + if args[i]._name is not None: + res_name += args[i]._name + "," else: res_name = None break if res_name is not None: - if args[p-1].name is not None: - res_name += args[p-1].name + ")" + if args[p-1]._name is not None: + res_name += args[p-1]._name + ")" else: res_name = None - res.name = res_name + res._name = res_name # LaTeX symbol of the output: - if hasattr(res, 'latex_name'): + if hasattr(res, '_latex_name'): res_latex = None - if self.latex_name is not None: - res_latex = self.latex_name + r"\left(" + if self._latex_name is not None: + res_latex = self._latex_name + r"\left(" for i in range(p-1): - if args[i].latex_name is not None: - res_latex += args[i].latex_name + "," + if args[i]._latex_name is not None: + res_latex += args[i]._latex_name + "," else: res_latex = None break if res_latex is not None: - if args[p-1].latex_name is not None: - res_latex += args[p-1].latex_name + r"\right)" + if args[p-1]._latex_name is not None: + res_latex += args[p-1]._latex_name + r"\right)" else: res_latex = None - res.latex_name = res_latex + res._latex_name = res_latex return res def self_contract(self, pos1, pos2): @@ -1430,7 +1486,7 @@ def self_contract(self, pos1, pos2): Contraction on the two slots of a type-(1,1) tensor:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring sage: a = M.tensor((1,1), name='a') ; a @@ -1484,8 +1540,8 @@ def self_contract(self, pos1, pos2): """ # The indices at pos1 and pos2 must be of different types: - k_con = self.tensor_type[0] - l_cov = self.tensor_type[1] + k_con = self._tensor_type[0] + l_cov = self._tensor_type[1] if pos1 < k_con and pos2 < k_con: raise IndexError("Contraction on two contravariant indices is " + "not allowed.") @@ -1493,15 +1549,15 @@ def self_contract(self, pos1, pos2): raise IndexError("Contraction on two covariant indices is " + "not allowed.") # Frame selection for the computation: - if self.fmodule.def_basis in self.components: - basis = self.fmodule.def_basis + if self._fmodule._def_basis in self._components: + basis = self._fmodule._def_basis else: # a basis is picked arbitrarily: basis = self.pick_a_basis() - resu_comp = self.components[basis].self_contract(pos1, pos2) - if self.tensor_rank == 2: # result is a scalar + resu_comp = self._components[basis].self_contract(pos1, pos2) + if self._tensor_rank == 2: # result is a scalar return resu_comp else: - return self.fmodule.tensor_from_comp((k_con-1, l_cov-1), resu_comp) + return self._fmodule.tensor_from_comp((k_con-1, l_cov-1), resu_comp) def contract(self, *args): r""" @@ -1524,7 +1580,7 @@ def contract(self, *args): Contraction of a tensor of type (0,1) with a tensor of type (1,0):: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form() # tensor of type (0,1) is a linear form sage: a[:] = [-3,2,1] @@ -1628,9 +1684,9 @@ def contract(self, *args): [ 7 6 -4] [ 3 -1 -2] sage: b.del_other_comp(h) # deletes components w.r.t. basis e - sage: b.components.keys() # indeed: + sage: b._components.keys() # indeed: [basis (h_0,h_1,h_2) on the rank-3 free module M over the Integer Ring] - sage: a.components.keys() # while a is known only in basis e: + sage: a._components.keys() # while a is known only in basis e: [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring] sage: s1 = a.contract(1, b, 1) ; s1 # yet the computation is possible type-(1,2) tensor on the rank-3 free module M over the Integer Ring @@ -1640,12 +1696,12 @@ def contract(self, *args): """ nargs = len(args) if nargs == 1: - pos1 = self.tensor_rank - 1 + pos1 = self._tensor_rank - 1 other = args[0] pos2 = 0 elif nargs == 2: if isinstance(args[0], FreeModuleTensor): - pos1 = self.tensor_rank - 1 + pos1 = self._tensor_rank - 1 other = args[0] pos2 = args[1] else: @@ -1661,10 +1717,9 @@ def contract(self, *args): str(nargs) + " arguments provided, while between 1 and 3 are expected.") if not isinstance(other, FreeModuleTensor): - raise TypeError("For the contraction, other must be a tensor " + - "field.") - k1, l1 = self.tensor_type - k2, l2 = other.tensor_type + raise TypeError("For the contraction, other must be a tensor ") + k1, l1 = self._tensor_type + k2, l2 = other._tensor_type if pos1 < k1 and pos2 < k2: raise TypeError("Contraction not possible: the two index " + "positions are both contravariant.") @@ -1674,8 +1729,8 @@ def contract(self, *args): basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the contraction.") - cmp_res = self.components[basis].contract(pos1, - other.components[basis], pos2) + cmp_res = self._components[basis].contract(pos1, + other._components[basis], pos2) # reordering of the indices to have all contravariant indices first: if k2 > 1: if pos1 < k1: @@ -1686,7 +1741,7 @@ def contract(self, *args): if type_res == (0, 0): return cmp_res # scalar case else: - return self.fmodule.tensor_from_comp(type_res, cmp_res) + return self._fmodule.tensor_from_comp(type_res, cmp_res) def symmetrize(self, pos=None, basis=None): @@ -1713,7 +1768,7 @@ def symmetrize(self, pos=None, basis=None): Symmetrization of a tensor of type (2,0):: - sage: M = FiniteFreeModule(QQ, 3, name='M') + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,0)) sage: t[:] = [[2,1,-3],[0,-4,5],[-1,4,2]] @@ -1833,9 +1888,9 @@ def symmetrize(self, pos=None, basis=None): """ if pos is None: - pos = range(self.tensor_rank) + pos = range(self._tensor_rank) # check whether the symmetrization is possible: - pos_cov = self.tensor_type[0] # first covariant position + pos_cov = self._tensor_type[0] # first covariant position pos0 = pos[0] if pos0 < pos_cov: # pos0 is a contravariant position for k in range(1,len(pos)): @@ -1855,8 +1910,8 @@ def symmetrize(self, pos=None, basis=None): "arguments of the same type.") if basis is None: basis = self.pick_a_basis() - res_comp = self.components[basis].symmetrize(pos) - return self.fmodule.tensor_from_comp(self.tensor_type, res_comp) + res_comp = self._components[basis].symmetrize(pos) + return self._fmodule.tensor_from_comp(self._tensor_type, res_comp) def antisymmetrize(self, pos=None, basis=None): @@ -1883,7 +1938,7 @@ def antisymmetrize(self, pos=None, basis=None): Antisymmetrization of a tensor of type (2,0):: - sage: M = FiniteFreeModule(QQ, 3, name='M') + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,0)) sage: t[:] = [[1,-2,3], [4,5,6], [7,8,-9]] @@ -2027,9 +2082,9 @@ def antisymmetrize(self, pos=None, basis=None): """ if pos is None: - pos = range(self.tensor_rank) + pos = range(self._tensor_rank) # check whether the antisymmetrization is possible: - pos_cov = self.tensor_type[0] # first covariant position + pos_cov = self._tensor_type[0] # first covariant position pos0 = pos[0] if pos0 < pos_cov: # pos0 is a contravariant position for k in range(1,len(pos)): @@ -2049,8 +2104,8 @@ def antisymmetrize(self, pos=None, basis=None): "arguments of the same type.") if basis is None: basis = self.pick_a_basis() - res_comp = self.components[basis].antisymmetrize(pos) - return self.fmodule.tensor_from_comp(self.tensor_type, res_comp) + res_comp = self._components[basis].antisymmetrize(pos) + return self._fmodule.tensor_from_comp(self._tensor_type, res_comp) @@ -2065,11 +2120,11 @@ def antisymmetrize(self, pos=None, basis=None): # ... -class FiniteFreeModuleElement(FreeModuleTensor): +class FiniteRankFreeModuleElement(FreeModuleTensor): r""" Element of a free module of finite rank over a commutative ring. - The class :class:`FiniteFreeModuleElement` inherits from + The class :class:`FiniteRankFreeModuleElement` inherits from :class:`FreeModuleTensor` because the elements of a free module `M` of finite rank over a commutative ring `R` are identified with tensors of type (1,0) on `M` via the canonical map @@ -2088,7 +2143,7 @@ class FiniteFreeModuleElement(FreeModuleTensor): INPUT: - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an - instance of :class:`FiniteFreeModule`) + instance of :class:`FiniteRankFreeModule`) - ``name`` -- (default: None) name given to the element - ``latex_name`` -- (default: None) LaTeX symbol to denote the element; if none is provided, the LaTeX symbol is set to ``name`` @@ -2097,11 +2152,11 @@ class FiniteFreeModuleElement(FreeModuleTensor): Let us consider a rank-3 free module over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring - There are four ways to construct an element of the free module M: the first + There are three ways to construct an element of the free module M: the first one (recommended) is via the operator __call__ acting on the free module:: sage: v = M([2,0,-1], basis=e, name='v') ; v @@ -2111,38 +2166,27 @@ class FiniteFreeModuleElement(FreeModuleTensor): sage: v.parent() is M True - The second way is by a direct call to the class constructor:: - - sage: from sage.tensor.modules.free_module_tensor import FiniteFreeModuleElement - sage: v2 = FiniteFreeModuleElement(M, name='v') - sage: v2[0], v2[2] = 2, -1 # setting the nonzero components in the default basis (e) - sage: v2 - element v of the rank-3 free module M over the Integer Ring - sage: v2.view() - v = 2 e_0 - e_2 - sage: v2 == v - True - The third way is to construct a tensor of type (1,0) on `M` (cf. the + The second way is to construct a tensor of type (1,0) on `M` (cf. the canonical identification `M^{**}=M` recalled above):: - sage: v3 = M.tensor((1,0), name='v') - sage: v3[0], v3[2] = 2, -1 ; v3 + sage: v2 = M.tensor((1,0), name='v') + sage: v2[0], v2[2] = 2, -1 ; v2 element v of the rank-3 free module M over the Integer Ring - sage: v3.view() + sage: v2.view() v = 2 e_0 - e_2 - sage: v3 == v + sage: v2 == v True - Finally, the fourth way is via some linear combination of the basis + Finally, the third way is via some linear combination of the basis elements:: - sage: v4 = 2*e[0] - e[2] - sage: v4.set_name('v') ; v4 # in this case, the name has to be set separately + sage: v3 = 2*e[0] - e[2] + sage: v3.set_name('v') ; v3 # in this case, the name has to be set separately element v of the rank-3 free module M over the Integer Ring - sage: v4.view() + sage: v3.view() v = 2 e_0 - e_2 - sage: v4 == v + sage: v3 == v True The canonical identification `M^{**}=M` is implemented by letting the @@ -2163,7 +2207,7 @@ class FiniteFreeModuleElement(FreeModuleTensor): Addition:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring sage: a = M([0,1,3], name='a') ; a @@ -2228,9 +2272,9 @@ def _repr_(self): String representation of the object. """ description = "element " - if self.name is not None: - description += self.name + " " - description += "of the " + str(self.fmodule) + if self._name is not None: + description += self._name + " " + description += "of the " + str(self._fmodule) return description def _new_comp(self, basis): @@ -2240,16 +2284,15 @@ def _new_comp(self, basis): This method, which is already implemented in :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency """ - fmodule = self.fmodule # the base free module - return Components(fmodule.ring, basis, 1, start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) + fmodule = self._fmodule # the base free module + return Components(fmodule._ring, basis, 1, start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) def _new_instance(self): r""" - Create a :class:`FiniteFreeModuleElement` instance. + Create an instance of the same class as ``self``. """ - return FiniteFreeModuleElement(self.fmodule) + return self.__class__(self._fmodule) - diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py index 548973fc0e9..b3570881d73 100644 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -1,27 +1,24 @@ """ -Specific rank-2 tensors on free modules +Type-(1,1) tensors on free modules -Four derived classes of +Three derived classes of :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` are devoted -to rank-2 tensors: +to type-(1,1) tensors: -* :class:`FreeModuleEndomorphism` for endomorphisms (type-(1,1) tensors) +* :class:`FreeModuleEndomorphism` for endomorphisms (generic type-(1,1) tensors) * :class:`FreeModuleAutomorphism` for invertible endomorphisms * :class:`FreeModuleIdentityMap` for the identity map on a free module -* :class:`FreeModuleSymBilinForm` for symmetric bilinear forms (symmetric - type-(0,2) tensors) - -Antisymmetric bilinear forms are dealt with by the class -:class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`. - AUTHORS: - Eric Gourgoulhon, Michal Bejger (2014): initial version -EXAMPLES: +TODO: + +* Implement these specific tensors as elements of a parent class for free + module endomorphisms, with coercion to the module of type-(1,1) tensors. """ #****************************************************************************** @@ -43,7 +40,7 @@ class FreeModuleEndomorphism(FreeModuleTensor): INPUT: - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteFreeModule`) + (must be an instance of :class:`FiniteRankFreeModule`) - ``name`` -- (default: None) name given to the endomorphism - ``latex_name`` -- (default: None) LaTeX symbol to denote the endomorphism; if none is provided, the LaTeX symbol is set to ``name`` @@ -52,7 +49,7 @@ class FreeModuleEndomorphism(FreeModuleTensor): Endomorphism on a rank-3 module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.endomorphism('T') ; t endomorphism T on the rank-3 free module M over the Integer Ring @@ -60,13 +57,13 @@ class FreeModuleEndomorphism(FreeModuleTensor): sage: t.parent() free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring - sage: t.tensor_type + sage: t.tensor_type() (1, 1) - sage: t.tensor_rank + sage: t.tensor_rank() 2 Consequently, an endomorphism can also be created by the module method - :meth:`~sage.tensor.modules.finite_free_module.FiniteFreeModule.tensor`:: + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor`:: sage: t = M.tensor((1,1), name='T') ; t endomorphism T on the rank-3 free module M over the Integer Ring @@ -107,24 +104,24 @@ def _repr_(self): String representation of the object. """ description = "endomorphism " - if self.name is not None: - description += self.name + " " - description += "on the " + str(self.fmodule) + if self._name is not None: + description += self._name + " " + description += "on the " + str(self._fmodule) return description def _new_instance(self): r""" - Create a :class:`FreeModuleEndomorphism` instance. + Create an instance of the same class as ``self``. """ - return FreeModuleEndomorphism(self.fmodule) + return self.__class__(self._fmodule) def __call__(self, *arg): r""" Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single argument (module element). """ - from free_module_tensor import FiniteFreeModuleElement + from free_module_tensor import FiniteRankFreeModuleElement if len(arg) > 1: # the endomorphism acting as a type (1,1) tensor on a pair # (linear form, module element), returning a scalar: @@ -132,12 +129,12 @@ def __call__(self, *arg): # the endomorphism acting as such, on a module element, returning a # module element: vector = arg[0] - if not isinstance(vector, FiniteFreeModuleElement): + if not isinstance(vector, FiniteRankFreeModuleElement): raise TypeError("The argument must be an element of a free module.") basis = self.common_basis(vector) - t = self.components[basis] - v = vector.components[basis] - fmodule = self.fmodule + t = self._components[basis] + v = vector._components[basis] + fmodule = self._fmodule result = vector._new_instance() for i in fmodule.irange(): res = 0 @@ -145,14 +142,14 @@ def __call__(self, *arg): res += t[[i,j]]*v[[j]] result.set_comp(basis)[i] = res # Name of the output: - result.name = None - if self.name is not None and vector.name is not None: - result.name = self.name + "(" + vector.name + ")" + result._name = None + if self._name is not None and vector._name is not None: + result._name = self._name + "(" + vector._name + ")" # LaTeX symbol for the output: - result.latex_name = None - if self.latex_name is not None and vector.latex_name is not None: - result.latex_name = self.latex_name + r"\left(" + \ - vector.latex_name + r"\right)" + result._latex_name = None + if self._latex_name is not None and vector._latex_name is not None: + result._latex_name = self._latex_name + r"\left(" + \ + vector._latex_name + r"\right)" return result #****************************************************************************** @@ -164,7 +161,7 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): INPUT: - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteFreeModule`) + (must be an instance of :class:`FiniteRankFreeModule`) - ``name`` -- (default: None) name given to the automorphism - ``latex_name`` -- (default: None) LaTeX symbol to denote the automorphism; if none is provided, the LaTeX symbol is set to ``name`` @@ -173,7 +170,7 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): Automorphism on a rank-2 free module (vector space) on `\QQ`:: - sage: M = FiniteFreeModule(QQ, 2, name='M') + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: a = M.automorphism('A') ; a automorphism A on the rank-2 free module M over the Rational Field @@ -181,9 +178,9 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): sage: a.parent() free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field - sage: a.tensor_type + sage: a.tensor_type() (1, 1) - sage: a.tensor_rank + sage: a.tensor_rank() 2 Setting the components in a basis:: @@ -221,16 +218,10 @@ def _repr_(self): String representation of the object. """ description = "automorphism " - if self.name is not None: - description += self.name + " " - description += "on the " + str(self.fmodule) + if self._name is not None: + description += self._name + " " + description += "on the " + str(self._fmodule) return description - - def _new_instance(self): - r""" - Create a :class:`FreeModuleAutomorphism` instance. - """ - return FreeModuleAutomorphism(self.fmodule) def _del_derived(self): r""" @@ -255,7 +246,7 @@ def inverse(self): Inverse of an automorphism on a rank-3 free module:: - sage: M = FiniteFreeModule(QQ, 3, name='M') + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: a = M.automorphism('A') sage: e = M.basis('e') sage: a[:] = [[1,0,-1], [0,3,0], [0,0,2]] @@ -283,19 +274,18 @@ def inverse(self): from sage.matrix.constructor import matrix from comp import Components if self._inverse is None: - if self.name is None: + if self._name is None: inv_name = None else: - inv_name = self.name + '^(-1)' - if self.latex_name is None: + inv_name = self._name + '^(-1)' + if self._latex_name is None: inv_latex_name = None else: - inv_latex_name = self.latex_name + r'^{-1}' - fmodule = self.fmodule - si = fmodule.sindex ; nsi = fmodule._rank + si - self._inverse = FreeModuleAutomorphism(fmodule, inv_name, - inv_latex_name) - for basis in self.components: + inv_latex_name = self._latex_name + r'^{-1}' + fmodule = self._fmodule + si = fmodule._sindex ; nsi = fmodule._rank + si + self._inverse = self.__class__(fmodule, inv_name, inv_latex_name) + for basis in self._components: try: mat_self = matrix( [[self.comp(basis)[[i, j]] @@ -303,12 +293,12 @@ def inverse(self): except (KeyError, ValueError): continue mat_inv = mat_self.inverse() - cinv = Components(fmodule.ring, basis, 2, start_index=si, - output_formatter=fmodule.output_formatter) + cinv = Components(fmodule._ring, basis, 2, start_index=si, + output_formatter=fmodule._output_formatter) for i in range(si, nsi): for j in range(si, nsi): cinv[i, j] = mat_inv[i-si,j-si] - self._inverse.components[basis] = cinv + self._inverse._components[basis] = cinv return self._inverse @@ -321,7 +311,7 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): INPUT: - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteFreeModule`) + (must be an instance of :class:`FiniteRankFreeModule`) - ``name`` -- (default: 'Id') name given to the identity map. - ``latex_name`` -- (default: None) LaTeX symbol to denote the identity map; if none is provided, the LaTeX symbol is set to ``name`` @@ -330,7 +320,7 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): Identity map on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() ; a identity map on the rank-3 free module M over the Integer Ring @@ -347,9 +337,9 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): sage: a.parent() free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring - sage: a.tensor_type + sage: a.tensor_type() (1, 1) - sage: a.tensor_rank + sage: a.tensor_rank() 2 Its components are Kronecker deltas in any basis:: @@ -421,9 +411,9 @@ def _repr_(self): String representation of the object. """ description = "identity map " - if self.name != 'Id': - description += self.name + " " - description += "on the " + str(self.fmodule) + if self._name != 'Id': + description += self._name + " " + description += "on the " + str(self._fmodule) return description def _del_derived(self): @@ -433,13 +423,6 @@ def _del_derived(self): """ # FreeModuleAutomorphism._del_derived is bypassed: FreeModuleEndomorphism._del_derived(self) - - def _new_instance(self): - r""" - Create a :class:`FreeModuleIdentityMap` instance. - - """ - return FreeModuleIdentityMap(self.fmodule) def _new_comp(self, basis): r""" @@ -447,11 +430,11 @@ def _new_comp(self, basis): """ from comp import KroneckerDelta - fmodule = self.fmodule # the base free module - return KroneckerDelta(fmodule.ring, basis, start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) + fmodule = self._fmodule # the base free module + return KroneckerDelta(fmodule._ring, basis, start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) - def comp(self, basis=None): + def comp(self, basis=None, from_basis=None): r""" Return the components in a given basis, as a Kronecker delta. @@ -460,6 +443,8 @@ def comp(self, basis=None): - ``basis`` -- (default: None) module basis in which the components are required; if none is provided, the components are assumed to refer to the module's default basis + - ``from_basis`` -- (default: None) unused (present just for ensuring + compatibility with FreeModuleTensor.comp calling list) OUTPUT: @@ -470,7 +455,7 @@ class :class:`~sage.tensor.modules.comp.KroneckerDelta` Components of the identity map on a rank-3 free module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() sage: a.comp(basis=e) @@ -487,10 +472,10 @@ class :class:`~sage.tensor.modules.comp.KroneckerDelta` """ if basis is None: - basis = self.fmodule.def_basis - if basis not in self.components: - self.components[basis] = self._new_comp(basis) - return self.components[basis] + basis = self._fmodule._def_basis + if basis not in self._components: + self._components[basis] = self._new_comp(basis) + return self._components[basis] def set_comp(self, basis=None): r""" @@ -514,12 +499,12 @@ def __call__(self, *arg): r""" Redefinition of :meth:`FreeModuleEndomorphism.__call__`. """ - from free_module_tensor import FiniteFreeModuleElement + from free_module_tensor import FiniteRankFreeModuleElement from free_module_alt_form import FreeModuleLinForm if len(arg) == 1: # the identity map acting as such, on a module element: vector = arg[0] - if not isinstance(vector, FiniteFreeModuleElement): + if not isinstance(vector, FiniteRankFreeModuleElement): raise TypeError("The argument must be a module element.") return vector #!# should it be return vector.copy() instead ? @@ -530,132 +515,9 @@ def __call__(self, *arg): if not isinstance(linform, FreeModuleLinForm): raise TypeError("The first argument must be a linear form.") vector = arg[1] - if not isinstance(vector, FiniteFreeModuleElement): + if not isinstance(vector, FiniteRankFreeModuleElement): raise TypeError("The second argument must be a module element.") return linform(vector) else: - raise TypeError("Bad number of arguments.") - - -#****************************************************************************** - -class FreeModuleSymBilinForm(FreeModuleTensor): - r""" - Symmetric bilinear form (considered as a type-(0,2) tensor) on a free - module. - - INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteFreeModule`) - - ``name`` -- (default: None) name given to the symmetric bilinear form - - ``latex_name`` -- (default: None) LaTeX symbol to denote the symmetric - bilinear form; if none is provided, the LaTeX symbol is set to ``name`` - - EXAMPLES: - - Symmetric bilinear form on a rank-3 free module:: - - sage: M = FiniteFreeModule(ZZ, 3, name='M') - sage: a = M.sym_bilinear_form('A') ; a - symmetric bilinear form A on the rank-3 free module M over the Integer Ring - - A symmetric bilinear form is a type-(0,2) tensor that is symmetric:: - - sage: a.parent() - free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring - sage: a.tensor_type - (0, 2) - sage: a.tensor_rank - 2 - sage: a.symmetries() - symmetry: (0, 1); no antisymmetry - - Components with respect to a given basis:: - - sage: e = M.basis('e') - sage: a[0,0], a[0,1], a[0,2] = 1, 2, 3 - sage: a[1,1], a[1,2] = 4, 5 - sage: a[2,2] = 6 - - Only independent components have been set; the other ones are deduced by - symmetry:: - - sage: a[1,0], a[2,0], a[2,1] - (2, 3, 5) - sage: a[:] - [1 2 3] - [2 4 5] - [3 5 6] - - A symmetric bilinear form acts on pairs of module elements:: - - sage: u = M([2,-1,3]) ; v = M([-2,4,1]) - sage: a(u,v) - 61 - sage: a(v,u) == a(u,v) - True - - The sum of two symmetric bilinear forms is another symmetric bilinear - form:: - - sage: b = M.sym_bilinear_form('B') - sage: b[0,0], b[0,1], b[1,2] = -2, 1, -3 - sage: s = a + b ; s - symmetric bilinear form A+B on the rank-3 free module M over the Integer Ring - sage: a[:], b[:], s[:] - ( - [1 2 3] [-2 1 0] [-1 3 3] - [2 4 5] [ 1 0 -3] [ 3 4 2] - [3 5 6], [ 0 -3 0], [ 3 2 6] - ) - - Adding a symmetric bilinear from with a non-symmetric one results in a - generic type-(0,2) tensor:: - - sage: c = M.tensor((0,2), name='C') - sage: c[0,1] = 4 - sage: s = a + c ; s - type-(0,2) tensor A+C on the rank-3 free module M over the Integer Ring - sage: s.symmetries() - no symmetry; no antisymmetry - sage: s[:] - [1 6 3] - [2 4 5] - [3 5 6] - - - - """ - def __init__(self, fmodule, name=None, latex_name=None): - FreeModuleTensor.__init__(self, fmodule, (0,2), name=name, - latex_name=latex_name, sym=(0,1)) - - def _repr_(self): - r""" - String representation of the object. - """ - description = "symmetric bilinear form " - if self.name is not None: - description += self.name + " " - description += "on the " + str(self.fmodule) - return description - - def _new_instance(self): - r""" - Create a :class:`FreeModuleSymBilinForm` instance. - - """ - return FreeModuleSymBilinForm(self.fmodule) - - def _new_comp(self, basis): - r""" - Create some components in the given basis. - - """ - from comp import CompFullySym - fmodule = self.fmodule # the base free module - return CompFullySym(fmodule.ring, basis, 2, start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) - + raise TypeError("Wrong number of arguments.") diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index 92b79972239..e466315ce9e 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -17,7 +17,7 @@ `T^{(k,l)}(M)` is itself a free module over `R`, of rank `n^{k+l}`, `n` being the rank of `M`. Accordingly the class :class:`TensorFreeModule` -inherits from the class :class:`FiniteFreeModule` +inherits from the class :class:`FiniteRankFreeModule` Thanks to the canonical isomorphism `M^{**}\simeq M` (which holds because `M` is a free module of finite rank), `T^{(k,l)}(M)` can be identified with the @@ -54,25 +54,10 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.modules.module import Module -from finite_free_module import FiniteFreeModule -from free_module_tensor import FreeModuleTensor, FiniteFreeModuleElement +from finite_rank_free_module import FiniteRankFreeModule +from free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement - -# From sage/modules/module.pyx: -# ---------------------------- -### The new Module class that should be the base of all Modules -### The derived Module class must implement the element -### constructor: -# -# class MyModule(sage.modules.module.Module): -# Element = MyElement -# def _element_constructor_(self, x): -# return self.element_class(x) -# - - -class TensorFreeModule(FiniteFreeModule): +class TensorFreeModule(FiniteRankFreeModule): r""" Class for the free modules over a commutative ring `R` that are tensor products of a given free module `M` over `R` with itself and its @@ -89,7 +74,7 @@ class TensorFreeModule(FiniteFreeModule): INPUT: - ``fmodule`` -- free module `M` of finite rank (must be an instance of - :class:`FiniteFreeModule`) + :class:`FiniteRankFreeModule`) - ``tensor_type`` -- pair `(k,l)` with `k` being the contravariant rank and `l` the covariant rank - ``name`` -- (string; default: None) name given to the tensor module @@ -100,14 +85,14 @@ class TensorFreeModule(FiniteFreeModule): Set of tensors of type (1,2) on a free module of rank 3 over `\ZZ`:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule sage: T = TensorFreeModule(M, (1,2)) ; T free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring Instead of importing TensorFreeModule in the global name space, it is recommended to use the module's method - :meth:`~sage.tensor.modules.finite_free_module.FiniteFreeModule.tensor_module`:: + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor_module`:: sage: T = M.tensor_module(1,2) ; T free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring @@ -197,34 +182,33 @@ class TensorFreeModule(FiniteFreeModule): Element = FreeModuleTensor def __init__(self, fmodule, tensor_type, name=None, latex_name=None): - self.fmodule = fmodule - self.tensor_type = tuple(tensor_type) + self._fmodule = fmodule + self._tensor_type = tuple(tensor_type) rank = pow(fmodule._rank, tensor_type[0] + tensor_type[1]) self._zero_element = 0 # provisory (to avoid infinite recursion in what # follows) if tensor_type == (0,1): # case of the dual - if name is None and fmodule.name is not None: - name = fmodule.name + '*' - if latex_name is None and fmodule.latex_name is not None: - latex_name = fmodule.latex_name + r'^*' - FiniteFreeModule.__init__(self, fmodule.ring, rank, name=name, + if name is None and fmodule._name is not None: + name = fmodule._name + '*' + if latex_name is None and fmodule._latex_name is not None: + latex_name = fmodule._latex_name + r'^*' + FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, latex_name=latex_name, - start_index=fmodule.sindex, - output_formatter=fmodule.output_formatter) + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) # Unique representation: - if self.tensor_type in self.fmodule._tensor_modules: + if self._tensor_type in self._fmodule._tensor_modules: raise TypeError("The module of tensors of type" + - str(self.tensor_type) + + str(self._tensor_type) + " has already been created.") else: - self.fmodule._tensor_modules[self.tensor_type] = self + self._fmodule._tensor_modules[self._tensor_type] = self # Zero element self._zero_element = self._element_constructor_(name='zero', latex_name='0') - def_basis = self.fmodule.def_basis - if def_basis is not None: - self._zero_element.components[def_basis] = \ - self._zero_element._new_comp(def_basis) + for basis in self._fmodule._known_bases: + self._zero_element._components[basis] = \ + self._zero_element._new_comp(basis) # (since new components are initialized to zero) #### Methods required for any Parent @@ -235,7 +219,7 @@ def _element_constructor_(self, comp=[], basis=None, name=None, """ if comp == 0: return self._zero_element - resu = self.element_class(self.fmodule, self.tensor_type, name=name, + resu = self.element_class(self._fmodule, self._tensor_type, name=name, latex_name=latex_name, sym=sym, antisym=antisym) if comp != []: @@ -246,11 +230,11 @@ def _an_element_(self): r""" Construct some (unamed) tensor """ - resu = self.element_class(self.fmodule, self.tensor_type) - if self.fmodule.def_basis is not None: - sindex = self.fmodule.sindex - ind = [sindex for i in range(resu.tensor_rank)] - resu.set_comp()[ind] = self.fmodule.ring.an_element() + resu = self.element_class(self._fmodule, self._tensor_type) + if self._fmodule._def_basis is not None: + sindex = self._fmodule._sindex + ind = [sindex for i in range(resu._tensor_rank)] + resu.set_comp()[ind] = self._fmodule._ring.an_element() return resu #### End of methods required for any Parent @@ -259,15 +243,15 @@ def _repr_(self): r""" String representation of the object. """ - if self.tensor_type == (0,1): - return "dual of the " + str(self.fmodule) + if self._tensor_type == (0,1): + return "dual of the " + str(self._fmodule) else: description = "free module " - if self.name is not None: - description += self.name + " " + if self._name is not None: + description += self._name + " " description += "of type-(%s,%s)" % \ - (str(self.tensor_type[0]), str(self.tensor_type[1])) - description += " tensors on the " + str(self.fmodule) + (str(self._tensor_type[0]), str(self._tensor_type[1])) + description += " tensors on the " + str(self._fmodule) return description def base_module(self): @@ -276,18 +260,18 @@ def base_module(self): OUTPUT: - - instance of :class:`FiniteFreeModule` representing the free module + - instance of :class:`FiniteRankFreeModule` representing the free module on which the tensor module is defined. EXAMPLE: Base module of a type-(1,2) tensor module:: - sage: M = FiniteFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: T = M.tensor_module(1,2) sage: T.base_module() rank-3 free module M over the Integer Ring """ - return self.fmodule + return self._fmodule From 3a043392d7b6c21a186303c4990204e06da00a6c Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Tue, 29 Jul 2014 13:18:53 -0700 Subject: [PATCH 014/129] Fixed failing test. --- src/sage/geometry/hyperbolic_space/hyperbolic_interface.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index 07131c94f31..632d1b7f3a0 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -90,7 +90,7 @@ def model_name(cls): sage: UHP.model_name() 'Upper Half Plane Model' sage: PD.model_name() - 'Poincaré Disk Model' + 'Poincare Disk Model' sage: KM.model_name() 'Klein Disk Model' sage: HM.model_name() From 58947b55679d1bd4b5b7c3d0804094653a17e830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Wed, 20 Aug 2014 10:39:01 +0200 Subject: [PATCH 015/129] trac #9439 fixing index file --- src/doc/en/reference/geometry/index.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/src/doc/en/reference/geometry/index.rst b/src/doc/en/reference/geometry/index.rst index bffcdc2e7bb..45eab53ff36 100644 --- a/src/doc/en/reference/geometry/index.rst +++ b/src/doc/en/reference/geometry/index.rst @@ -43,6 +43,7 @@ polytopes and polyhedra (with rational or numerical coordinates). Hyperbolic Geometry ------------------- + .. toctree:: :maxdepth: 1 From 373e5858694adf822102b2a1b9e40ce53cd776a6 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Fri, 22 Aug 2014 12:28:27 +0900 Subject: [PATCH 016/129] Implemented unordered_tuples using itertools. --- src/sage/combinat/combinat.py | 65 +++++++++++++++++++++++++---------- 1 file changed, 47 insertions(+), 18 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 56b9aa6eb2b..136648993b5 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2220,33 +2220,62 @@ def number_of_tuples(S,k): ans=gap.eval("NrTuples(%s,%s)"%(S,ZZ(k))) return ZZ(ans) -def unordered_tuples(S,k): +def unordered_tuples(S, k, algorithm='itertools'): """ - An unordered tuple of length k of set is a unordered selection with - repetitions of set and is represented by a sorted list of length k - containing elements from set. + Return the set of all unordered tuples of length ``k`` of the set ``S``. - unordered_tuples returns the set of all unordered tuples of length - k of the set. Wraps GAP's UnorderedTuples. + An unordered tuple of length `k` of set `S` is a unordered selection + with repetitions of `S` and is represented by a sorted list of length + `k` containing elements from `S`. - .. warning:: + INPUT: + + - ``S`` -- the base set + - ``k`` -- the length of the tuples + - ``algorithm`` -- can be one of the following: + + * ``'itertools'`` - (default) use python's itertools + * ``'gap'`` - wraps GAP's ``UnorderedTuples`` - Wraps GAP - hence mset must be a list of objects that have - string representations that can be interpreted by the GAP - interpreter. If mset consists of at all complicated Sage - objects, this function does *not* do what you expect. A proper - function should be written! (TODO!) + .. WARNING:: + + When using ``algorithm='gap'``, ``S`` must be a list of objects + that have string representations that can be interpreted by the GAP + interpreter. If ``S`` consists of at all complicated Sage + objects, this function might *not* do what you expect. EXAMPLES:: sage: S = [1,2] - sage: unordered_tuples(S,3) - [[1, 1, 1], [1, 1, 2], [1, 2, 2], [2, 2, 2]] - sage: unordered_tuples(["a","b","c"],2) - ['aa', 'ab', 'ac', 'bb', 'bc', 'cc'] + sage: unordered_tuples(S, 3) + [(1, 1, 1), (1, 1, 2), (1, 2, 2), (2, 2, 2)] + + We check that this agrees with GAP:: + + sage: unordered_tuples(S, 3, algorithm='gap') + [(1, 1, 1), (1, 1, 2), (1, 2, 2), (2, 2, 2)] + + We check the result on strings:: + + sage: S = ["a","b","c"] + sage: unordered_tuples(S, 2) + [('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')] + sage: unordered_tuples(S, 2, algorithm='gap') + [('a', 'a'), ('a', 'b'), ('a', 'c'), ('b', 'b'), ('b', 'c'), ('c', 'c')] + + Lastly we check on a multiset:: + + sage: S = [1,1,2] + sage: unordered_tuples(S, 3) == unordered_tuples(S, 3, 'gap') + True """ - ans=gap.eval("UnorderedTuples(%s,%s)"%(S,ZZ(k))) - return eval(ans) + if algorithm == 'itertools': + import itertools + return list(itertools.combinations_with_replacement(sorted(set(S)), k)) + if algorithm == 'gap': + ans = gap.eval("UnorderedTuples(%s,%s)"%(S,ZZ(k))) + return map(tuple, eval(ans)) + raise ValueError('invalid algorithm') def number_of_unordered_tuples(S,k): """ From 8f0878d7c04e15559a76c427fac9a764fa1ecca8 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Fri, 22 Aug 2014 14:47:46 +0200 Subject: [PATCH 017/129] Added index notation for tensor contractions and symmetrizations --- .../reference/tensor_free_modules/index.rst | 2 + src/doc/en/tensors_free_module/index.rst | 4 +- .../tensor_with_indices.rst | 16 + src/sage/tensor/modules/free_module_tensor.py | 216 ++++++++++++- .../tensor/modules/tensor_with_indices.py | 292 ++++++++++++++++++ 5 files changed, 525 insertions(+), 5 deletions(-) create mode 100644 src/doc/en/tensors_free_module/tensor_with_indices.rst create mode 100644 src/sage/tensor/modules/tensor_with_indices.py diff --git a/src/doc/en/reference/tensor_free_modules/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst index 6e137827636..cd6b237e3ac 100644 --- a/src/doc/en/reference/tensor_free_modules/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -18,4 +18,6 @@ Tensors on free modules of finite rank sage/tensor/modules/comp + sage/tensor/modules/tensor_with_indices + .. include:: ../footer.txt diff --git a/src/doc/en/tensors_free_module/index.rst b/src/doc/en/tensors_free_module/index.rst index 77482e593be..6c01e9e7081 100644 --- a/src/doc/en/tensors_free_module/index.rst +++ b/src/doc/en/tensors_free_module/index.rst @@ -30,9 +30,11 @@ __ http://creativecommons.org/licenses/by-sa/3.0/ free_module_tensor_spec free_module_alt_form - + comp + tensor_with_indices + Indices and Tables ------------------ diff --git a/src/doc/en/tensors_free_module/tensor_with_indices.rst b/src/doc/en/tensors_free_module/tensor_with_indices.rst new file mode 100644 index 00000000000..8d9430be73a --- /dev/null +++ b/src/doc/en/tensors_free_module/tensor_with_indices.rst @@ -0,0 +1,16 @@ +.. nodoctest + +.. _sage.tensor.modules.tensor_with_indices: + +Index notation for tensors +========================== + +.. This file has been autogenerated. + + +.. automodule:: sage.tensor.modules.tensor_with_indices + :members: + :undoc-members: + :show-inheritance: + + diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index dd9e196aa63..cc111d44196 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -166,6 +166,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. from sage.rings.integer import Integer from sage.structure.element import ModuleElement from comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym +from tensor_with_indices import TensorWithIndices class FreeModuleTensor(ModuleElement): r""" @@ -905,6 +906,10 @@ def del_other_comp(self, basis=None): def __getitem__(self, args): r""" Return a component w.r.t. some basis. + + NB: if ``args`` is a string, this method acts as a shortcut for + tensor contractions and symmetrizations, the string containing + abstract indices. INPUT: @@ -914,6 +919,8 @@ def __getitem__(self, args): basis is assumed. """ + if isinstance(args, str): # tensor with specified indices + return TensorWithIndices(self, args).update() if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], (int, Integer, slice)): basis = self._fmodule._def_basis @@ -1496,6 +1503,25 @@ def self_contract(self, pos1, pos2): 15 sage: a.self_contract(1,0) # the order of the slots does not matter 15 + + Instead of the explicit call to the method :meth:`self_contract`, one + may use the index notation with Einstein convention (summation over + repeated indices); it suffices to pass the indices as a string inside + square brackets:: + + sage: a['^i_i'] + 15 + + The letter 'i' to denote the repeated index can be replaced by any + other letter:: + + sage: a['^s_s'] + 15 + + Moreover, the symbol '^' can be omitted:: + + sage: a['i_i'] + 15 The contraction on two slots having the same tensor type cannot occur:: @@ -1538,6 +1564,27 @@ def self_contract(self, pos1, pos2): sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) for j in M.irange()] for i in M.irange()] ) # check True + Use of index notation instead of :meth:`self_contract`:: + + sage: t['^k_kij'] == t.self_contract(0,1) + True + sage: t['^k_{kij}'] == t.self_contract(0,1) # LaTeX notation + True + sage: t['^k_ikj'] == t.self_contract(0,2) + True + sage: t['^k_ijk'] == t.self_contract(0,3) + True + + Index symbols not involved in the contraction may be replaced by + dots:: + + sage: t['^k_k..'] == t.self_contract(0,1) + True + sage: t['^k_.k.'] == t.self_contract(0,2) + True + sage: t['^k_..k'] == t.self_contract(0,3) + True + """ # The indices at pos1 and pos2 must be of different types: k_con = self._tensor_type[0] @@ -1600,7 +1647,17 @@ def contract(self, *args): True sage: s == a.contract(b, 0) True - + + Instead of the explicit call to the method :meth:`contract`, the index + notation can be used to specify the contraction, via Einstein + conventation (summation on repeated indices); it suffices to pass the + indices as a string inside square brackets:: + + sage: s1 = a['_i']*b['^i'] ; s1 + 2 + sage: s1 == s + True + In the present case, performing the contraction is identical to applying the linear form to the module element:: @@ -1638,6 +1695,23 @@ def contract(self, *args): sage: a.contract(b) == b.contract(a, 1) True + Using the index notation with Einstein convention:: + + sage: a['^i_j']*b['^j'] == a.contract(b) + True + + The index i can be replaced by a dot:: + + sage: a['^._j']*b['^j'] == a.contract(b) + True + + and the symbol '^' may be omitted, the distinction between + contravariant and covariant indices being the position with respect to + the symbol '_':: + + sage: a['._j']*b['j'] == a.contract(b) + True + Contraction is possible only between a contravariant index and a covariant one:: @@ -1672,6 +1746,21 @@ def contract(self, *args): ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True + Using index notation:: + + sage: a['il_j']*b['_kl'] == a.contract(1, b, 1) + True + + LaTeX notation are allowed:: + + sage: a['^{il}_j']*b['_{kl}'] == a.contract(1, b, 1) + True + + Indices not involved in the contraction may be replaced by dots:: + + sage: a['.l_.']*b['_.l'] == a.contract(1, b, 1) + True + The two tensors do not have to be defined on the same basis for the contraction to take place, reflecting the fact that the contraction is basis-independent:: @@ -1789,6 +1878,28 @@ def symmetrize(self, pos=None, basis=None): ....: True True True True True True True True True + Instead of invoking the method :meth:`symmetrize`, one may use the + index notation with parentheses to denote the symmetrization; it + suffices to pass the indices as a string inside square brackets:: + + sage: t['(ij)'] + type-(2,0) tensor on the rank-3 free module M over the Rational Field + sage: t['(ij)'].symmetries() + symmetry: (0, 1); no antisymmetry + sage: t['(ij)'] == t.symmetrize() + True + + The indices names are not significant; they can even be replaced by + dots:: + + sage: t['(..)'] == t.symmetrize() + True + + The LaTeX notation can be used as well:: + + sage: t['^{(ij)}'] == t.symmetrize() + True + Symmetrization of a tensor of type (0,3) on the first two arguments:: sage: t = M.tensor((0,3)) @@ -1811,6 +1922,17 @@ def symmetrize(self, pos=None, basis=None): sage: s.symmetrize((0,1)) == s # another test True + Again the index notation can be used:: + + sage: t['_(ij)k'] == t.symmetrize((0,1)) + True + sage: t['_(..).'] == t.symmetrize((0,1)) # no index name + True + sage: t['_{(ij)k}'] == t.symmetrize((0,1)) # LaTeX notation + True + sage: t['_{(..).}'] == t.symmetrize((0,1)) # this also allowed + True + Symmetrization of a tensor of type (0,3) on the first and last arguments:: sage: s = t.symmetrize((0,2)) ; s # (0,2) = first and last arguments @@ -1850,6 +1972,15 @@ def symmetrize(self, pos=None, basis=None): sage: s.symmetrize((1,2)) == s # another test True + Use of the index notation:: + + sage: t['_i(jk)'] == t.symmetrize((1,2)) + True + sage: t['_.(..)'] == t.symmetrize((1,2)) + True + sage: t['_{i(jk)}'] == t.symmetrize((1,2)) # LaTeX notation + True + Full symmetrization of a tensor of type (0,3):: sage: s = t.symmetrize() ; s @@ -1869,7 +2000,14 @@ def symmetrize(self, pos=None, basis=None): True True True True True True True True True True True True True True True True True True True True True True True True True True True sage: s.symmetrize() == s # another test True - + + Index notation for the full symmetrization:: + + sage: t['_(ijk)'] == t.symmetrize() + True + sage: t['_{(ijk)}'] == t.symmetrize() # LaTeX notation + True + Symmetrization can be performed only on arguments on the same type:: sage: t = M.tensor((1,2)) @@ -1885,7 +2023,25 @@ def symmetrize(self, pos=None, basis=None): sage: t.symmetrize((2,1)) == t.symmetrize((1,2)) True - + + Use of the index notation:: + + sage: t['^i_(jk)'] == t.symmetrize((1,2)) + True + sage: t['^._(..)'] == t.symmetrize((1,2)) + True + + The character '^' can be skipped, the character '_' being sufficient + to separate contravariant indices from covariant ones:: + + sage: t['i_(jk)'] == t.symmetrize((1,2)) + True + + The LaTeX notation can be employed:: + + sage: t['^{i}_{(jk)}'] == t.symmetrize((1,2)) + True + """ if pos is None: pos = range(self._tensor_rank) @@ -1987,7 +2143,30 @@ def antisymmetrize(self, pos=None, basis=None): True sage: s.symmetrize((0,1)) == 0 # of course True + + Instead of invoking the method :meth:`antisymmetrize`, one can use + the index notation with square brackets denoting the + antisymmetrization; it suffices to pass the indices as a string + inside square brackets:: + sage: s1 = t['_[ij]k'] ; s1 + type-(0,3) tensor on the rank-3 free module M over the Rational Field + sage: s1.symmetries() + no symmetry; antisymmetry: (0, 1) + sage: s1 == s + True + + The LaTeX notation is recognized:: + + sage: t['_{[ij]k}'] == s + True + + Note that in the index notation, the name of the indices is irrelevant; + they can even be replaced by dots:: + + sage: t['_[..].'] == s + True + Antisymmetrization of a tensor of type (0,3) over the first and last arguments:: @@ -2036,6 +2215,12 @@ def antisymmetrize(self, pos=None, basis=None): sage: s.symmetrize((1,2)) == 0 # of course True + The index notation can be used instead of the explicit call to + :meth:`antisymmetrize`:: + + sage: t['_i[jk]'] == t.antisymmetrize((1,2)) + True + Full antisymmetrization of a tensor of type (0,3):: sage: s = t.antisymmetrize() ; s @@ -2064,6 +2249,18 @@ def antisymmetrize(self, pos=None, basis=None): sage: t.antisymmetrize() == t.antisymmetrize((0,1,2)) True + The index notation can be used instead of the explicit call to + :meth:`antisymmetrize`:: + + sage: t['_[ijk]'] == t.antisymmetrize() + True + sage: t['_[abc]'] == t.antisymmetrize() + True + sage: t['_[...]'] == t.antisymmetrize() + True + sage: t['_{[ijk]}'] == t.antisymmetrize() # LaTeX notation + True + Antisymmetrization can be performed only on arguments on the same type:: sage: t = M.tensor((1,2)) @@ -2079,6 +2276,18 @@ def antisymmetrize(self, pos=None, basis=None): sage: t.antisymmetrize((2,1)) == t.antisymmetrize((1,2)) True + + Again, the index notation can be used:: + + sage: t['^i_[jk]'] == t.antisymmetrize((1,2)) + True + sage: t['^i_{[jk]}'] == t.antisymmetrize((1,2)) # LaTeX notation + True + + The character '^' can be skipped:: + + sage: t['i_[jk]'] == t.antisymmetrize((1,2)) + True """ if pos is None: @@ -2106,7 +2315,6 @@ def antisymmetrize(self, pos=None, basis=None): basis = self.pick_a_basis() res_comp = self._components[basis].antisymmetrize(pos) return self._fmodule.tensor_from_comp(self._tensor_type, res_comp) - #****************************************************************************** diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py new file mode 100644 index 00000000000..c3feeebbddd --- /dev/null +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -0,0 +1,292 @@ +r""" +Index notation for tensors. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.sage_object import SageObject + +class TensorWithIndices(SageObject): + r""" + Index notation for tensors. + + This is a technical class to allow one to write some tensor operations + (contractions and symmetrizations) in index notation. + + INPUT: + + - ``tensor`` -- a tensor (or a tensor field) + - ``indices`` -- string containing the indices, as single letters; the + contravariant indices must be stated first and separated from the + covariant indices by the character '_'. + + EXAMPLES: + + Index representation of tensors on a rank-3 free module:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] + sage: b = M.tensor((0,2), name='b') + sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] + sage: t = a*b ; t.set_name('t') ; t + type-(2,2) tensor t on the rank-3 free module M over the Rational Field + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: T = TensorWithIndices(t, '^ij_kl') ; T + t^ij_kl + + The :class:`TensorWithIndices` object is returned by the square + bracket operator acting on the tensor and fed with the string specifying + the indices:: + + sage: a['^ij'] + a^ij + sage: type(a['^ij']) + + sage: b['_ef'] + b_ef + sage: t['^ij_kl'] + t^ij_kl + + The symbol '^' may be omitted, since the distinction between covariant + and contravariant indices is performed by the index position relative to + the symbol '_':: + + sage: t['ij_kl'] + t^ij_kl + + Also, LaTeX notation may be used:: + + sage: t['^{ij}_{kl}'] + t^ij_kl + + If some operation is asked in the index notation, the resulting tensor + is returned, not a :class:`TensorWithIndices` object; for instance, for + a symmetrization:: + + sage: s = t['^(ij)_kl'] ; s # the symmetrization on i,j is indicated by parentheses + type-(2,2) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + symmetry: (0, 1); no antisymmetry + sage: s == t.symmetrize((0,1)) + True + + The letters denoting the indices can be chosen freely; since they carry no + information, they can even be replaced by dots:: + + sage: t['^(..)_..'] == t.symmetrize((0,1)) + True + + Similarly, for an antisymmetrization:: + + sage: s = t['^ij_[kl]'] ; s # the symmetrization on k,l is indicated by square brackets + type-(2,2) tensor on the rank-3 free module M over the Rational Field + sage: s.symmetries() + no symmetry; antisymmetry: (2, 3) + sage: s == t.antisymmetrize((2,3)) + True + + Another example of an operation indicated by indices is a contraction:: + + sage: s = t['^ki_kj'] ; s # contraction on the repeated index k + endomorphism on the rank-3 free module M over the Rational Field + sage: s == t.self_contract(0,2) + True + + Indices not involved in the contraction may be replaced by dots:: + + sage: s == t['^k._k.'] + True + + The contraction of two tensors is indicated by repeated indices and + the * operator:: + + sage: s = a['^ik']*b['_kj'] ; s + endomorphism on the rank-3 free module M over the Rational Field + sage: s == a.contract(1, b, 0) + True + sage: s = t['^.k_..']*b['_.k'] ; s + type-(1,3) tensor on the rank-3 free module M over the Rational Field + sage: s == t.contract(1, b, 1) + True + sage: t['^{ik}_{jl}']*b['_{mk}'] == s # LaTeX notation + True + + """ + def __init__(self, tensor, indices): + self._tensor = tensor # may be changed below + self._changed = False # indicates whether self contains an altered + # version of the original tensor (True if + # symmetries or contractions are indicated in the + # indices) + # Suppress all '{' and '}' comming from LaTeX notations: + indices = indices.replace('{','').replace('}','') + # Suppress the first '^': + if indices[0] == '^': + indices = indices[1:] + if '^' in indices: + raise IndexError("The contravariant indices must be placed first.") + con_cov = indices.split('_') + con = con_cov[0] + # Contravariant indices + # --------------------- + # Search for (anti)symmetries: + if '(' in con: + sym1 = con.index('(') + sym2 = con.index(')')-2 + if con.find('(', sym1+1) != -1 or '[' in con: + raise NotImplementedError("Multiple symmetries are not " + + "treated yet.") + self._tensor = self._tensor.symmetrize(range(sym1, sym2+1)) + self._changed = True # self does no longer contain the original tensor + con = con.replace('(','').replace(')','') + if '[' in con: + sym1 = con.index('[') + sym2 = con.index(']')-2 + if con.find('[', sym1+1) != -1 or '(' in con: + raise NotImplementedError("Multiple symmetries are not " + + "treated yet.") + self._tensor = self._tensor.antisymmetrize(range(sym1, sym2+1)) + self._changed = True # self does no longer contain the original tensor + con = con.replace('[','').replace(']','') + if len(con) != self._tensor._tensor_type[0]: + raise IndexError("Number of contravariant indices not compatible " + + "with the tensor type.") + self._con = con + # Covariant indices + # ----------------- + if len(con_cov) == 1: + if tensor._tensor_type[1] != 0: + raise IndexError("Number of covariant indices not compatible " + + "with the tensor type.") + self._cov = '' + elif len(con_cov) == 2: + cov = con_cov[1] + # Search for (anti)symmetries: + if '(' in cov: + sym1 = cov.index('(') + sym2 = cov.index(')')-2 + if cov.find('(', sym1+1) != -1 or '[' in cov: + raise NotImplementedError("Multiple symmetries are not " + + "treated yet.") + csym1 = sym1 + self._tensor._tensor_type[0] + csym2 = sym2 + self._tensor._tensor_type[0] + self._tensor = self._tensor.symmetrize(range(csym1, csym2+1)) + self._changed = True # self does no longer contain the original + # tensor + cov = cov.replace('(','').replace(')','') + if '[' in cov: + sym1 = cov.index('[') + sym2 = cov.index(']')-2 + if cov.find('[', sym1+1) != -1 or '(' in cov: + raise NotImplementedError("Multiple symmetries are not " + + "treated yet.") + csym1 = sym1 + self._tensor._tensor_type[0] + csym2 = sym2 + self._tensor._tensor_type[0] + self._tensor = self._tensor.antisymmetrize(range(csym1, csym2+1)) + self._changed = True # self does no longer contain the original + # tensor + cov = cov.replace('[','').replace(']','') + if len(cov) != tensor._tensor_type[1]: + raise IndexError("Number of covariant indices not compatible " + + "with the tensor type.") + self._cov = cov + else: + raise IndexError("Two many '_' in the list of indices.") + # Treatment of possible self-contractions: + # --------------------------------------- + contraction_pairs = [] + for ind in self._con: + if ind != '.' and ind in self._cov: + pos1 = self._con.index(ind) + pos2 = self._tensor._tensor_type[0] + self._cov.index(ind) + contraction_pairs.append((pos1, pos2)) + if len(contraction_pairs) > 1: + raise NotImplementedError("Multiple contractions are not " + + "implemented yet.") + if len(contraction_pairs) == 1: + pos1 = contraction_pairs[0][0] + pos2 = contraction_pairs[0][1] + self._tensor = self._tensor.self_contract(pos1, pos2) + self._changed = True # self does no longer contain the original + # tensor + ind = self._con[pos1] + self._con = self._con.replace(ind, '') + self._cov = self._cov.replace(ind, '') + + + def _repr_(self): + r""" + String representation of the object. + """ + if self._tensor._name is not None: + name = self._tensor._name + else: + name = 'X' + if self._con == '': + return name + '_' + self._cov + elif self._cov == '': + return name + '^' + self._con + else: + return name + '^' + self._con + '_' + self._cov + + def update(self): + r""" + Return the tensor contains in ``self`` if it differs from that used + for creating ``self``, otherwise return ``self``. + """ + if self._changed: + return self._tensor + else: + return self + + def __mul__(self, other): + r""" + Tensor contraction on specified indices + """ + if not isinstance(other, TensorWithIndices): + raise TypeError("The second item of * must be a tensor with " + + "specified indices.") + contraction_pairs = [] + for ind in self._con: + if ind != '.': + if ind in other._cov: + pos1 = self._con.index(ind) + pos2 = other._tensor._tensor_type[0] + other._cov.index(ind) + contraction_pairs.append((pos1, pos2)) + if ind in other._con: + raise IndexError("The index " + str(ind) + " appears twice " + + "in a contravariant position.") + for ind in self._cov: + if ind != '.': + if ind in other._con: + pos1 = self._tensor._tensor_type[0] + self._cov.index(ind) + pos2 = other._con.index(ind) + contraction_pairs.append((pos1, pos2)) + if ind in other._cov: + raise IndexError("The index " + str(ind) + " appears twice " + + "in a covariant position.") + if contraction_pairs == []: + # No contraction is performed: the tensor product is returned + return self._tensor * other._tensor + if len(contraction_pairs) > 1: + raise NotImplementedError("Multiple contractions are not " + + "implemented yet.") + pos1 = contraction_pairs[0][0] + pos2 = contraction_pairs[0][1] + return self._tensor.contract(pos1, other._tensor, pos2) + + From 87f703a2769d26d739c60861e63d0870b34a5635 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 23 Aug 2014 10:13:49 +0900 Subject: [PATCH 018/129] Some more trivial cleanup. --- .../hyperbolic_space/hyperbolic_factory.py | 2 +- .../hyperbolic_space/hyperbolic_methods.py | 72 ++++++++++--------- 2 files changed, 38 insertions(+), 36 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py index da882372f5b..619cf644bdf 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py @@ -333,7 +333,7 @@ def get_background_graphic(cls, **bdry_options): from sage.plot.circle import circle return circle((0,0), 1, axes=False, color='black') -class HyperbolicFactoryHM (HyperbolicAbstractFactory, UniqueRepresentation): +class HyperbolicFactoryHM(HyperbolicAbstractFactory, UniqueRepresentation): """ Factory for creating the HM model. """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index 7d6a82cb215..eb4b83d4703 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -2,7 +2,7 @@ Hyperbolic Methods This module should not be used directly by users. It is provided for -developers of sage. +developers of Sage. This module implements computational methods for some models of hyperbolic space. The methods may operate on points, geodesics, or @@ -105,15 +105,15 @@ def model_name(cls): return cls.HModel.short_name -class HyperbolicMethodsUHP (HyperbolicAbstractMethods): +class HyperbolicMethodsUHP(HyperbolicAbstractMethods): r""" Hyperbolic methods for the UHP model of hyperbolic space. """ HModel = HyperbolicModelUHP -################# -# Point Methods # -################# + ################# + # Point Methods # + ################# @classmethod def point_dist(cls, p1, p2): @@ -175,9 +175,9 @@ def random_point(cls, **kwargs): return RR.random_element(min = real_min ,max=real_max) +\ I*RR.random_element(min = imag_min,max = imag_max) -#################### -# Geodesic Methods # -#################### + #################### + # Geodesic Methods # + #################### @classmethod def boundary_points(cls, p1, p2): @@ -525,10 +525,10 @@ def angle(cls, start_1, end_1, start_2, end_2): else: return real(arccos((b_1+b_2)/abs(b_2-b_1))) + #################### + # Isometry Methods # + #################### -#################### -# Isometry Methods # -#################### @classmethod def orientation_preserving(cls, M): r""" @@ -597,8 +597,8 @@ def classification(cls, M): elif tau - 2 > EPSILON: return 'hyperbolic' else: - raise ValueError("Something went wrong with classification!" + - "Trace is " + str(A.trace())) + raise ValueError("something went wrong with classification:" + + "trace is " + str(A.trace())) else: #The isometry reverses orientation. if tau < EPSILON: return 'reflection' @@ -631,8 +631,8 @@ def translation_length(cls, M): if cls.classification(M) in ['hyperbolic', 'oriention-reversing hyperbolic']: return 2*arccosh(tau/2) - raise TypeError("Translation length is only defined for hyperbolic" - " transformations.") + raise TypeError("translation length is only defined for hyperbolic" + " transformations") @classmethod def isometry_from_fixed_points(cls, repel, attract): @@ -677,7 +677,7 @@ def fixed_point_set(cls, M): OUTPUT: - - a list of hyperbolic points. + - a list of hyperbolic points EXAMPLES:: @@ -744,12 +744,12 @@ def fixed_point_set(cls, M): @classmethod def repelling_fixed_point(cls, M): r""" - For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ValueError. + For a hyperbolic isometry, return the attracting fixed point; + otherwise raise a ``ValueError``. OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -760,8 +760,8 @@ def repelling_fixed_point(cls, M): """ if cls.classification(M) not in ['hyperbolic', 'orientation-reversing hyperbolic']: - raise ValueError("Repelling fixed point is defined only" + - "for hyperbolic isometries.") + raise ValueError("repelling fixed point is defined only" + + "for hyperbolic isometries") v = M.eigenmatrix_right()[1].column(1) if v[1] == 0: return infinity @@ -770,12 +770,12 @@ def repelling_fixed_point(cls, M): @classmethod def attracting_fixed_point(cls, M): r""" - For a hyperbolic isometry, return the attracting fixed point. - Otherwise raise a ValueError. + For a hyperbolic isometry, return the attracting fixed point; + otherwise raise a ``ValueError``. OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -800,11 +800,12 @@ def random_isometry(cls, preserve_orientation = True, **kwargs): INPUT: - - ``preserve_orientation`` -- if ``True`` return an orientation-preserving isometry. + - ``preserve_orientation`` -- if ``True`` return an + orientation-preserving isometry. OUTPUT: - - a hyperbolic isometry. + - a hyperbolic isometry EXAMPLES:: @@ -830,10 +831,9 @@ def random_isometry(cls, preserve_orientation = True, **kwargs): else: return M -################### -# Helping Methods # -################### - + ################### + # Helping Methods # + ################### @classmethod def _to_std_geod(cls, start, p, end): @@ -861,18 +861,19 @@ def _to_std_geod(cls, start, p, end): @classmethod def _crossratio_matrix(cls, p_0, p_1, p_2): r""" - Given three points (the list `p`) in `\mathbb{CP}^{1}' in affine + Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine coordinates, return the linear fractional transformation taking the elements of `p` to `0`,`1`, and `\infty'. INPUT: - - A list of three distinct elements of three distinct elements of `\mathbb{CP}^1` in affine coordinates. That is, each element must be a complex number, `\infty`, or symbolic. + - a list of three distinct elements of three distinct elements + of `\mathbb{CP}^1` in affine coordinates; that is, each element + must be a complex number, `\infty`, or symbolic. OUTPUT: - - An element of '\GL(2,\CC)`. - + - an element of `\GL(2,\CC)` EXAMPLES:: @@ -921,10 +922,11 @@ def _mobius_sending(cls, list1, list2): """ if len(list1) != 3 or len(list2) != 3: raise TypeError("mobius_sending requires each list to be three" - "points long.") + "points long") pl = list1 + list2 z = pl[0:3] w = pl[3:6] A = cls._crossratio_matrix(z[0],z[1],z[2]) B = cls._crossratio_matrix(w[0],w[1],w[2]) return B.inverse()*A + From 2759b74b3ba6d87c0c3b1cab81fd509d76b552d4 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 23 Aug 2014 14:48:10 +0900 Subject: [PATCH 019/129] Changed global interface to HyperbolicPlane. --- src/sage/geometry/hyperbolic_space/all.py | 5 +- .../hyperbolic_space/hyperbolic_bdry_point.py | 4 +- .../hyperbolic_space/hyperbolic_geodesic.py | 24 +-- .../hyperbolic_space/hyperbolic_interface.py | 109 +++++++----- .../hyperbolic_space/hyperbolic_isometry.py | 46 ++--- .../hyperbolic_space/hyperbolic_methods.py | 26 +-- .../hyperbolic_space/hyperbolic_model.py | 158 +++++++++--------- .../hyperbolic_space/hyperbolic_point.py | 24 +-- 8 files changed, 208 insertions(+), 188 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/all.py b/src/sage/geometry/hyperbolic_space/all.py index 66d065f53fc..88da542543d 100644 --- a/src/sage/geometry/hyperbolic_space/all.py +++ b/src/sage/geometry/hyperbolic_space/all.py @@ -1,7 +1,4 @@ from sage.misc.lazy_import import lazy_import -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'UHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'PD') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'KM') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'HM') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'HyperbolicPlane') diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py index a117326e145..a1694222a82 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py @@ -19,12 +19,12 @@ We can construct boundary points in the upper half plane model, abbreviated UHP for convenience:: - sage: UHP.point(3) + sage: HyperbolicPlane.UHP.point(3) Boundary point in UHP 3 Points on the boundary are infinitely far from interior points:: - sage: UHP.point(3).dist(UHP.point(I)) + sage: HyperbolicPlane.UHP.point(3).dist(HyperbolicPlane.UHP.point(I)) +Infinity """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index bbaf4f466db..6dc91e5f89a 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -14,9 +14,9 @@ We can construct geodesics in the upper half plane model, abbreviated UHP for convenience:: - sage: UHP.geodesic(2, 3) + sage: HyperbolicPlane.UHP.geodesic(2, 3) Geodesic in UHP from 2 to 3 - sage: g = UHP.geodesic(I, 3 + I) + sage: g = HyperbolicPlane.UHP.geodesic(I, 3 + I) sage: g.length() arccosh(11/2) @@ -24,7 +24,7 @@ graph will only be equal if their starting and ending points are the same:: - sage: UHP.geodesic(1,2) == UHP.geodesic(2,1) + sage: HyperbolicPlane.UHP.geodesic(1,2) == HyperbolicPlane.UHP.geodesic(2,1) False """ @@ -82,6 +82,7 @@ class HyperbolicGeodesic(SageObject): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: UHP = HyperbolicPlane.UHP sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) sage: g = HyperbolicGeodesicUHP(I, 2 + I) """ @@ -346,7 +347,7 @@ def graphics_options(self): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: p = UHP.point(2 + I, color="red") + sage: p = HyperbolicPlane.UHP.point(2 + I, color="red") sage: p.graphics_options() {'color': 'red'} """ @@ -354,7 +355,7 @@ def graphics_options(self): def update_graphics(self, update=False, **options): r""" - Update the graphics options of a HyperbolicPoint. + Update the graphics options of a :class:`HyperbolicPoint`. INPUT: @@ -838,7 +839,7 @@ def dist(self, other): first object's endpoints, then return +infinity:: sage: g = HyperbolicGeodesicUHP(2, 2+I) - sage: p = UHP.point(5) + sage: p = HyperbolicPlane.UHP.point(5) sage: g.dist(p) +Infinity """ @@ -933,14 +934,10 @@ class HyperbolicGeodesicUHP(HyperbolicGeodesic): - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point in hyperbolic space representing the end of the geodesic - - OUTPUT: - - A hyperbolic geodesic. - EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: UHP = HyperbolicPlane.UHP sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) sage: g = HyperbolicGeodesicUHP(I, 2 + I) """ @@ -1019,6 +1016,7 @@ class HyperbolicGeodesicPD(HyperbolicGeodesic): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: PD = HyperbolicPlane.PD sage: g = HyperbolicGeodesicPD(PD.point(I), PD.point(I/2)) sage: g = HyperbolicGeodesicPD(I, I/2) """ @@ -1091,6 +1089,7 @@ class HyperbolicGeodesicKM(HyperbolicGeodesic): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: KM = HyperbolicPlane.KM sage: g = HyperbolicGeodesicKM(KM.point((0,1)), KM.point((0,1/2))) sage: g = HyperbolicGeodesicKM((0,1), (0,1/2)) """ @@ -1130,6 +1129,7 @@ class HyperbolicGeodesicHM(HyperbolicGeodesic): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * + sage: HM = HyperbolicPlane.HM sage: g = HyperbolicGeodesicHM(HM.point((0, 0, 1)), HM.point((0,1,sqrt(2)))) sage: g = HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))) """ @@ -1168,6 +1168,6 @@ def show(self, show_hyperboloid=True, **graphics_options): pic = parametric_plot3d(hyperbola,(x,0, endtime),**graphics_options) if show_hyperboloid: bd_pic = self.HFactory.get_background_graphic() - pic= bd_pic + pic + pic = bd_pic + pic return pic diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index 632d1b7f3a0..729f5b3422b 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -19,10 +19,10 @@ EXAMPLES:: - sage: UHP.point(2 + I) + sage: HyperbolicPlane.UHP.point(2 + I) Point in UHP I + 2 - sage: PD.point(1/2 + I/2) + sage: HyperbolicPlane.PD.point(1/2 + I/2) Point in PD 1/2*I + 1/2 """ @@ -87,13 +87,13 @@ def model_name(cls): EXAMPLES:: - sage: UHP.model_name() + sage: HyperbolicPlane.UHP.model_name() 'Upper Half Plane Model' - sage: PD.model_name() + sage: HyperbolicPlane.PD.model_name() 'Poincare Disk Model' - sage: KM.model_name() + sage: HyperbolicPlane.KM.model_name() 'Klein Disk Model' - sage: HM.model_name() + sage: HyperbolicPlane.HM.model_name() 'Hyperboloid Model' """ return cls.HModel.name @@ -105,13 +105,13 @@ def short_name(cls): EXAMPLES:: - sage: UHP.short_name() + sage: HyperbolicPlane.UHP.short_name() 'UHP' - sage: PD.short_name() + sage: HyperbolicPlane.PD.short_name() 'PD' - sage: HM.short_name() + sage: HyperbolicPlane.HM.short_name() 'HM' - sage: KM.short_name() + sage: HyperbolicPlane.KM.short_name() 'KM' """ return cls.HModel.short_name @@ -123,13 +123,13 @@ def is_bounded(cls): EXAMPLES:: - sage: UHP.is_bounded() + sage: HyperbolicPlane.UHP.is_bounded() True - sage: PD.is_bounded() + sage: HyperbolicPlane.PD.is_bounded() True - sage: KM.is_bounded() + sage: HyperbolicPlane.KM.is_bounded() True - sage: HM.is_bounded() + sage: HyperbolicPlane.HM.is_bounded() False """ return cls.HModel.bounded @@ -142,16 +142,16 @@ def point(cls, p, **kwargs): EXAMPLES:: - sage: UHP.point(0) + sage: HyperbolicPlane.UHP.point(0) Boundary point in UHP 0 - sage: PD.point(I/2) + sage: HyperbolicPlane.PD.point(I/2) Point in PD 1/2*I - sage: KM.point((0,1)) + sage: HyperbolicPlane.KM.point((0,1)) Boundary point in KM (0, 1) - sage: HM.point((0,0,1)) + sage: HyperbolicPlane.HM.point((0,0,1)) Point in HM (0, 0, 1) """ return cls.HFactory.get_point(p, **kwargs) @@ -164,11 +164,11 @@ def point_in_model(cls, p): EXAMPLES:: - sage: UHP.point_in_model(I) + sage: HyperbolicPlane.UHP.point_in_model(I) True - sage: UHP.point_in_model(0) # Not interior point. + sage: HyperbolicPlane.UHP.point_in_model(0) # Not interior point. False - sage: HM.point_in_model((0,1, sqrt(2))) + sage: HyperbolicPlane.HM.point_in_model((0,1, sqrt(2))) True """ return cls.HModel.point_in_model(p) @@ -181,9 +181,9 @@ def bdry_point_in_model(cls, p): EXAMPLES:: - sage: UHP.bdry_point_in_model(0) + sage: HyperbolicPlane.UHP.bdry_point_in_model(0) True - sage: UHP.bdry_point_in_model(I) # Not boundary point + sage: HyperbolicPlane.UHP.bdry_point_in_model(I) # Not boundary point False """ return cls.HModel.bdry_point_in_model(p) @@ -197,7 +197,7 @@ def isometry_in_model(cls, A): EXAMPLES:: sage: A = matrix(2,[10,0,0,10]) # det(A) is not 1 - sage: UHP.isometry_in_model(A) # But A acts isometrically + sage: HyperbolicPlane.UHP.isometry_in_model(A) # But A acts isometrically True """ return cls.HModel.isometry_in_model(A) @@ -210,16 +210,16 @@ def geodesic(cls, start, end, **kwargs): EXAMPLES:: - sage: UHP.geodesic(1, 0) + sage: HyperbolicPlane.UHP.geodesic(1, 0) Geodesic in UHP from 1 to 0 - sage: PD.geodesic(1, 0) + sage: HyperbolicPlane.PD.geodesic(1, 0) Geodesic in PD from 1 to 0 - sage: KM.geodesic((0,1/2), (1/2, 0)) + sage: HyperbolicPlane.KM.geodesic((0,1/2), (1/2, 0)) Geodesic in KM from (0, 1/2) to (1/2, 0) - sage: HM.geodesic((0,0,1), (0,1, sqrt(2))) + sage: HyperbolicPlane.HM.geodesic((0,0,1), (0,1, sqrt(2))) Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ return cls.HFactory.get_geodesic(start, end, **kwargs) @@ -233,23 +233,23 @@ def isometry(cls, A): EXAMPLES:: - sage: UHP.isometry(identity_matrix(2)) + sage: HyperbolicPlane.UHP.isometry(identity_matrix(2)) Isometry in UHP [1 0] [0 1] - sage: PD.isometry(identity_matrix(2)) + sage: HyperbolicPlane.PD.isometry(identity_matrix(2)) Isometry in PD [1 0] [0 1] - sage: KM.isometry(identity_matrix(3)) + sage: HyperbolicPlane.KM.isometry(identity_matrix(3)) Isometry in KM [1 0 0] [0 1 0] [0 0 1] - sage: HM.isometry(identity_matrix(3)) + sage: HyperbolicPlane.HM.isometry(identity_matrix(3)) Isometry in HM [1 0 0] [0 1 0] @@ -264,7 +264,7 @@ def random_point(cls, **kwargs): EXAMPLES:: - sage: p = UHP.random_point() + sage: p = HyperbolicPlane.UHP.random_point() """ return cls.HPoint.random_element(**kwargs) @@ -275,7 +275,7 @@ def random_geodesic(cls, **kwargs): EXAMPLES:: - sage: p = UHP.random_geodesic() + sage: p = HyperbolicPlane.UHP.random_geodesic() """ return cls.HGeodesic.random_element(**kwargs) @@ -286,7 +286,7 @@ def random_isometry(cls, **kwargs): EXAMPLES:: - sage: p = UHP.random_isometry() + sage: p = HyperbolicPlane.UHP.random_isometry() """ return cls.HIsometry.random_element(**kwargs) @@ -308,6 +308,7 @@ def isometry_from_fixed_points(cls, p1, p2): EXAMPLES:: + sage: UHP = HyperbolicPlane.UHP sage: UHP.isometry_from_fixed_points(0, 4) Isometry in UHP [ -1 0] @@ -331,9 +332,10 @@ def dist(cls, a, b): EXAMPLES:: + sage: UHP = HyperbolicPlane.UHP sage: UHP.dist(UHP.point(I), UHP.point(2*I)) arccosh(5/4) - sage: UHP.dist(I, 2*I) + sage: HyperbolicPlane.UHP.dist(I, 2*I) arccosh(5/4) """ try: @@ -356,11 +358,11 @@ def point_to_model(cls, p, model): EXAMPLES:: - sage: UHP.point_to_model(I, 'PD') + sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') 0 - sage: PD.point_to_model(I, 'UHP') + sage: HyperbolicPlane.PD.point_to_model(I, 'UHP') +Infinity - sage: UHP.point_to_model(UHP.point(I), 'HM') + sage: HyperbolicPlane.UHP.point_to_model(HyperbolicPlane.UHP.point(I), 'HM') (0, 0, 1) """ if isinstance(p, HyperbolicPoint): @@ -381,6 +383,7 @@ def isometry_to_model(cls, A, model): EXAMPLES:: + sage: UHP = HyperbolicPlane.UHP sage: I2 = identity_matrix(2) sage: UHP.isometry_to_model(I2, 'HM') [1 0 0] @@ -401,7 +404,7 @@ class UHP(HyperbolicUserInterface): EXAMPLES:: - sage: UHP.point(I) + sage: HyperbolicPlane.UHP.point(I) Point in UHP I """ HModel = HyperbolicModelUHP @@ -417,7 +420,7 @@ class PD(HyperbolicUserInterface): EXAMPLES:: - sage: PD.point(I) + sage: HyperbolicPlane.PD.point(I) Boundary point in PD I """ HModel = HyperbolicModelPD @@ -433,7 +436,7 @@ class KM(HyperbolicUserInterface): EXAMPLES:: - sage: KM.point((0,0)) + sage: HyperbolicPlane.KM.point((0,0)) Point in KM (0, 0) """ HModel = HyperbolicModelKM @@ -449,7 +452,7 @@ class HM(HyperbolicUserInterface): EXAMPLES:: - sage: HM.point((0,0,1)) + sage: HyperbolicPlane.HM.point((0,0,1)) Point in HM (0, 0, 1) """ HModel = HyperbolicModelHM @@ -458,3 +461,23 @@ class HM(HyperbolicUserInterface): HIsometry = HyperbolicIsometryHM HGeodesic = HyperbolicGeodesicHM +class HyperbolicPlane(UniqueRepresentation): + """ + The hyperbolic plane `\mathbb{H}^2` in a given model. + + Here are the models currently implemented: + + - ``UHP`` -- upper half plane + - ``PD`` -- Poincare disk + - ``KM`` -- Klein disk + - ``HM`` -- hyperboloid model + """ + UHP = UHP + UpperHalfPlane = UHP + PD = PD + PoincareDisk = PD + KM = KM + KleinDisk = KM + HM = HM + Hyperboloid = HM + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index ddc071d08f6..eb7f7aa7f79 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -20,11 +20,11 @@ We can construct isometries in the upper half plane model, abbreviated UHP for convenience:: - sage: UHP.isometry(matrix(2,[1,2,3,4])) + sage: HyperbolicPlane.UHP.isometry(matrix(2,[1,2,3,4])) Isometry in UHP [1 2] [3 4] - sage: A = UHP.isometry(matrix(2,[0,1,1,0])) + sage: A = HyperbolicPlane.UHP.isometry(matrix(2,[0,1,1,0])) sage: A.inverse() Isometry in UHP [0 1] @@ -83,7 +83,7 @@ class HyperbolicIsometry(SageObject): def __init__(self, A): r""" - See `HyperbolicIsometry` for full documentation. + See :class:`HyperbolicIsometry` for full documentation. EXAMPLES:: @@ -103,7 +103,7 @@ def _cached_matrix(self): The representation of the current isometry used for calculations. For example, if the current model uses the HyperbolicMethodsUHP class, then _cached_matrix will hold the - SL(2,`\Bold{R}`) representation of self.matrix(). + `SL(2,\RR)` representation of ``self.matrix()``. EXAMPLES:: @@ -139,8 +139,8 @@ def _latex_(self): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = UHP.isometry(identity_matrix(2)) - sage: B = HM.isometry(identity_matrix(3)) + sage: A = HyperbolicPlane.UHP.isometry(identity_matrix(2)) + sage: B = HyperbolicPlane.HM.isometry(identity_matrix(3)) sage: latex(A) \pm \left(\begin{array}{rr} 1 & 0 \\ @@ -203,11 +203,11 @@ def __mul__(self, other): [16 8] [ 7 6] sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) - sage: p = UHP.point(2 + I) + sage: p = HyperbolicPlane.UHP.point(2 + I) sage: A*p Point in UHP 8/17*I + 53/17 - sage: g = UHP.geodesic(2 + I,4 + I) + sage: g = HyperbolicPlane.UHP.geodesic(2 + I,4 + I) sage: A*g Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37 @@ -215,7 +215,7 @@ def __mul__(self, other): sage: A = HyperbolicIsometryHM(A) sage: A.orientation_preserving() False - sage: p = HM.point((0, 1, sqrt(2))) + sage: p = HyperbolicPlane.HM.point((0, 1, sqrt(2))) sage: A*p Point in HM (0, -1, sqrt(2)) """ @@ -232,7 +232,7 @@ def __mul__(self, other): elif isinstance(other, HyperbolicGeodesic): return self.HFactory.get_geodesic(self*other.start(), self*other.end()) else: - NotImplementedError("Multiplication is not defined between a " + NotImplementedError("multiplication is not defined between a " "hyperbolic isometry and {0}".format(other)) # def __call__ (self, other): @@ -346,7 +346,7 @@ def to_model(self, model_name): INPUT: - - ``model_name`` -- a string representing the image model. + - ``model_name`` -- a string representing the image model EXAMPLES:: @@ -428,10 +428,10 @@ def translation_length(self): :: - sage: f_1 = UHP.point(-1) - sage: f_2 = UHP.point(1) + sage: f_1 = HyperbolicPlane.UHP.point(-1) + sage: f_2 = HyperbolicPlane.UHP.point(1) sage: H = HyperbolicIsometryUHP.isometry_from_fixed_points(f_1, f_2) - sage: p = UHP.point(exp(i*7*pi/8)) + sage: p = HyperbolicPlane.UHP.point(exp(i*7*pi/8)) sage: bool((p.dist(H*p) - H.translation_length()) < 10**-9) True """ @@ -449,19 +449,19 @@ def axis(self, **graphics_options): sage: H.axis() Geodesic in UHP from 0 to +Infinity - It is an error to call this function on an isometry that is - not hyperbolic:: + It is an error to call this function on an isometry that is + not hyperbolic:: sage: P = HyperbolicIsometryUHP(matrix(2,[1,4,0,1])) sage: P.axis() Traceback (most recent call last): ... - ValueError: the isometry is not hyperbolic: axis is undefined. + ValueError: the isometry is not hyperbolic: axis is undefined """ if self.classification() not in ( ['hyperbolic', 'orientation-reversing hyperbolic']): raise ValueError("the isometry is not hyperbolic: axis is" - " undefined.") + " undefined") return self.HFactory.get_geodesic(*self.fixed_point_set()) def fixed_point_set(self, **graphics_options): @@ -507,7 +507,7 @@ def fixed_geodesic(self, **graphics_options): EXAMPLES:: - sage: A = UHP.isometry(matrix(2, [0, 1, 1, 0])) + sage: A = HyperbolicPlane.UHP.isometry(matrix(2, [0, 1, 1, 0])) sage: A.fixed_geodesic() Geodesic in UHP from 1 to -1 """ @@ -515,7 +515,7 @@ def fixed_geodesic(self, **graphics_options): if len(fps) < 2: raise ValueError("Isometries of type" " {0}".format(self.classification()) - + " don't fix geodesics.") + + " don't fix geodesics") from sage.geometry.hyperbolic_space.model_factory import ModelFactory fact = ModelFactory.find_factory(self.HMethods.model_name()) geod = fact.get_geodesic(fps[0], fps[1]) @@ -571,13 +571,13 @@ def isometry_from_fixed_points(cls, repel, attract): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: p, q = [UHP.point(k) for k in [2 + I, 3 + I]] + sage: p, q = [HyperbolicPlane.UHP.point(k) for k in [2 + I, 3 + I]] sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) Traceback (most recent call last): ... ValueError: fixed points of hyperbolic elements must be ideal - sage: p, q = [UHP.point(k) for k in [2, 0]] + sage: p, q = [HyperbolicPlane.UHP.point(k) for k in [2, 0]] sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) Isometry in UHP [ -1 0] @@ -594,7 +594,7 @@ def isometry_from_fixed_points(cls, repel, attract): return cls.isometry_from_fixed_points(repel, attract) @classmethod - def random_element(cls, preserve_orientation = True, **kwargs): + def random_element(cls, preserve_orientation=True, **kwargs): r""" Return a random isometry in the Upper Half Plane model. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index eb4b83d4703..38808989c23 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -188,11 +188,11 @@ def boundary_points(cls, p1, p2): INPUT: - - ``p1``, ``p2`` -- points in the hyperbolic plane. + - ``p1``, ``p2`` -- points in the hyperbolic plane OUTPUT: - - a list of boundary points. + - a list of boundary points EXAMPLES:: @@ -257,11 +257,11 @@ def common_perpendicular(cls, start_1, end_1, start_2, end_2): INPUT: - - ``other`` -- a hyperbolic geodesic in current model. + - ``other`` -- a hyperbolic geodesic in current model OUTPUT: - - a hyperbolic geodesic. + - a hyperbolic geodesic EXAMPLES:: @@ -293,11 +293,11 @@ def intersection(cls, start_1, end_1, start_2, end_2): INPUT: - - ``other`` -- a hyperbolic geodesic in the current model. + - ``other`` -- a hyperbolic geodesic in the current model OUTPUT: - - a hyperbolic point. + - a hyperbolic point EXAMPLES:: @@ -311,7 +311,7 @@ def intersection(cls, start_1, end_1, start_2, end_2): sage: HyperbolicMethodsUHP.intersection(4, 5, 5, 7) Traceback (most recent call last): ... - ValueError: Geodesics don't intersect. + ValueError: geodesics don't intersect If the given geodesics are identical, return that geodesic:: @@ -331,7 +331,7 @@ def intersection(cls, start_1, end_1, start_2, end_2): B = cls.reflection_in(start_2, end_2) C = A*B if cls.classification(C) in ['hyperbolic', 'parabolic']: - raise ValueError("Geodesics don't intersect.") + raise ValueError("geodesics don't intersect") return cls.fixed_point_set(C) @classmethod @@ -412,11 +412,11 @@ def geod_dist_from_point(cls, start, end, p): INPUT: - - ``other`` -- a hyperbolic point in the same model. + - ``other`` -- a hyperbolic point in the same model OUTPUT: - - the hyperbolic distance. + - the hyperbolic distance EXAMPLES:: @@ -424,7 +424,7 @@ def geod_dist_from_point(cls, start, end, p): sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) - If p is a boundary point, the distance is infinity:: + If `p` is a boundary point, the distance is infinity:: sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) +Infinity @@ -458,7 +458,7 @@ def uncomplete(cls, start, end): INPUT: - - ``start`` -- a real number or infinity. + - ``start`` -- a real number or infinity - ``end`` -- a real number or infinity EXAMPLES:: @@ -928,5 +928,5 @@ def _mobius_sending(cls, list1, list2): w = pl[3:6] A = cls._crossratio_matrix(z[0],z[1],z[2]) B = cls._crossratio_matrix(w[0],w[1],w[2]) - return B.inverse()*A + return B.inverse() * A diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index b794a012a96..23766521991 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -300,9 +300,9 @@ def point_in_model(cls, p): #Abstract EXAMPLES:: - sage: UHP.point_in_model(I) + sage: HyperbolicPlane.UHP.point_in_model(I) True - sage: UHP.point_in_model(-I) + sage: HyperbolicPlane.UHP.point_in_model(-I) False """ return True @@ -342,7 +342,7 @@ def bdry_point_in_model(cls, p): #Abstract EXAMPLES:: - sage: UHP.bdry_point_in_model(I) + sage: HyperbolicPlane.UHP.bdry_point_in_model(I) False """ return True @@ -382,10 +382,10 @@ def isometry_in_model(cls, A): #Abstract EXAMPLES:: - sage: UHP.isometry_in_model(identity_matrix(2)) + sage: HyperbolicPlane.UHP.isometry_in_model(identity_matrix(2)) True - sage: UHP.isometry_in_model(identity_matrix(3)) + sage: HyperbolicPlane.UHP.isometry_in_model(identity_matrix(3)) False """ return True @@ -400,7 +400,7 @@ def isometry_act_on_point(cls, A, p): #Abtsract sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP sage: I2 = identity_matrix(2) - sage: p = UHP.random_point().coordinates() + sage: p = HyperbolicPlane.UHP.random_point().coordinates() sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) True """ @@ -447,33 +447,33 @@ def point_to_model(cls, coordinates, model_name): #Abstract EXAMPLES:: - sage: UHP.point_to_model(I, 'UHP') + sage: HyperbolicPlane.UHP.point_to_model(I, 'UHP') I - sage: UHP.point_to_model(I, 'PD') + sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') 0 - sage: UHP.point_to_model(3 + I, 'KM') + sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'KM') (6/11, 9/11) - sage: UHP.point_to_model(3 + I, 'HM') + sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'HM') (3, 9/2, 11/2) - sage: UHP.point_to_model(I, 'PD') + sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') 0 - sage: PD.point_to_model(0, 'UHP') + sage: HyperbolicPlane.PD.point_to_model(0, 'UHP') I - sage: UHP.point_to_model(I, 'UHP') + sage: HyperbolicPlane.UHP.point_to_model(I, 'UHP') I - sage: KM.point_to_model((0, 0), 'UHP') + sage: HyperbolicPlane.KM.point_to_model((0, 0), 'UHP') I - sage: KM.point_to_model((0, 0), 'HM') + sage: HyperbolicPlane.KM.point_to_model((0, 0), 'HM') (0, 0, 1) - sage: HM.point_to_model(vector((0,0,1)), 'UHP') + sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'UHP') I - sage: HM.point_to_model(vector((0,0,1)), 'KM') + sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'KM') (0, 0) It is an error to try to convert a boundary point to a model that doesn't support boundary points:: - sage: UHP.point_to_model(infinity, 'HM') + sage: HyperbolicPlane.UHP.point_to_model(infinity, 'HM') Traceback (most recent call last): ... NotImplementedError: boundary points are not implemented for the HM model @@ -503,30 +503,30 @@ def isometry_to_model(cls, A, model_name): #Abstract EXAMPLES:: sage: A = matrix(2,[I, 0, 0, -I]) - sage: PD.isometry_to_model(A, 'UHP') + sage: HyperbolicPlane.PD.isometry_to_model(A, 'UHP') [ 0 1] [-1 0] - sage: PD.isometry_to_model(A, 'HM') + sage: HyperbolicPlane.PD.isometry_to_model(A, 'HM') [-1 0 0] [ 0 -1 0] [ 0 0 1] - sage: PD.isometry_to_model(A, 'KM') + sage: HyperbolicPlane.PD.isometry_to_model(A, 'KM') [-1 0 0] [ 0 -1 0] [ 0 0 1] sage: B = diagonal_matrix([-1, -1, 1]) - sage: HM.isometry_to_model(B, 'UHP') + sage: HyperbolicPlane.HM.isometry_to_model(B, 'UHP') [ 0 -1] [ 1 0] - sage: HM.isometry_to_model(B, 'PD') + sage: HyperbolicPlane.HM.isometry_to_model(B, 'PD') [-I 0] [ 0 I] - sage: HM.isometry_to_model(B, 'KM') + sage: HyperbolicPlane.HM.isometry_to_model(B, 'KM') [-1 0 0] [ 0 -1 0] [ 0 0 1] @@ -571,21 +571,21 @@ def point_in_model(cls, p): #UHP EXAMPLES:: - sage: UHP.point_in_model(1 + I) + sage: HyperbolicPlane.UHP.point_in_model(1 + I) True - sage: UHP.point_in_model(infinity) + sage: HyperbolicPlane.UHP.point_in_model(infinity) False - sage: UHP.point_in_model(CC(infinity)) + sage: HyperbolicPlane.UHP.point_in_model(CC(infinity)) False - sage: UHP.point_in_model(RR(infinity)) + sage: HyperbolicPlane.UHP.point_in_model(RR(infinity)) False - sage: UHP.point_in_model(1) + sage: HyperbolicPlane.UHP.point_in_model(1) False - sage: UHP.point_in_model(12) + sage: HyperbolicPlane.UHP.point_in_model(12) False - sage: UHP.point_in_model(1 - I) + sage: HyperbolicPlane.UHP.point_in_model(1 - I) False - sage: UHP.point_in_model(-2*I) + sage: HyperbolicPlane.UHP.point_in_model(-2*I) False """ return bool(imag(CC(p)) > 0) @@ -599,21 +599,21 @@ def bdry_point_in_model(cls, p): #UHP EXAMPLES:: - sage: UHP.bdry_point_in_model(1 + I) + sage: HyperbolicPlane.UHP.bdry_point_in_model(1 + I) False - sage: UHP.bdry_point_in_model(infinity) + sage: HyperbolicPlane.UHP.bdry_point_in_model(infinity) True - sage: UHP.bdry_point_in_model(CC(infinity)) + sage: HyperbolicPlane.UHP.bdry_point_in_model(CC(infinity)) True - sage: UHP.bdry_point_in_model(RR(infinity)) + sage: HyperbolicPlane.UHP.bdry_point_in_model(RR(infinity)) True - sage: UHP.bdry_point_in_model(1) + sage: HyperbolicPlane.UHP.bdry_point_in_model(1) True - sage: UHP.bdry_point_in_model(12) + sage: HyperbolicPlane.UHP.bdry_point_in_model(12) True - sage: UHP.bdry_point_in_model(1 - I) + sage: HyperbolicPlane.UHP.bdry_point_in_model(1 - I) False - sage: UHP.bdry_point_in_model(-2*I) + sage: HyperbolicPlane.UHP.bdry_point_in_model(-2*I) False """ im = abs(imag(CC(p)).n()) @@ -629,7 +629,7 @@ def isometry_act_on_point(cls, A, p): #UHP sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP sage: I2 = identity_matrix(2) - sage: p = UHP.random_point().coordinates() + sage: p = HyperbolicPlane.UHP.random_point().coordinates() sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) True """ @@ -645,10 +645,10 @@ def isometry_in_model(cls, A): #UHP EXAMPLES:: sage: A = matrix(2,[1,2,3,4]) - sage: UHP.isometry_in_model(A) + sage: HyperbolicPlane.UHP.isometry_in_model(A) True sage: B = matrix(2,[I,2,4,1]) - sage: UHP.isometry_in_model(B) + sage: HyperbolicPlane.UHP.isometry_in_model(B) False """ return bool(A.ncols() == 2 and A.nrows() == 2 and @@ -673,19 +673,19 @@ def point_to_model(cls, coordinates, model_name): #UHP EXAMPLES:: - sage: UHP.point_to_model(I, 'UHP') + sage: HyperbolicPlane.UHP.point_to_model(I, 'UHP') I - sage: UHP.point_to_model(I, 'PD') + sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') 0 - sage: UHP.point_to_model(3 + I, 'KM') + sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'KM') (6/11, 9/11) - sage: UHP.point_to_model(3 + I, 'HM') + sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'HM') (3, 9/2, 11/2) It is an error to try to convert a boundary point to a model that doesn't support boundary points:: - sage: UHP.point_to_model(infinity, 'HM') + sage: HyperbolicPlane.UHP.point_to_model(infinity, 'HM') Traceback (most recent call last): ... NotImplementedError: boundary points are not implemented for the HM model @@ -740,7 +740,7 @@ class HyperbolicModelPD(HyperbolicModel, UniqueRepresentation): bounded = True conformal = True dimension = 2 - isometry_group = "PU (1, 1)" + isometry_group = "PU(1, 1)" isometry_group_is_projective = True pt_conversion_dict = { 'PD': lambda p : p, @@ -772,13 +772,13 @@ def point_in_model(cls, p): #PD EXAMPLES:: - sage: PD.point_in_model(1.00) + sage: HyperbolicPlane.PD.point_in_model(1.00) False - sage: PD.point_in_model(1/2 + I/2) + sage: HyperbolicPlane.PD.point_in_model(1/2 + I/2) True - sage: PD.point_in_model(1 + .2*I) + sage: HyperbolicPlane.PD.point_in_model(1 + .2*I) False """ return bool(abs(CC(p)) < 1) @@ -791,13 +791,13 @@ def bdry_point_in_model(cls, p): #PD EXAMPLES:: - sage: PD.bdry_point_in_model(1.00) + sage: HyperbolicPlane.PD.bdry_point_in_model(1.00) True - sage: PD.bdry_point_in_model(1/2 + I/2) + sage: HyperbolicPlane.PD.bdry_point_in_model(1/2 + I/2) False - sage: PD.bdry_point_in_model(1 + .2*I) + sage: HyperbolicPlane.PD.bdry_point_in_model(1 + .2*I) False """ return bool(abs(abs(CC(p))- 1) < EPSILON) @@ -813,7 +813,7 @@ def isometry_act_on_point(cls, A, p): #PD sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD sage: I2 = identity_matrix(2) - sage: q = PD.random_point().coordinates() + sage: q = HyperbolicPlane.PD.random_point().coordinates() sage: bool(norm(HyperbolicModelPD.isometry_act_on_point(I2, q) - q) < 10**-9) True """ @@ -832,7 +832,7 @@ def isometry_in_model(cls, A): #PD sage: z = [CC.random_element() for k in range(2)]; z.sort(key=abs) sage: A = matrix(2,[z[1], z[0],z[0].conjugate(),z[1].conjugate()]) - sage: PD.isometry_in_model(A) + sage: HyperbolicPlane.PD.isometry_in_model(A) True """ # alpha = A[0][0] @@ -858,13 +858,13 @@ def point_to_model(cls, coordinates, model_name): #PD EXAMPLES:: - sage: PD.point_to_model(0, 'UHP') + sage: HyperbolicPlane.PD.point_to_model(0, 'UHP') I - sage: PD.point_to_model(I, 'UHP') + sage: HyperbolicPlane.PD.point_to_model(I, 'UHP') +Infinity - sage: PD.point_to_model(-I, 'UHP') + sage: HyperbolicPlane.PD.point_to_model(-I, 'UHP') 0 """ if model_name == 'UHP' and coordinates == I: @@ -892,7 +892,7 @@ def isometry_to_model(cls, A, model_name): # PD We check that orientation-reversing isometries behave as they should:: - sage: PD.isometry_to_model(matrix(2,[0,I,I,0]),'UHP') + sage: HyperbolicPlane.PD.isometry_to_model(matrix(2,[0,I,I,0]),'UHP') [ 0 -1] [-1 0] """ @@ -938,13 +938,13 @@ def point_in_model(cls, p): #KM EXAMPLES:: - sage: KM.point_in_model((1,0)) + sage: HyperbolicPlane.KM.point_in_model((1,0)) False - sage: KM.point_in_model((1/2 , 1/2)) + sage: HyperbolicPlane.KM.point_in_model((1/2 , 1/2)) True - sage: KM.point_in_model((1 , .2)) + sage: HyperbolicPlane.KM.point_in_model((1 , .2)) False """ return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) @@ -957,13 +957,13 @@ def bdry_point_in_model(cls, p): #KM EXAMPLES:: - sage: KM.bdry_point_in_model((1,0)) + sage: HyperbolicPlane.KM.bdry_point_in_model((1,0)) True - sage: KM.bdry_point_in_model((1/2 , 1/2)) + sage: HyperbolicPlane.KM.bdry_point_in_model((1/2 , 1/2)) False - sage: KM.bdry_point_in_model((1 , .2)) + sage: HyperbolicPlane.KM.bdry_point_in_model((1 , .2)) False """ return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) @@ -979,7 +979,7 @@ def isometry_act_on_point(cls, A, p): #KM sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelKM sage: I3 = identity_matrix(3) - sage: v = vector(KM.random_point().coordinates()) + sage: v = vector(HyperbolicPlane.KM.random_point().coordinates()) sage: bool(norm(HyperbolicModelKM.isometry_act_on_point(I3, v) - v) < 10**-9) True """ @@ -996,7 +996,7 @@ def isometry_in_model(cls, A): #KM EXAMPLES:: sage: A = matrix(3,[1, 0, 0, 0, 17/8, 15/8, 0, 15/8, 17/8]) - sage: KM.isometry_in_model(A) + sage: HyperbolicPlane.KM.isometry_in_model(A) True """ from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM @@ -1021,13 +1021,13 @@ def point_to_model(cls, coordinates, model_name): #KM EXAMPLES:: - sage: KM.point_to_model((0, 0), 'UHP') + sage: HyperbolicPlane.KM.point_to_model((0, 0), 'UHP') I - sage: KM.point_to_model((0, 0), 'HM') + sage: HyperbolicPlane.KM.point_to_model((0, 0), 'HM') (0, 0, 1) - sage: KM.point_to_model((0,1), 'UHP') + sage: HyperbolicPlane.KM.point_to_model((0,1), 'UHP') +Infinity """ if model_name == 'UHP' and tuple(coordinates) == (0,1): @@ -1069,13 +1069,13 @@ def point_in_model(cls, p): #HM EXAMPLES:: - sage: HM.point_in_model((0,0,1)) + sage: HyperbolicPlane.HM.point_in_model((0,0,1)) True - sage: HM.point_in_model((1,0,sqrt(2))) + sage: HyperbolicPlane.HM.point_in_model((1,0,sqrt(2))) True - sage: HM.point_in_model((1,2,1)) + sage: HyperbolicPlane.HM.point_in_model((1,2,1)) False """ return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 + 1 < EPSILON) @@ -1087,13 +1087,13 @@ def bdry_point_in_model(cls, p): #HM EXAMPLES:: - sage: HM.bdry_point_in_model((0,0,1)) + sage: HyperbolicPlane.HM.bdry_point_in_model((0,0,1)) False - sage: HM.bdry_point_in_model((1,0,sqrt(2))) + sage: HyperbolicPlane.HM.bdry_point_in_model((1,0,sqrt(2))) False - sage: HM.bdry_point_in_model((1,2,1)) + sage: HyperbolicPlane.HM.bdry_point_in_model((1,2,1)) False """ return False @@ -1107,7 +1107,7 @@ def isometry_in_model(cls, A): #HM EXAMPLES:: sage: A = diagonal_matrix([1,1,-1]) - sage: HM.isometry_in_model(A) + sage: HyperbolicPlane.HM.isometry_in_model(A) True """ from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index f7dc70aa5e8..d5e98664b15 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -14,10 +14,10 @@ We can construct points in the upper half plane model, abbreviated UHP for convenience:: - sage: UHP.point(2 + I) + sage: HyperbolicPlane.UHP.point(2 + I) Point in UHP I + 2 - sage: g = UHP.point(3 + I) - sage: g.dist(UHP.point(I)) + sage: g = HyperbolicPlane.UHP.point(3 + I) + sage: g.dist(HyperbolicPlane.UHP.point(I)) arccosh(11/2) """ @@ -134,8 +134,8 @@ def _cached_coordinates(self): r""" The representation of the current point used for calculations. For example, if the current model uses the HyperbolicMethodsUHP - class, then _cached_coordinates will hold the upper half plane - representation of self.coordinates(). + class, then ``_cached_coordinates`` will hold the upper half plane + representation of ``self.coordinates()``. EXAMPLES:: @@ -176,10 +176,10 @@ def _latex_(self): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = UHP.point(0) + sage: p = HyperbolicPlane.UHP.point(0) sage: latex(p) 0 - sage: q = HM.point((0,0,1)) + sage: q = HyperbolicPlane.HM.point((0,0,1)) sage: latex(q) \left(0,\,0,\,1\right) """ @@ -232,10 +232,10 @@ def __rmul__(self, other): EXAMPLES:: sage: A = matrix(2, [0, 1, 1, 0]) - sage: A*UHP.point(2 + I) + sage: A * HyperbolicPlane.UHP.point(2 + I) Point in UHP 1/5*I + 2/5 sage: B = diagonal_matrix([-1, -1, 1]) - sage: B*HM.point((0, 1, sqrt(2))) + sage: B * HyperbolicPlane.HM.point((0, 1, sqrt(2))) Point in HM (0, -1, sqrt(2)) """ from sage.matrix.matrix import is_Matrix @@ -470,7 +470,7 @@ def symmetry_in (self): sage: A.orientation_preserving() True - sage: A*A == UHP.isometry(identity_matrix(2)) + sage: A*A == HyperbolicPlane.UHP.isometry(identity_matrix(2)) True """ A = self.HMethods.symmetry_in(self._cached_coordinates) @@ -492,11 +492,11 @@ def random_element(cls, **kwargs): True sage: p = HyperbolicPointPD.random_element() - sage: PD.point_in_model(p.coordinates()) + sage: HyperbolicPlane.PD.point_in_model(p.coordinates()) True sage: p = HyperbolicPointKM.random_element() - sage: KM.point_in_model(p.coordinates()) + sage: HyperbolicPlane.KM.point_in_model(p.coordinates()) True sage: p = HyperbolicPointHM.random_element().coordinates() From 0572434edc03d53f81b087d106fcc5584de248c5 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 23 Aug 2014 23:43:47 +0900 Subject: [PATCH 020/129] About 85% of the refactoring is done and basic functionality is working. --- .../hyperbolic_space/hyperbolic_bdry_point.py | 233 ---- .../hyperbolic_space/hyperbolic_coercion.py | 557 +++++++++ .../hyperbolic_space/hyperbolic_factory.py | 367 ------ .../hyperbolic_space/hyperbolic_geodesic.py | 430 +++---- .../hyperbolic_space/hyperbolic_interface.py | 224 ++-- .../hyperbolic_space/hyperbolic_isometry.py | 289 ++--- .../hyperbolic_space/hyperbolic_methods.py | 76 +- .../hyperbolic_space/hyperbolic_model.py | 1026 ++++++++++------- .../hyperbolic_space/hyperbolic_point.py | 307 +++-- .../hyperbolic_space/model_factory.py | 68 -- 10 files changed, 1861 insertions(+), 1716 deletions(-) delete mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py create mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py delete mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_factory.py delete mode 100644 src/sage/geometry/hyperbolic_space/model_factory.py diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py deleted file mode 100644 index a1694222a82..00000000000 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_bdry_point.py +++ /dev/null @@ -1,233 +0,0 @@ -""" -Ideal Boundary Points in Hyperbolic Space - -This module implements the abstract base class for ideal points in -hyperbolic space of arbitrary dimension. It also contains the -implementations for specific models of hyperbolic geometry. - -Note that not all models of hyperbolic space are bounded, meaning that -the ideal boundary is not the topological boundary of the set underlying -tho model. For example, the unit disk model is bounded with boundary -given by the unit sphere. The hyperboloid model is not bounded. - -AUTHORS: - -- Greg Laun (2013): initial version - -EXAMPLES: - -We can construct boundary points in the upper half plane model, -abbreviated UHP for convenience:: - - sage: HyperbolicPlane.UHP.point(3) - Boundary point in UHP 3 - -Points on the boundary are infinitely far from interior points:: - - sage: HyperbolicPlane.UHP.point(3).dist(HyperbolicPlane.UHP.point(I)) - +Infinity -""" - - -#*********************************************************************** -# Copyright (C) 2013 Greg Laun -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#*********************************************************************** - -from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint -from sage.symbolic.pynac import I -from sage.rings.infinity import infinity -from sage.rings.all import RR -from sage.misc.lazy_import import lazy_import - -lazy_import('sage.plot.point', 'point') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', - ['HyperbolicAbstractFactory', 'HyperbolicFactoryHM', - 'HyperbolicFactoryUHP', 'HyperbolicFactoryPD', - 'HyperbolicFactoryKM']) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', - ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) -lazy_import('sage.modules.free_module_element', 'vector') - - -class HyperbolicBdryPoint(HyperbolicPoint): - r""" - Abstract base class for points on the ideal boundary of hyperbolic - space. This class should never be instantiated. - - INPUT: - - - the coordinates of a hyperbolic boundary point in the appropriate model - - EXAMPLES: - - Note that the coordinate does not differentiate the different models:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: p = HyperbolicBdryPointUHP(1); p - Boundary point in UHP 1 - - sage: q = HyperbolicBdryPointPD(1); q - Boundary point in PD 1 - - sage: p == q - False - - sage: bool(p.coordinates() == q.coordinates()) - True - - It is an error to specify a an interior point of hyperbolic space:: - - sage: HyperbolicBdryPointUHP(0.2 + 0.3*I) - Traceback (most recent call last): - ... - ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model - """ - def __init__(self, coordinates, **graphics_options): - r""" - See ``HyperbolicPoint`` for full documentation. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: HyperbolicBdryPointUHP(1) - Boundary point in UHP 1 - """ - if not self.model().bounded: - raise NotImplementedError( - "{0} is not a bounded model; boundary" - " points not implemented".format(self.model_name())) - elif self.model().bdry_point_in_model(coordinates): - if type(coordinates) == tuple: - coordinates = vector(coordinates) - self._coordinates = coordinates - else: - raise ValueError( - "{0} is not a valid".format(coordinates) + - " boundary point in the {0} model".format(self.model_name())) - self._graphics_options = graphics_options - - def _repr_(self): - r""" - Return a string representation of ``self``. - - OUTPUT: - - - string - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: HyperbolicBdryPointUHP(infinity) - Boundary point in UHP +Infinity - - sage: HyperbolicBdryPointPD(-1) - Boundary point in PD -1 - - sage: HyperbolicBdryPointKM((0, -1)) - Boundary point in KM (0, -1) - """ - return "Boundary point in {0} {1}".format(self.model_name(), - self.coordinates()) - - -class HyperbolicBdryPointUHP(HyperbolicBdryPoint): - r""" - Create a boundary point for the UHP model. - - INPUT: - - - the coordinates of a hyperbolic boundary point in the upper half plane - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointUHP - sage: q = HyperbolicBdryPointUHP(1); q - Boundary point in UHP 1 - """ - HFactory = HyperbolicFactoryUHP - HMethods = HyperbolicMethodsUHP - - def show(self, boundary=True, **options): - r""" - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: HyperbolicBdryPointUHP(0).show() - sage: HyperbolicBdryPointUHP(infinity).show() - Traceback (most recent call last): - ... - NotImplementedError: can't draw the point infinity - """ - opts = dict([('axes', False), ('aspect_ratio',1)]) - opts.update(self.graphics_options()) - opts.update(options) - from sage.misc.functional import numerical_approx - p = self.coordinates() - if p == infinity: - raise NotImplementedError("can't draw the point infinity") - p = numerical_approx(p) - pic = point((p, 0), **opts) - if boundary: - bd_pic = self.HFactory.get_background_graphic(bd_min = p - 1, - bd_max = p + 1) - pic = bd_pic + pic - return pic - - -class HyperbolicBdryPointPD(HyperbolicBdryPoint): - r""" - Create a boundary point for the PD model. - - INPUT: - - - the coordinates of a hyperbolic boundary point in the Poincare disk - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointPD - sage: q = HyperbolicBdryPointPD(1); q - Boundary point in PD 1 - """ - HFactory = HyperbolicFactoryPD - HMethods = HyperbolicMethodsUHP - - -class HyperbolicBdryPointKM(HyperbolicBdryPoint): - r""" - Create a boundary point for the KM model. - - INPUT: - - - the coordinates of a hyperbolic boundary point in the Klein disk - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointKM - sage: q = HyperbolicBdryPointKM((1,0)); q - Boundary point in KM (1, 0) - """ - HFactory = HyperbolicFactoryKM - HMethods = HyperbolicMethodsUHP - -class HyperbolicBdryPointHM(HyperbolicBdryPoint): - r""" - A dummy class for the boundary points of the hyperboloid model. The model - is not bounded, so there are no boundary points. The class is needed for - compatibility reasons. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointHM - sage: q = HyperbolicBdryPointHM((1,0,0)); q - Traceback (most recent call last): - ... - NotImplementedError: HM is not a bounded model; boundary points not implemented - """ - HFactory = HyperbolicFactoryHM - HMethods = HyperbolicMethodsUHP - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py new file mode 100644 index 00000000000..89624d6e2eb --- /dev/null +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -0,0 +1,557 @@ +""" +Coercion Maps Between Hyperbolic Plane Models + +This module implements the coercion maps between different hyperbolic +plane models. + +AUTHORS: + +- Travis Scrimshaw (2014): initial version +""" + +#*********************************************************************** +# Copyright (C) 2014 Travis Scrimshaw +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#*********************************************************************** + +from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint +from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometry +from sage.categories.morphism import Morphism +from sage.symbolic.pynac import I +from sage.matrix.constructor import matrix +from sage.rings.all import RR +from sage.rings.integer import Integer +from sage.rings.infinity import infinity +from sage.misc.lazy_import import lazy_import + +class HyperbolicModelCoercion(Morphism): + """ + Abstract base class for morphisms between the hyperbolic models. + """ + def _repr_type(self): + """ + Return the type of morphism. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = UHP.coerce_map_from(PD) + sage: phi._repr_type() + 'Coercion Isometry' + """ + return "Coercion Isometry" + + def _call_(self, x): + """ + Return the image of ``x`` under ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = UHP.coerce_map_from(PD) + sage: phi(PD.get_point(0.5+0.5*I)) + Point in UHP 2.00000000000000 + 1.00000000000000*I + + It is an error to try to convert a boundary point to a model + that doesn't support boundary points:: + + sage: HM = HyperbolicPlane().HM() + sage: psi = HM.coerce_map_from(UHP) + sage: psi(UHP(infinity) + Traceback (most recent call last): + ... + NotImplementedError: boundary points are not implemented for the Hyperbolid Model + """ + C = self.codomain() + bdry = False + if C.is_bounded(): + if self.domain().is_bounded(): + bdry = x.is_boundary() + else: + bdry = C.bdry_point_in_model(x) + elif self.domain().is_bounded() and x.is_boundary(): + raise NotImplementedError("boundary points are not implemented for" + " the {0}".format(C.name())) + return C.element_class(C, self.image_point(x.coordinates()), bdry, + check=False, **x.graphics_options()) + + def convert_geodesic(self, x): + """ + Convert the geodesic ``x`` of the domain into a geodesic of + the codomain. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = UHP.coerce_map_from(PD) + sage: phi.convert_geodesic(PD.get_geodesic(0.5+0.5*I, -I)) + Geodesic in UHP from 2.00000000000000 + 1.00000000000000*I to 0 + """ + return self.codomain().get_geodesic(self(x.start()), self(x.end()), + **x.graphics_options()) + + def convert_isometry(self, x): + """ + Convert the hyperbolic isometry ``x`` of the domain into an + isometry of the codomain. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = UHP.coerce_map_from(PD) + sage: phi(PD.get_point(0.5+0.5*I)) + Point in UHP 2.00000000000000 + 1.00000000000000*I + """ + return self.codomain().get_isometry(self.image_isometry(x._matrix)) + + def __invert__(self): + """ + Return the inverse coercion of ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = UHP.coerce_map_from(PD) + sage: ~phi + Coercion Isometry morphism: + From: Hyperbolic plane in the Upper Half Plane Model model + To: Hyperbolic plane in the Poincare Disk Model model + """ + return self.domain().coerce_map_from(self.codomain()) + +############ +# From UHP # +############ + +class CoercionUHPtoPD(HyperbolicModelCoercion): + """ + Coercion from the UHP to PD model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = PD.coerce_map_from(UHP) + sage: phi.image_point(I) + Point in PD 0 + """ + if x == infinity: + return I + return (x - I)/(Integer(1) - I*x) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return matrix(2,[1,-I,-I,1]) * x * matrix(2,[1,I,I,1])/Integer(2) + +class CoercionUHPtoKM(HyperbolicModelCoercion): + """ + Coercion from the UHP to KM model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'KM') + (6/11, 9/11) + """ + if p == infinity: + return (0, 1) + return ((2*real(p))/(real(p)**2 + imag(p)**2 + 1), + (real(p)**2 + imag(p)**2 - 1)/(real(p)**2 + imag(p)**2 + 1)) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return SL2R_to_SO21(x) + +class CoercionUHPtoHM(HyperbolicModelCoercion): + """ + Coercion from the UHP to HM model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'HM') + (3, 9/2, 11/2) + """ + return vector((real(x)/imag(x), + (real(x)**2 + imag(x)**2 - 1)/(2*imag(x)), + (real(x)**2 + imag(x)**2 + 1)/(2*imag(x)))) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return SL2R_to_SO21(x) + +########### +# From PD # +########### + +class CoercionPDtoUHP(HyperbolicModelCoercion): + """ + Coercion from the PD to UHP model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: UHP = HyperbolicPlane().UHP() + sage: phi = UHP.coerce_map_from(PD) + sage: phi.image_point(0.5+0.5*I) + 2.00000000000000 + 1.00000000000000*I + sage: phi.image_point(0) + I + """ + if x == I: + return infinity + return (x + I)/(Integer(1) + I*x) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return matrix(2,[1,I,I,1]) * x * matrix(2,[1,-I,-I,1])/Integer(2) + +class CoercionPDtoKM(HyperbolicModelCoercion): + """ + Coercion from the PD to KM model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + """ + return (2*real(x)/(Integer(1) + real(x)**2 +imag(x)**2), + 2*imag(x)/(Integer(1) + real(x)**2 + imag(x)**2)) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return SL2R_to_SO21( matrix(2,[1,I,I,1]) * x * + matrix(2,[1,-I,-I,1])/Integer(2) ) + +class CoercionPDtoHM(HyperbolicModelCoercion): + """ + Coercion from the PD to HM model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + """ + return vector(( + 2*real(x)/(1 - real(x)**2 - imag(x)**2), + 2*imag(x)/(1 - real(x)**2 - imag(x)**2), + (real(x)**2 + imag(x)**2 + 1)/(1 - real(x)**2 - imag(x)**2) + )) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return SL2R_to_SO21( matrix(2,[1,I,I,1]) * x * + matrix(2,[1,-I,-I,1])/Integer(2) ) + +########### +# From KM # +########### + +class CoercionKMtoUHP(HyperbolicModelCoercion): + """ + Coercion from the KM to UHP model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane.KM.point_to_model((0, 0), 'UHP') + I + """ + if tuple(x) == (0, 1): + return infinity + return ( -x[0]/(x[1] - 1) + + I*(-(sqrt(-x[0]**2 -x[1]**2 + 1) - x[0]**2 - x[1]**2 + 1) + / ((x[1] - 1)*sqrt(-x[0]**2 - x[1]**2 + 1) + x[1] - 1)) ) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return SO21_to_SL2R(x) + +class CoercionKMtoPD(HyperbolicModelCoercion): + """ + Coercion from the KM to PD model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + """ + return ( x[0]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) + + I*x[1]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) ) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return (matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(x) * + matrix(2,[1,I,I,1])/Integer(2)) + +class CoercionKMtoHM(HyperbolicModelCoercion): + """ + Coercion from the KM to HM model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane.KM.point_to_model((0, 0), 'HM') + (0, 0, 1) + """ + return (vector((2*x[0], 2*x[1], 1 + x[0]**2 + x[1]**2)) + / (1 - x[0]**2 - x[1]**2)) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return x + +########### +# From HM # +########### + +class CoercionHMtoUHP(HyperbolicModelCoercion): + """ + Coercion from the HM to UHP model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'UHP') + I + """ + return -((x[0]*x[2] + x[0]) + I*(x[2] + 1)) / ((x[1] - 1)*x[2] + - x[0]**2 - x[1]**2 + x[1] - 1) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return SO21_to_SL2R(x) + +class CoercionHMtoPD(HyperbolicModelCoercion): + """ + Coercion from the HM to PD model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + """ + return x[0]/(1 + x[2]) + I*(x[1]/(1 + x[2])) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return (matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(x) * + matrix(2,[1,I,I,1])/Integer(2)) + +class CoercionHMtoKM(HyperbolicModelCoercion): + """ + Coercion from the HM to KM model. + """ + def image_point(self, x): + """ + Return the image of the coordinates of the hyperbolic point ``x`` + under ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'KM') + (0, 0) + """ + return (x[0]/(1 + x[2]), x[1]/(1 + x[2])) + + def image_isometry(self, x): + """ + Return the image of the matrix of the hyperbolic isometry ``x`` + under ``self``. + + EXAMPLES:: + """ + return x + +##################################################################### +## Helper functions + +def SL2R_to_SO21(A): + r""" + Given a matrix in `SL(2, \RR)` return its irreducible representation in + `O(2,1)`. + + Note that this is not the only homomorphism, but it is the only one + that works in the context of the implemented 2D hyperbolic geometry + models. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_coercion import SL2R_to_SO21 + sage: A = SL2R_to_SO21(identity_matrix(2)) + sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix + sage: norm(A.transpose()*J*A - J) < 10**-4 + True + """ + a,b,c,d = (A/A.det().sqrt()).list() + B = matrix(3, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, + Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, + Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, + a*b + c*d, Integer(1)/Integer(2)*a**2 - + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - + Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + + Integer(1)/Integer(2)*d**2]) + B = B.apply_map(attrcall('real')) # Kill ~0 imaginary parts + if A.det() > 0: + return B + else: + # Orientation-reversing isometries swap the nappes of + # the lightcone. This fixes that issue. + return -B + +def SO21_to_SL2R(M): + r""" + A homomorphism from `SO(2, 1)` to `SL(2, \RR)`. + + Note that this is not the only homomorphism, but it is the only one + that works in the context of the implemented 2D hyperbolic geometry + models. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_coercion import SO21_to_SL2R + sage: (SO21_to_SL2R(identity_matrix(3)) - identity_matrix(2)).norm() < 10**-4 + True + """ + #################################################################### + # SL(2,R) is the double cover of SO (2,1)^+, so we need to choose # + # a lift. I have formulas for the absolute values of each entry # + # a,b ,c,d of the lift matrix(2,[a,b,c,d]), but we need to choose # + # one entry to be positive. I choose d for no particular reason, # + # unless d = 0, then we choose c > 0. The basic strategy for this # + # function is to find the linear map induced by the SO(2,1) # + # element on the Lie algebra sl(2, R). This corresponds to the # + # Adjoint action by a matrix A or -A in SL(2,R). To find which # + # matrix let X,Y,Z be a basis for sl(2,R) and look at the images # + # of X,Y,Z as well as the second and third standard basis vectors # + # for 2x2 matrices (these are traceless, so are in the Lie # + # algebra). These corresponds to AXA^-1 etc and give formulas # + # for the entries of A. # + #################################################################### + (m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8,m_9) = M.list() + d = sqrt(Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 - + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + if M.det() > 0: #EPSILON? + det_sign = 1 + elif M.det() < 0: #EPSILON? + det_sign = -1 + if d > 0: #EPSILON? + c = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/d + b = (-Integer(1)/Integer(2)*m_2 + Integer(1)/Integer(2)*m_3)/d + ad = det_sign*1 + b*c # ad - bc = pm 1 + a = ad/d + else: # d is 0, so we make c > 0 + c = sqrt(-Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 + + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + d = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/c + #d = 0, so ad - bc = -bc = pm 1. + b = - (det_sign*1)/c + a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b + A = matrix(2,[a,b,c,d]) + return A + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py b/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py deleted file mode 100644 index 619cf644bdf..00000000000 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_factory.py +++ /dev/null @@ -1,367 +0,0 @@ -r""" -Hyerbolic Factory - -AUTHORS: - -- Greg Laun (2013): initial version -""" - -#*********************************************************************** -# Copyright (C) 2013 Greg Laun -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#*********************************************************************** - -from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.lazy_import import lazy_import -lazy_import('sage.functions.other','sqrt') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', - ['HyperbolicModel', 'HyperbolicModelUHP', 'HyperbolicModelPD', - 'HyperbolicModelKM', 'HyperbolicModelHM', 'HyperbolicBdryPointHM']) - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', - ['HyperbolicPoint', 'HyperbolicPointUHP', 'HyperbolicPointPD', - 'HyperbolicPointKM', 'HyperbolicPointHM']) - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', - ['HyperbolicBdryPoint', 'HyperbolicBdryPointUHP', - 'HyperbolicBdryPointPD', 'HyperbolicBdryPointKM']) - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', - ['HyperbolicIsometry', 'HyperbolicIsometryUHP', - 'HyperbolicIsometryPD', 'HyperbolicIsometryKM', - 'HyperbolicIsometryHM']) - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', - ['HyperbolicGeodesic', 'HyperbolicGeodesicUHP', - 'HyperbolicGeodesicPD', 'HyperbolicGeodesicKM', - 'HyperbolicGeodesicHM']) - -class HyperbolicAbstractFactory(UniqueRepresentation): - """ - Abstract factory for creating the hyperbolic models. - """ - HModel = HyperbolicModel - HPoint = HyperbolicPoint - HBdryPoint = HyperbolicBdryPoint - HIsometry = HyperbolicIsometry - HGeodesic = HyperbolicGeodesic - - @classmethod - def get_model(cls): - r""" - Return the current model as a class rather than a string. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: HyperbolicFactoryUHP.get_model() - - - sage: HyperbolicFactoryPD.get_model() - - - sage: HyperbolicFactoryKM.get_model() - - - sage: HyperbolicFactoryHM.get_model() - - """ - return cls.HModel - - @classmethod - def get_interior_point(cls, coordinates, **graphics_options): - r""" - Return a point in the appropriate model given the coordinates. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: HyperbolicFactoryUHP.get_interior_point(2 + 3*I) - Point in UHP 3*I + 2 - - sage: HyperbolicFactoryPD.get_interior_point(0) - Point in PD 0 - - sage: HyperbolicFactoryKM.get_interior_point((0,0)) - Point in KM (0, 0) - - sage: HyperbolicFactoryHM.get_interior_point((0,0,1)) - Point in HM (0, 0, 1) - - sage: p = HyperbolicFactoryUHP.get_interior_point(I, color="red") - sage: p.graphics_options() - {'color': 'red'} - """ - return cls.HPoint(coordinates, **graphics_options) - - @classmethod - def get_bdry_point(cls, coordinates, **graphics_options): - r""" - Return a boundary point in the appropriate model given the - coordinates. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: HyperbolicFactoryUHP.get_bdry_point(12) - Boundary point in UHP 12 - - sage: HyperbolicFactoryUHP.get_bdry_point(infinity) - Boundary point in UHP +Infinity - - sage: HyperbolicFactoryPD.get_bdry_point(I) - Boundary point in PD I - - sage: HyperbolicFactoryKM.get_bdry_point((0,-1)) - Boundary point in KM (0, -1) - """ - return cls.HBdryPoint(coordinates, **graphics_options) - - @classmethod - def get_point(cls, coordinates, **graphics_options): - r""" - Automatically determine the type of point to return given either - (1) the coordinates of a point in the interior or ideal boundary - of hyperbolic space or (2) a :class:`HyperbolicPoint` or - :class:`HyperbolicBdryPoint` object. - - INPUT: - - - a point in hyperbolic space or on the ideal boundary - - OUTPUT: - - - a :class:`HyperbolicPoint` or :class:`HyperbolicBdryPoint` - - EXAMPLES: - - We can create an interior point via the coordinates:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: p = HyperbolicFactoryUHP.get_point(2*I); p - Point in UHP 2*I - - Or we can create a boundary point via the coordinates:: - - sage: q = HyperbolicFactoryUHP.get_point(23); q - Boundary point in UHP 23 - - Or we can create both types of points by passing the - HyperbolicPoint or HyperbolicBdryPoint object:: - - sage: HyperbolicFactoryUHP.get_point(p) - Point in UHP 2*I - - sage: HyperbolicFactoryUHP.get_point(q) - Boundary point in UHP 23 - - sage: HyperbolicFactoryUHP.get_point(12 - I) - Traceback (most recent call last): - ... - ValueError: -I + 12 is neither an interior nor boundary point in the UHP model - """ - from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint - if isinstance(coordinates, HyperbolicPoint): - coordinates.update_graphics(True, **graphics_options) - return coordinates #both Point and BdryPoint - elif cls.HModel.point_in_model(coordinates): - return cls.HPoint(coordinates, **graphics_options) - elif cls.HModel.bdry_point_in_model(coordinates): - return cls.HBdryPoint(coordinates, **graphics_options) - else: - raise ValueError("{0} is neither an interior nor boundary".format(coordinates) - + " point in the {0} model".format(cls.get_model().short_name)) - - @classmethod - def get_geodesic(cls, start, end, **graphics_options): - r""" - Return a geodesic in the appropriate model. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: HyperbolicFactoryUHP.get_geodesic(I, 2*I) - Geodesic in UHP from I to 2*I - - sage: HyperbolicFactoryPD.get_geodesic(0, I/2) - Geodesic in PD from 0 to 1/2*I - - sage: HyperbolicFactoryKM.get_geodesic((1/2, 1/2), (0,0)) - Geodesic in KM from (1/2, 1/2) to (0, 0) - - sage: HyperbolicFactoryHM.get_geodesic((0,0,1), (1,0, sqrt(2))) - Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)) - """ - return cls.HGeodesic(start, end, **graphics_options) - - @classmethod - def get_isometry(cls, A): - r""" - Return an isometry in the appropriate model given the matrix. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: HyperbolicFactoryUHP.get_isometry(identity_matrix(2)) - Isometry in UHP - [1 0] - [0 1] - - sage: HyperbolicFactoryPD.get_isometry(identity_matrix(2)) - Isometry in PD - [1 0] - [0 1] - - sage: HyperbolicFactoryKM.get_isometry(identity_matrix(3)) - Isometry in KM - [1 0 0] - [0 1 0] - [0 0 1] - - sage: HyperbolicFactoryHM.get_isometry(identity_matrix(3)) - Isometry in HM - [1 0 0] - [0 1 0] - [0 0 1] - """ - return cls.HIsometry(A) - - @classmethod - def get_background_graphic(cls, **bdry_options): - r""" - Return a graphic object that makes the model easier to - visualize. For bounded models, such as the upper half space and - the unit ball models, the background object is the ideal - boundary. For the hyperboloid model, the background object is - the hyperboloid itself. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryPD.get_background_graphic() - """ - return None - -class HyperbolicFactoryUHP(HyperbolicAbstractFactory, UniqueRepresentation): - """ - Factory for creating the UHP model. - """ - HModel = HyperbolicModelUHP - HPoint = HyperbolicPointUHP - HBdryPoint = HyperbolicBdryPointUHP - HIsometry = HyperbolicIsometryUHP - HGeodesic = HyperbolicGeodesicUHP - - @classmethod - def get_background_graphic(cls, **bdry_options): - r""" - Return a graphic object that makes the model easier to visualize. - For bounded models, such as the upper half space and the unit - ball models, the background object is the ideal boundary. For - the hyperboloid model, the background object is the hyperboloid - itself. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryUHP.get_background_graphic() - """ - from sage.plot.line import line - bd_min = bdry_options.get('bd_min', -5) - bd_max = bdry_options.get('bd_max', 5) - return line(((bd_min, 0), (bd_max, 0)), color='black') - -class HyperbolicFactoryPD(HyperbolicAbstractFactory, UniqueRepresentation): - """ - Factory for creating the PD model. - """ - from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD - HModel = HyperbolicModelPD - HPoint = HyperbolicPointPD - HBdryPoint = HyperbolicBdryPointPD - HIsometry = HyperbolicIsometryPD - HGeodesic = HyperbolicGeodesicPD - - @classmethod - def get_background_graphic(cls, **bdry_options): - r""" - Return a graphic object that makes the model easier to visualize. - For bounded models, such as the upper half space and the unit - ball models, the background object is the ideal boundary. For - the hyperboloid model, the background object is the hyperboloid - itself. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryPD.get_background_graphic() - """ - from sage.plot.circle import circle - return circle((0,0), 1, axes=False, color='black') - - -class HyperbolicFactoryKM(HyperbolicAbstractFactory, UniqueRepresentation): - """ - Factory for creating the KM model. - """ - from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelKM - HModel = HyperbolicModelKM - HPoint = HyperbolicPointKM - HBdryPoint = HyperbolicBdryPointKM - HIsometry = HyperbolicIsometryKM - HGeodesic = HyperbolicGeodesicKM - - @classmethod - def get_background_graphic(cls, **bdry_options): - r""" - Return a graphic object that makes the model easier to visualize. - For bounded models, such as the upper half space and the unit - ball models, the background object is the ideal boundary. For - the hyperboloid model, the background object is the hyperboloid - itself. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryKM.get_background_graphic() - """ - from sage.plot.circle import circle - return circle((0,0), 1, axes=False, color='black') - -class HyperbolicFactoryHM(HyperbolicAbstractFactory, UniqueRepresentation): - """ - Factory for creating the HM model. - """ - HModel = HyperbolicModelHM - HPoint = HyperbolicPointHM - HIsometry = HyperbolicIsometryHM - HGeodesic = HyperbolicGeodesicHM - - @classmethod - def get_background_graphic(cls, **bdry_options): - r""" - Return a graphic object that makes the model easier to visualize. - For bounded models, such as the upper half space and the unit - ball models, the background object is the ideal boundary. For - the hyperboloid model, the background object is the hyperboloid - itself. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryPD.get_background_graphic() - """ - hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1) - z_height = bdry_options.get('z_height', 7.0) - x_max = sqrt((z_height**2 - 1)/2.0) - from sage.plot.plot3d.all import plot3d - from sage.all import var - (x,y) = var('x,y') - return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max),\ - (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 6dc91e5f89a..b65d997a397 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -14,9 +14,9 @@ We can construct geodesics in the upper half plane model, abbreviated UHP for convenience:: - sage: HyperbolicPlane.UHP.geodesic(2, 3) + sage: HyperbolicPlane().UHP().get_geodesic(2, 3) Geodesic in UHP from 2 to 3 - sage: g = HyperbolicPlane.UHP.geodesic(I, 3 + I) + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3 + I) sage: g.length() arccosh(11/2) @@ -24,8 +24,12 @@ graph will only be equal if their starting and ending points are the same:: - sage: HyperbolicPlane.UHP.geodesic(1,2) == HyperbolicPlane.UHP.geodesic(2,1) + sage: HyperbolicPlane().UHP().get_geodesic(1,2) == HyperbolicPlane().UHP().get_geodesic(2,1) False + +.. TODO:: + + Implement a parent for all geodesics of the hyperbolic plane? """ @@ -59,12 +63,8 @@ lazy_import('sage.functions.hyperbolic', 'sinh') lazy_import('sage.functions.hyperbolic', 'arcsinh') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicAbstractMethods') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', 'HyperbolicMethodsUHP') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', - ['HyperbolicFactoryUHP', 'HyperbolicFactoryPD', - 'HyperbolicFactoryKM', 'HyperbolicFactoryHM']) +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', + ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) class HyperbolicGeodesic(SageObject): r""" @@ -81,31 +81,28 @@ class HyperbolicGeodesic(SageObject): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: UHP = HyperbolicPlane.UHP - sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) - sage: g = HyperbolicGeodesicUHP(I, 2 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I)) + sage: g = UHP.get_geodesic(I, 2 + I) """ - HFactory = HyperbolicAbstractFactory - HMethods = HyperbolicAbstractMethods + _HMethods = HyperbolicAbstractMethods ##################### # "Private" Methods # ##################### - def __init__(self, start, end, **graphics_options): + def __init__(self, model, start, end, **graphics_options): r""" See :class:`HyperbolicGeodesic` for full documentation. EXAMPLES :: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(I, 2 + I) + sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I) Geodesic in UHP from I to I + 2 """ - self._model = self.HFactory.get_model() - self._start = self.HFactory.get_point(start) - self._end = self.HFactory.get_point(end) + self._model = model + self._start = start + self._end = end self._graphics_options = graphics_options @lazy_attribute @@ -119,12 +116,11 @@ def _cached_start(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicPD(0, 1/2)._cached_start + sage: HyperbolicPlane().PD().get_geodesic(0, 1/2)._cached_start I """ return self.model().point_to_model(self.start().coordinates(), - self.HMethods.model_name()) + self._HMethods.model_name()) @lazy_attribute def _cached_end(self): @@ -137,12 +133,11 @@ def _cached_end(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicPD(0, 1/2)._cached_end + sage: HyperbolicPlane().PD().get_geodesic(0, 1/2)._cached_end 3/5*I + 4/5 """ return self.model().point_to_model(self.end().coordinates(), - self.HMethods.model_name()) + self._HMethods.model_name()) @lazy_attribute def _cached_endpoints(self): @@ -151,8 +146,7 @@ def _cached_endpoints(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: A = HyperbolicGeodesicPD(0, 1/2) + sage: A = HyperbolicPlane().PD().get_geodesic(0, 1/2) sage: A._cached_endpoints [I, 3/5*I + 4/5] """ @@ -167,18 +161,17 @@ def _complete(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(1, -12)._complete + sage: HyperbolicPlane().UHP().get_geodesic(1, -12)._complete True - sage: HyperbolicGeodesicUHP(I, 2 + I)._complete + sage: HyperbolicPlane().UHP().get_geodesic(I, 2 + I)._complete False - sage: g = HyperbolicGeodesicHM((0,0,1), (0,1, sqrt(2))) + sage: g = HyperbolicPlane().HM().get_geodesic((0,0,1), (0,1, sqrt(2))) sage: g._complete False sage: g.complete()._complete True """ - if self.model().bounded: + if self.model().is_bounded(): return (self.model().bdry_point_in_model(self.start().coordinates()) and self.model().bdry_point_in_model(self.end().coordinates())) else: @@ -190,20 +183,19 @@ def _repr_(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(3 + 4*I, I) + sage: HyperbolicPlane().UHP().get_geodesic(3 + 4*I, I) Geodesic in UHP from 4*I + 3 to I - sage: HyperbolicGeodesicPD(1/2 + I/2, 0) + sage: HyperbolicPlane().PD().get_geodesic(1/2 + I/2, 0) Geodesic in PD from 1/2*I + 1/2 to 0 - sage: HyperbolicGeodesicKM((1/2, 1/2), (0, 0)) + sage: HyperbolicPlane().KM().get_geodesic((1/2, 1/2), (0, 0)) Geodesic in KM from (1/2, 1/2) to (0, 0) - sage: HyperbolicGeodesicHM((0,0,1), (0, 1, sqrt(Integer(2)))) + sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (0, 1, sqrt(Integer(2)))) Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ - return "Geodesic in {0} from {1} to {2}".format(self.model_name(), + return "Geodesic in {0} from {1} to {2}".format(self._model.short_name(), self.start().coordinates(), self.end().coordinates()) def __eq__(self, other): @@ -212,15 +204,14 @@ def __eq__(self, other): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g1 = HyperbolicGeodesicUHP(I, 2*I) - sage: g2 = HyperbolicGeodesicUHP(2*I,I) + sage: g1 = HyperbolicPlane().UHP().get_geodesic(I, 2*I) + sage: g2 = HyperbolicPlane().UHP().get_geodesic(2*I,I) sage: g1 == g2 False sage: g1 == g1 True """ - return (self.model_name() == other.model_name() + return (self.model() is other.model() and self.start() == other.start() and self.end() == other.end()) @@ -234,8 +225,7 @@ def start(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(I, 3*I) + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g.start() Point in UHP I """ @@ -247,8 +237,7 @@ def end(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(I, 3*I) + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g.end() Point in UHP 3*I """ @@ -260,56 +249,51 @@ def endpoints(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(I, 3*I) + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 3*I) sage: g.endpoints() [Point in UHP I, Point in UHP 3*I] """ return [self.start(), self.end()] - @classmethod - def model(cls): + def model(self): r""" - Return the model to which the :class:`HyperbolicPoint` belongs. + Return the model to which the :class:`HyperbolicGeodesic` belongs. EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(I, 2*I).model() + + sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).model() - sage: HyperbolicGeodesicPD(0, I/2).model() + sage: HyperbolicPlane().PD().get_geodesic(0, I/2).model() - sage: HyperbolicGeodesicKM((0, 0), (0, 1/2)).model() + sage: HyperbolicPlane().KM().get_geodesic((0, 0), (0, 1/2)).model() - sage: HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))).model() + sage: HyperbolicPlane().HM().get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model() """ - return cls.HFactory.get_model() + return self._model - @classmethod - def model_name(cls): + def model_name(self): r""" Return the short name of the hyperbolic model. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(I, 2*I).model_name() + sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).model_name() 'UHP' - sage: HyperbolicGeodesicPD(0, I/2).model_name() + sage: HyperbolicPlane().PD().get_geodesic(0, I/2).model_name() 'PD' - sage: HyperbolicGeodesicKM((0, 0), (0, 1/2)).model_name() + sage: HyperbolicPlane().KM().get_geodesic((0, 0), (0, 1/2)).model_name() 'KM' - sage: HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))).model_name() + sage: HyperbolicPlane().HM().get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model_name() 'HM' """ - return cls.model().short_name + return self.model().short_name() def to_model(self, model_name): r""" @@ -321,20 +305,19 @@ def to_model(self, model_name): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(I, 2*I).to_model('PD') + sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).to_model('PD') Geodesic in PD from 0 to 1/3*I """ from sage.geometry.hyperbolic_space.model_factory import ModelFactory factory = ModelFactory.find_factory(model_name) - if not factory.get_model().bounded and self.is_complete(): + if not factory.get_model().is_bounded() and self.is_complete(): g = self.uncomplete() return g.to_model(model_name).complete() start = self.model().point_to_model(self.start().coordinates(), model_name) end = self.model().point_to_model(self.end().coordinates(), model_name) g = factory.get_geodesic(start, end) - if not self.model().bounded and factory.get_model().bounded \ + if not self.model().is_bounded() and factory.get_model().is_bounded() \ and self.is_complete(): # Converting from a non-bounded model to a bounded model return g.complete() @@ -342,20 +325,19 @@ def to_model(self, model_name): def graphics_options(self): r""" - Return the graphics options of the current point. + Return the graphics options of ``self``. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: p = HyperbolicPlane.UHP.point(2 + I, color="red") - sage: p.graphics_options() + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I, color="red") + sage: g.graphics_options() {'color': 'red'} """ return self._graphics_options def update_graphics(self, update=False, **options): r""" - Update the graphics options of a :class:`HyperbolicPoint`. + Update the graphics options of ``self``. INPUT: @@ -364,8 +346,8 @@ def update_graphics(self, update=False, **options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(I, 2*I); g.graphics_options() + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2*I) + sage: g.graphics_options() {} sage: g.update_graphics(color = "red"); g.graphics_options() @@ -392,14 +374,13 @@ def is_complete(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(I, 2*I).is_complete() + sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).is_complete() False - sage: HyperbolicGeodesicUHP(0, I).is_complete() + sage: HyperbolicPlane().UHP().get_geodesic(0, I).is_complete() False - sage: HyperbolicGeodesicUHP(0, infinity).is_complete() + sage: HyperbolicPlane().UHP().get_geodesic(0, infinity).is_complete() True """ return self._complete @@ -407,7 +388,7 @@ def is_complete(self): def is_asymptotically_parallel(self, other): r""" Return ``True`` if ``self`` and ``other`` are asymptotically - parallel, ``False`` otherwise. + parallel and ``False`` otherwise. INPUT: @@ -415,34 +396,33 @@ def is_asymptotically_parallel(self, other): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(-2,5) - sage: h = HyperbolicGeodesicUHP(-2,4) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) + sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4) sage: g.is_asymptotically_parallel(h) True Ultraparallel geodesics are not asymptotically parallel:: - sage: g = HyperbolicGeodesicUHP(-2,5) - sage: h = HyperbolicGeodesicUHP(-1,4) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) + sage: h = HyperbolicPlane().UHP().get_geodesic(-1,4) sage: g.is_asymptotically_parallel(h) False No hyperbolic geodesic is asymptotically parallel to itself:: - sage: g = HyperbolicGeodesicUHP(-2,5) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: g.is_asymptotically_parallel(g) False """ p1, p2 = self.complete().endpoints() q1, q2 = other.complete().endpoints() - return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1,q2])) - and self.model() == other.model()) + return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1,q2])) + and self.model() is other.model()) def is_ultra_parallel(self,other): r""" - Return ``True`` if ``self`` and ``other`` are asymptotically - parallel, ``False`` otherwise. + Return ``True`` if ``self`` and ``other`` are ultra parallel + and ``False`` otherwise. INPUT: @@ -451,21 +431,21 @@ def is_ultra_parallel(self,other): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(0,1) - sage: h = HyperbolicGeodesicUHP(-3,3) + sage: g = HyperbolicPlane().UHP().get_geodesic(0,1) + sage: h = HyperbolicPlane().UHP().get_geodesic(-3,3) sage: g.is_ultra_parallel(h) True :: - sage: g = HyperbolicGeodesicUHP(-2,5) - sage: h = HyperbolicGeodesicUHP(-2,6) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) + sage: h = HyperbolicPlane().UHP().get_geodesic(2,6) sage: g.is_ultra_parallel(h) False :: - sage: g = HyperbolicGeodesicUHP(-2,5) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: g.is_ultra_parallel(g) False """ @@ -474,9 +454,8 @@ def is_ultra_parallel(self,other): def is_parallel(self, other): r""" - Return ``True`` if the two given hyperbolic - geodesics are either ultraparallel or asymptotically parallel, - ``False`` otherwise. + Return ``True`` if the two given hyperbolic geodesics are either + ultra parallel or asymptotically parallel and``False`` otherwise. INPUT: @@ -484,28 +463,27 @@ def is_parallel(self, other): OUTPUT: - ``True`` if the given geodesics are either ultraparallel or + ``True`` if the given geodesics are either ultra parallel or asymptotically parallel, ``False`` if not. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(-2,5) - sage: h = HyperbolicGeodesicUHP(5,12) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) + sage: h = HyperbolicPlane().UHP().get_geodesic(5,12) sage: g.is_parallel(h) True :: - sage: g = HyperbolicGeodesicUHP(-2,5) - sage: h = HyperbolicGeodesicUHP(-2,4) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) + sage: h = HyperbolicPlane().UHP().get_geodesic(-2,4) sage: g.is_parallel(h) True - No hyperbolic geodesic is either ultraparallel or + No hyperbolic geodesic is either ultra parallel or asymptotically parallel to itself:: - sage: g = HyperbolicGeodesicUHP(-2,5) + sage: g = HyperbolicPlane().UHP().get_geodesic(-2,5) sage: g.is_parallel(g) False """ @@ -513,7 +491,7 @@ def is_parallel(self, other): return (R_self*R_other).classification() in ['parabolic', 'hyperbolic'] ################################### - # Methods implemented in HMethods # + # Methods implemented in _HMethods # ################################### def ideal_endpoints(self): @@ -523,30 +501,30 @@ def ideal_endpoints(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(1 + I, 1 + 3*I).ideal_endpoints() + sage: H = HyperbolicPlane() + sage: H.UHP().get_geodesic(1 + I, 1 + 3*I).ideal_endpoints() [Boundary point in UHP 1, Boundary point in UHP +Infinity] - sage: HyperbolicGeodesicPD(0, I/2).ideal_endpoints() + sage: H.PD().get_geodesic(0, I/2).ideal_endpoints() [Boundary point in PD -I, Boundary point in PD I] - sage: HyperbolicGeodesicKM((0,0), (0, 1/2)).ideal_endpoints() + sage: H.KM().get_geodesic((0,0), (0, 1/2)).ideal_endpoints() [Boundary point in KM (0, -1), Boundary point in KM (0, 1)] - sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).ideal_endpoints() + sage: H.HM().get_geodesic((0,0,1), (1, 0, sqrt(2))).ideal_endpoints() Traceback (most recent call last): ... NotImplementedError: boundary points are not implemented in the HM model """ - if not self.model().bounded: + if not self.model().is_bounded(): raise NotImplementedError("boundary points are not implemented in the " + "{0} model".format(self.model_name())) if self.is_complete(): return self.endpoints() - ends = self.HMethods.boundary_points(*self._cached_endpoints) - ends = [self.HMethods.model().point_to_model(k, self.model_name()) for + ends = self._HMethods.boundary_points(*self._cached_endpoints) + ends = [self._HMethods.model().point_to_model(k, self.model_name()) for k in ends] - return [self.HFactory.get_bdry_point(k) for k in ends] + return [self._model.get_bdry_point(k) for k in ends] def complete(self): r""" @@ -555,24 +533,24 @@ def complete(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(1 + I, 1 + 3*I).complete() + sage: H = HyperbolicPlane() + sage: H.UHP().get_geodesic(1 + I, 1 + 3*I).complete() Geodesic in UHP from 1 to +Infinity - sage: HyperbolicGeodesicPD(0, I/2).complete() + sage: H.PD().get_geodesic(0, I/2).complete() Geodesic in PD from -I to I - sage: HyperbolicGeodesicKM((0,0), (0, 1/2)).complete() + sage: H.KM().get_geodesic((0,0), (0, 1/2)).complete() Geodesic in KM from (0, -1) to (0, 1) - sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).complete() + sage: H.HM().get_geodesic((0,0,1), (1, 0, sqrt(2))).complete() Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)) - sage: HyperbolicGeodesicHM((0,0,1), (1, 0, sqrt(2))).complete().is_complete() + sage: H.HM().get_geodesic((0,0,1), (1, 0, sqrt(2))).complete().is_complete() True """ - if self.model().bounded: - return self.HFactory.get_geodesic(*self.ideal_endpoints()) + if self._model.is_bounded(): + return self._model.get_geodesic(*self.ideal_endpoints()) from copy import copy g = copy(self) @@ -586,24 +564,23 @@ def uncomplete(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(I, 2 + 3*I) + sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2 + 3*I) sage: g.uncomplete() Geodesic in UHP from I to 3*I + 2 sage: g.uncomplete().complete() == g.complete() True - sage: h = HyperbolicGeodesicUHP(2, 3) + sage: h = HyperbolicPlane().UHP().get_geodesic(2, 3) sage: h.uncomplete().complete() Geodesic in UHP from 2 to 3 """ if not self.is_complete(): return self - ends = self.HMethods.uncomplete(*self._cached_endpoints) - ends = [self.HMethods.model().point_to_model(k, self.model_name()) + ends = self._HMethods.uncomplete(*self._cached_endpoints) + ends = [self._HMethods.model().point_to_model(k, self.model_name()) for k in ends] - return self.HFactory.get_geodesic(*ends) + return self._model.get_geodesic(*ends) def reflection_in(self): r""" @@ -611,31 +588,31 @@ def reflection_in(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(2,4).reflection_in() + sage: H = HyperbolicPlane() + sage: H.UHP().get_geodesic(2,4).reflection_in() Isometry in UHP [ 3 -8] [ 1 -3] - sage: HyperbolicGeodesicPD(0, I).reflection_in() + sage: H.PD().get_geodesic0, I).reflection_in() Isometry in PD [ 0 -1] [ 1 0] - sage: HyperbolicGeodesicKM((0,0), (0,1)).reflection_in() + sage: H.HM().get_geodesic((0,0), (0,1)).reflection_in() Isometry in KM [-1 0 0] [ 0 1 0] [ 0 0 1] - sage: A = HyperbolicGeodesicHM((0,0,1), (1,0, n(sqrt(2)))).reflection_in() + sage: A = H.HM().get_geodesic((0,0,1), (1,0, n(sqrt(2)))).reflection_in() sage: B = diagonal_matrix([1, -1, 1]) sage: bool((B - A.matrix()).norm() < 10**-9) True """ - A = self.HMethods.reflection_in(*self._cached_endpoints) - A = self.HMethods.model().isometry_to_model(A, self.model_name()) - return self.HFactory.get_isometry(A) + A = self._HMethods.reflection_in(*self._cached_endpoints) + A = self._HMethods.model().isometry_to_model(A, self.model_name()) + return self._model.get_isometry(A) def common_perpendicular(self, other, **graphics_options): r""" @@ -653,17 +630,16 @@ def common_perpendicular(self, other, **graphics_options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(2,3) - sage: h = HyperbolicGeodesicUHP(4,5) + sage: g = HyperbolicPlane().UHP().get_geodesic(2,3) + sage: h = HyperbolicPlane().UHP().get_geodesic(4,5) sage: g.common_perpendicular(h) Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2 It is an error to ask for the common perpendicular of two intersecting geodesics:: - sage: g = HyperbolicGeodesicUHP(2,4) - sage: h = HyperbolicGeodesicUHP(3, infinity) + sage: g = HyperbolicPlane().UHP().get_geodesic(2,4) + sage: h = HyperbolicPlane().UHP().get_geodesic(3, infinity) sage: g.common_perpendicular(h) Traceback (most recent call last): ... @@ -671,12 +647,12 @@ def common_perpendicular(self, other, **graphics_options): """ if not self.is_parallel(other): raise ValueError('geodesics intersect, no common perpendicular exists') - perp_ends = self.HMethods.common_perpendicular( + perp_ends = self._HMethods.common_perpendicular( *(self._cached_endpoints + other._cached_endpoints)) - M = self.HMethods.model() + M = self._HMethods.model() perp_ends = [M.point_to_model(k, self.model_name()) for k in perp_ends] - return self.HFactory.get_geodesic(*perp_ends, **graphics_options) + return self._model.get_geodesic(*perp_ends, **graphics_options) def intersection(self, other, **graphics_options): r""" @@ -696,16 +672,16 @@ def intersection(self, other, **graphics_options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(3,5) - sage: h = HyperbolicGeodesicUHP(4,7) + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(3,5) + sage: h = UHP.get_geodesic(4,7) sage: g.intersection(h) Point in UHP 2/3*sqrt(-2) + 13/3 If the given geodesics do not intersect, raise an error:: - sage: g = HyperbolicGeodesicUHP(4,5) - sage: h = HyperbolicGeodesicUHP(5,7) + sage: g = UHP.get_geodesic(4,5) + sage: h = UHP.get_geodesic(5,7) sage: g.intersection(h) Traceback (most recent call last): ... @@ -713,8 +689,8 @@ def intersection(self, other, **graphics_options): If the given geodesics are identical, return that geodesic:: - sage: g = HyperbolicGeodesicUHP(4+I,18*I) - sage: h = HyperbolicGeodesicUHP(4+I,18*I) + sage: g = UHP.get_geodesic(4+I,18*I) + sage: h = UHP.get_geodesic4+I,18*I) sage: g.intersection(h) Geodesic in UHP from I + 4 to 18*I """ @@ -722,14 +698,14 @@ def intersection(self, other, **graphics_options): return self elif self.is_parallel(other): raise ValueError("geodesics don't intersect") - inters = self.HMethods.intersection(*(self._cached_endpoints + + inters = self._HMethods.intersection(*(self._cached_endpoints + other._cached_endpoints)) if len(inters) == 2: return self elif len(inters) == 1: - inters = self.HMethods.model().point_to_model(inters[0], + inters = self._HMethods.model().point_to_model(inters[0], self.model_name()) - return self.HFactory.get_point(inters, **graphics_options) + return self._model.get_point(inters, **graphics_options) else: raise ValueError("can't calculate the intersection of" "{1} and {2}".format(self, other)) @@ -742,48 +718,28 @@ def perpendicular_bisector(self, **graphics_options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP.random_element() + sage: g = HyperbolicPlane().UHP().random_geodesic() sage: h = g.perpendicular_bisector() - sage: bool(h.intersection(g).coordinates() - g.midpoint().coordinates() < 10**-9) + sage: bool(h.intersection(g).coordinates() - g.midpoint().coordinates() < 10**-9) True Complete geodesics cannot be bisected:: - sage: g = HyperbolicGeodesicUHP(0, 1) + sage: g = HyperbolicPlane().UHP().get_geodesic(0, 1) sage: g.perpendicular_bisector() Traceback (most recent call last): ... ValueError: perpendicular bisector is not defined for complete geodesics """ if self.is_complete(): - raise ValueError("perpendicular bisector is not defined for " \ - "complete geodesics") - bisect_ends = self.HMethods.perpendicular_bisector( + raise ValueError("perpendicular bisector is not defined for " + "complete geodesics") + bisect_ends = self._HMethods.perpendicular_bisector( *self._cached_endpoints) - M = self.HMethods.model() + M = self._HMethods.model() bisect_ends = [M.point_to_model(k, self.model_name()) for k in bisect_ends] - return self.HFactory.get_geodesic(*bisect_ends, **graphics_options) - - @classmethod - def random_element(cls, **kwargs): - r""" - Return a random hyperbolic geodesic. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: h = HyperbolicGeodesicUHP.random_element() - sage: bool((h.endpoints()[0].coordinates()).imag() >= 0) - True - """ - # Some kwargs are for parametrizing the random geodesic - # others are for graphics options. Is there a better way to do this? - g_ends = [cls.HMethods.random_point(**kwargs) for k in range(2)] - g_ends = [cls.HMethods.model().point_to_model(k, cls.model_name()) - for k in g_ends] - return cls.HFactory.get_geodesic(*g_ends, **kwargs) + return self._model.get_geodesic(*bisect_ends, **graphics_options) def midpoint(self, **graphics_options): r""" @@ -791,8 +747,7 @@ def midpoint(self, **graphics_options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP.random_element() + sage: g = HyperbolicPlane().UHP().random_geodesic() sage: m = g.midpoint() sage: end1, end2 = g.endpoints() sage: bool(abs(m.dist(end1) - m.dist(end2)) < 10**-9) @@ -800,17 +755,17 @@ def midpoint(self, **graphics_options): Complete geodesics have no midpoint:: - sage: HyperbolicGeodesicUHP(0,2).midpoint() + sage: HyperbolicPlane().UHP().get_geodesic(0,2).midpoint() Traceback (most recent call last): ... ValueError: midpoint not defined for complete geodesics """ if self.is_complete(): raise ValueError("midpoint not defined for complete geodesics") - mid = self.HMethods.midpoint(*self._cached_endpoints, + mid = self._HMethods.midpoint(*self._cached_endpoints, **graphics_options) - mid = self.HMethods.model().point_to_model(mid, self.model_name()) - return self.HFactory.get_point(mid) + mid = self._HMethods.model().point_to_model(mid, self.model_name()) + return self._model.get_point(mid) def dist(self, other): r""" @@ -828,9 +783,8 @@ def dist(self, other): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(2, 4.0) - sage: h = HyperbolicGeodesicUHP(5, 7.0) + sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4.0) + sage: h = HyperbolicPlane().UHP().get_geodesic(5, 7.0) sage: bool(abs(g.dist(h).n() - 1.92484730023841) < 10**-9) True @@ -838,8 +792,8 @@ def dist(self, other): or if it is a point on the boundary that is not one of the first object's endpoints, then return +infinity:: - sage: g = HyperbolicGeodesicUHP(2, 2+I) - sage: p = HyperbolicPlane.UHP.point(5) + sage: g = HyperbolicPlane().UHP().get_geodesic(2, 2+I) + sage: p = HyperbolicPlane().UHP().get_point(5) sage: g.dist(p) +Infinity """ @@ -854,7 +808,7 @@ def dist(self, other): q = other.intersection(perp) # and return their distance return p.dist(q) - return self.HMethods.geod_dist_from_point(self._cached_start, + return self._HMethods.geod_dist_from_point(self._cached_start, self._cached_end, other._cached_coordinates) @@ -871,17 +825,16 @@ def angle(self, other): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(2, 4) - sage: h = HyperbolicGeodesicUHP(3, 3+I) + sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4) + sage: h = HyperbolicPlane().UHP().get_geodesic(3, 3+I) sage: g.angle(h) 1/2*pi It is an error to ask for the angle of two geodesics that do not intersect:: - sage: g = HyperbolicGeodesicUHP(2, 4) - sage: h = HyperbolicGeodesicUHP(5, 7) + sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4) + sage: h = HyperbolicPlane().UHP().get_geodesic(5, 7) sage: g.angle(h) Traceback (most recent call last): ... @@ -889,7 +842,7 @@ def angle(self, other): If the geodesics are identical, return angle 0:: - sage: g = HyperbolicGeodesicUHP(2, 4) + sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4) sage: g.angle(g) 0 """ @@ -904,7 +857,7 @@ def angle(self, other): "The angle between them is not defined.\n" "Returning the angle between their completions.") return self.complete().angle(other.complete()) - return self.HMethods.angle(*(self._cached_endpoints + + return self._HMethods.angle(*(self._cached_endpoints + other._cached_endpoints)) def length(self): @@ -913,8 +866,7 @@ def length(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicUHP(2 + I, 3 + I/2) + sage: g = HyperbolicPlane().UHP().get_geodesic(2 + I, 3 + I/2) sage: g.length() arccosh(9/4) """ @@ -936,20 +888,17 @@ class HyperbolicGeodesicUHP(HyperbolicGeodesic): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: UHP = HyperbolicPlane.UHP - sage: g = HyperbolicGeodesicUHP(UHP.point(I), UHP.point(2 + I)) - sage: g = HyperbolicGeodesicUHP(I, 2 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(UHP.point(I), UHP.point(2 + I)) + sage: g = UHP.get_geodesic(I, 2 + I) """ - HFactory = HyperbolicFactoryUHP - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP def show(self, boundary=True, **options): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicUHP(0, 1).show() + sage: HyperbolicPlane().UHP().get_geodesic(0, 1).show() """ opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) @@ -970,7 +919,7 @@ def show(self, boundary=True, **options): if boundary: cent = min(bd_1,bd_2) bd_dict = {'bd_min': cent - 3, 'bd_max': cent + 3} - bd_pic = self.HFactory.get_background_graphic(**bd_dict) + bd_pic = self._model.get_background_graphic(**bd_dict) pic = bd_pic + pic return pic else: @@ -996,7 +945,7 @@ def show(self, boundary=True, **options): length = abs(shadow_1 - shadow_2) bd_dict = {'bd_min': midpoint - length, 'bd_max': midpoint + length} - bd_pic = self.HFactory.get_background_graphic(**bd_dict) + bd_pic = self._model.get_background_graphic(**bd_dict) pic = bd_pic + pic return pic @@ -1015,20 +964,17 @@ class HyperbolicGeodesicPD(HyperbolicGeodesic): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: PD = HyperbolicPlane.PD - sage: g = HyperbolicGeodesicPD(PD.point(I), PD.point(I/2)) - sage: g = HyperbolicGeodesicPD(I, I/2) + sage: PD = HyperbolicPlane().PD() + sage: g = PD.get_geodesic(PD.get_point(I), PD.get_point(I/2)) + sage: g = PD.get_geodesic(I, I/2) """ - HFactory = HyperbolicFactoryPD - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP def show(self, boundary=True, **options): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicPD(0, 1).show() + sage: HyperbolicPlane().PD().get_geodesic(0, 1).show() """ opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) @@ -1069,7 +1015,7 @@ def show(self, boundary=True, **options): radius*sin(x) + imag(center)), (x, theta1, theta2), **opts) if boundary: - bd_pic = self.HFactory.get_background_graphic() + bd_pic = self._model.get_background_graphic() pic = bd_pic + pic return pic @@ -1088,20 +1034,17 @@ class HyperbolicGeodesicKM(HyperbolicGeodesic): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: KM = HyperbolicPlane.KM - sage: g = HyperbolicGeodesicKM(KM.point((0,1)), KM.point((0,1/2))) - sage: g = HyperbolicGeodesicKM((0,1), (0,1/2)) + sage: KM = HyperbolicPlane().KM() + sage: g = KM.get_geodesic(KM.point((0,1)), KM.point((0,1/2))) + sage: g = KM.get_geodesic((0,1), (0,1/2)) """ - HFactory = HyperbolicFactoryKM - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP def show(self, boundary=True, **options): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HyperbolicGeodesicKM((0,0), (1,0)).show() + sage: HyperbolicPlane().KM().get_geodesic((0,0), (1,0)).show() """ from sage.plot.line import line opts = dict ([('axes', False), ('aspect_ratio', 1)]) @@ -1109,7 +1052,7 @@ def show(self, boundary=True, **options): end_1,end_2 = [k.coordinates() for k in self.endpoints()] pic = line([end_1,end_2], **opts) if boundary: - bd_pic = self.HFactory.get_background_graphic() + bd_pic = self._model.get_background_graphic() pic = bd_pic + pic return pic @@ -1129,19 +1072,18 @@ class HyperbolicGeodesicHM(HyperbolicGeodesic): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: HM = HyperbolicPlane.HM - sage: g = HyperbolicGeodesicHM(HM.point((0, 0, 1)), HM.point((0,1,sqrt(2)))) - sage: g = HyperbolicGeodesicHM((0, 0, 1), (0, 1, sqrt(2))) + sage: HM = HyperbolicPlane().HM() + sage: g = HM.get_geodesic(HM.point((0, 0, 1)), HM.point((0,1,sqrt(2)))) + sage: g = HM.get_geodesic((0, 0, 1), (0, 1, sqrt(2))) """ - HFactory = HyperbolicFactoryHM - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP def show(self, show_hyperboloid=True, **graphics_options): r""" EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * - sage: g = HyperbolicGeodesicHM.random_element() + sage: g = HyperbolicPlane().HM().random_geodesic() sage: g.show() """ from sage.calculus.var import var @@ -1167,7 +1109,7 @@ def show(self, show_hyperboloid=True, **graphics_options): from sage.plot.plot3d.all import parametric_plot3d pic = parametric_plot3d(hyperbola,(x,0, endtime),**graphics_options) if show_hyperboloid: - bd_pic = self.HFactory.get_background_graphic() + bd_pic = self._model.get_background_graphic() pic = bd_pic + pic return pic diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index 729f5b3422b..cf6e486dc2c 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -38,48 +38,131 @@ # http://www.gnu.org/licenses/ #*********************************************************************** from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.lazy_import import lazy_import -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', - ['HyperbolicModel','HyperbolicModelUHP', - 'HyperbolicModelPD', 'HyperbolicModelHM', 'HyperbolicModelKM']) +from sage.structure.parent import Parent +from sage.categories.sets_cat import Sets +from sage.categories.realizations import Realizations, Category_realization_of_parent +from sage.misc.lazy_attribute import lazy_attribute +from sage.geometry.hyperbolic_space.hyperbolic_model import ( + HyperbolicModelUHP, HyperbolicModelPD, + HyperbolicModelHM, HyperbolicModelKM) + +def HyperbolicSpace(n): + """ + Return ``n`` dimensional hyperbolic space. + """ + if n == 2: + return HyperbolicPlane() + raise NotImplementedError("currently only implemented in dimension 2") + +class HyperbolicPlane(Parent, UniqueRepresentation): + """ + The hyperbolic plane `\mathbb{H}^2`. + + Here are the models currently implemented: + + - ``UHP`` -- upper half plane + - ``PD`` -- Poincare disk + - ``KM`` -- Klein disk + - ``HM`` -- hyperboloid model + """ + def __init__(self): + """ + Initialize ``self``. + """ + Parent.__init__(self, category=Sets().WithRealizations()) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', - ['HyperbolicFactory', 'HyperbolicFactoryUHP', - 'HyperbolicFactoryPD', 'HyperbolicFactoryHM', - 'HyperbolicFactoryKM']) + def _repr_(self): + """ + Return a string representation of ``self``. + """ + return "Hyperbolic plane" -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_point', - ['HyperbolicPoint', 'HyperbolicPointUHP', 'HyperbolicPointPD', - 'HyperbolicPointHM', 'HyperbolicPointKM']) + def a_realization(self): + """ + Return a realization of ``self``. + """ + return self.UHP() + + UHP = HyperbolicModelUHP + UpperHalfPlane = UHP + + PD = HyperbolicModelPD + PoincareDisk = PD + + KM = HyperbolicModelKM + KleinDisk = KM + + HM = HyperbolicModelHM + Hyperboloid = HM + +class HyperbolicModels(Category_realization_of_parent): + r""" + The category of hyperbolic models of hyperbolic space. + """ + def __init__(self, base): + r""" + Initialize the hyperbolic models of hyperbolic space. + + INPUT: + + - ``base`` -- a hyperbolic space + + TESTS:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels + sage: H = HyperbolicPlane() + sage: models = HyperbolicModels(H) + sage: H.UHP() in models + True + """ + Category_realization_of_parent.__init__(self, base) + + def _repr_(self): + r""" + Return the representation of ``self``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels + sage: H = HyperbolicPlane() + sage: HyperbolicModels(H) + Category of hyperbolic models of Hyperbolic plane + """ + return "Category of hyperbolic models of {}".format(self.base()) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_bdry_point', - ['HyperbolicBdryPointUHP', 'HyperbolicBdryPointPD', - 'HyperbolicBdryPointHM', 'HyperbolicBdryPointKM']) + def super_categories(self): + r""" + The super categories of ``self``. -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', - ['HyperbolicGeodesic', 'HyperbolicGeodesicUHP', - 'HyperbolicGeodesicPD', 'HyperbolicGeodesicHM', - 'HyperbolicGeodesicKM']) + EXAMPLES:: + sage: from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels + sage: H = HyperbolicPlane() + sage: models = HyperbolicModels(H) + sage: models.super_categories() + [Category of finite dimensional algebras with basis over Rational Field, + Category of realizations of Descent algebra of 4 over Rational Field] + """ + return [Sets(), Realizations(self.base())] -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', - ['HyperbolicIsometry', 'HyperbolicIsometryUHP', - 'HyperbolicIsometryPD', 'HyperbolicIsometryHM', - 'HyperbolicIsometryKM']) + class ParentMethods: + @lazy_attribute + def _computation_model(self): + """ + Return the model in which to do computations by default. + """ + return self.realization_of().UHP() + class ElementMethods: + pass +# TODO: Remove this class and move its doctests class HyperbolicUserInterface(UniqueRepresentation): r""" Abstract base class for hyperbolic interfaces. These provide a user interface for interacting with models of hyperbolic geometry without having the interface dictate the class structure. """ - HModel = HyperbolicModel - HFactory = HyperbolicFactory - HPoint = HyperbolicPoint - HIsometry = HyperbolicIsometry - HGeodesic = HyperbolicGeodesic - @classmethod def model_name(cls): r""" @@ -396,88 +479,3 @@ def isometry_to_model(cls, A, model): if isinstance(A, HyperbolicIsometry): A = A.matrix() return cls.HModel.isometry_to_model(A, model) - - -class UHP(HyperbolicUserInterface): - r""" - Hyperbolic interface for the UHP model. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.point(I) - Point in UHP I - """ - HModel = HyperbolicModelUHP - HFactory = HyperbolicFactoryUHP - HPoint = HyperbolicPointUHP - HIsometry = HyperbolicIsometryUHP - HGeodesic = HyperbolicGeodesicUHP - - -class PD(HyperbolicUserInterface): - r""" - Hyperbolic interface for the PD model. - - EXAMPLES:: - - sage: HyperbolicPlane.PD.point(I) - Boundary point in PD I - """ - HModel = HyperbolicModelPD - HFactory = HyperbolicFactoryPD - HPoint = HyperbolicPointPD - HIsometry = HyperbolicIsometryPD - HGeodesic = HyperbolicGeodesicPD - - -class KM(HyperbolicUserInterface): - r""" - Hyperbolic interface for the KM model. - - EXAMPLES:: - - sage: HyperbolicPlane.KM.point((0,0)) - Point in KM (0, 0) - """ - HModel = HyperbolicModelKM - HFactory = HyperbolicFactoryKM - HPoint = HyperbolicPointKM - HIsometry = HyperbolicIsometryKM - HGeodesic = HyperbolicGeodesicKM - - -class HM(HyperbolicUserInterface): - r""" - Hyperbolic interface for the HM model. - - EXAMPLES:: - - sage: HyperbolicPlane.HM.point((0,0,1)) - Point in HM (0, 0, 1) - """ - HModel = HyperbolicModelHM - HFactory = HyperbolicFactoryHM - HPoint = HyperbolicPointHM - HIsometry = HyperbolicIsometryHM - HGeodesic = HyperbolicGeodesicHM - -class HyperbolicPlane(UniqueRepresentation): - """ - The hyperbolic plane `\mathbb{H}^2` in a given model. - - Here are the models currently implemented: - - - ``UHP`` -- upper half plane - - ``PD`` -- Poincare disk - - ``KM`` -- Klein disk - - ``HM`` -- hyperboloid model - """ - UHP = UHP - UpperHalfPlane = UHP - PD = PD - PoincareDisk = PD - KM = KM - KleinDisk = KM - HM = HM - Hyperboloid = HM - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index eb7f7aa7f79..77a57b4cdfa 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -40,7 +40,8 @@ # http://www.gnu.org/licenses/ #*********************************************************************** -from sage.structure.sage_object import SageObject +from sage.categories.homset import Hom +from sage.categories.morphism import Morphism from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute lazy_import('sage.modules.free_module_element', 'vector') @@ -50,15 +51,10 @@ from sage.rings.all import CC from sage.functions.other import real, imag -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', 'HyperbolicAbstractFactory') lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', - ['HyperbolicFactoryUHP', 'HyperbolicFactoryPD', - 'HyperbolicFactoryKM', 'HyperbolicFactoryHM']) - -class HyperbolicIsometry(SageObject): +class HyperbolicIsometry(Morphism): r""" Abstract base class for hyperbolic isometries. This class should never be instantiated. @@ -74,14 +70,13 @@ class HyperbolicIsometry(SageObject): sage: A = HyperbolicIsometryUHP(identity_matrix(2)) sage: B = HyperbolicIsometryHM(identity_matrix(3)) """ - HFactory = HyperbolicAbstractFactory - HMethods = HyperbolicAbstractMethods + _HMethods = HyperbolicAbstractMethods ##################### # "Private" Methods # ##################### - def __init__(self, A): + def __init__(self, model, A): r""" See :class:`HyperbolicIsometry` for full documentation. @@ -93,9 +88,10 @@ def __init__(self, A): [ 0 1] [-1 0] """ - self._model = self.HFactory.get_model() - self._model.isometry_test(A) + model.isometry_test(A) + self._model = model self._matrix = A + Morphism.__init__(self, Hom(model, model)) @lazy_attribute def _cached_matrix(self): @@ -114,7 +110,7 @@ def _cached_matrix(self): [0 1] """ return self.model().isometry_to_model(self.matrix(), - self.HMethods.model().short_name) + self._HMethods.model().short_name) def _repr_(self): r""" @@ -132,7 +128,13 @@ def _repr_(self): [1 0] [0 1] """ - return "Isometry in {0}\n{1}".format(self.model_name(), self.matrix()) + return self._repr_type() + " in {0}\n{1}".format(self._model.short_name(), self.matrix()) + + def _repr_type(self): + r""" + Return the type of morphism. + """ + return "Isometry" def _latex_(self): r""" @@ -155,9 +157,9 @@ def _latex_(self): \end{array}\right) """ if self.model().isometry_group_is_projective: - return "\pm " + latex(self.matrix()) + return "\pm " + latex(self._matrix) else: - return latex(self.matrix()) + return latex(self._matrix) def __eq__(self, other): r""" @@ -173,10 +175,10 @@ def __eq__(self, other): """ pos_matrix = bool(abs(self.matrix() - other.matrix()) < EPSILON) neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) - if self._model.isometry_group_is_projective: - return self.model() == other.model() and (pos_matrix or neg_matrix) + if self._model.is_isometry_group_projective(): + return self._model is other._model and (pos_matrix or neg_matrix) else: - return self.model_name() == other.model_name() and pos_matrix + return self._model is other._model and pos_matrix def __pow__(self, n): r""" @@ -189,7 +191,7 @@ def __pow__(self, n): [41 15] [30 11] """ - return self.__class__(self.matrix()**n) + return self.__class__(self._model, self.matrix()**n) def __mul__(self, other): r""" @@ -225,45 +227,46 @@ def __mul__(self, other): raise TypeError("{0} and {1} are not in the same" "model".format(self, other)) if isinstance(other, HyperbolicIsometry): - return type (self) (self.matrix()*other.matrix()) + return self.__class__(self._model, self.matrix()*other.matrix()) elif isinstance(other, HyperbolicPoint): - return self.HFactory.get_point(self.model().isometry_act_on_point( + return self._model.get_point(self.model().isometry_act_on_point( self.matrix(), other.coordinates())) elif isinstance(other, HyperbolicGeodesic): - return self.HFactory.get_geodesic(self*other.start(), self*other.end()) + return self._model.get_geodesic(self*other.start(), self*other.end()) else: NotImplementedError("multiplication is not defined between a " "hyperbolic isometry and {0}".format(other)) - # def __call__ (self, other): - # r""" - # EXAMPLES:: - # - # sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - # sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) - # sage: p = UHP.point(2 + I) - # sage: A(p) - # Point in UHP 8/17*I + 53/17. - # - # sage: g = UHP.geodesic(2 + I,4 + I) - # sage: A (g) - # Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. - # - # sage: A = diagonal_matrix([1, -1, 1]) - # sage: A = HyperbolicIsometryHM(A) - # sage: A.orientation_preserving() - # False - # sage: p = HM.point((0, 1, sqrt(2))) - # sage: A(p) - # Point in HM (0, -1, sqrt(2)). - # """ - # from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic - # if self.model() != other.model(): - # raise TypeError("{0} is not in the {1} model.".format(other, self.model_name())) - # if isinstance(other, HyperbolicGeodesic): - # return self.HFactory.get_geodesic(self(other.start()), self(other.end())) - # return self.HFactory.get_point(self.model().isometry_act_on_point( - # self.matrix(), other.coordinates())) + def _call_(self, other): + r""" + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * + sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) + sage: p = UHP.point(2 + I) + sage: A(p) + Point in UHP 8/17*I + 53/17. + + sage: g = UHP.geodesic(2 + I,4 + I) + sage: A (g) + Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. + + sage: A = diagonal_matrix([1, -1, 1]) + sage: A = HyperbolicIsometryHM(A) + sage: A.orientation_preserving() + False + sage: p = HM.point((0, 1, sqrt(2))) + sage: A(p) + Point in HM (0, -1, sqrt(2)). + """ + from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic + if self.model() is not other.model(): + raise TypeError("{0} is not in the {1} model".format(other, self.model_name())) + + if isinstance(other, HyperbolicGeodesic): + return self._model.get_geodesic(self(other.start()), self(other.end())) + return self._model.get_point(self.model().isometry_act_on_point( + self.matrix(), other.coordinates())) ####################### # Setters and Getters # @@ -273,6 +276,12 @@ def matrix(self): r""" Return the matrix of the isometry. + .. NOTE:: + + We do not allow the ``matrix`` constructor to work as these may + be elements of a projective group (ex. `PSL(n, \RR)`), so these + isometries aren't true matrices. + EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * @@ -294,12 +303,19 @@ def inverse(self): sage: A*B == HyperbolicIsometryUHP(identity_matrix(2)) True """ - return self.HFactory.get_isometry(self.matrix().inverse()) + return self.__class__(self._model, self.matrix().inverse()) + + __invert__ = inverse + + def is_identity(self): + """ + Return ``True`` if ``self`` is the identity isometry. + """ + return self._matrix.is_one() - @classmethod - def model(this): + def model(self): r""" - Return the model to which the HyperbolicIsometry belongs. + Return the model to which ``self`` belongs. EXAMPLES:: @@ -316,10 +332,9 @@ def model(this): sage: HyperbolicIsometryHM(identity_matrix(3)).model() """ - return this.HFactory.get_model() + return self._model - @classmethod - def model_name(this): + def model_name(self): r""" Return the short name of the hyperbolic model. @@ -338,29 +353,69 @@ def model_name(this): sage: HyperbolicIsometryHM(identity_matrix(3)).model_name() 'HM' """ - return this.model().short_name + return self._model.short_name() - def to_model(self, model_name): + def to_model(self, other): r""" Convert the current object to image in another model. INPUT: - - ``model_name`` -- a string representing the image model + - ``other`` -- (a string representing) the image model EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(identity_matrix(2)).to_model('HM') + sage: H = HyperbolicPlane() + sage: UHP = H.UHP() + sage: PD = H.PD() + sage: KM = H.KM() + sage: HM = H.HM() + + sage: I = UHP.get_isometry(identity_matrix(2)) + sage: I.to_model(HM) Isometry in HM [1 0 0] [0 1 0] [0 0 1] + sage: I.to_model('HM') + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1] + + sage: I = PD.isometry(matrix(2,[I, 0, 0, -I])) + sage: I.to_model(UHP) + [ 0 1] + [-1 0] + + sage: I.to_model(HM) + [-1 0 0] + [ 0 -1 0] + [ 0 0 1] + + sage: Ito_model(KM) + [-1 0 0] + [ 0 -1 0] + [ 0 0 1] + + sage: J = HM.isosometry(diagonal_matrix([-1, -1, 1])) + sage: J.to_model(UHP) + [ 0 -1] + [ 1 0] + + sage: J.to_model(PD) + [-I 0] + [ 0 I] + + sage: J.to_model(KM) + [-1 0 0] + [ 0 -1 0] + [ 0 0 1] """ - from sage.geometry.hyperbolic_space.model_factory import ModelFactory - factory = ModelFactory.find_factory(model_name) - matrix = self.model().isometry_to_model(self.matrix(), model_name) - return factory.get_isometry(matrix) + if isinstance(other, str): + other = getattr(self._model.realization_of(), other) + phi = other.coerce_map_from(self._model) + return phi.convert_isometry(self) ################### # Boolean Methods # @@ -381,11 +436,11 @@ def orientation_preserving(self): sage: B.orientation_preserving() False """ - return self.HMethods.orientation_preserving(self._cached_matrix) + return self._HMethods.orientation_preserving(self._cached_matrix) - ################################### - # Methods implemented in HMethods # - ################################### + #################################### + # Methods implemented in _HMethods # + #################################### def classification(self): r""" @@ -412,7 +467,7 @@ def classification(self): sage: E.classification() 'reflection' """ - return self.HMethods.classification(self._cached_matrix) + return self._HMethods.classification(self._cached_matrix) def translation_length(self): r""" @@ -435,7 +490,7 @@ def translation_length(self): sage: bool((p.dist(H*p) - H.translation_length()) < 10**-9) True """ - return self.HMethods.translation_length(self._cached_matrix) + return self._HMethods.translation_length(self._cached_matrix) def axis(self, **graphics_options): r""" @@ -460,9 +515,8 @@ def axis(self, **graphics_options): """ if self.classification() not in ( ['hyperbolic', 'orientation-reversing hyperbolic']): - raise ValueError("the isometry is not hyperbolic: axis is" - " undefined") - return self.HFactory.get_geodesic(*self.fixed_point_set()) + raise ValueError("the isometry is not hyperbolic: axis is undefined") + return self._model.get_geodesic(*self.fixed_point_set()) def fixed_point_set(self, **graphics_options): r""" @@ -496,10 +550,10 @@ def fixed_point_set(self, **graphics_options): ... ValueError: the identity transformation fixes the entire hyperbolic plane """ - pts = self.HMethods.fixed_point_set(self._cached_matrix) - pts = [self.HMethods.model().point_to_model(k, self.model_name()) for\ - k in pts] - return [self.HFactory.get_point(k, **graphics_options) for k in pts] + pts = self._HMethods.fixed_point_set(self._cached_matrix) + pts = [self._HMethods.model().point_to_model(k, self.model_name()) + for k in pts] + return [self._model.get_point(k, **graphics_options) for k in pts] def fixed_geodesic(self, **graphics_options): r""" @@ -511,13 +565,13 @@ def fixed_geodesic(self, **graphics_options): sage: A.fixed_geodesic() Geodesic in UHP from 1 to -1 """ - fps = self.HMethods.fixed_point_set(self._cached_matrix) + fps = self._HMethods.fixed_point_set(self._cached_matrix) if len(fps) < 2: raise ValueError("Isometries of type" " {0}".format(self.classification()) + " don't fix geodesics") from sage.geometry.hyperbolic_space.model_factory import ModelFactory - fact = ModelFactory.find_factory(self.HMethods.model_name()) + fact = ModelFactory.find_factory(self._HMethods.model_name()) geod = fact.get_geodesic(fps[0], fps[1]) return geod.to_model(self.model_name()) @@ -537,9 +591,9 @@ def repelling_fixed_point(self, **graphics_options): sage: A.repelling_fixed_point() Boundary point in UHP 0 """ - fp = self.HMethods.repelling_fixed_point(self._cached_matrix) - fp = self.HMethods.model().point_to_model(fp, self.model_name()) - return self.HFactory.get_point(fp) + fp = self._HMethods.repelling_fixed_point(self._cached_matrix) + fp = self._HMethods.model().point_to_model(fp, self.model_name()) + return self._model.get_point(fp) def attracting_fixed_point(self, **graphics_options): r""" @@ -557,12 +611,11 @@ def attracting_fixed_point(self, **graphics_options): sage: A.attracting_fixed_point() Boundary point in UHP +Infinity """ - fp = self.HMethods.attracting_fixed_point(self._cached_matrix) - fp = self.HMethods.model().point_to_model(fp, self.model_name()) - return self.HFactory.get_point(fp) + fp = self._HMethods.attracting_fixed_point(self._cached_matrix) + fp = self._HMethods.model().point_to_model(fp, self.model_name()) + return self._model.get_point(fp) - @classmethod - def isometry_from_fixed_points(cls, repel, attract): + def isometry_from_fixed_points(self, repel, attract): r""" Given two fixed points ``repel`` and ``attract`` as hyperbolic points return a hyperbolic isometry with ``repel`` as repelling @@ -584,41 +637,14 @@ def isometry_from_fixed_points(cls, repel, attract): [-1/3 -1/3] """ try: - A = cls.HMethods.isometry_from_fixed_points(repel._cached_coordinates, - attract._cached_coordinates) - A = cls.HMethods.model().isometry_to_model(A, cls.model_name()) - return cls.HFactory.get_isometry(A) + A = self._HMethods.isometry_from_fixed_points(repel._cached_coordinates, + attract._cached_coordinates) + A = self._HMethods.model().isometry_to_model(A, self.model_name()) + return self._model.get_isometry(A) except(AttributeError): - repel = cls.HFactory.get_point(repel) - attract = cls.HFactory.get_point(attract) - return cls.isometry_from_fixed_points(repel, attract) - - @classmethod - def random_element(cls, preserve_orientation=True, **kwargs): - r""" - Return a random isometry in the Upper Half Plane model. - - INPUT: - - - ``preserve_orientation`` -- if ``True`` return an - orientation-preserving isometry - - OUTPUT: - - - a hyperbolic isometry - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP.random_element() - sage: B = HyperbolicIsometryUHP.random_element(preserve_orientation=False) - sage: B.orientation_preserving() - False - """ - A = cls.HMethods.random_isometry(preserve_orientation, **kwargs) - A = cls.HMethods.model().isometry_to_model(A, cls.model_name()) - return cls.HFactory.get_isometry(A) - + repel = self._model.get_point(repel) + attract = self._model.get_point(attract) + return self.isometry_from_fixed_points(repel, attract) class HyperbolicIsometryUHP(HyperbolicIsometry): r""" @@ -633,9 +659,7 @@ class HyperbolicIsometryUHP(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryUHP sage: A = HyperbolicIsometryUHP(identity_matrix(2)) """ - - HFactory = HyperbolicFactoryUHP - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP class HyperbolicIsometryPD(HyperbolicIsometry): r""" @@ -650,8 +674,7 @@ class HyperbolicIsometryPD(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD sage: A = HyperbolicIsometryPD(identity_matrix(2)) """ - HFactory = HyperbolicFactoryPD - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP class HyperbolicIsometryKM(HyperbolicIsometry): r""" @@ -666,8 +689,7 @@ class HyperbolicIsometryKM(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryKM sage: A = HyperbolicIsometryKM(identity_matrix(3)) """ - HFactory = HyperbolicFactoryKM - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP class HyperbolicIsometryHM(HyperbolicIsometry): r""" @@ -682,6 +704,5 @@ class HyperbolicIsometryHM(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryHM sage: A = HyperbolicIsometryHM(identity_matrix(3)) """ - HFactory = HyperbolicFactoryHM - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index 38808989c23..544c3237c14 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -158,7 +158,7 @@ def random_point(cls, **kwargs): r""" Return a random point in the upper half plane. The points are uniformly distributed over the rectangle - `[-10,10] \times [-10i,10i]`. + `[-10, 10] \times [0, 10i]`. EXAMPLES:: @@ -275,14 +275,14 @@ def common_perpendicular(cls, start_1, end_1, start_2, end_2): sage: HyperbolicMethodsUHP.common_perpendicular(2, 4, 3, infinity) Traceback (most recent call last): ... - ValueError: Geodesics intersect. No common perpendicular exists. + ValueError: geodesics intersect; no common perpendicular exists """ A = cls.reflection_in(start_1, end_1) B = cls.reflection_in(start_2, end_2) C = A*B if cls.classification(C) != 'hyperbolic': - raise ValueError("Geodesics intersect. " + - "No common perpendicular exists.") + raise ValueError("geodesics intersect; " + + "no common perpendicular exists") return cls.fixed_point_set(C) @classmethod @@ -930,3 +930,71 @@ def _mobius_sending(cls, list1, list2): B = cls._crossratio_matrix(w[0],w[1],w[2]) return B.inverse() * A +##################################################################### +## Helper functions + +def mobius_transform(A, z): + r""" + Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex + plane return the mobius transformation action of ``A`` on ``z``. + + INPUT: + + - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers + - ``z`` -- a complex number or infinity + + OUTPUT: + + - a complex number or infinity + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import mobius_transform + sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) + 2/109*I + 43/109 + sage: y = var('y') + sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) + x + I*y + + The matrix must be square and `2 \times 2`:: + + sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + sage: mobius_transform(identity_matrix(3),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + The matrix can be symbolic or can be a matrix over the real + or complex numbers, but must be invertible:: + + sage: (a,b,c,d) = var('a,b,c,d'); + sage: mobius_transform(matrix(2,[a,b,c,d]),I) + (I*a + b)/(I*c + d) + + sage: mobius_transform(matrix(2,[0,0,0,0]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + """ + if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: + (a,b,c,d) = A.list() + if z == infinity: + if c == 0: + return infinity + return a/c + if a*d - b*c < 0: + w = z.conjugate() # Reverses orientation + else: + w = z + if c*z + d == 0: + return infinity + else: + return (a*w + b)/(c*w + d) + else: + raise TypeError("A must be an invertible 2x2 matrix over the" + " complex numbers or a symbolic ring") + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 23766521991..77970a4ca4c 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -40,9 +40,9 @@ We note that the UHP and PD models are bounded while the HM model is not:: - sage: U.bounded and P.bounded + sage: U.is_bounded() and P.is_bounded() True - sage: H.bounded + sage: H.is_bounded() False The isometry groups of UHP and PD are projective, while that of HM is @@ -66,6 +66,9 @@ sage: U.bdry_point_in_model(2) True +.. TODO:: + + Implement a category for metric spaces. """ #*********************************************************************** @@ -79,213 +82,138 @@ #*********************************************************************** from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.misc.bindable_class import BindableClass from sage.misc.lazy_import import lazy_import from sage.functions.other import imag, real from sage.rings.all import CC, RR from sage.rings.integer import Integer -from sage.symbolic.pynac import I from sage.rings.infinity import infinity -from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.symbolic.pynac import I from sage.matrix.all import matrix +from sage.categories.homset import Hom +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint, HyperbolicPointUHP +from sage.geometry.hyperbolic_space.hyperbolic_isometry import ( + HyperbolicIsometryUHP, HyperbolicIsometryPD, + HyperbolicIsometryKM, HyperbolicIsometryHM) +from sage.geometry.hyperbolic_space.hyperbolic_geodesic import ( + HyperbolicGeodesicUHP, HyperbolicGeodesicPD, + HyperbolicGeodesicKM, HyperbolicGeodesicHM) +from sage.geometry.hyperbolic_space.hyperbolic_coercion import ( + CoercionUHPtoPD, CoercionUHPtoKM, CoercionUHPtoHM, + CoercionPDtoUHP, CoercionPDtoKM, CoercionPDtoHM, + CoercionKMtoUHP, CoercionKMtoPD, CoercionKMtoHM, + CoercionHMtoUHP, CoercionHMtoPD, CoercionHMtoKM) lazy_import('sage.misc.misc', 'attrcall') lazy_import('sage.modules.free_module_element', 'vector') lazy_import('sage.functions.other','sqrt') -lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') - -##################################################################### -## Some helper functions - -def SL2R_to_SO21(A): - r""" - Given a matrix in `SL(2, \RR)` return its irreducible representation in - `O(2,1)`. - - Note that this is not the only homomorphism, but it is the only one - that works in the context of the implemented 2D hyperbolic geometry - models. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import SL2R_to_SO21 - sage: A = SL2R_to_SO21(identity_matrix(2)) - sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix - sage: norm(A.transpose()*J*A - J) < 10**-4 - True - """ - a,b,c,d = (A/A.det().sqrt()).list() - B = matrix(3, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, - Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - - Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, - Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - - Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, - a*b + c*d, Integer(1)/Integer(2)*a**2 - - Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - - Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + - Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + - Integer(1)/Integer(2)*d**2]) - B = B.apply_map(attrcall('real')) # Kill ~0 imaginary parts - if A.det() > 0: - return B - else: - # Orientation-reversing isometries swap the nappes of - # the lightcone. This fixes that issue. - return -B - -def SO21_to_SL2R(M): +class HyperbolicModel(Parent, UniqueRepresentation, BindableClass): r""" - A homomorphism from `SO(2, 1)` to `SL(2, \RR)`. - - Note that this is not the only homomorphism, but it is the only one - that works in the context of the implemented 2D hyperbolic geometry - models. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import SO21_to_SL2R - sage: (SO21_to_SL2R(identity_matrix(3)) - identity_matrix(2)).norm() < 10**-4 - True + Abstract base class for hyperbolic models. """ - #################################################################### - # SL(2,R) is the double cover of SO (2,1)^+, so we need to choose # - # a lift. I have formulas for the absolute values of each entry # - # a,b ,c,d of the lift matrix(2,[a,b,c,d]), but we need to choose # - # one entry to be positive. I choose d for no particular reason, # - # unless d = 0, then we choose c > 0. The basic strategy for this # - # function is to find the linear map induced by the SO(2,1) # - # element on the Lie algebra sl(2, R). This corresponds to the # - # Adjoint action by a matrix A or -A in SL(2,R). To find which # - # matrix let X,Y,Z be a basis for sl(2,R) and look at the images # - # of X,Y,Z as well as the second and third standard basis vectors # - # for 2x2 matrices (these are traceless, so are in the Lie # - # algebra). These corresponds to AXA^-1 etc and give formulas # - # for the entries of A. # - #################################################################### - (m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8,m_9) = M.list() - d = sqrt(Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 - - Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) - if M.det() > 0: #EPSILON? - det_sign = 1 - elif M.det() < 0: #EPSILON? - det_sign = -1 - if d > 0: #EPSILON? - c = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/d - b = (-Integer(1)/Integer(2)*m_2 + Integer(1)/Integer(2)*m_3)/d - ad = det_sign*1 + b*c # ad - bc = pm 1 - a = ad/d - else: # d is 0, so we make c > 0 - c = sqrt(-Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 + - Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) - d = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/c - #d = 0, so ad - bc = -bc = pm 1. - b = - (det_sign*1)/c - a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b - A = matrix(2,[a,b,c,d]) - return A - -def mobius_transform(A, z): - r""" - Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex - plane return the mobius transformation action of ``A`` on ``z``. + #name = "abstract hyperbolic space" + #short_name = "Abstract" + #bounded = False + #conformal = False + #dimension = 0 + #isometry_group = None + #isometry_group_is_projective = False + #pt_conversion_dict = {} + #isom_conversion_dict = {} + + def __init__(self, space, name, short_name, bounded, conformal, + dimension, isometry_group, isometry_group_is_projective): + """ + Initialize ``self``. + """ + self._name = name + self._short_name = short_name + self._bounded = bounded + self._conformal = conformal + self._dimension = dimension + self._isometry_group = isometry_group + self._isometry_group_is_projective = isometry_group_is_projective + from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels + Parent.__init__(self, category=HyperbolicModels(space)) + + def _repr_(self): + """ + Return a string representation of ``self``. + """ + return "Hyperbolic plane in the {} model".format(self._name) - INPUT: + def _element_constructor_(self, x, is_boundary=None, **graphics_options): + """ + Construct an element of ``self``. + """ + return self.get_point(x, is_boundary, **graphics_options) - - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers - - ``z`` -- a complex number or infinity + Element = HyperbolicPoint - OUTPUT: + def name(self): + """ + Return the name of this model. - - a complex number or infinity + EXAMPLES:: - EXAMPLES:: + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.name() + 'Upper Half Plane Model' + """ + return self._name - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform as mobius_transform - sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) - 2/109*I + 43/109 - sage: y = var('y') - sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) - x + I*y + def short_name(self): + """ + Return the short name of this model. - The matrix must be square and `2`x`2`:: + EXAMPLES:: - sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.short_name() + 'UHP' + """ + return self._short_name - sage: mobius_transform(identity_matrix(3),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + def is_bounded(self): + """ + Return ``True`` if ``self`` is a bounded model. - The matrix can be symbolic or can be a matrix over the real - or complex numbers, but must be invertible:: + EXAMPLES:: - sage: (a,b,c,d) = var('a,b,c,d'); - sage: mobius_transform(matrix(2,[a,b,c,d]),I) - (I*a + b)/(I*c + d) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.is_bounded() + True + """ + return self._bounded - sage: mobius_transform(matrix(2,[0,0,0,0]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - """ - if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: - (a,b,c,d) = A.list() - if z == infinity: - if c == 0: - return infinity - return a/c - if a*d - b*c < 0: - w = z.conjugate() # Reverses orientation - else: - w = z - if c*z + d == 0: - return infinity - else: - return (a*w + b)/(c*w + d) - else: - raise TypeError("A must be an invertible 2x2 matrix over the" - " complex numbers or a symbolic ring") + def is_conformal(self): + """ + Return ``True`` if ``self`` is a conformal model. -def PD_preserve_orientation(A): - r""" - For a PD isometry, determine if it preserves orientation. - This test is more more involved than just checking the sign - of the determinant, and it is used a few times in this file. + EXAMPLES:: - EXAMPLES:: + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.is_conformal() + True + """ + return self._conformal - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import PD_preserve_orientation as orient - sage: orient(matrix(2, [-I, 0, 0, I])) - True - sage: orient(matrix(2, [0, I, I, 0])) - False - """ - return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() - and abs(A[0][0]) - abs(A[0][1]) != 0) + def is_isometry_group_projective(self): + """ + Return ``True`` if the isometry group of ``self`` is projective. + EXAMPLES:: -##################################################################### -## The actual classes + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.is_isometry_group_projective() + True + """ + return self._isometry_group_is_projective -class HyperbolicModel(UniqueRepresentation): - r""" - Abstract base class for Hyperbolic Models. - """ - name = "Abstract Hyperbolic Model" - short_name = "Abstract" - bounded = False - conformal = False - dimension = 0 - isometry_group = None - isometry_group_is_projective = False - pt_conversion_dict = {} - isom_conversion_dict = {} - - @classmethod - def point_in_model(cls, p): #Abstract + def point_in_model(self, p): #Abstract r""" Return ``True`` if the point is in the given model and ``False`` otherwise. @@ -307,8 +235,7 @@ def point_in_model(cls, p): #Abstract """ return True - @classmethod - def point_test(cls, p): #Abstract + def point_test(self, p): #Abstract r""" Test whether a point is in the model. If the point is in the model, do nothing. Otherwise, raise a ``ValueError``. @@ -326,8 +253,7 @@ def point_test(cls, p): #Abstract error_string = "{0} is not a valid point in the {1} model" raise ValueError(error_string.format(p, cls.short_name)) - @classmethod - def bdry_point_in_model(cls, p): #Abstract + def bdry_point_in_model(self, p): #Abstract r""" Return ``True`` if the point is on the ideal boundary of hyperbolic space and ``False`` otherwise. @@ -347,8 +273,7 @@ def bdry_point_in_model(cls, p): #Abstract """ return True - @classmethod - def bdry_point_test(cls, p): #Abstract + def bdry_point_test(self, p): #Abstract r""" Test whether a point is in the model. If the point is in the model, do nothing; otherwise raise a ``ValueError``. @@ -362,12 +287,11 @@ def bdry_point_test(cls, p): #Abstract ... ValueError: I + 1 is not a valid boundary point in the UHP model """ - if not cls.bounded or not cls.bdry_point_in_model(p): + if not self._bounded or not cls.bdry_point_in_model(p): error_string = "{0} is not a valid boundary point in the {1} model" - raise ValueError(error_string.format(p, cls.short_name)) + raise ValueError(error_string.format(p, self._short_name)) - @classmethod - def isometry_in_model(cls, A): #Abstract + def isometry_in_model(self, A): #Abstract r""" Return ``True`` if the input matrix represents an isometry of the given model and ``False`` otherwise. @@ -390,8 +314,7 @@ def isometry_in_model(cls, A): #Abstract """ return True - @classmethod - def isometry_act_on_point(cls, A, p): #Abtsract + def isometry_act_on_point(self, A, p): #Abstract r""" Given an isometry ``A`` and a point ``p`` in the current model, return image of ``p`` unduer the action `A \cdot p`. @@ -406,8 +329,7 @@ def isometry_act_on_point(cls, A, p): #Abtsract """ return A * vector(p) - @classmethod - def isometry_test(cls, A): #Abstract + def isometry_test(self, A): #Abstract r""" Test whether an isometry is in the model. @@ -429,145 +351,286 @@ def isometry_test(cls, A): #Abstract error_string = "\n{0} is not a valid isometry in the {1} model." raise ValueError(error_string.format(A, cls.short_name)) - @classmethod - def point_to_model(cls, coordinates, model_name): #Abstract + def get_point(self, coordinates, is_boundary=None, **graphics_options): #Abstract r""" - Convert ``coordinates`` from the current model to the model - specified in ``model_name``. + Return a point in ``self``. + + Automatically determine the type of point to return given either + (1) the coordinates of a point in the interior or ideal boundary + of hyperbolic space or (2) a :class:`HyperbolicPoint` or + :class:`HyperbolicBdryPoint` object. INPUT: - - ``coordinates`` -- the coordinates of a valid point in the - current model - - ``model_name`` -- a string denoting the model to be converted to + - a point in hyperbolic space or on the ideal boundary OUTPUT: - - the coordinates of a point in the ``short_name`` model + - a :class:`HyperbolicPoint` - EXAMPLES:: + EXAMPLES: - sage: HyperbolicPlane.UHP.point_to_model(I, 'UHP') - I - sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') - 0 - sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'KM') - (6/11, 9/11) - sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'HM') - (3, 9/2, 11/2) - sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') - 0 - sage: HyperbolicPlane.PD.point_to_model(0, 'UHP') - I - sage: HyperbolicPlane.UHP.point_to_model(I, 'UHP') - I - sage: HyperbolicPlane.KM.point_to_model((0, 0), 'UHP') - I - sage: HyperbolicPlane.KM.point_to_model((0, 0), 'HM') - (0, 0, 1) - sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'UHP') - I - sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'KM') - (0, 0) + We can create an interior point via the coordinates:: - It is an error to try to convert a boundary point to a model - that doesn't support boundary points:: + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicPlane().UHP().get_point(2*I) + Point in UHP 2*I - sage: HyperbolicPlane.UHP.point_to_model(infinity, 'HM') + Or we can create a boundary point via the coordinates:: + + sage: HyperbolicPlane().UHP().get_point(23) + Boundary point in UHP 23 + + Or we can create both types of points:: + + sage: HyperbolicPlane().UHP().get_point(p) + Point in UHP 2*I + + sage: HyperbolicPlane().UHP().get_point(q) + Boundary point in UHP 23 + + sage: HyperbolicPlane().UHP().get_point(12 - I) Traceback (most recent call last): ... - NotImplementedError: boundary points are not implemented for the HM model + ValueError: -I + 12 is neither an interior nor boundary point in the UHP model + + :: + + sage: HyperbolicPlane().UHP().get_point(2 + 3*I) + Point in UHP 3*I + 2 + + sage: HyperbolicPlane().PD().get_point(0) + Point in PD 0 + + sage: HyperbolicPlane().KM().get_point((0,0)) + Point in KM (0, 0) + + sage: HyperbolicPlane().HM().get_point((0,0,1)) + Point in HM (0, 0, 1) + + sage: p = HyperbolicPlane().UHP().get_point(I, color="red") + sage: p.graphics_options() + {'color': 'red'} + + :: + + sage: HyperbolicPlane().UHP().get_point(12) + Boundary point in UHP 12 + + sage: HyperbolicPlane().UHP().get_point(infinity) + Boundary point in UHP +Infinity + + sage: HyperbolicPlane().PD().get_point(I) + Boundary point in PD I + + sage: HyperbolicPlane().KM().get_point((0,-1)) + Boundary point in KM (0, -1) """ - cls.point_test(coordinates) - model = ModelFactory.find_model(model_name) - if (not model.bounded) and cls.bdry_point_in_model(coordinates): - raise NotImplementedError("boundary points are not implemented for" - " the {0} model".format(model_name)) - return cls.pt_conversion_dict[model_name](coordinates) - @classmethod - def isometry_to_model(cls, A, model_name): #Abstract + if isinstance(coordinates, HyperbolicPoint): + if coordinates.parent() is not self: + coordinates = self(coordinates) + coordinates.update_graphics(True, **graphics_options) + return coordinates #both Point and BdryPoint + + if is_boundary is None: + is_boundary = self.bdry_point_in_model(coordinates) + return self.element_class(self, coordinates, is_boundary, **graphics_options) + + def get_geodesic(self, start, end=None, **graphics_options): #Abstract r""" - Convert ``A`` from the current model to the model specified in - ``model_name``. + Return a geodesic in the appropriate model. - INPUT: + EXAMPLES:: - - ``A`` -- a matrix in the current model - - ``model_name`` -- a string denoting the model to be converted to + sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I) + Geodesic in UHP from I to 2*I - OUTPUT: + sage: HyperbolicPlane().PD().get_geodesic(0, I/2) + Geodesic in PD from 0 to 1/2*I - - the coordinates of a point in the ``short_name`` model + sage: HyperbolicPlane().KM().get_geodesic((1/2, 1/2), (0,0)) + Geodesic in KM from (1/2, 1/2) to (0, 0) + + sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (1,0, sqrt(2))) + Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)) + """ + if end is None: + if isinstance(start, HyperbolicGeodesic): + G = start + if G.model() is not self: + G = G.to_model(self) + G.update_graphics(True, **graphics_options) + return G + raise ValueError("the start and end points must be specified") + return self._Geodesic(self, self(start), self(end), **graphics_options) + + def get_isometry(self, A): + r""" + Return an isometry in the appropriate model given the matrix. EXAMPLES:: - sage: A = matrix(2,[I, 0, 0, -I]) - sage: HyperbolicPlane.PD.isometry_to_model(A, 'UHP') - [ 0 1] - [-1 0] + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: HyperbolicFactoryUHP.get_isometry(identity_matrix(2)) + Isometry in UHP + [1 0] + [0 1] + + sage: HyperbolicFactoryPD.get_isometry(identity_matrix(2)) + Isometry in PD + [1 0] + [0 1] + + sage: HyperbolicFactoryKM.get_isometry(identity_matrix(3)) + Isometry in KM + [1 0 0] + [0 1 0] + [0 0 1] + + sage: HyperbolicFactoryHM.get_isometry(identity_matrix(3)) + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1] + """ + if isinstance(A, HyperbolicIsometry): + if A.model() is not self: + return A.to_model(self) + return A + return self._Isometry(self, A) - sage: HyperbolicPlane.PD.isometry_to_model(A, 'HM') - [-1 0 0] - [ 0 -1 0] - [ 0 0 1] + def random_element(self, **kwargs): + r""" + Return a random point in ``self``. - sage: HyperbolicPlane.PD.isometry_to_model(A, 'KM') - [-1 0 0] - [ 0 -1 0] - [ 0 0 1] + The points are uniformly distributed over the rectangle + `[-10, 10] \times [0, 10 i]` in the upper half plane model. - sage: B = diagonal_matrix([-1, -1, 1]) - sage: HyperbolicPlane.HM.isometry_to_model(B, 'UHP') - [ 0 -1] - [ 1 0] + EXAMPLES:: - sage: HyperbolicPlane.HM.isometry_to_model(B, 'PD') - [-I 0] - [ 0 I] + sage: p = HyperbolicPlane().UHP().random_element() + sage: bool((p.coordinates().imag()) > 0) + True - sage: HyperbolicPlane.HM.isometry_to_model(B, 'KM') - [-1 0 0] - [ 0 -1 0] - [ 0 0 1] + sage: p = HyperbolicPointPD.random_element() + sage: HyperbolicPlane.PD.point_in_model(p.coordinates()) + True + + sage: p = HyperbolicPointKM.random_element() + sage: HyperbolicPlane.KM.point_in_model(p.coordinates()) + True + + sage: p = HyperbolicPointHM.random_element().coordinates() + sage: bool((p[0]**2 + p[1]**2 - p[2]**2 - 1) < 10**-8) + True """ - cls.isometry_test(A) - return cls.isom_conversion_dict[model_name](A) + return self(self._computation_model.random_element(**kwargs)) + + def random_point(self, **kwds): + """ + Return a random point of ``self``. + + EXAMPLES:: + + sage: p = HyperbolicPlane().UHP().random_point() + sage: bool((p.coordinates().imag()) > 0) + True + + sage: PD = HyperbolicPlane().PD() + sage: p = PD.random_point() + sage: PD.point_in_model(p.coordinates()) + True + """ + return self.random_element(**kwds) + + def random_geodesic(self, **kwargs): + r""" + Return a random hyperbolic geodesic. + + EXAMPLES:: + + sage: h = HyperbolicPlane().UHP().random_geodesic() + sage: bool((h.endpoints()[0].coordinates()).imag() >= 0) + True + """ + g_ends = [self._computation_model.random_point(**kwargs) for k in range(2)] + return self.get_geodesic(self(g_ends[0]), self(g_ends[1])) + + def random_isometry(self, preserve_orientation=True, **kwargs): + r""" + Return a random isometry in the model of ``self``. + + INPUT: + + - ``preserve_orientation`` -- if ``True`` return an + orientation-preserving isometry + + OUTPUT: + + - a hyperbolic isometry + EXAMPLES:: + + sage: A = HyperbolicPlane().UHP().random_isometry() + sage: A.orientation_preserving() + True + sage: B = HyperbolicPlane().UHP().random_isometry(preserve_orientation=False) + sage: B.orientation_preserving() + False + """ + A = self._computation_model.random_isometry(preserve_orientation, **kwargs) + return A.to_model(self) -class HyperbolicModelUHP(HyperbolicModel, UniqueRepresentation): +class HyperbolicModelUHP(HyperbolicModel): r""" Upper Half Plane model. """ - name = "Upper Half Plane Model" - short_name = "UHP" - bounded = True - conformal = True - dimension = 2 - isometry_group = "PSL(2, \\Bold{R})" - isometry_group_is_projective = True - pt_conversion_dict = { - 'UHP' : lambda p : p, - 'PD' : lambda p : (p - I)/(Integer(1) - I*p), - 'HM' : lambda p : vector((real(p)/imag(p), - (real(p)**2 + imag(p)**2 - 1)/(2*imag(p)), - (real(p)**2 + imag(p)**2 + 1)/(2*imag(p)))), - 'KM' : lambda p : ((2*real(p))/(real(p)**2 + imag(p)**2 + 1), - (real(p)**2 + imag(p)**2 - 1)/(real(p)**2 + - imag(p)**2 + 1)) - } - isom_conversion_dict = { - 'UHP': lambda A : A, - 'PD' : lambda A : matrix(2,[1,-I,-I,1]) * A * matrix(2,[1,I,I,1])/Integer(2), - 'HM' : SL2R_to_SO21, - 'KM' : SL2R_to_SO21 - } - - @classmethod - def point_in_model(cls, p): #UHP + Element = HyperbolicPointUHP + _Geodesic = HyperbolicGeodesicUHP + _Isometry = HyperbolicIsometryUHP + + def __init__(self, space): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: TestSuite(UHP).run() + """ + HyperbolicModel.__init__(self, space, + name="Upper Half Plane Model", short_name="UHP", + bounded=True, conformal=True, dimension=2, + isometry_group="PSL(2, \\RR)", isometry_group_is_projective=True) + + def _coerce_map_from_(self, X): + """ + Return if the there is a coercion map from ``X`` to ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.has_coerce_map_from(HyperbolicPlane().PD()) + True + sage: UHP.has_coerce_map_from(HyperbolicPlane().KM()) + True + sage: UHP.has_coerce_map_from(HyperbolicPlane().HM()) + True + sage: UHP.has_coerce_map_from(QQ) + False + """ + if isinstance(X, HyperbolicModelPD): + return CoercionPDtoUHP(Hom(X, self)) + if isinstance(X, HyperbolicModelKM): + return CoercionKMtoUHP(Hom(X, self)) + if isinstance(X, HyperbolicModelHM): + return CoercionHMtoUHP(Hom(X, self)) + return super(HyperbolicModelUHP, self)._coerce_map_from_(X) + + def point_in_model(self, p): #UHP r""" - Check whether a complex number lies in the open upper half - plane. In the UHP.model_name_name, this is hyperbolic space. + Check whether a complex number lies in the open upper half plane. EXAMPLES:: @@ -590,8 +653,7 @@ def point_in_model(cls, p): #UHP """ return bool(imag(CC(p)) > 0) - @classmethod - def bdry_point_in_model(cls, p): #UHP + def bdry_point_in_model(self, p): #UHP r""" Check whether a complex number is a real number or ``\infty``. In the ``UHP.model_name_name``, this is the ideal boundary of @@ -619,8 +681,7 @@ def bdry_point_in_model(cls, p): #UHP im = abs(imag(CC(p)).n()) return bool( (im < EPSILON) or (p == infinity) ) - @classmethod #UHP - def isometry_act_on_point(cls, A, p): #UHP + def isometry_act_on_point(self, A, p): #UHP r""" Given an isometry ``A`` and a point ``p`` in the current model, return image of ``p`` unduer the action `A \cdot p`. @@ -635,8 +696,7 @@ def isometry_act_on_point(cls, A, p): #UHP """ return mobius_transform(A, p) - @classmethod - def isometry_in_model(cls, A): #UHP + def isometry_in_model(self, A): #UHP r""" Check that ``A`` acts as an isometry on the upper half plane. That is, ``A`` must be an invertible `2 \times 2` matrix with real @@ -655,8 +715,7 @@ def isometry_in_model(cls, A): #UHP sum([k in RR for k in A.list()]) == 4 and abs(A.det()) > -EPSILON) - @classmethod - def point_to_model(cls, coordinates, model_name): #UHP + def point_to_model(self, coordinates, model_name): #UHP r""" Convert ``coordinates`` from the current model to the model specified in ``model_name``. @@ -692,19 +751,12 @@ def point_to_model(cls, coordinates, model_name): #UHP """ p = coordinates if (cls.bdry_point_in_model(p) and not - ModelFactory.find_model(model_name).bounded): + ModelFactory.find_model(model_name).is_bounded()): raise NotImplementedError("boundary points are not implemented for" " the {0} model".format(model_name)) - if p == infinity: - return { - 'UHP' : p, - 'PD' : I, - 'KM' : (0, 1) - }[model_name] return cls.pt_conversion_dict[model_name](coordinates) - @classmethod # UHP - def isometry_to_model(cls, A, model_name): # UHP + def isometry_to_model(self, A, model_name): # UHP r""" Convert ``A`` from the current model to the model specified in ``model_name``. @@ -730,43 +782,68 @@ def isometry_to_model(cls, A, model_name): # UHP return cls.isom_conversion_dict[model_name](I * A) return cls.isom_conversion_dict[model_name](A) + def get_background_graphic(self, **bdry_options): #UHP + r""" + Return a graphic object that makes the model easier to visualize. + For the upper half space, the background object is the ideal boundary. + + EXAMPLES:: -class HyperbolicModelPD(HyperbolicModel, UniqueRepresentation): + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryUHP.get_background_graphic() + """ + from sage.plot.line import line + bd_min = bdry_options.get('bd_min', -5) + bd_max = bdry_options.get('bd_max', 5) + return line(((bd_min, 0), (bd_max, 0)), color='black') + +class HyperbolicModelPD(HyperbolicModel): r""" Poincaré Disk Model. """ - name = "Poincare Disk Model" # u"Poincaré Disk Model" - short_name = "PD" - bounded = True - conformal = True - dimension = 2 - isometry_group = "PU(1, 1)" - isometry_group_is_projective = True - pt_conversion_dict = { - 'PD': lambda p : p, - 'UHP': lambda p : (p + I)/(Integer(1) + I*p), - 'HM' : lambda p : vector(( - 2*real(p)/(1 - real(p)**2 - imag(p)**2), - 2*imag(p)/(1 - real(p)**2 - imag(p)**2), - (real(p)**2 + imag(p)**2 + 1)/(1 -real(p)**2 - imag(p)**2) - )), - 'KM' : lambda p : ( - 2*real(p)/(Integer(1) + real(p)**2 +imag(p)**2), - 2*imag(p)/(Integer(1) + real(p)**2 + imag(p)**2) - ) - } - isom_conversion_dict = { - 'PD' : lambda A : A, - 'UHP': lambda A : (matrix(2,[1,I,I,1])*A* - matrix(2,[1,-I,-I,1])/Integer(2)), - 'KM' : lambda A : SL2R_to_SO21( matrix(2,[1,I,I,1]) * A * - matrix(2,[1,-I,-I,1])/Integer(2)), - 'HM' : lambda A : SL2R_to_SO21( matrix(2,[1,I,I,1]) * A * - matrix(2,[1,-I,-I,1])/Integer(2)) - } - - @classmethod - def point_in_model(cls, p): #PD + _Geodesic = HyperbolicGeodesicPD + _Isometry = HyperbolicIsometryPD + + def __init__(self, space): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: TestSuite(PD).run() + """ + HyperbolicModel.__init__(self, space, + name="Poincare Disk Model", # u"Poincaré Disk Model" + short_name="PD", + bounded=True, conformal=True, dimension=2, + isometry_group="PU(1, 1)", isometry_group_is_projective=True) + + def _coerce_map_from_(self, X): + """ + Return if the there is a coercion map from ``X`` to ``self``. + + EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: PD.has_coerce_map_from(HyperbolicPlane().UHP()) + True + sage: PD.has_coerce_map_from(HyperbolicPlane().KM()) + True + sage: PD.has_coerce_map_from(HyperbolicPlane().HM()) + True + sage: PD.has_coerce_map_from(QQ) + False + """ + if isinstance(X, HyperbolicModelUHP): + return CoercionUHPtoPD(Hom(X, self)) + if isinstance(X, HyperbolicModelKM): + return CoercionKMtoPD(Hom(X, self)) + if isinstance(X, HyperbolicModelHM): + return CoercionHMtoPD(Hom(X, self)) + return super(HyperbolicModelPD, self)._coerce_map_from_(X) + + def point_in_model(self, p): #PD r""" Check whether a complex number lies in the open unit disk. @@ -783,9 +860,7 @@ def point_in_model(cls, p): #PD """ return bool(abs(CC(p)) < 1) - - @classmethod - def bdry_point_in_model(cls, p): #PD + def bdry_point_in_model(self, p): #PD r""" Check whether a complex number lies in the open unit disk. @@ -800,11 +875,9 @@ def bdry_point_in_model(cls, p): #PD sage: HyperbolicPlane.PD.bdry_point_in_model(1 + .2*I) False """ - return bool(abs(abs(CC(p))- 1) < EPSILON) + return bool(abs(abs(CC(p))- 1) < EPSILON) - - @classmethod - def isometry_act_on_point(cls, A, p): #PD + def isometry_act_on_point(self, A, p): #PD r""" Given an isometry ``A`` and a point ``p`` in the current model, return image of ``p`` unduer the action `A \cdot p`. @@ -822,9 +895,7 @@ def isometry_act_on_point(cls, A, p): #PD return mobius_transform(I*matrix(2,[0,1,1,0]), _image) return _image - - @classmethod - def isometry_in_model(cls, A): #PD + def isometry_in_model(self, A): #PD r""" Check if the given matrix ``A`` is in the group `U(1,1)`. @@ -840,8 +911,7 @@ def isometry_in_model(cls, A): #PD # Orientation preserving and reversing return PD_preserve_orientation(A) or PD_preserve_orientation(I*A) - @classmethod - def point_to_model(cls, coordinates, model_name): #PD + def point_to_model(self, coordinates, model_name): #PD r""" Convert ``coordinates`` from the current model to the model specified in ``model_name``. @@ -869,11 +939,9 @@ def point_to_model(cls, coordinates, model_name): #PD """ if model_name == 'UHP' and coordinates == I: return infinity - return super(HyperbolicModelPD, cls).point_to_model(coordinates, - model_name) + return super(HyperbolicModelPD, cls).point_to_model(coordinates, model_name) - @classmethod # PD - def isometry_to_model(cls, A, model_name): # PD + def isometry_to_model(self, A, model_name): #PD r""" Convert ``A`` from the current model to the model specified in ``model_name``. @@ -902,37 +970,68 @@ def isometry_to_model(cls, A, model_name): # PD return cls.isom_conversion_dict[model_name](I*A) return cls.isom_conversion_dict[model_name](A) -class HyperbolicModelKM(HyperbolicModel, UniqueRepresentation): + def get_background_graphic(self, **bdry_options): #PD + r""" + Return a graphic object that makes the model easier to visualize. + For the Poincare disk, the background object is the ideal boundary. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryPD.get_background_graphic() + """ + from sage.plot.circle import circle + return circle((0,0), 1, axes=False, color='black') + +##################################################################### +## Klein disk model + +class HyperbolicModelKM(HyperbolicModel): r""" Klein Model. """ - name = "Klein Disk Model" - short_name = "KM" - bounded = True - conformal = False - dimension = 2 - isometry_group_is_projective = True - isometry_group = "PSO(2, 1)" - pt_conversion_dict = { - 'UHP' : lambda p: -p[0]/(p[1] - 1) +\ - I*(-(sqrt(-p[0]**2 -p[1]**2 + 1) - p[0]**2 - p[1]**2 + - 1)/((p[1] - 1)*sqrt(-p[0]**2 - p[1]**2 + 1) + p[1] - 1)), - 'PD' : lambda p : (p[0]/(1 + (1 - p[0]**2 - p[1]**2).sqrt()) + \ - I*p[1]/(1 + (1 - p[0]**2 - p[1]**2).sqrt())), - 'KM' : lambda p : p, - 'HM' : lambda p : vector((2*p[0],2*p[1], 1 + p[0]**2 + - p[1]**2))/(1 - p[0]**2 - p[1]**2) - } - isom_conversion_dict = { - 'UHP' : SO21_to_SL2R, - 'PD' : lambda A : matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(A) *\ - matrix(2,[1,I,I,1])/Integer(2), - 'KM' : lambda A : A, - 'HM' : lambda A : A - } - - @classmethod - def point_in_model(cls, p): #KM + _Geodesic = HyperbolicGeodesicKM + _Isometry = HyperbolicIsometryKM + + def __init__(self, space): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: KM = HyperbolicPlane().KM() + sage: TestSuite(KM).run() + """ + HyperbolicModel.__init__(self, space, + name="Klein Disk Model", short_name="PD", + bounded=True, conformal=False, dimension=2, + isometry_group="PSO(2, 1)", isometry_group_is_projective=True) + + def _coerce_map_from_(self, X): + """ + Return if the there is a coercion map from ``X`` to ``self``. + + EXAMPLES:: + + sage: KM = HyperbolicPlane().UHP() + sage: KM.has_coerce_map_from(HyperbolicPlane().UHP()) + True + sage: KM.has_coerce_map_from(HyperbolicPlane().PD()) + True + sage: KM.has_coerce_map_from(HyperbolicPlane().HM()) + True + sage: KM.has_coerce_map_from(QQ) + False + """ + if isinstance(X, HyperbolicModelUHP): + return CoercionUHPtoKM(Hom(X, self)) + if isinstance(X, HyperbolicModelPD): + return CoercionPDtoKM(Hom(X, self)) + if isinstance(X, HyperbolicModelHM): + return CoercionHMtoKM(Hom(X, self)) + return super(HyperbolicModelKM, self)._coerce_map_from_(X) + + def point_in_model(self, p): #KM r""" Check whether a point lies in the open unit disk. @@ -949,8 +1048,7 @@ def point_in_model(cls, p): #KM """ return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) - @classmethod - def bdry_point_in_model(cls, p): #KM + def bdry_point_in_model(self, p): #KM r""" Check whether a point lies in the unit circle, which corresponds to the ideal boundary of the hyperbolic plane in the Klein model. @@ -968,9 +1066,7 @@ def bdry_point_in_model(cls, p): #KM """ return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) - - @classmethod #KM - def isometry_act_on_point(cls, A, p): #KM + def isometry_act_on_point(self, A, p): #KM r""" Given an isometry ``A`` and a point ``p`` in the current model, return image of ``p`` unduer the action `A \cdot p`. @@ -988,8 +1084,7 @@ def isometry_act_on_point(cls, A, p): #KM return infinity return v[0:2]/v[2] - @classmethod - def isometry_in_model(cls, A): #KM + def isometry_in_model(self, A): #KM r""" Check if the given matrix ``A`` is in the group `SO(2,1)`. @@ -1003,8 +1098,7 @@ def isometry_in_model(cls, A): #KM return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON) - @classmethod - def point_to_model(cls, coordinates, model_name): #KM + def point_to_model(self, coordinates, model_name): #KM r""" Convert ``coordinates`` from the current model to the model specified in ``model_name``. @@ -1032,38 +1126,70 @@ def point_to_model(cls, coordinates, model_name): #KM """ if model_name == 'UHP' and tuple(coordinates) == (0,1): return infinity - return super(HyperbolicModelKM, cls).point_to_model(coordinates, - model_name) + return super(HyperbolicModelKM, cls).point_to_model(coordinates, model_name) + + def get_background_graphic(self, **bdry_options): #KM + r""" + Return a graphic object that makes the model easier to visualize. + For the Klein model, the background object is the ideal boundary. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryKM.get_background_graphic() + """ + from sage.plot.circle import circle + return circle((0,0), 1, axes=False, color='black') +##################################################################### +## Hyperboloid model -class HyperbolicModelHM(HyperbolicModel, UniqueRepresentation): +class HyperbolicModelHM(HyperbolicModel): r""" Hyperboloid Model. """ - name = "Hyperboloid Model" - short_name = "HM" - bounded = False - conformal = True - dimension = 2 - isometry_group = "SO(2, 1)" - pt_conversion_dict = { - 'UHP' : lambda p : -((p[0]*p[2] + p[0]) + - I*(p[2] +1))/((p[1] - 1)*p[2] - p[0]**2 - - p[1]**2 + p[1] - 1), - 'PD' : lambda p : p[0]/(1 + p[2]) + I* (p[1]/(1 + p[2])), - 'KM' : lambda p : (p[0]/(1 + p[2]), p[1]/(1 + p[2])), - 'HM' : lambda p : p - } - isom_conversion_dict = { - 'UHP' : SO21_to_SL2R, - 'PD' : lambda A : matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(A) *\ - matrix(2,[1,I,I,1])/Integer(2), - 'KM' : lambda A : A, - 'HM' : lambda A : A - } - - @classmethod - def point_in_model(cls, p): #HM + _Geodesic = HyperbolicGeodesicHM + _Isometry = HyperbolicIsometryHM + + def __init__(self, space): + """ + Initialize ``self``. + + EXAMPLES:: + + sage: HM = HyperbolicPlane().HM() + sage: TestSuite(HM).run() + """ + HyperbolicModel.__init__(self, space, + name="Hyperboloid Model", short_name="HM", + bounded=False, conformal=True, dimension=2, + isometry_group="SO(2, 1)", isometry_group_is_projective=True) + + def _coerce_map_from_(self, X): + """ + Return if the there is a coercion map from ``X`` to ``self``. + + EXAMPLES:: + + sage: HM = HyperbolicPlane().UHP() + sage: HM.has_coerce_map_from(HyperbolicPlane().UHP()) + True + sage: HM.has_coerce_map_from(HyperbolicPlane().PD()) + True + sage: HM.has_coerce_map_from(HyperbolicPlane().KM()) + True + sage: HM.has_coerce_map_from(QQ) + False + """ + if isinstance(X, HyperbolicModelUHP): + return CoercionUHPtoHM(Hom(X, self)) + if isinstance(X, HyperbolicModelPD): + return CoercionPDtoHM(Hom(X, self)) + if isinstance(X, HyperbolicModelHM): + return CoercionKMtoHM(Hom(X, self)) + return super(HyperbolicModelHM, self)._coerce_map_from_(X) + + def point_in_model(self, p): #HM r""" Check whether a complex number lies in the hyperboloid. @@ -1080,8 +1206,7 @@ def point_in_model(cls, p): #HM """ return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 + 1 < EPSILON) - @classmethod - def bdry_point_in_model(cls, p): #HM + def bdry_point_in_model(self, p): #HM r""" Return ``False`` since the Hyperboloid model has no boundary points. @@ -1098,9 +1223,7 @@ def bdry_point_in_model(cls, p): #HM """ return False - - @classmethod - def isometry_in_model(cls, A): #HM + def isometry_in_model(self, A): #HM r""" Test that the matrix ``A`` is in the group `SO(2,1)^+`. @@ -1113,3 +1236,44 @@ def isometry_in_model(cls, A): #HM from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON) + def get_background_graphic(self, **bdry_options): #HM + r""" + Return a graphic object that makes the model easier to visualize. + For the hyperboloid model, the background object is the hyperboloid + itself. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * + sage: circ = HyperbolicFactoryPD.get_background_graphic() + """ + hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1) + z_height = bdry_options.get('z_height', 7.0) + x_max = sqrt((z_height**2 - 1) / 2.0) + from sage.plot.plot3d.all import plot3d + from sage.all import var + (x,y) = var('x,y') + return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max), + (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) + +##################################################################### +## Helper functions + +def PD_preserve_orientation(A): + r""" + For a PD isometry, determine if it preserves orientation. + This test is more more involved than just checking the sign + of the determinant, and it is used a few times in this file. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import PD_preserve_orientation as orient + sage: orient(matrix(2, [-I, 0, 0, I])) + True + sage: orient(matrix(2, [0, I, I, 0])) + False + """ + return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() + and abs(A[0][0]) - abs(A[0][1]) != 0) + + diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index d5e98664b15..2918408450f 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -1,9 +1,18 @@ r""" Hyperbolic Points -This module implements the abstract base class for points in -hyperbolic space of arbitrary dimension. It also contains the -implementations for specific models of hyperbolic geometry. +This module implements points in hyperbolic space of arbitrary dimension. +It also contains the implementations for specific models of +hyperbolic geometry. + +This module also implements ideal points in hyperbolic space of arbitrary +dimension. It also contains the implementations for specific models +of hyperbolic geometry. + +Note that not all models of hyperbolic space are bounded, meaning that +the ideal boundary is not the topological boundary of the set underlying +tho model. For example, the unit disk model is bounded with boundary +given by the unit sphere. The hyperboloid model is not bounded. AUTHORS: @@ -19,6 +28,16 @@ sage: g = HyperbolicPlane.UHP.point(3 + I) sage: g.dist(HyperbolicPlane.UHP.point(I)) arccosh(11/2) + +We can also construct boundary points in the upper half plane model:: + + sage: HyperbolicPlane.UHP.point(3) + Boundary point in UHP 3 + +Points on the boundary are infinitely far from interior points:: + + sage: HyperbolicPlane.UHP.point(3).dist(HyperbolicPlane.UHP.point(I)) + +Infinity """ #*********************************************************************** @@ -30,11 +49,12 @@ # http://www.gnu.org/licenses/ #*********************************************************************** -from sage.structure.sage_object import SageObject +from sage.structure.element import Element from sage.symbolic.pynac import I from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex +from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometry lazy_import('sage.rings.all', ['RR', 'CC']) lazy_import('sage.functions.other', 'real') @@ -42,22 +62,22 @@ lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) -lazy_import('sage.geometry.hyperbolic_space.model_factory', 'ModelFactory') -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', - ['HyperbolicAbstractFactory', 'HyperbolicFactoryUHP', - 'HyperbolicFactoryPD', 'HyperbolicFactoryKM', - 'HyperbolicFactoryHM']) lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') -class HyperbolicPoint(SageObject): +class HyperbolicPoint(Element): r""" Abstract base class for hyperbolic points. This class should never be instantiated. INPUT: - - the coordinates of a hyperbolic point in the appropriate model + - ``model`` -- the model of the hyperbolic space + - ``coordinates`` -- the coordinates of a hyperbolic point in the + appropriate model + - ``is_boundary`` -- whether the point is a boundary point + - ``check`` -- (default: ``True``) if ``True``, then check to make sure + the coordinates give a valid point the the model EXAMPLES: @@ -77,6 +97,21 @@ class HyperbolicPoint(SageObject): sage: bool(p.coordinates() == q.coordinates()) True + Similarly for boundary points:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * + sage: p = HyperbolicBdryPointUHP(1); p + Boundary point in UHP 1 + + sage: q = HyperbolicBdryPointPD(1); q + Boundary point in PD 1 + + sage: p == q + False + + sage: bool(p.coordinates() == q.coordinates()) + True + It is an error to specify a point that does not lie in the appropriate model:: @@ -100,11 +135,16 @@ class HyperbolicPoint(SageObject): Traceback (most recent call last): ... ValueError: (1, 1, 1) is not a valid point in the HM model - """ - HFactory = HyperbolicAbstractFactory - HMethods = HyperbolicAbstractMethods - def __init__(self, coordinates, **graphics_options): + It is an error to specify an interior point of hyperbolic space as a + boundary point:: + + sage: HyperbolicBdryPointUHP(0.2 + 0.3*I) + Traceback (most recent call last): + ... + ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model + """ + def __init__(self, model, coordinates, is_boundary, check=True, **graphics_options): r""" See ``HyperbolicPoint`` for full documentation. @@ -115,16 +155,26 @@ def __init__(self, coordinates, **graphics_options): sage: p Point in UHP I """ - if self.model().point_in_model(coordinates): - if isinstance(coordinates, tuple): - coordinates = vector(coordinates) - self._coordinates = coordinates - else: + if is_boundary: + if not model.is_bounded(): + raise NotImplementedError("boundary points are not implemented in the {0} model".format(model.short_name())) + if check and not model.bdry_point_in_model(coordinates): + raise ValueError( + "{0} is not a valid".format(coordinates) + + " boundary point in the {0} model".format(model.short_name())) + elif check and not model.point_in_model(coordinates): raise ValueError( "{0} is not a valid".format(coordinates) + - " point in the {0} model".format(self.model().short_name)) + " point in the {0} model".format(model.short_name())) + + if isinstance(coordinates, tuple): + coordinates = vector(coordinates) + self._coordinates = coordinates + self._bdry = is_boundary self._graphics_options = graphics_options + Element.__init__(self, model) + ##################### # "Private" Methods # ##################### @@ -144,46 +194,54 @@ def _cached_coordinates(self): sage: A._cached_coordinates I """ - return self.model().point_to_model(self.coordinates(), - self.HMethods.model_name()) + return self.parent().point_to_model(self.coordinates(), + self._HMethods.model_name()) def _repr_(self): r""" Return a string representation of ``self``. - OUTPUT: - - - string - EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(3 + 4*I) + sage: HyperbolicPlane().UHP().get_point(3 + 4*I) Point in UHP 4*I + 3 - sage: HyperbolicPointPD(1/2 + I/2) + sage: HyperbolicPlane().PD().get_point(1/2 + I/2) Point in PD 1/2*I + 1/2 - sage: HyperbolicPointKM((1/2, 1/2)) + sage: HyperbolicPlane().KM().get_point((1/2, 1/2)) Point in KM (1/2, 1/2) - sage: HyperbolicPointHM((0,0,1)) + sage: HyperbolicPlane().HM().get_point((0,0,1)) Point in HM (0, 0, 1) + + sage: HyperbolicPlane().UHP().get_point(infinity) + Boundary point in UHP +Infinity + + sage: HyperbolicPlane().PD().get_point(-1) + Boundary point in PD -1 + + sage: HyperbolicPlane().KM().get_point((0, -1)) + Boundary point in KM (0, -1) """ - return "Point in {0} {1}".format(self.model_name(), self.coordinates()) + if self._bdry: + base = "Boundary point" + else: + base = "Point" + return base + " in {0} {1}".format(self.parent().short_name(), self._coordinates) def _latex_(self): r""" EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = HyperbolicPlane.UHP.point(0) + sage: p = HyperbolicPlane().UHP().get_point(0) sage: latex(p) 0 - sage: q = HyperbolicPlane.HM.point((0,0,1)) + sage: q = HyperbolicPlane().HM().get_point((0,0,1)) sage: latex(q) \left(0,\,0,\,1\right) """ - return latex(self.coordinates()) + return latex(self._coordinates) def __eq__(self, other): r""" @@ -192,8 +250,8 @@ def __eq__(self, other): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p1 = HyperbolicPointUHP(1 + I) - sage: p2 = HyperbolicPointUHP(2 + I) + sage: p1 = HyperbolicPlane().UHP().get_point(1 + I) + sage: p2 = HyperbolicPlane().UHP().get_point(2 + I) sage: p1 == p2 False sage: p1 == p1 @@ -201,8 +259,8 @@ def __eq__(self, other): :: - sage: p1 = HyperbolicPointPD(0) - sage: p2 = HyperbolicPointPD(1/2 + 2*I/3) + sage: p1 = HyperbolicPlane().PD().get_point(0) + sage: p2 = HyperbolicPlane().PD().get_point(1/2 + 2*I/3) sage: p1 == p2 False sage: p1 == p1 @@ -210,21 +268,22 @@ def __eq__(self, other): :: - sage: p1 = HyperbolicPointKM((0,0)) - sage: p2 = HyperbolicPointKM((0, 1/2)) + sage: p1 = HyperbolicPlane().KM().get_point((0,0)) + sage: p2 = HyperbolicPlane().KM().get_point((0, 1/2)) sage: p1 == p2 False :: - sage: p1 = HyperbolicPointHM((0,0,1)) - sage: p2 = HyperbolicPointHM((0,0,1/1)) + sage: p1 = HyperbolicPlane().HM().get_point((0,0,1)) + sage: p2 = HyperbolicPlane().HM().get_point((0,0,1/1)) sage: p1 == p2 True """ - return (self.model_name() == other.model_name() - and bool(self.coordinates() == other.coordinates())) + return (self.parent() is other.parent() + and bool(self._coordinates == other._coordinates)) + # TODO: Add a test that this works with isometries def __rmul__(self, other): r""" Implement the action of matrices on points of hyperbolic space. @@ -240,8 +299,10 @@ def __rmul__(self, other): """ from sage.matrix.matrix import is_Matrix if is_Matrix(other): - A = self.HFactory.get_isometry(other) - return A*self + A = self.parent().get_isometry(other) + return A * self + elif isinstance(other, HyperbolicIsometry): + return other * self else: raise TypeError("unsupported operand type(s) for *:" "{0} and {1}".format(self, other)) @@ -271,8 +332,7 @@ def coordinates(self): """ return self._coordinates - @classmethod - def model(cls): + def model(self): r""" Return the model to which the :class:`HyperbolicPoint` belongs. @@ -291,10 +351,9 @@ def model(cls): sage: HyperbolicPointHM((0,0,1)).model() """ - return cls.HFactory.get_model() + return self.parent() - @classmethod - def model_name(cls): + def model_name(self): r""" Return the short name of the hyperbolic model. @@ -313,30 +372,28 @@ def model_name(cls): sage: HyperbolicPointHM((0,0,1)).model_name() 'HM' """ - return cls.model().short_name - - def to_model(self, model_name): - r""" - Convert the current object to image in another model. + return self.parent().short_name - INPUT: - - - ``model_name`` -- a string representing the image model + def is_boundary(self): + """ + Return ``True`` if ``self`` is a boundary point. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(I).to_model('PD') - Point in PD 0 + sage: PD = HyperbolicPlane().PD() + sage: p = PD.get_point(0.5+.2*I) + sage: p.is_boundary() + False + sage: p = PD.get_point(I) + sage: p.is_boundary() + True """ - factory = ModelFactory.find_factory(model_name) - coordinates = self.model().point_to_model(self.coordinates(), model_name) - return factory.get_point(coordinates) + return self._bdry def update_graphics(self, update=False, **options): r""" - Update the graphics options of a HyperbolicPoint. If ``update`` is - ``True``, update rather than overwrite. + Update the graphics options of a :class:`HyperbolicPoint`. + If ``update`` is ``True``, update rather than overwrite. EXAMPLES:: @@ -370,9 +427,9 @@ def graphics_options(self): """ return self._graphics_options - ################################### - # Methods implemented in HMethods # - ################################### + #################################### + # Methods implemented in _HMethods # + #################################### def dist(self, other): r""" @@ -421,13 +478,13 @@ def dist(self, other): """ tmp_other = other.to_model(self.model_name()) if isinstance(other, HyperbolicPoint): - return self.HMethods.point_dist(self._cached_coordinates, tmp_other._cached_coordinates) + return self._HMethods.point_dist(self._cached_coordinates, tmp_other._cached_coordinates) elif isinstance(other, HyperbolicGeodesic): - return self.HMethods.geod_dist_from_point( + return self._HMethods.geod_dist_from_point( *(other._cached_endpoints + self._cached_coordinates )) - def symmetry_in (self): + def symmetry_in(self): r""" Return the involutary isometry fixing the given point. @@ -473,39 +530,9 @@ def symmetry_in (self): sage: A*A == HyperbolicPlane.UHP.isometry(identity_matrix(2)) True """ - A = self.HMethods.symmetry_in(self._cached_coordinates) - A = self.HMethods.model().isometry_to_model(A, self.model_name()) - return self.HFactory.get_isometry(A) - - @classmethod - def random_element(cls, **kwargs): - r""" - Return a random point in the upper half - plane. The points are uniformly distributed over the rectangle - `[-10, 10] \times [-10 i, 10 i]`. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = HyperbolicPointUHP.random_element() - sage: bool((p.coordinates().imag()) > 0) - True - - sage: p = HyperbolicPointPD.random_element() - sage: HyperbolicPlane.PD.point_in_model(p.coordinates()) - True - - sage: p = HyperbolicPointKM.random_element() - sage: HyperbolicPlane.KM.point_in_model(p.coordinates()) - True - - sage: p = HyperbolicPointHM.random_element().coordinates() - sage: bool((p[0]**2 + p[1]**2 - p[2]**2 - 1) < 10**-8) - True - """ - p = cls.HMethods.random_point(**kwargs) - p = cls.HMethods.model().point_to_model(p, cls.model().short_name) - return cls.HFactory.get_point(p) + A = self._HMethods.symmetry_in(self._cached_coordinates) + A = self._HMethods.model().isometry_to_model(A, self.model_name()) + return self.parent().get_isometry(A) ########### # Display # @@ -520,12 +547,33 @@ def show(self, boundary=True, **options): sage: HyperbolicPointPD(0).show() sage: HyperbolicPointKM((0,0)).show() sage: HyperbolicPointHM((0,0,1)).show() + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * + sage: HyperbolicBdryPointUHP(0).show() + sage: HyperbolicBdryPointUHP(infinity).show() + Traceback (most recent call last): + ... + NotImplementedError: can't draw the point infinity """ - opts = dict([('axes', False),('aspect_ratio',1)]) + p = self.coordinates() + if p == infinity: + raise NotImplementedError("can't draw the point infinity") + + opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) opts.update(options) + + if self._bdry: # It is a boundary point + p = numerical_approx(p) + pic = point((p, 0), **opts) + if boundary: + bd_pic = self._model.get_background_graphic(bd_min = p - 1, + bd_max = p + 1) + pic = bd_pic + pic + return pic + + # It is an interior point from sage.misc.functional import numerical_approx - p = self.coordinates() if p in RR: p = CC(p) elif hasattr(p, 'iteritems') or hasattr(p, '__iter__'): @@ -535,7 +583,7 @@ def show(self, boundary=True, **options): from sage.plot.point import point pic = point(p, **opts) if boundary: - bd_pic = self.HFactory.get_background_graphic() + bd_pic = self.parent().get_background_graphic() pic = bd_pic + pic return pic @@ -552,9 +600,13 @@ class HyperbolicPointUHP(HyperbolicPoint): sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointUHP sage: HyperbolicPointUHP(2*I) Point in UHP 2*I + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointUHP + sage: q = HyperbolicBdryPointUHP(1); q + Boundary point in UHP 1 + """ - HFactory = HyperbolicFactoryUHP - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP def show(self, boundary=True, **options): r""" @@ -576,12 +628,12 @@ def show(self, boundary=True, **options): pic = point(p, **opts) if boundary: cent = real(p) - bd_pic = self.HFactory.get_background_graphic(bd_min = cent - 1, + bd_pic = self.parent().get_background_graphic(bd_min = cent - 1, bd_max = cent + 1) pic = bd_pic + pic return pic -class HyperbolicPointPD (HyperbolicPoint): +class HyperbolicPointPD(HyperbolicPoint): r""" Create a point in the PD model. @@ -594,9 +646,12 @@ class HyperbolicPointPD (HyperbolicPoint): sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointPD sage: HyperbolicPointPD(0) Point in PD 0 + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointPD + sage: q = HyperbolicBdryPointPD(1); q + Boundary point in PD 1 """ - HFactory = HyperbolicFactoryPD - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP class HyperbolicPointKM(HyperbolicPoint): r""" @@ -611,11 +666,14 @@ class HyperbolicPointKM(HyperbolicPoint): sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointKM sage: HyperbolicPointKM((0,0)) Point in KM (0, 0) + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointKM + sage: q = HyperbolicBdryPointKM((1,0)); q + Boundary point in KM (1, 0) """ - HFactory = HyperbolicFactoryKM - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP -class HyperbolicPointHM (HyperbolicPoint): +class HyperbolicPointHM(HyperbolicPoint): r""" Create a point in the HM model. @@ -629,7 +687,12 @@ class HyperbolicPointHM (HyperbolicPoint): sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointHM sage: HyperbolicPointHM((0,0,1)) Point in HM (0, 0, 1) + + sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointHM + sage: q = HyperbolicBdryPointHM((1,0,0)); q + Traceback (most recent call last): + ... + NotImplementedError: boundary points are not implemented in the HM model """ - HFactory = HyperbolicFactoryHM - HMethods = HyperbolicMethodsUHP + _HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/model_factory.py b/src/sage/geometry/hyperbolic_space/model_factory.py deleted file mode 100644 index b846c323370..00000000000 --- a/src/sage/geometry/hyperbolic_space/model_factory.py +++ /dev/null @@ -1,68 +0,0 @@ -r""" -Factory for Hyperbolic Models - -AUTHORS: - -- Greg Laun (2013): initial version -""" - -#***************************************************************************** -# Copyright (C) 2013 Greg Laun -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#***************************************************************************** - -from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.lazy_import import lazy_import - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_model', - ['HyperbolicModelUHP', 'HyperbolicModelPD', - 'HyperbolicModelHM', 'HyperbolicModelKM']) - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_factory', - ['HyperbolicFactoryUHP', 'HyperbolicFactoryPD', - 'HyperbolicFactoryHM', 'HyperbolicFactoryKM']) - -class ModelFactory(UniqueRepresentation): - """ - Factory for creating the hyperbolic models. - """ - @classmethod - def find_model(cls, model_name): - r""" - Given the short name of a hyperbolic model, return that model. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory - sage: ModelFactory.find_model('UHP') - - """ - return { - 'UHP': HyperbolicModelUHP, - 'PD' : HyperbolicModelPD, - 'HM' : HyperbolicModelHM, - 'KM' : HyperbolicModelKM - }[model_name] - - @classmethod - def find_factory(cls, model_name): - r""" - Given the short name of a hyperbolic model, return the associated factory. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.model_factory import ModelFactory - sage: ModelFactory.find_factory('UHP') - - """ - return { - 'UHP' : HyperbolicFactoryUHP, - 'PD' : HyperbolicFactoryPD, - 'HM' : HyperbolicFactoryHM, - 'KM' : HyperbolicFactoryKM - }[model_name] - From b203a5390284a678a4cf79c44f3f3c368c1a9fe9 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 25 Aug 2014 16:30:58 +0900 Subject: [PATCH 021/129] Some more refactoring and starting to fix doctests. --- .../hyperbolic_space/hyperbolic_coercion.py | 116 ++- .../hyperbolic_space/hyperbolic_geodesic.py | 4 +- .../hyperbolic_space/hyperbolic_interface.py | 88 +- .../hyperbolic_space/hyperbolic_isometry.py | 11 +- .../hyperbolic_space/hyperbolic_methods.py | 891 ----------------- .../hyperbolic_space/hyperbolic_model.py | 934 +++++++++++++++++- .../hyperbolic_space/hyperbolic_point.py | 168 ++-- 7 files changed, 1051 insertions(+), 1161 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 89624d6e2eb..2a78ee403c3 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -23,10 +23,14 @@ from sage.categories.morphism import Morphism from sage.symbolic.pynac import I from sage.matrix.constructor import matrix +from sage.modules.free_module_element import vector from sage.rings.all import RR from sage.rings.integer import Integer from sage.rings.infinity import infinity +#from sage.functions.other import real, imag, sqrt from sage.misc.lazy_import import lazy_import +lazy_import('sage.functions.other', ['real','imag','sqrt']) +lazy_import('sage.misc.misc', 'attrcall') class HyperbolicModelCoercion(Morphism): """ @@ -54,19 +58,21 @@ def _call_(self, x): sage: UHP = HyperbolicPlane().UHP() sage: PD = HyperbolicPlane().PD() + sage: HM = HyperbolicPlane().HM() sage: phi = UHP.coerce_map_from(PD) sage: phi(PD.get_point(0.5+0.5*I)) Point in UHP 2.00000000000000 + 1.00000000000000*I + sage: psi = HM.coerce_map_from(UHP) + sage: psi(UHP.get_point(I)) + (0, 0, 1) It is an error to try to convert a boundary point to a model that doesn't support boundary points:: - sage: HM = HyperbolicPlane().HM() - sage: psi = HM.coerce_map_from(UHP) - sage: psi(UHP(infinity) + sage: psi(UHP(infinity)) Traceback (most recent call last): ... - NotImplementedError: boundary points are not implemented for the Hyperbolid Model + NotImplementedError: boundary points are not implemented for the Hyperboloid Model """ C = self.codomain() bdry = False @@ -74,11 +80,11 @@ def _call_(self, x): if self.domain().is_bounded(): bdry = x.is_boundary() else: - bdry = C.bdry_point_in_model(x) + bdry = C.boundary_point_in_model(x) elif self.domain().is_bounded() and x.is_boundary(): raise NotImplementedError("boundary points are not implemented for" " the {0}".format(C.name())) - return C.element_class(C, self.image_point(x.coordinates()), bdry, + return C.element_class(C, self.image_coordinates(x.coordinates()), bdry, check=False, **x.graphics_options()) def convert_geodesic(self, x): @@ -107,7 +113,7 @@ def convert_isometry(self, x): sage: UHP = HyperbolicPlane().UHP() sage: PD = HyperbolicPlane().PD() sage: phi = UHP.coerce_map_from(PD) - sage: phi(PD.get_point(0.5+0.5*I)) + sage: phi(PD.get_point(0.5+0.5*I)) # Wrong test... Point in UHP 2.00000000000000 + 1.00000000000000*I """ return self.codomain().get_isometry(self.image_isometry(x._matrix)) @@ -136,7 +142,7 @@ class CoercionUHPtoPD(HyperbolicModelCoercion): """ Coercion from the UHP to PD model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. @@ -146,8 +152,10 @@ def image_point(self, x): sage: UHP = HyperbolicPlane().UHP() sage: PD = HyperbolicPlane().PD() sage: phi = PD.coerce_map_from(UHP) - sage: phi.image_point(I) - Point in PD 0 + sage: phi.image_coordinates(I) + 0 + sage: phi.image_coorindates(I) + +Infinity """ if x == infinity: return I @@ -166,20 +174,23 @@ class CoercionUHPtoKM(HyperbolicModelCoercion): """ Coercion from the UHP to KM model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. EXAMPLES:: - sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'KM') + sage: UHP = HyperbolicPlane().UHP() + sage: KM = HyperbolicPlane().KM() + sage: phi = KM.coerce_map_from(UHP) + sage: phi.image_coordinates(3 + I) (6/11, 9/11) """ - if p == infinity: + if x == infinity: return (0, 1) - return ((2*real(p))/(real(p)**2 + imag(p)**2 + 1), - (real(p)**2 + imag(p)**2 - 1)/(real(p)**2 + imag(p)**2 + 1)) + return ((2*real(x))/(real(x)**2 + imag(x)**2 + 1), + (real(x)**2 + imag(x)**2 - 1)/(real(x)**2 + imag(x)**2 + 1)) def image_isometry(self, x): """ @@ -194,14 +205,17 @@ class CoercionUHPtoHM(HyperbolicModelCoercion): """ Coercion from the UHP to HM model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. EXAMPLES:: - sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'HM') + sage: UHP = HyperbolicPlane().UHP() + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(UHP) + sage: phi.image_coordinates(3 + I) (3, 9/2, 11/2) """ return vector((real(x)/imag(x), @@ -225,7 +239,7 @@ class CoercionPDtoUHP(HyperbolicModelCoercion): """ Coercion from the PD to UHP model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. @@ -235,9 +249,9 @@ def image_point(self, x): sage: PD = HyperbolicPlane().PD() sage: UHP = HyperbolicPlane().UHP() sage: phi = UHP.coerce_map_from(PD) - sage: phi.image_point(0.5+0.5*I) + sage: phi.image_coordinates(0.5+0.5*I) 2.00000000000000 + 1.00000000000000*I - sage: phi.image_point(0) + sage: phi.image_coordinates(0) I """ if x == I: @@ -257,7 +271,7 @@ class CoercionPDtoKM(HyperbolicModelCoercion): """ Coercion from the PD to KM model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. @@ -281,7 +295,7 @@ class CoercionPDtoHM(HyperbolicModelCoercion): """ Coercion from the PD to HM model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. @@ -312,14 +326,17 @@ class CoercionKMtoUHP(HyperbolicModelCoercion): """ Coercion from the KM to UHP model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. EXAMPLES:: - sage: HyperbolicPlane.KM.point_to_model((0, 0), 'UHP') + sage: KM = HyperbolicPlane().KM() + sage: UHP = HyperbolicPlane().UHP() + sage: phi = UHP.coerce_map_from(KM) + sage: phi.image_coordinates((0, 0)) I """ if tuple(x) == (0, 1): @@ -341,7 +358,7 @@ class CoercionKMtoPD(HyperbolicModelCoercion): """ Coercion from the KM to PD model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. @@ -365,14 +382,17 @@ class CoercionKMtoHM(HyperbolicModelCoercion): """ Coercion from the KM to HM model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. EXAMPLES:: - sage: HyperbolicPlane.KM.point_to_model((0, 0), 'HM') + sage: KM = HyperbolicPlane().KM() + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(KM) + sage: phi.image_coordinates((0, 0)) (0, 0, 1) """ return (vector((2*x[0], 2*x[1], 1 + x[0]**2 + x[1]**2)) @@ -395,14 +415,17 @@ class CoercionHMtoUHP(HyperbolicModelCoercion): """ Coercion from the HM to UHP model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. EXAMPLES:: - sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'UHP') + sage: HM = HyperbolicPlane().HM() + sage: UHP = HyperbolicPlane().UHP() + sage: phi = UHP.coerce_map_from(HM) + sage: phi.image_coordinates( vector((0,0,1)) ) I """ return -((x[0]*x[2] + x[0]) + I*(x[2] + 1)) / ((x[1] - 1)*x[2] @@ -421,7 +444,7 @@ class CoercionHMtoPD(HyperbolicModelCoercion): """ Coercion from the HM to PD model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. @@ -444,14 +467,17 @@ class CoercionHMtoKM(HyperbolicModelCoercion): """ Coercion from the HM to KM model. """ - def image_point(self, x): + def image_coordinates(self, x): """ Return the image of the coordinates of the hyperbolic point ``x`` under ``self``. EXAMPLES:: - sage: HyperbolicPlane.HM.point_to_model(vector((0,0,1)), 'KM') + sage: HM = HyperbolicPlane().HM() + sage: KM = HyperbolicPlane().KM() + sage: phi = KM.coerce_map_from(HM) + sage: phi.image_coordinates( vector((0,0,1)) ) (0, 0) """ return (x[0]/(1 + x[2]), x[1]/(1 + x[2])) @@ -481,22 +507,24 @@ def SL2R_to_SO21(A): sage: from sage.geometry.hyperbolic_space.hyperbolic_coercion import SL2R_to_SO21 sage: A = SL2R_to_SO21(identity_matrix(2)) - sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix + sage: J = matrix([[1,0,0],[0,1,0],[0,0,-1]]) #Lorentzian Gram matrix sage: norm(A.transpose()*J*A - J) < 10**-4 True """ a,b,c,d = (A/A.det().sqrt()).list() - B = matrix(3, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, - Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - - Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, - Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - - Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, - a*b + c*d, Integer(1)/Integer(2)*a**2 - - Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - - Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + - Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + - Integer(1)/Integer(2)*d**2]) - B = B.apply_map(attrcall('real')) # Kill ~0 imaginary parts + B = matrix(3, map(real, + [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, + Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2, + Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 - + Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, + a*b + c*d, Integer(1)/Integer(2)*a**2 - + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - + Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + + Integer(1)/Integer(2)*d**2] + )) # Kill ~0 imaginary parts + #B = B.apply_map(attrcall('real')) if A.det() > 0: return B else: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index b65d997a397..4ccd1960f50 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -172,8 +172,8 @@ def _complete(self): True """ if self.model().is_bounded(): - return (self.model().bdry_point_in_model(self.start().coordinates()) - and self.model().bdry_point_in_model(self.end().coordinates())) + return (self.model().boundary_point_in_model(self.start().coordinates()) + and self.model().boundary_point_in_model(self.end().coordinates())) else: return False #All non-bounded geodesics start life incomplete. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index cf6e486dc2c..bb91c69720e 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -146,12 +146,23 @@ def super_categories(self): return [Sets(), Realizations(self.base())] class ParentMethods: - @lazy_attribute - def _computation_model(self): + def _an_element_(self): """ - Return the model in which to do computations by default. + Return an element of ``self``. + + EXAMPLES:: + + sage: H = HyperbolicPlane() + sage: H.UHP().an_element() + Point in UHP I + sage: H.PD().an_element() + Point in PD 0 + sage: H.KM().an_element() + Point in KM (0, 0) + sage: H.HM().an_element() + Point in HM (0, 0, 1) """ - return self.realization_of().UHP() + return self(self.realization_of().PD().get_point(0)) class ElementMethods: pass @@ -163,41 +174,6 @@ class HyperbolicUserInterface(UniqueRepresentation): interface for interacting with models of hyperbolic geometry without having the interface dictate the class structure. """ - @classmethod - def model_name(cls): - r""" - Return the full name of the hyperbolic model. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.model_name() - 'Upper Half Plane Model' - sage: HyperbolicPlane.PD.model_name() - 'Poincare Disk Model' - sage: HyperbolicPlane.KM.model_name() - 'Klein Disk Model' - sage: HyperbolicPlane.HM.model_name() - 'Hyperboloid Model' - """ - return cls.HModel.name - - @classmethod - def short_name(cls): - r""" - Return the short name of the hyperbolic model. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.short_name() - 'UHP' - sage: HyperbolicPlane.PD.short_name() - 'PD' - sage: HyperbolicPlane.HM.short_name() - 'HM' - sage: HyperbolicPlane.KM.short_name() - 'KM' - """ - return cls.HModel.short_name @classmethod def is_bounded(cls): @@ -257,19 +233,19 @@ def point_in_model(cls, p): return cls.HModel.point_in_model(p) @classmethod - def bdry_point_in_model(cls, p): + def boundary_point_in_model(cls, p): r""" Return ``True`` if ``p`` gives the coordinates of a point on the ideal boundary of hyperbolic space in the current model. EXAMPLES:: - sage: HyperbolicPlane.UHP.bdry_point_in_model(0) + sage: HyperbolicPlane.UHP.boundary_point_in_model(0) True - sage: HyperbolicPlane.UHP.bdry_point_in_model(I) # Not boundary point + sage: HyperbolicPlane.UHP.boundary_point_in_model(I) # Not boundary point False """ - return cls.HModel.bdry_point_in_model(p) + return cls.HModel.boundary_point_in_model(p) @classmethod def isometry_in_model(cls, A): @@ -426,32 +402,6 @@ def dist(cls, a, b): except(AttributeError): return cls.point(a).dist(cls.point(b)) - @classmethod - def point_to_model(cls, p, model): - r""" - Return the image of ``p`` in the model ``model``. - - INPUT: - - - ``p`` -- a point in the current model of hyperbolic space - either as coordinates or as a :class:`HyperbolicPoint` - - - ``model`` -- the name of an implemented model of hyperbolic - space of the same dimension - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') - 0 - sage: HyperbolicPlane.PD.point_to_model(I, 'UHP') - +Infinity - sage: HyperbolicPlane.UHP.point_to_model(HyperbolicPlane.UHP.point(I), 'HM') - (0, 0, 1) - """ - if isinstance(p, HyperbolicPoint): - p = p.coordinates() - return cls.HModel.point_to_model(p, model) - @classmethod def isometry_to_model(cls, A, model): r""" diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 77a57b4cdfa..c9bc75dbedd 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -51,9 +51,6 @@ from sage.rings.all import CC from sage.functions.other import real, imag -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', - ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) - class HyperbolicIsometry(Morphism): r""" Abstract base class for hyperbolic isometries. This class should @@ -70,7 +67,6 @@ class HyperbolicIsometry(Morphism): sage: A = HyperbolicIsometryUHP(identity_matrix(2)) sage: B = HyperbolicIsometryHM(identity_matrix(3)) """ - _HMethods = HyperbolicAbstractMethods ##################### # "Private" Methods # @@ -567,8 +563,7 @@ def fixed_geodesic(self, **graphics_options): """ fps = self._HMethods.fixed_point_set(self._cached_matrix) if len(fps) < 2: - raise ValueError("Isometries of type" - " {0}".format(self.classification()) + raise ValueError("isometries of type {0}".format(self.classification()) + " don't fix geodesics") from sage.geometry.hyperbolic_space.model_factory import ModelFactory fact = ModelFactory.find_factory(self._HMethods.model_name()) @@ -659,7 +654,6 @@ class HyperbolicIsometryUHP(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryUHP sage: A = HyperbolicIsometryUHP(identity_matrix(2)) """ - _HMethods = HyperbolicMethodsUHP class HyperbolicIsometryPD(HyperbolicIsometry): r""" @@ -674,7 +668,6 @@ class HyperbolicIsometryPD(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD sage: A = HyperbolicIsometryPD(identity_matrix(2)) """ - _HMethods = HyperbolicMethodsUHP class HyperbolicIsometryKM(HyperbolicIsometry): r""" @@ -689,7 +682,6 @@ class HyperbolicIsometryKM(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryKM sage: A = HyperbolicIsometryKM(identity_matrix(3)) """ - _HMethods = HyperbolicMethodsUHP class HyperbolicIsometryHM(HyperbolicIsometry): r""" @@ -704,5 +696,4 @@ class HyperbolicIsometryHM(HyperbolicIsometry): sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryHM sage: A = HyperbolicIsometryHM(identity_matrix(3)) """ - _HMethods = HyperbolicMethodsUHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py index 544c3237c14..635f3d2937b 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py @@ -65,10 +65,6 @@ from sage.functions.all import imag, real from sage.matrix.all import matrix from sage.rings.all import Integer, RR, RDF, infinity -from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON -from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModel -from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP -from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform class HyperbolicAbstractMethods(UniqueRepresentation): @@ -111,890 +107,3 @@ class HyperbolicMethodsUHP(HyperbolicAbstractMethods): """ HModel = HyperbolicModelUHP - ################# - # Point Methods # - ################# - - @classmethod - def point_dist(cls, p1, p2): - r""" - Compute the distance between two points in the Upper Half Plane - using the hyperbolic metric. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) - 1.38629436111989 - """ - cls.model().point_test(p1) - cls.model().point_test(p2) - num = (real(p2) - real(p1))**2 + (imag(p2) - imag(p1))**2 - denom = 2*imag(p1)*imag(p2) - if denom == 0: - return infinity - else: - return arccosh(1 + num/denom) - - @classmethod - def symmetry_in(cls, p): - r""" - Return the involutary isometry fixing the given point. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.symmetry_in(3 + 2*I) - [ 3/2 -13/2] - [ 1/2 -3/2] - """ - cls.model().point_test(p) - x, y = real(p), imag(p) - if y > 0: - return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) - - @classmethod - def random_point(cls, **kwargs): - r""" - Return a random point in the upper half - plane. The points are uniformly distributed over the rectangle - `[-10, 10] \times [0, 10i]`. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: p = HyperbolicMethodsUHP.random_point() - sage: bool((p.imag()) > 0) - True - """ - # TODO use **kwargs to allow these to be set - real_min = -10 - real_max = 10 - imag_min = 0 - imag_max = 10 - return RR.random_element(min = real_min ,max=real_max) +\ - I*RR.random_element(min = imag_min,max = imag_max) - - #################### - # Geodesic Methods # - #################### - - @classmethod - def boundary_points(cls, p1, p2): - r""" - Given two points ``p1`` and ``p2`` in the hyperbolic plane, - determine the endpoints of the complete hyperbolic geodesic - through ``p1`` and ``p2``. - - INPUT: - - - ``p1``, ``p2`` -- points in the hyperbolic plane - - OUTPUT: - - - a list of boundary points - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.boundary_points(I, 2*I) - [0, +Infinity] - sage: HyperbolicMethodsUHP.boundary_points(1 + I, 2 + 4*I) - [-sqrt(65) + 9, sqrt(65) + 9] - """ - if p1 == p2: - raise ValueError(str(p1) + " and " + str(p2) + - " are not distinct.") - [x1, x2] = [real(k) for k in [p1,p2]] - [y1,y2] = [imag(k) for k in [p1,p2]] - # infinity is the first endpoint, so the other ideal endpoint - # is just the real part of the second coordinate - if p1 == infinity: - return [p1, x2] - # Same idea as above - elif p2 == infinity: - return [x1, p2] - # We could also have a vertical line with two interior points - elif x1 == x2: - return [x1, infinity] - # Otherwise, we have a semicircular arc in the UHP - else: - c = ((x1+x2)*(x2-x1)+(y1+y2)*(y2-y1))/(2*(x2-x1)) - r = sqrt((c - x1)**2 + y1**2) - return [c-r, c + r] - - @classmethod - def reflection_in(cls, start, end): - r""" - Return the matrix of the involution fixing the geodesic through - ``start`` and ``end``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.reflection_in(0, 1) - [ 1 0] - [ 2 -1] - sage: HyperbolicMethodsUHP.reflection_in(I, 2*I) - [ 1 0] - [ 0 -1] - """ - x,y = [real(k) for k in cls.boundary_points(start, end)] - if x == infinity: - M = matrix(2,[1,-2*y,0,-1]) - elif y == infinity: - M = matrix(2,[1,-2*x,0,-1]) - else: - M = matrix(2,[(x+y)/(y-x),-2*x*y/(y-x),2/(y-x),-(x+y)/(y-x)]) - return M - - @classmethod - def common_perpendicular(cls, start_1, end_1, start_2, end_2): - r""" - Return the unique hyperbolic geodesic perpendicular to two given - geodesics, if such a geodesic exists. If none exists, raise a - ValueError. - - INPUT: - - - ``other`` -- a hyperbolic geodesic in current model - - OUTPUT: - - - a hyperbolic geodesic - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.common_perpendicular(2, 3, 4, 5) - [1/2*sqrt(3) + 7/2, -1/2*sqrt(3) + 7/2] - - It is an error to ask for the common perpendicular of two - intersecting geodesics:: - - sage: HyperbolicMethodsUHP.common_perpendicular(2, 4, 3, infinity) - Traceback (most recent call last): - ... - ValueError: geodesics intersect; no common perpendicular exists - """ - A = cls.reflection_in(start_1, end_1) - B = cls.reflection_in(start_2, end_2) - C = A*B - if cls.classification(C) != 'hyperbolic': - raise ValueError("geodesics intersect; " + - "no common perpendicular exists") - return cls.fixed_point_set(C) - - @classmethod - def intersection(cls, start_1, end_1, start_2, end_2): - r""" - Return the point of intersection of two complete geodesics - (if such a point exists). - - INPUT: - - - ``other`` -- a hyperbolic geodesic in the current model - - OUTPUT: - - - a hyperbolic point - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.intersection(3, 5, 4, 7) - [2/3*sqrt(-2) + 13/3] - - If the given geodesics do not intersect, the function raises an - error:: - - sage: HyperbolicMethodsUHP.intersection(4, 5, 5, 7) - Traceback (most recent call last): - ... - ValueError: geodesics don't intersect - - If the given geodesics are identical, return that - geodesic:: - - sage: HyperbolicMethodsUHP.intersection(4 + I, 18*I, 4 + I, 18*I) - [-1/8*sqrt(114985) - 307/8, 1/8*sqrt(114985) - 307/8] - """ - start_1, end_1 = sorted(cls.boundary_points(start_1, end_1)) - start_2, end_2 = sorted(cls.boundary_points(start_2, end_2)) - if start_1 == start_2 and end_1 == end_2: # Unoriented geods are same - return [start_1, end_1] - if start_1 == start_2: - return start_1 - elif end_1 == end_2: - return end_1 - A = cls.reflection_in(start_1, end_1) - B = cls.reflection_in(start_2, end_2) - C = A*B - if cls.classification(C) in ['hyperbolic', 'parabolic']: - raise ValueError("geodesics don't intersect") - return cls.fixed_point_set(C) - - @classmethod - def perpendicular_bisector(cls, start, end): - r""" - Return the perpendicular bisector of the hyperbolic geodesic - with endpoints start and end if that geodesic has finite length. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] - sage: g = [a, b] - sage: h = HyperbolicMethodsUHP.perpendicular_bisector(*g) - sage: bool(HyperbolicMethodsUHP.intersection(*(g + h))[0] - HyperbolicMethodsUHP.midpoint(*g) < 10**-9) - True - - Complete geodesics cannot be bisected:: - - sage: HyperbolicMethodsUHP.perpendicular_bisector(0, 1) - Traceback (most recent call last): - ... - ZeroDivisionError: input matrix must be nonsingular - """ - from sage.symbolic.constants import pi - from sage.functions.log import exp - start, end = sorted((start, end)) - d = cls.point_dist(start, end)/2 - end_1, end_2 = cls.boundary_points(start, end) - T = (matrix(2,[exp(d/2),0,0,exp(-d/2)])* - matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)])) - S= cls._to_std_geod(end_1, start, end_2) - H = S.inverse()*T*S - return [mobius_transform(H ,k) for k in [end_1, end_2]] - - @classmethod - def midpoint(cls, start, end): - r""" - Return the (hyperbolic) midpoint of a hyperbolic line segment. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] - sage: g = [a, b] - sage: m = HyperbolicMethodsUHP.midpoint(*g) - sage: d1 =HyperbolicMethodsUHP.point_dist(m, g[0]) - sage: d2 = HyperbolicMethodsUHP.point_dist(m, g[1]) - sage: bool(abs(d1 - d2) < 10**-9) - True - - Complete geodesics have no midpoint:: - - sage: HyperbolicMethodsUHP.midpoint(0, 2) - Traceback (most recent call last): - ... - ZeroDivisionError: input matrix must be nonsingular - """ - half_dist = cls.point_dist(start, end)/2 - end_1,end_2 = cls.boundary_points(start, end) - S = cls._to_std_geod(end_1, start , end_2) - T = matrix(2,[exp(half_dist), 0, 0, 1]) - M = S.inverse()*T*S - if ((real(start - end) < EPSILON) or - (abs(real(start - end)) < EPSILON and - imag(start - end) < EPSILON)): - end_p = start - else: - end_p = end - end_p = mobius_transform (M, end_p) - return end_p - - @classmethod - def geod_dist_from_point(cls, start, end, p): - r""" - Return the hyperbolic distance from a given hyperbolic geodesic - and a hyperbolic point. - - INPUT: - - - ``other`` -- a hyperbolic point in the same model - - OUTPUT: - - - the hyperbolic distance - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) - arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) - - If `p` is a boundary point, the distance is infinity:: - - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) - +Infinity - """ - # Here is the trick for computing distance to a geodesic: - # find an isometry mapping the geodesic to the geodesic between - # 0 and infinity (so corresponding to the line imag(z) = 0. - # then any complex number is r exp(i*theta) in polar coordinates. - # the mutual perpendicular between this point and imag(z) = 0 - # intersects imag(z) = 0 at ri. So we calculate the distance - # between r exp(i*theta) and ri after we transform the original - # point. - (bd_1, bd_2) = cls.boundary_points(start, end) - if bd_1 + bd_2 != infinity: - # Not a straight line - # Map the endpoints to 0 and infinity and the midpoint to 1. - T = cls._crossratio_matrix(bd_1, (bd_1 + bd_2)/2, bd_2) - else: - # Is a straight line - # Map the endpoints to 0 and infinity and another endpoint - # to 1 - T = cls._crossratio_matrix(bd_1, bd_1 + 1, bd_2) - x = mobius_transform(T, p) - return cls.point_dist(x, abs(x)*I) - - @classmethod - def uncomplete(cls, start, end): - r""" - Return starting and ending points of a geodesic whose completion - is the the geodesic starting at ``start`` and ending at ``end``. - - INPUT: - - - ``start`` -- a real number or infinity - - ``end`` -- a real number or infinity - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.uncomplete(0,1) - [3/10*I + 1/10, 3/10*I + 9/10] - """ - if start + end == infinity: - p = min(real(start), real(end)) + I - else: - center = (real(start) + real(end))/Integer(2) - radius = abs(real(end) - center) - p = center + radius*I - A = cls._to_std_geod(start, p, end).inverse() - p1 = mobius_transform(A, I/Integer(3)) - p2 = mobius_transform(A, 3*I) - return [p1, p2] - - @classmethod - def angle(cls, start_1, end_1, start_2, end_2): - r""" - Return the angle between any two given completed geodesics if - they intersect. - - INPUT: - - -``other`` -- a hyperbolic geodesic in the UHP model - - OUTPUT: - - - The angle in radians between the two given geodesics - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: numerical_approx(HyperbolicMethodsUHP.angle(2, 4, 3, 3 + I)) - 1.57079632679490 - - If the geodesics are identical, return angle 0:: - - sage: HyperbolicMethodsUHP.angle(2, 4, 2, 4) - 0 - """ - (p_1,p_2) = sorted(cls.boundary_points(start_1, end_1)) - (q_1,q_2) = sorted(cls.boundary_points(start_2, end_2)) - # if the geodesics are equal, the angle between them is 0 - if (abs(p_1 - q_1) < EPSILON \ - and abs(p_2 - q_2) < EPSILON): - return 0 - elif p_2 != infinity: # geodesic not a straight line - # So we send it to the geodesic with endpoints [0, oo] - T = cls._crossratio_matrix(p_1, (p_1+p_2)/2, p_2) - else: - # geodesic is a straight line, so we send it to the geodesic - # with endpoints [0,oo] - T = cls._crossratio_matrix(p_1, p_1 +1, p_2) - # b_1 and b_2 are the endpoints of the image of other - b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] - # If other is now a straight line... - if (b_1 == infinity or b_2 == infinity): - # then since they intersect, they are equal - return 0 - else: - return real(arccos((b_1+b_2)/abs(b_2-b_1))) - - #################### - # Isometry Methods # - #################### - - @classmethod - def orientation_preserving(cls, M): - r""" - Return ``True`` if ``self`` is orientation preserving and ``False`` - otherwise. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = identity_matrix(2) - sage: HyperbolicMethodsUHP.orientation_preserving(A) - True - sage: B = matrix(2,[0,1,1,0]) - sage: HyperbolicMethodsUHP.orientation_preserving(B) - False - """ - return bool(M.det() > 0) - - @classmethod - def classification(cls, M): - r""" - Classify the hyperbolic isometry as elliptic, parabolic, or - hyperbolic. - - A hyperbolic isometry fixes two points on the boundary of - hyperbolic space, a parabolic isometry fixes one point on the - boundary of hyperbolic space, and an elliptic isometry fixes - no points. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.classification(identity_matrix(2)) - 'identity' - - sage: HyperbolicMethodsUHP.classification(4*identity_matrix(2)) - 'identity' - - sage: HyperbolicMethodsUHP.classification(matrix(2,[2,0,0,1/2])) - 'hyperbolic' - - sage: HyperbolicMethodsUHP.classification(matrix(2, [0, 3, -1/3, 6])) - 'hyperbolic' - - sage: HyperbolicMethodsUHP.classification(matrix(2,[1,1,0,1])) - 'parabolic' - - sage: HyperbolicMethodsUHP.classification(matrix(2,[-1,0,0,1])) - 'reflection' - """ - A = M.n() - A = A/(abs(A.det()).sqrt()) - tau = abs(A.trace()) - a = A.list() - if A.det() > 0: - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < - EPSILON) - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < - EPSILON) - if tf: - return 'identity' - if tau - 2 < -EPSILON: - return 'elliptic' - elif tau -2 > -EPSILON and tau - 2 < EPSILON: - return 'parabolic' - elif tau - 2 > EPSILON: - return 'hyperbolic' - else: - raise ValueError("something went wrong with classification:" + - "trace is " + str(A.trace())) - else: #The isometry reverses orientation. - if tau < EPSILON: - return 'reflection' - else: - return 'orientation-reversing hyperbolic' - - @classmethod - def translation_length(cls, M): - r""" - For hyperbolic elements, return the translation length. - Otherwise, raise a ValueError. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.translation_length(matrix(2,[2,0,0,1/2])) - 2*arccosh(5/4) - - :: - - sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) - sage: p = exp(i*7*pi/8) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: Hp = mobius_transform(H, p) - sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) - True - """ - d = sqrt(M.det()**2) - tau = sqrt((M/sqrt(d)).trace()**2) - if cls.classification(M) in ['hyperbolic', - 'oriention-reversing hyperbolic']: - return 2*arccosh(tau/2) - raise TypeError("translation length is only defined for hyperbolic" - " transformations") - - @classmethod - def isometry_from_fixed_points(cls, repel, attract): - r""" - Given two fixed points ``repel`` and ``attract`` as complex - numbers return a hyperbolic isometry with ``repel`` as repelling - fixed point and ``attract`` as attracting fixed point. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2 + I, 3 + I) - Traceback (most recent call last): - ... - ValueError: fixed points of hyperbolic elements must be ideal - - sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2, 0) - [ -1 0] - [-1/3 -1/3] - """ - if imag(repel) + imag(attract) > EPSILON: - raise ValueError("fixed points of hyperbolic elements must be ideal") - repel = real(repel) - attract = real(attract) - if repel == infinity: - A = cls._mobius_sending([infinity, attract, attract + 1], - [infinity, attract, attract + 2]) - elif attract == infinity: - A = cls._mobius_sending([repel, infinity, repel + 1], - [repel, infinity, repel + 2]) - else: - A = cls._mobius_sending([repel, attract, infinity], - [repel, attract, max(repel, attract) + 1]) - return A - - - @classmethod - def fixed_point_set(cls, M): - r""" - Return the a list containing the fixed point set of - orientation-preserving isometries. - - OUTPUT: - - - a list of hyperbolic points - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) - sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: bool(mobius_transform(H, p1) == p1) - True - sage: bool(mobius_transform(H, p2) == p2) - True - - sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) - Traceback (most recent call last): - ... - ValueError: the identity transformation fixes the entire hyperbolic plane - """ - d = sqrt(M.det()**2) - M = M/sqrt(d) - tau = M.trace()**2 - M_cls = cls.classification(M) - if M_cls == 'identity': - raise ValueError("the identity transformation fixes the entire " - "hyperbolic plane") - if M_cls == 'parabolic': - if abs(M[1,0]) < EPSILON: - return [infinity] - else: - # boundary point - return [(M[0,0] - M[1,1])/(2*M[1,0])] - elif M_cls=='elliptic': - d = sqrt(tau - 4) - return [(M[0,0] - M[1,1] + sign(M[1,0])*d)/(2*M[1,0])] - elif M_cls == 'hyperbolic': - if M[1,0]!= 0: #if the isometry doesn't fix infinity - d = sqrt(tau - 4) - p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0]) - p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0]) - return [p_1, p_2] - else: #else, it fixes infinity. - p_1 = M[0,1]/(M[1,1]-M[0,0]) - p_2 = infinity - return [p_1, p_2] - else: - # raise NotImplementedError("fixed point set not implemented for" - # " isometries of type {0}".format(M_cls)) - try: - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] - except (IndexError): - M = M.change_ring(RDF) - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] - if p[1] == 0: - p = infinity - else: - p = p[0]/p[1] - if q[1] == 0: - q = infinity - else: - q = q[0]/q[1] - pts = [p, q] - return [k for k in pts if imag(k) >= 0] - - - @classmethod - def repelling_fixed_point(cls, M): - r""" - For a hyperbolic isometry, return the attracting fixed point; - otherwise raise a ``ValueError``. - - OUTPUT: - - - a hyperbolic point - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = matrix(2,[4,0,0,1/4]) - sage: HyperbolicMethodsUHP.repelling_fixed_point(A) - 0 - """ - if cls.classification(M) not in ['hyperbolic', - 'orientation-reversing hyperbolic']: - raise ValueError("repelling fixed point is defined only" + - "for hyperbolic isometries") - v = M.eigenmatrix_right()[1].column(1) - if v[1] == 0: - return infinity - return v[0]/v[1] - - @classmethod - def attracting_fixed_point(cls, M): - r""" - For a hyperbolic isometry, return the attracting fixed point; - otherwise raise a ``ValueError``. - - OUTPUT: - - - a hyperbolic point - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = matrix(2,[4,0,0,1/4]) - sage: HyperbolicMethodsUHP.attracting_fixed_point(A) - +Infinity - """ - if cls.classification(M) not in \ - ['hyperbolic', 'orientation-reversing hyperbolic']: - raise ValueError("Attracting fixed point is defined only" + - "for hyperbolic isometries.") - v = M.eigenmatrix_right()[1].column(0) - if v[1] == 0: - return infinity - return v[0]/v[1] - - @classmethod - def random_isometry(cls, preserve_orientation = True, **kwargs): - r""" - Return a random isometry in the Upper Half Plane model. - - INPUT: - - - ``preserve_orientation`` -- if ``True`` return an - orientation-preserving isometry. - - OUTPUT: - - - a hyperbolic isometry - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = HyperbolicMethodsUHP.random_isometry() - sage: B = HyperbolicMethodsUHP.random_isometry(preserve_orientation=False) - sage: B.det() < 0 - True - """ - [a,b,c,d] = [RR.random_element() for k in range(4)] - while abs(a*d - b*c) < EPSILON: - [a,b,c,d] = [RR.random_element() for k in range(4)] - M = matrix(RDF, 2,[a,b,c,d]) - M = M/(M.det()).abs().sqrt() - if M.det() > 0: - if preserve_orientation: - return M - else: - return M*matrix(2,[0,1,1,0]) - else: - if preserve_orientation: - return M*matrix(2,[0,1,1,0]) - else: - return M - - ################### - # Helping Methods # - ################### - - @classmethod - def _to_std_geod(cls, start, p, end): - r""" - Given the points of a geodesic in hyperbolic space, return the - hyperbolic isometry that sends that geodesic to the geodesic - through 0 and infinity that also sends the point ``p`` to ``I``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] - sage: A = HyperbolicMethodsUHP._to_std_geod(p_1, p_2, p_3) - sage: bool(abs(mobius_transform(A, p_1)) < 10**-9) - True - sage: bool(abs(mobius_transform(A, p_2) - I) < 10**-9) - True - sage: bool(mobius_transform(A, p_3) == infinity) - True - """ - B = matrix(2, [1, 0, 0, -I]) - return B*cls._crossratio_matrix(start, p, end) - - @classmethod - def _crossratio_matrix(cls, p_0, p_1, p_2): - r""" - Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine - coordinates, return the linear fractional transformation taking - the elements of `p` to `0`,`1`, and `\infty'. - - INPUT: - - - a list of three distinct elements of three distinct elements - of `\mathbb{CP}^1` in affine coordinates; that is, each element - must be a complex number, `\infty`, or symbolic. - - OUTPUT: - - - an element of `\GL(2,\CC)` - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] - sage: A = HyperbolicMethodsUHP._crossratio_matrix(p_1, p_2, p_3) - sage: bool(abs(mobius_transform(A, p_1) < 10**-9)) - True - sage: bool(abs(mobius_transform(A, p_2) - 1) < 10**-9) - True - sage: bool(mobius_transform(A, p_3) == infinity) - True - sage: (x,y,z) = var('x,y,z'); HyperbolicMethodsUHP._crossratio_matrix(x,y,z) - [ y - z -x*(y - z)] - [ -x + y (x - y)*z] - """ - if p_0 == infinity: - return matrix(2,[0,-(p_1 - p_2), -1, p_2]) - elif p_1 == infinity: - return matrix(2,[1, -p_0,1,-p_2]) - elif p_2 == infinity: - return matrix(2,[1,-p_0, 0, p_1 - p_0]) - else: - return matrix(2,[p_1 - p_2, (p_1 - p_2)*(-p_0), p_1 - p_0, - ( p_1 - p_0)*(-p_2)]) - - - @classmethod - def _mobius_sending(cls, list1, list2): - r""" - Given two lists ``list1``, ``list2``, of three points each in - `\mathbb{CP}^1`, return the linear fractional transformation - taking the points in ``list1`` to the points in ``list2``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) - True - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) - True - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) - True - """ - if len(list1) != 3 or len(list2) != 3: - raise TypeError("mobius_sending requires each list to be three" - "points long") - pl = list1 + list2 - z = pl[0:3] - w = pl[3:6] - A = cls._crossratio_matrix(z[0],z[1],z[2]) - B = cls._crossratio_matrix(w[0],w[1],w[2]) - return B.inverse() * A - -##################################################################### -## Helper functions - -def mobius_transform(A, z): - r""" - Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex - plane return the mobius transformation action of ``A`` on ``z``. - - INPUT: - - - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers - - ``z`` -- a complex number or infinity - - OUTPUT: - - - a complex number or infinity - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import mobius_transform - sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) - 2/109*I + 43/109 - sage: y = var('y') - sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) - x + I*y - - The matrix must be square and `2 \times 2`:: - - sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - - sage: mobius_transform(identity_matrix(3),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - - The matrix can be symbolic or can be a matrix over the real - or complex numbers, but must be invertible:: - - sage: (a,b,c,d) = var('a,b,c,d'); - sage: mobius_transform(matrix(2,[a,b,c,d]),I) - (I*a + b)/(I*c + d) - - sage: mobius_transform(matrix(2,[0,0,0,0]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - """ - if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: - (a,b,c,d) = A.list() - if z == infinity: - if c == 0: - return infinity - return a/c - if a*d - b*c < 0: - w = z.conjugate() # Reverses orientation - else: - w = z - if c*z + d == 0: - return infinity - else: - return (a*w + b)/(c*w + d) - else: - raise TypeError("A must be an invertible 2x2 matrix over the" - " complex numbers or a symbolic ring") - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 77970a4ca4c..a62c90f74c9 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -63,7 +63,7 @@ False sage: U.point_in_model(2) False - sage: U.bdry_point_in_model(2) + sage: U.boundary_point_in_model(2) True .. TODO:: @@ -85,8 +85,11 @@ from sage.structure.parent import Parent from sage.misc.bindable_class import BindableClass from sage.misc.lazy_import import lazy_import -from sage.functions.other import imag, real -from sage.rings.all import CC, RR +from sage.functions.other import imag, real, sqrt +from sage.symbolic.constants import pi +from sage.functions.log import exp +from sage.functions.all import cos, sin, arccosh, arccos, sign +from sage.rings.all import CC, RR, RDF from sage.rings.integer import Integer from sage.rings.infinity import infinity from sage.symbolic.pynac import I @@ -106,9 +109,7 @@ CoercionKMtoUHP, CoercionKMtoPD, CoercionKMtoHM, CoercionHMtoUHP, CoercionHMtoPD, CoercionHMtoKM) -lazy_import('sage.misc.misc', 'attrcall') lazy_import('sage.modules.free_module_element', 'vector') -lazy_import('sage.functions.other','sqrt') class HyperbolicModel(Parent, UniqueRepresentation, BindableClass): r""" @@ -249,11 +250,11 @@ def point_test(self, p): #Abstract ... ValueError: -I + 2 is not a valid point in the UHP model """ - if not (cls.point_in_model(p) or cls.bdry_point_in_model(p)): + if not (cls.point_in_model(p) or cls.boundary_point_in_model(p)): error_string = "{0} is not a valid point in the {1} model" raise ValueError(error_string.format(p, cls.short_name)) - def bdry_point_in_model(self, p): #Abstract + def boundary_point_in_model(self, p): #Abstract r""" Return ``True`` if the point is on the ideal boundary of hyperbolic space and ``False`` otherwise. @@ -268,7 +269,7 @@ def bdry_point_in_model(self, p): #Abstract EXAMPLES:: - sage: HyperbolicPlane.UHP.bdry_point_in_model(I) + sage: HyperbolicPlane.UHP.boundary_point_in_model(I) False """ return True @@ -287,7 +288,7 @@ def bdry_point_test(self, p): #Abstract ... ValueError: I + 1 is not a valid boundary point in the UHP model """ - if not self._bounded or not cls.bdry_point_in_model(p): + if not self._bounded or not cls.boundary_point_in_model(p): error_string = "{0} is not a valid boundary point in the {1} model" raise ValueError(error_string.format(p, self._short_name)) @@ -434,7 +435,7 @@ def get_point(self, coordinates, is_boundary=None, **graphics_options): #Abstrac return coordinates #both Point and BdryPoint if is_boundary is None: - is_boundary = self.bdry_point_in_model(coordinates) + is_boundary = self.boundary_point_in_model(coordinates) return self.element_class(self, coordinates, is_boundary, **graphics_options) def get_geodesic(self, start, end=None, **graphics_options): #Abstract @@ -525,7 +526,7 @@ def random_element(self, **kwargs): sage: bool((p[0]**2 + p[1]**2 - p[2]**2 - 1) < 10**-8) True """ - return self(self._computation_model.random_element(**kwargs)) + return self.random_point(**kwds) def random_point(self, **kwds): """ @@ -542,7 +543,8 @@ def random_point(self, **kwds): sage: PD.point_in_model(p.coordinates()) True """ - return self.random_element(**kwds) + UHP = self.realization_of().UHP() + return self(UHP.random_point(**kwargs)) def random_geodesic(self, **kwargs): r""" @@ -554,7 +556,8 @@ def random_geodesic(self, **kwargs): sage: bool((h.endpoints()[0].coordinates()).imag() >= 0) True """ - g_ends = [self._computation_model.random_point(**kwargs) for k in range(2)] + UHP = self.realization_of().UHP() + g_ends = [UHP.random_point(**kwargs) for k in range(2)] return self.get_geodesic(self(g_ends[0]), self(g_ends[1])) def random_isometry(self, preserve_orientation=True, **kwargs): @@ -579,7 +582,8 @@ def random_isometry(self, preserve_orientation=True, **kwargs): sage: B.orientation_preserving() False """ - A = self._computation_model.random_isometry(preserve_orientation, **kwargs) + UHP = self.realization_of().UHP() + A = UHP.random_isometry(preserve_orientation, **kwargs) return A.to_model(self) class HyperbolicModelUHP(HyperbolicModel): @@ -653,7 +657,7 @@ def point_in_model(self, p): #UHP """ return bool(imag(CC(p)) > 0) - def bdry_point_in_model(self, p): #UHP + def boundary_point_in_model(self, p): #UHP r""" Check whether a complex number is a real number or ``\infty``. In the ``UHP.model_name_name``, this is the ideal boundary of @@ -661,21 +665,21 @@ def bdry_point_in_model(self, p): #UHP EXAMPLES:: - sage: HyperbolicPlane.UHP.bdry_point_in_model(1 + I) + sage: HyperbolicPlane.UHP.boundary_point_in_model(1 + I) False - sage: HyperbolicPlane.UHP.bdry_point_in_model(infinity) + sage: HyperbolicPlane.UHP.boundary_point_in_model(infinity) True - sage: HyperbolicPlane.UHP.bdry_point_in_model(CC(infinity)) + sage: HyperbolicPlane.UHP.boundary_point_in_model(CC(infinity)) True - sage: HyperbolicPlane.UHP.bdry_point_in_model(RR(infinity)) + sage: HyperbolicPlane.UHP.boundary_point_in_model(RR(infinity)) True - sage: HyperbolicPlane.UHP.bdry_point_in_model(1) + sage: HyperbolicPlane.UHP.boundary_point_in_model(1) True - sage: HyperbolicPlane.UHP.bdry_point_in_model(12) + sage: HyperbolicPlane.UHP.boundary_point_in_model(12) True - sage: HyperbolicPlane.UHP.bdry_point_in_model(1 - I) + sage: HyperbolicPlane.UHP.boundary_point_in_model(1 - I) False - sage: HyperbolicPlane.UHP.bdry_point_in_model(-2*I) + sage: HyperbolicPlane.UHP.boundary_point_in_model(-2*I) False """ im = abs(imag(CC(p)).n()) @@ -750,7 +754,7 @@ def point_to_model(self, coordinates, model_name): #UHP NotImplementedError: boundary points are not implemented for the HM model """ p = coordinates - if (cls.bdry_point_in_model(p) and not + if (cls.boundary_point_in_model(p) and not ModelFactory.find_model(model_name).is_bounded()): raise NotImplementedError("boundary points are not implemented for" " the {0} model".format(model_name)) @@ -797,6 +801,796 @@ def get_background_graphic(self, **bdry_options): #UHP bd_max = bdry_options.get('bd_max', 5) return line(((bd_min, 0), (bd_max, 0)), color='black') + ################# + # Point Methods # + ################# + + def point_dist(self, p1, p2): + r""" + Compute the distance between two points in the Upper Half Plane + using the hyperbolic metric. + + EXAMPLES:: + + sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) + 1.38629436111989 + """ + cls.model().point_test(p1) + cls.model().point_test(p2) + num = (real(p2) - real(p1))**2 + (imag(p2) - imag(p1))**2 + denom = 2*imag(p1)*imag(p2) + if denom == 0: + return infinity + return arccosh(1 + num/denom) + + def symmetry_in(self, p): + r""" + Return the involutary isometry fixing the given point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.symmetry_in(3 + 2*I) + [ 3/2 -13/2] + [ 1/2 -3/2] + """ + self.point_test(p) + x, y = real(p), imag(p) + if y > 0: + return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) + + def random_point(self, **kwargs): + r""" + Return a random point in the upper half plane. The points are + uniformly distributed over the rectangle `[-10, 10] \times [0, 10i]`. + + EXAMPLES:: + + sage: p = HyperbolicPlane().UHP().random_point() + sage: bool((p.imag()) > 0) + True + """ + # TODO use **kwargs to allow these to be set + real_min = -10 + real_max = 10 + imag_min = 0 + imag_max = 10 + return RR.random_element(min = real_min ,max=real_max) +\ + I*RR.random_element(min = imag_min,max = imag_max) + + #################### + # Geodesic Methods # + #################### + + def boundary_points(self, p1, p2): + r""" + Given two points ``p1`` and ``p2`` in the hyperbolic plane, + determine the endpoints of the complete hyperbolic geodesic + through ``p1`` and ``p2``. + + INPUT: + + - ``p1``, ``p2`` -- points in the hyperbolic plane + + OUTPUT: + + - a list of boundary points + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.boundary_points(I, 2*I) + [0, +Infinity] + sage: HyperbolicMethodsUHP.boundary_points(1 + I, 2 + 4*I) + [-sqrt(65) + 9, sqrt(65) + 9] + """ + if p1 == p2: + raise ValueError("{} and {} are not distinct".format(p1, p2)) + [x1, x2] = [real(k) for k in [p1,p2]] + [y1,y2] = [imag(k) for k in [p1,p2]] + # infinity is the first endpoint, so the other ideal endpoint + # is just the real part of the second coordinate + if p1 == infinity: + return [p1, x2] + # Same idea as above + elif p2 == infinity: + return [x1, p2] + # We could also have a vertical line with two interior points + elif x1 == x2: + return [x1, infinity] + # Otherwise, we have a semicircular arc in the UHP + else: + c = ((x1+x2)*(x2-x1)+(y1+y2)*(y2-y1))/(2*(x2-x1)) + r = sqrt((c - x1)**2 + y1**2) + return [c-r, c + r] + + def reflection_in(self, start, end): + r""" + Return the matrix of the involution fixing the geodesic through + ``start`` and ``end``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.reflection_in(0, 1) + [ 1 0] + [ 2 -1] + sage: HyperbolicMethodsUHP.reflection_in(I, 2*I) + [ 1 0] + [ 0 -1] + """ + x,y = [real(k) for k in self.boundary_points(start, end)] + if x == infinity: + M = matrix(2,[1,-2*y,0,-1]) + elif y == infinity: + M = matrix(2,[1,-2*x,0,-1]) + else: + M = matrix(2,[(x+y)/(y-x),-2*x*y/(y-x),2/(y-x),-(x+y)/(y-x)]) + return M + + def common_perpendicular(self, start_1, end_1, start_2, end_2): + r""" + Return the unique hyperbolic geodesic perpendicular to two given + geodesics, if such a geodesic exists; otherwise raise a + ``ValueError``. + + INPUT: + + - ``other`` -- a hyperbolic geodesic in current model + + OUTPUT: + + - a hyperbolic geodesic + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.common_perpendicular(2, 3, 4, 5) + [1/2*sqrt(3) + 7/2, -1/2*sqrt(3) + 7/2] + + It is an error to ask for the common perpendicular of two + intersecting geodesics:: + + sage: HyperbolicMethodsUHP.common_perpendicular(2, 4, 3, infinity) + Traceback (most recent call last): + ... + ValueError: geodesics intersect; no common perpendicular exists + """ + A = self.reflection_in(start_1, end_1) + B = self.reflection_in(start_2, end_2) + C = A*B + if self.classification(C) != 'hyperbolic': + raise ValueError("geodesics intersect; " + + "no common perpendicular exists") + return self.fixed_point_set(C) + + def intersection(self, start_1, end_1, start_2, end_2): + r""" + Return the point of intersection of two complete geodesics + (if such a point exists). + + INPUT: + + - ``other`` -- a hyperbolic geodesic in the current model + + OUTPUT: + + - a hyperbolic point + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.intersection(3, 5, 4, 7) + [2/3*sqrt(-2) + 13/3] + + If the given geodesics do not intersect, the function raises an + error:: + + sage: HyperbolicMethodsUHP.intersection(4, 5, 5, 7) + Traceback (most recent call last): + ... + ValueError: geodesics don't intersect + + If the given geodesics are identical, return that + geodesic:: + + sage: HyperbolicMethodsUHP.intersection(4 + I, 18*I, 4 + I, 18*I) + [-1/8*sqrt(114985) - 307/8, 1/8*sqrt(114985) - 307/8] + """ + start_1, end_1 = sorted(self.boundary_points(start_1, end_1)) + start_2, end_2 = sorted(self.boundary_points(start_2, end_2)) + if start_1 == start_2 and end_1 == end_2: # Unoriented geods are same + return [start_1, end_1] + if start_1 == start_2: + return start_1 + elif end_1 == end_2: + return end_1 + A = self.reflection_in(start_1, end_1) + B = self.reflection_in(start_2, end_2) + C = A*B + if self.classification(C) in ['hyperbolic', 'parabolic']: + raise ValueError("geodesics don't intersect") + return self.fixed_point_set(C) + + def perpendicular_bisector(self, start, end): + r""" + Return the perpendicular bisector of the hyperbolic geodesic + with endpoints start and end if that geodesic has finite length. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] + sage: g = [a, b] + sage: h = HyperbolicMethodsUHP.perpendicular_bisector(*g) + sage: bool(HyperbolicMethodsUHP.intersection(*(g + h))[0] - HyperbolicMethodsUHP.midpoint(*g) < 10**-9) + True + + Complete geodesics cannot be bisected:: + + sage: HyperbolicMethodsUHP.perpendicular_bisector(0, 1) + Traceback (most recent call last): + ... + ZeroDivisionError: input matrix must be nonsingular + """ + start, end = sorted((start, end)) + d = self.point_dist(start, end)/2 + end_1, end_2 = self.boundary_points(start, end) + T = (matrix(2,[exp(d/2),0,0,exp(-d/2)])* + matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)])) + S= self._to_std_geod(end_1, start, end_2) + H = S.inverse()*T*S + return [mobius_transform(H ,k) for k in [end_1, end_2]] + + def midpoint(self, start, end): + r""" + Return the (hyperbolic) midpoint of a hyperbolic line segment. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] + sage: g = [a, b] + sage: m = HyperbolicMethodsUHP.midpoint(*g) + sage: d1 =HyperbolicMethodsUHP.point_dist(m, g[0]) + sage: d2 = HyperbolicMethodsUHP.point_dist(m, g[1]) + sage: bool(abs(d1 - d2) < 10**-9) + True + + Complete geodesics have no midpoint:: + + sage: HyperbolicMethodsUHP.midpoint(0, 2) + Traceback (most recent call last): + ... + ZeroDivisionError: input matrix must be nonsingular + """ + half_dist = self.point_dist(start, end)/2 + end_1,end_2 = self.boundary_points(start, end) + S = cls._to_std_geod(end_1, start , end_2) + T = matrix(2,[exp(half_dist), 0, 0, 1]) + M = S.inverse()*T*S + if ((real(start - end) < EPSILON) or + (abs(real(start - end)) < EPSILON and + imag(start - end) < EPSILON)): + end_p = start + else: + end_p = end + end_p = mobius_transform (M, end_p) + return end_p + + def geod_dist_from_point(self, start, end, p): + r""" + Return the hyperbolic distance from a given hyperbolic geodesic + and a hyperbolic point. + + INPUT: + + - ``other`` -- a hyperbolic point in the same model + + OUTPUT: + + - the hyperbolic distance + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) + arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) + + If `p` is a boundary point, the distance is infinity:: + + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) + +Infinity + """ + # Here is the trick for computing distance to a geodesic: + # find an isometry mapping the geodesic to the geodesic between + # 0 and infinity (so corresponding to the line imag(z) = 0. + # then any complex number is r exp(i*theta) in polar coordinates. + # the mutual perpendicular between this point and imag(z) = 0 + # intersects imag(z) = 0 at ri. So we calculate the distance + # between r exp(i*theta) and ri after we transform the original + # point. + (bd_1, bd_2) = self.boundary_points(start, end) + if bd_1 + bd_2 != infinity: + # Not a straight line + # Map the endpoints to 0 and infinity and the midpoint to 1. + T = self._crossratio_matrix(bd_1, (bd_1 + bd_2)/2, bd_2) + else: + # Is a straight line + # Map the endpoints to 0 and infinity and another endpoint + # to 1 + T = self._crossratio_matrix(bd_1, bd_1 + 1, bd_2) + x = mobius_transform(T, p) + return self.point_dist(x, abs(x)*I) + + def uncomplete(self, start, end): + r""" + Return starting and ending points of a geodesic whose completion + is the geodesic starting at ``start`` and ending at ``end``. + + INPUT: + + - ``start`` -- a real number or infinity + - ``end`` -- a real number or infinity + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.uncomplete(0,1) + [3/10*I + 1/10, 3/10*I + 9/10] + """ + if start + end == infinity: + p = min(real(start), real(end)) + I + else: + center = (real(start) + real(end))/Integer(2) + radius = abs(real(end) - center) + p = center + radius*I + A = self._to_std_geod(start, p, end).inverse() + p1 = mobius_transform(A, I/Integer(3)) + p2 = mobius_transform(A, 3*I) + return [p1, p2] + + def angle(self, start_1, end_1, start_2, end_2): + r""" + Return the angle between any two given completed geodesics if + they intersect. + + INPUT: + + -``other`` -- a hyperbolic geodesic in the UHP model + + OUTPUT: + + - the angle in radians between the two given geodesics + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: numerical_approx(HyperbolicMethodsUHP.angle(2, 4, 3, 3 + I)) + 1.57079632679490 + + If the geodesics are identical, return angle 0:: + + sage: HyperbolicMethodsUHP.angle(2, 4, 2, 4) + 0 + """ + (p_1,p_2) = sorted(self.boundary_points(start_1, end_1)) + (q_1,q_2) = sorted(self.boundary_points(start_2, end_2)) + # if the geodesics are equal, the angle between them is 0 + if (abs(p_1 - q_1) < EPSILON \ + and abs(p_2 - q_2) < EPSILON): + return 0 + elif p_2 != infinity: # geodesic not a straight line + # So we send it to the geodesic with endpoints [0, oo] + T = self._crossratio_matrix(p_1, (p_1+p_2)/2, p_2) + else: + # geodesic is a straight line, so we send it to the geodesic + # with endpoints [0,oo] + T = self._crossratio_matrix(p_1, p_1 +1, p_2) + # b_1 and b_2 are the endpoints of the image of other + b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] + # If other is now a straight line... + if (b_1 == infinity or b_2 == infinity): + # then since they intersect, they are equal + return 0 + else: + return real(arccos((b_1+b_2)/abs(b_2-b_1))) + + #################### + # Isometry Methods # + #################### + + # Move to HyperbolicIsometryUHP + def orientation_preserving(self, M): + r""" + Return ``True`` if ``self`` is orientation preserving and ``False`` + otherwise. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = identity_matrix(2) + sage: HyperbolicMethodsUHP.orientation_preserving(A) + True + sage: B = matrix(2,[0,1,1,0]) + sage: HyperbolicMethodsUHP.orientation_preserving(B) + False + """ + return bool(M.det() > 0) + + # Move to HyperbolicIsometryUHP + def classification(self, M): + r""" + Classify the hyperbolic isometry as elliptic, parabolic, or + hyperbolic. + + A hyperbolic isometry fixes two points on the boundary of + hyperbolic space, a parabolic isometry fixes one point on the + boundary of hyperbolic space, and an elliptic isometry fixes + no points. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.classification(identity_matrix(2)) + 'identity' + + sage: HyperbolicMethodsUHP.classification(4*identity_matrix(2)) + 'identity' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[2,0,0,1/2])) + 'hyperbolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2, [0, 3, -1/3, 6])) + 'hyperbolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[1,1,0,1])) + 'parabolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[-1,0,0,1])) + 'reflection' + """ + A = M.n() + A = A / (abs(A.det()).sqrt()) + tau = abs(A.trace()) + a = A.list() + if A.det() > 0: + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < + EPSILON) + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < + EPSILON) + if tf: + return 'identity' + if tau - 2 < -EPSILON: + return 'elliptic' + elif tau -2 > -EPSILON and tau - 2 < EPSILON: + return 'parabolic' + elif tau - 2 > EPSILON: + return 'hyperbolic' + else: + raise ValueError("something went wrong with classification:" + + " trace is {}".format(A.trace())) + else: #The isometry reverses orientation. + if tau < EPSILON: + return 'reflection' + else: + return 'orientation-reversing hyperbolic' + + # Move to HyperbolicIsometryUHP + def translation_length(self, M): + r""" + For hyperbolic elements, return the translation length; + otherwise, raise a ``ValueError``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.translation_length(matrix(2,[2,0,0,1/2])) + 2*arccosh(5/4) + + :: + + sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) + sage: p = exp(i*7*pi/8) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: Hp = mobius_transform(H, p) + sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) + True + """ + d = sqrt(M.det()**2) + tau = sqrt((M / sqrt(d)).trace()**2) + if self.classification(M) in ['hyperbolic', 'oriention-reversing hyperbolic']: + return 2*arccosh(tau/2) + raise TypeError("translation length is only defined for hyperbolic" + " transformations") + + # Move to HyperbolicIsometryUHP + def fixed_point_set(self, M): + r""" + Return the a list containing the fixed point set of + orientation-preserving isometries. + + OUTPUT: + + - a list of hyperbolic points + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) + sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(mobius_transform(H, p1) == p1) + True + sage: bool(mobius_transform(H, p2) == p2) + True + + sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) + Traceback (most recent call last): + ... + ValueError: the identity transformation fixes the entire hyperbolic plane + """ + d = sqrt(M.det()**2) + M = M / sqrt(d) + tau = M.trace()**2 + M_cls = self.classification(M) + if M_cls == 'identity': + raise ValueError("the identity transformation fixes the entire " + "hyperbolic plane") + if M_cls == 'parabolic': + if abs(M[1,0]) < EPSILON: + return [infinity] + else: + # boundary point + return [(M[0,0] - M[1,1])/(2*M[1,0])] + elif M_cls=='elliptic': + d = sqrt(tau - 4) + return [(M[0,0] - M[1,1] + sign(M[1,0])*d)/(2*M[1,0])] + elif M_cls == 'hyperbolic': + if M[1,0]!= 0: #if the isometry doesn't fix infinity + d = sqrt(tau - 4) + p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0]) + p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0]) + return [p_1, p_2] + else: #else, it fixes infinity. + p_1 = M[0,1]/(M[1,1]-M[0,0]) + p_2 = infinity + return [p_1, p_2] + else: + # raise NotImplementedError("fixed point set not implemented for" + # " isometries of type {0}".format(M_cls)) + try: + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + except (IndexError): + M = M.change_ring(RDF) + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + if p[1] == 0: + p = infinity + else: + p = p[0]/p[1] + if q[1] == 0: + q = infinity + else: + q = q[0]/q[1] + pts = [p, q] + return [k for k in pts if imag(k) >= 0] + + # Move to HyperbolicIsometryUHP + def repelling_fixed_point(self, M): + r""" + For a hyperbolic isometry, return the repelling fixed point; + otherwise raise a ``ValueError``. + + OUTPUT: + + - a hyperbolic point + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = matrix(2,[4,0,0,1/4]) + sage: HyperbolicMethodsUHP.repelling_fixed_point(A) + 0 + """ + if self.classification(M) not in ['hyperbolic', + 'orientation-reversing hyperbolic']: + raise ValueError("repelling fixed point is defined only" + + "for hyperbolic isometries") + v = M.eigenmatrix_right()[1].column(1) + if v[1] == 0: + return infinity + return v[0]/v[1] + + # Move to HyperbolicIsometryUHP + def attracting_fixed_point(self, M): + r""" + For a hyperbolic isometry, return the attracting fixed point; + otherwise raise a ``ValueError``. + + OUTPUT: + + - a hyperbolic point + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = matrix(2,[4,0,0,1/4]) + sage: HyperbolicMethodsUHP.attracting_fixed_point(A) + +Infinity + """ + if self.classification(M) not in \ + ['hyperbolic', 'orientation-reversing hyperbolic']: + raise ValueError("Attracting fixed point is defined only" + + "for hyperbolic isometries.") + v = M.eigenmatrix_right()[1].column(0) + if v[1] == 0: + return infinity + return v[0]/v[1] + + def isometry_from_fixed_points(self, repel, attract): + r""" + Given two fixed points ``repel`` and ``attract`` as complex + numbers return a hyperbolic isometry with ``repel`` as repelling + fixed point and ``attract`` as attracting fixed point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2 + I, 3 + I) + Traceback (most recent call last): + ... + ValueError: fixed points of hyperbolic elements must be ideal + + sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2, 0) + [ -1 0] + [-1/3 -1/3] + """ + if imag(repel) + imag(attract) > EPSILON: + raise ValueError("fixed points of hyperbolic elements must be ideal") + repel = real(repel) + attract = real(attract) + if repel == infinity: + A = self._mobius_sending([infinity, attract, attract + 1], + [infinity, attract, attract + 2]) + elif attract == infinity: + A = self._mobius_sending([repel, infinity, repel + 1], + [repel, infinity, repel + 2]) + else: + A = self._mobius_sending([repel, attract, infinity], + [repel, attract, max(repel, attract) + 1]) + return A + + def random_isometry(self, preserve_orientation=True, **kwargs): + r""" + Return a random isometry in the Upper Half Plane model. + + INPUT: + + - ``preserve_orientation`` -- if ``True`` return an + orientation-preserving isometry + + OUTPUT: + + - a hyperbolic isometry + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = HyperbolicMethodsUHP.random_isometry() + sage: B = HyperbolicMethodsUHP.random_isometry(preserve_orientation=False) + sage: B.det() < 0 + True + """ + [a,b,c,d] = [RR.random_element() for k in range(4)] + while abs(a*d - b*c) < EPSILON: + [a,b,c,d] = [RR.random_element() for k in range(4)] + M = matrix(RDF, 2,[a,b,c,d]) + M = M / (M.det()).abs().sqrt() + if M.det() > 0: + if preserve_orientation: + return M + else: + return M*matrix(2,[0,1,1,0]) + else: + if preserve_orientation: + return M*matrix(2,[0,1,1,0]) + else: + return M + + ################### + # Helping Methods # + ################### + + def _to_std_geod(self, start, p, end): + r""" + Given the points of a geodesic in hyperbolic space, return the + hyperbolic isometry that sends that geodesic to the geodesic + through 0 and infinity that also sends the point ``p`` to ``I``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] + sage: A = HyperbolicMethodsUHP._to_std_geod(p_1, p_2, p_3) + sage: bool(abs(mobius_transform(A, p_1)) < 10**-9) + True + sage: bool(abs(mobius_transform(A, p_2) - I) < 10**-9) + True + sage: bool(mobius_transform(A, p_3) == infinity) + True + """ + B = matrix(2, [1, 0, 0, -I]) + return B * self._crossratio_matrix(start, p, end) + + def _crossratio_matrix(self, p_0, p_1, p_2): + r""" + Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine + coordinates, return the linear fractional transformation taking + the elements of `p` to `0`,`1`, and `\infty'. + + INPUT: + + - a list of three distinct elements of three distinct elements + of `\mathbb{CP}^1` in affine coordinates; that is, each element + must be a complex number, `\infty`, or symbolic. + + OUTPUT: + + - an element of `\GL(2,\CC)` + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] + sage: A = HyperbolicMethodsUHP._crossratio_matrix(p_1, p_2, p_3) + sage: bool(abs(mobius_transform(A, p_1) < 10**-9)) + True + sage: bool(abs(mobius_transform(A, p_2) - 1) < 10**-9) + True + sage: bool(mobius_transform(A, p_3) == infinity) + True + sage: (x,y,z) = var('x,y,z'); HyperbolicMethodsUHP._crossratio_matrix(x,y,z) + [ y - z -x*(y - z)] + [ -x + y (x - y)*z] + """ + if p_0 == infinity: + return matrix(2,[0,-(p_1 - p_2), -1, p_2]) + elif p_1 == infinity: + return matrix(2,[1, -p_0,1,-p_2]) + elif p_2 == infinity: + return matrix(2,[1,-p_0, 0, p_1 - p_0]) + else: + return matrix(2,[p_1 - p_2, (p_1 - p_2)*(-p_0), p_1 - p_0, + ( p_1 - p_0)*(-p_2)]) + + def _mobius_sending(self, list1, list2): + r""" + Given two lists ``list1``, ``list2``, of three points each in + `\mathbb{CP}^1`, return the linear fractional transformation + taking the points in ``list1`` to the points in ``list2``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) + True + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) + True + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) + True + """ + if len(list1) != 3 or len(list2) != 3: + raise TypeError("mobius_sending requires each list to be three points long") + pl = list1 + list2 + z = pl[0:3] + w = pl[3:6] + A = self._crossratio_matrix(z[0],z[1],z[2]) + B = self._crossratio_matrix(w[0],w[1],w[2]) + return B.inverse() * A + class HyperbolicModelPD(HyperbolicModel): r""" Poincaré Disk Model. @@ -860,19 +1654,19 @@ def point_in_model(self, p): #PD """ return bool(abs(CC(p)) < 1) - def bdry_point_in_model(self, p): #PD + def boundary_point_in_model(self, p): #PD r""" Check whether a complex number lies in the open unit disk. EXAMPLES:: - sage: HyperbolicPlane.PD.bdry_point_in_model(1.00) + sage: HyperbolicPlane.PD.boundary_point_in_model(1.00) True - sage: HyperbolicPlane.PD.bdry_point_in_model(1/2 + I/2) + sage: HyperbolicPlane.PD.boundary_point_in_model(1/2 + I/2) False - sage: HyperbolicPlane.PD.bdry_point_in_model(1 + .2*I) + sage: HyperbolicPlane.PD.boundary_point_in_model(1 + .2*I) False """ return bool(abs(abs(CC(p))- 1) < EPSILON) @@ -1003,7 +1797,7 @@ def __init__(self, space): sage: TestSuite(KM).run() """ HyperbolicModel.__init__(self, space, - name="Klein Disk Model", short_name="PD", + name="Klein Disk Model", short_name="KM", bounded=True, conformal=False, dimension=2, isometry_group="PSO(2, 1)", isometry_group_is_projective=True) @@ -1048,20 +1842,20 @@ def point_in_model(self, p): #KM """ return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) - def bdry_point_in_model(self, p): #KM + def boundary_point_in_model(self, p): #KM r""" Check whether a point lies in the unit circle, which corresponds to the ideal boundary of the hyperbolic plane in the Klein model. EXAMPLES:: - sage: HyperbolicPlane.KM.bdry_point_in_model((1,0)) + sage: HyperbolicPlane.KM.boundary_point_in_model((1,0)) True - sage: HyperbolicPlane.KM.bdry_point_in_model((1/2 , 1/2)) + sage: HyperbolicPlane.KM.boundary_point_in_model((1/2 , 1/2)) False - sage: HyperbolicPlane.KM.bdry_point_in_model((1 , .2)) + sage: HyperbolicPlane.KM.boundary_point_in_model((1 , .2)) False """ return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) @@ -1185,7 +1979,7 @@ def _coerce_map_from_(self, X): return CoercionUHPtoHM(Hom(X, self)) if isinstance(X, HyperbolicModelPD): return CoercionPDtoHM(Hom(X, self)) - if isinstance(X, HyperbolicModelHM): + if isinstance(X, HyperbolicModelKM): return CoercionKMtoHM(Hom(X, self)) return super(HyperbolicModelHM, self)._coerce_map_from_(X) @@ -1206,19 +2000,19 @@ def point_in_model(self, p): #HM """ return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 + 1 < EPSILON) - def bdry_point_in_model(self, p): #HM + def boundary_point_in_model(self, p): #HM r""" Return ``False`` since the Hyperboloid model has no boundary points. EXAMPLES:: - sage: HyperbolicPlane.HM.bdry_point_in_model((0,0,1)) + sage: HyperbolicPlane.HM.boundary_point_in_model((0,0,1)) False - sage: HyperbolicPlane.HM.bdry_point_in_model((1,0,sqrt(2))) + sage: HyperbolicPlane.HM.boundary_point_in_model((1,0,sqrt(2))) False - sage: HyperbolicPlane.HM.bdry_point_in_model((1,2,1)) + sage: HyperbolicPlane.HM.boundary_point_in_model((1,2,1)) False """ return False @@ -1276,4 +2070,68 @@ def PD_preserve_orientation(A): return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() and abs(A[0][0]) - abs(A[0][1]) != 0) +def mobius_transform(A, z): + r""" + Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex + plane return the mobius transformation action of ``A`` on ``z``. + + INPUT: + + - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers + - ``z`` -- a complex number or infinity + + OUTPUT: + + - a complex number or infinity + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import mobius_transform + sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) + 2/109*I + 43/109 + sage: y = var('y') + sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) + x + I*y + + The matrix must be square and `2 \times 2`:: + + sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + sage: mobius_transform(identity_matrix(3),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + The matrix can be symbolic or can be a matrix over the real + or complex numbers, but must be invertible:: + + sage: (a,b,c,d) = var('a,b,c,d'); + sage: mobius_transform(matrix(2,[a,b,c,d]),I) + (I*a + b)/(I*c + d) + + sage: mobius_transform(matrix(2,[0,0,0,0]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + """ + if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: + (a,b,c,d) = A.list() + if z == infinity: + if c == 0: + return infinity + return a/c + if a*d - b*c < 0: + w = z.conjugate() # Reverses orientation + else: + w = z + if c*z + d == 0: + return infinity + else: + return (a*w + b)/(c*w + d) + else: + raise TypeError("A must be an invertible 2x2 matrix over the" + " complex numbers or a symbolic ring") diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 2918408450f..89e6795f789 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -51,12 +51,13 @@ from sage.structure.element import Element from sage.symbolic.pynac import I -from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometry +from sage.rings.infinity import infinity +from sage.rings.all import RR, CC -lazy_import('sage.rings.all', ['RR', 'CC']) +from sage.misc.lazy_import import lazy_import lazy_import('sage.functions.other', 'real') lazy_import('sage.modules.free_module_element', 'vector') @@ -84,11 +85,10 @@ class HyperbolicPoint(Element): Note that the coordinate representation does not differentiate the different models:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = HyperbolicPointUHP(.2 + .3*I); p + sage: p = HyperbolicPlane().UHP().get_point(.2 + .3*I); p Point in UHP 0.200000000000000 + 0.300000000000000*I - sage: q = HyperbolicPointPD(0.2 + 0.3*I); q + sage: q = HyperbolicPlane().PD().get_point(0.2 + 0.3*I); q Point in PD 0.200000000000000 + 0.300000000000000*I sage: p == q @@ -99,11 +99,10 @@ class HyperbolicPoint(Element): Similarly for boundary points:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: p = HyperbolicBdryPointUHP(1); p + sage: p = HyperbolicPlane().UHP().get_point(1); p Boundary point in UHP 1 - sage: q = HyperbolicBdryPointPD(1); q + sage: q = HyperbolicPlane().PD().get_point(1); q Boundary point in PD 1 sage: p == q @@ -115,23 +114,22 @@ class HyperbolicPoint(Element): It is an error to specify a point that does not lie in the appropriate model:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(0.2 - 0.3*I) + sage: HyperbolicPlane().UHP().get_point(0.2 - 0.3*I) Traceback (most recent call last): ... ValueError: 0.200000000000000 - 0.300000000000000*I is not a valid point in the UHP model - sage: HyperbolicPointPD(1.2) + sage: HyperbolicPlane().PD().get_point(1.2) Traceback (most recent call last): ... ValueError: 1.20000000000000 is not a valid point in the PD model - sage: HyperbolicPointKM((1,1)) + sage: HyperbolicPlane().KM().get_point((1,1)) Traceback (most recent call last): ... ValueError: (1, 1) is not a valid point in the KM model - sage: HyperbolicPointHM((1, 1, 1)) + sage: HyperbolicPlane().HM().get_point((1, 1, 1)) Traceback (most recent call last): ... ValueError: (1, 1, 1) is not a valid point in the HM model @@ -139,7 +137,7 @@ class HyperbolicPoint(Element): It is an error to specify an interior point of hyperbolic space as a boundary point:: - sage: HyperbolicBdryPointUHP(0.2 + 0.3*I) + sage: HyperbolicPlane().UHP().get_point(0.2 + 0.3*I, boundary=True) Traceback (most recent call last): ... ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model @@ -150,15 +148,13 @@ def __init__(self, model, coordinates, is_boundary, check=True, **graphics_optio EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = HyperbolicPointUHP(I) - sage: p - Point in UHP I + sage: p = HyperbolicPlane().UHP().get_point(I) + sage: TestSuite(p).run() """ if is_boundary: if not model.is_bounded(): raise NotImplementedError("boundary points are not implemented in the {0} model".format(model.short_name())) - if check and not model.bdry_point_in_model(coordinates): + if check and not model.boundary_point_in_model(coordinates): raise ValueError( "{0} is not a valid".format(coordinates) + " boundary point in the {0} model".format(model.short_name())) @@ -189,8 +185,7 @@ def _cached_coordinates(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: A = HyperbolicPointHM((0, 0, 1)) + sage: A = HyperbolicPlane().HM().get_point((0, 0, 1)) sage: A._cached_coordinates I """ @@ -317,17 +312,16 @@ def coordinates(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(2 + I).coordinates() + sage: HyperbolicPlane().UHP().get_point(2 + I).coordinates() I + 2 - sage: HyperbolicPointPD(1/2 + 1/2*I).coordinates() + sage: HyperbolicPlane().PD().get_point(1/2 + 1/2*I).coordinates() 1/2*I + 1/2 - sage: HyperbolicPointKM((1/3, 1/4)).coordinates() + sage: HyperbolicPlane().KM().get_point((1/3, 1/4)).coordinates() (1/3, 1/4) - sage: HyperbolicPointHM((0,0,1)).coordinates() + sage: HyperbolicPlane().HM().get_point((0,0,1)).coordinates() (0, 0, 1) """ return self._coordinates @@ -339,41 +333,20 @@ def model(self): EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(I).model() + sage: HyperbolicPlane().UHP().get_point(I).model() - sage: HyperbolicPointPD(0).model() + sage: HyperbolicPlane().PD().get_point(0).model() - sage: HyperbolicPointKM((0,0)).model() + sage: HyperbolicPlane().KM().get_point((0,0)).model() - sage: HyperbolicPointHM((0,0,1)).model() + sage: HyperbolicPlane().HM().get_point((0,0,1)).model() """ return self.parent() - def model_name(self): - r""" - Return the short name of the hyperbolic model. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(I).model_name() - 'UHP' - - sage: HyperbolicPointPD(0).model_name() - 'PD' - - sage: HyperbolicPointKM((0,0)).model_name() - 'KM' - - sage: HyperbolicPointHM((0,0,1)).model_name() - 'HM' - """ - return self.parent().short_name - def is_boundary(self): """ Return ``True`` if ``self`` is a boundary point. @@ -446,33 +419,33 @@ def dist(self, other): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p1 = HyperbolicPointUHP(5 + 7*I) - sage: p2 = HyperbolicPointUHP(1.0 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) sage: p1.dist(p2) 2.23230104635820 - sage: p1 = HyperbolicPointPD(0) - sage: p2 = HyperbolicPointPD(I/2) + sage: p1 = HyperbolicPlane().PD().get_point(0) + sage: p2 = HyperbolicPlane().PD().get_point(I/2) sage: p1.dist(p2) arccosh(5/3) - sage: p1.to_model('UHP').dist(p2.to_model('UHP')) + sage: UHP(p1).dist(UHP(p2)) arccosh(5/3) - sage: p1 = HyperbolicPointKM((0, 0)) - sage: p2 = HyperbolicPointKM((1/2, 1/2)) + sage: p1 = HyperbolicPlane().KM().get_point((0, 0)) + sage: p2 = HyperbolicPlane().KM().get_point((1/2, 1/2)) sage: numerical_approx(p1.dist(p2)) 0.881373587019543 - sage: p1 = HyperbolicPointHM((0,0,1)) - sage: p2 = HyperbolicPointHM((1,0,sqrt(2))) + sage: p1 = HyperbolicPlane().HM().get_point((0,0,1)) + sage: p2 = HyperbolicPlane().HM().get_point((1,0,sqrt(2))) sage: numerical_approx(p1.dist(p2)) 0.881373587019543 Distance between a point and itself is 0:: - sage: p = HyperbolicPointUHP(47 + I) + sage: p = HyperbolicPlane().UHP().get_point(47 + I) sage: p.dist(p) 0 """ @@ -490,36 +463,35 @@ def symmetry_in(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: z = HyperbolicPointUHP(3 + 2*I) + sage: z = HyperbolicPlane().UHP().get_point(3 + 2*I) sage: z.symmetry_in() Isometry in UHP [ 3/2 -13/2] [ 1/2 -3/2] - sage: HyperbolicPointUHP(I).symmetry_in() + sage: HyperbolicPlane().UHP().get_point(I).symmetry_in() Isometry in UHP [ 0 -1] [ 1 0] - sage: HyperbolicPointPD(0).symmetry_in() + sage: HyperbolicPlane().PD().get_point(0).symmetry_in() Isometry in PD [-I 0] [ 0 I] - sage: HyperbolicPointKM((0, 0)).symmetry_in() + sage: HyperbolicPlane().KM().get_point((0, 0)).symmetry_in() Isometry in KM [-1 0 0] [ 0 -1 0] [ 0 0 1] - sage: HyperbolicPointHM((0,0,1)).symmetry_in() + sage: HyperbolicPlane().HM().get_point((0,0,1)).symmetry_in() Isometry in HM [-1 0 0] [ 0 -1 0] [ 0 0 1] - sage: p = HyperbolicPointUHP.random_element() + sage: p = HyperbolicPlane().UHP().random_element() sage: A = p.symmetry_in() sage: A*p == p True @@ -527,7 +499,7 @@ def symmetry_in(self): sage: A.orientation_preserving() True - sage: A*A == HyperbolicPlane.UHP.isometry(identity_matrix(2)) + sage: A*A == HyperbolicPlane().UHP().isometry(identity_matrix(2)) True """ A = self._HMethods.symmetry_in(self._cached_coordinates) @@ -542,18 +514,9 @@ def show(self, boundary=True, **options): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(I).show() - sage: HyperbolicPointPD(0).show() - sage: HyperbolicPointKM((0,0)).show() - sage: HyperbolicPointHM((0,0,1)).show() - - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import * - sage: HyperbolicBdryPointUHP(0).show() - sage: HyperbolicBdryPointUHP(infinity).show() - Traceback (most recent call last): - ... - NotImplementedError: can't draw the point infinity + sage: HyperbolicPlane().PD().get_point(0).show() + sage: HyperbolicPlane().KM().get_point((0,0)).show() + sage: HyperbolicPlane().HM().get_point((0,0,1)).show() """ p = self.coordinates() if p == infinity: @@ -597,27 +560,27 @@ class HyperbolicPointUHP(HyperbolicPoint): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointUHP - sage: HyperbolicPointUHP(2*I) + sage: HyperbolicPlane().UHP().get_point(2*I) Point in UHP 2*I - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointUHP - sage: q = HyperbolicBdryPointUHP(1); q + sage: HyperbolicPlane().UHP().get_point(1) Boundary point in UHP 1 """ - _HMethods = HyperbolicMethodsUHP def show(self, boundary=True, **options): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: HyperbolicPointUHP(I).show() - sage: HyperbolicPointPD(0).show() - sage: HyperbolicPointKM((0,0)).show() - sage: HyperbolicPointHM((0,0,1)).show() + sage: HyperbolicPlane().UHP().get_point(I).show() + + sage: HyperbolicPlane().UHP().get_point(0).show() + sage: HyperbolicPlane().UHP().get_point(infinity).show() + Traceback (most recent call last): + ... + NotImplementedError: can't draw the point infinity """ + # FIXME: Something didn't get put into the UHP point's show() properly opts = dict([('axes', False),('aspect_ratio',1)]) opts.update(self.graphics_options()) opts.update(options) @@ -643,15 +606,12 @@ class HyperbolicPointPD(HyperbolicPoint): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointPD - sage: HyperbolicPointPD(0) + sage: HyperbolicPlane().PD().get_point(0) Point in PD 0 - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointPD - sage: q = HyperbolicBdryPointPD(1); q + sage: HyperbolicPlane().PD().get_point(1) Boundary point in PD 1 """ - _HMethods = HyperbolicMethodsUHP class HyperbolicPointKM(HyperbolicPoint): r""" @@ -663,15 +623,12 @@ class HyperbolicPointKM(HyperbolicPoint): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointKM - sage: HyperbolicPointKM((0,0)) + sage: HyperbolicPlane().KM().get_point((0,0)) Point in KM (0, 0) - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointKM - sage: q = HyperbolicBdryPointKM((1,0)); q + sage: HyperbolicPlane().KM().get_point((1,0)) Boundary point in KM (1, 0) """ - _HMethods = HyperbolicMethodsUHP class HyperbolicPointHM(HyperbolicPoint): r""" @@ -684,15 +641,12 @@ class HyperbolicPointHM(HyperbolicPoint): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPointHM - sage: HyperbolicPointHM((0,0,1)) + sage: HyperbolicPlane().HM().get_point((0,0,1)) Point in HM (0, 0, 1) - sage: from sage.geometry.hyperbolic_space.hyperbolic_bdry_point import HyperbolicBdryPointHM - sage: q = HyperbolicBdryPointHM((1,0,0)); q + sage: HyperbolicPlane().HM().get_point((1,0,0), is_boundary=True) Traceback (most recent call last): ... NotImplementedError: boundary points are not implemented in the HM model """ - _HMethods = HyperbolicMethodsUHP From 970fde5ed4df264be62bc54c7b09e75e56643a08 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=B6dinger?= Date: Thu, 28 Aug 2014 15:20:28 +0200 Subject: [PATCH 022/129] Rename of weak_popov_form() to row_reduced_form() added deprecation warning, rework of the use of the ascend parameter, removed sorting and returning d if ascend is not set. --- src/sage/matrix/matrix2.pyx | 25 ++++++++++-------- src/sage/matrix/matrix_misc.py | 46 +++++++++++++++++++--------------- 2 files changed, 41 insertions(+), 30 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 6d6f10d4386..56022ec03c5 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -6387,9 +6387,14 @@ cdef class Matrix(matrix1.Matrix): extended.set_immutable() return extended - def weak_popov_form(self, ascend=True): + def weak_popov_form(self, ascend=None): + from sage.misc.superseded import deprecation + deprecation(16888, 'You can just call row_reduced_form() instead') + return self.row_reduced_form(ascend=True) + + def row_reduced_form(self, ascend=None): """ - This function computes a weak Popov form of a matrix over a rational + This function computes a row reduced form of a matrix over a rational function field `k(x)`, for `k` a field. INPUT: @@ -6422,7 +6427,7 @@ cdef class Matrix(matrix1.Matrix): sage: R. = GF(3)['t'] sage: K = FractionField(R) sage: M = matrix([[(t-1)^2/t],[(t-1)]]) - sage: M.weak_popov_form() + sage: M.row_reduced_form() ( [ 0] [ t 2*t + 1] [(2*t + 1)/t], [ 1 2], [-Infinity, 0] @@ -6435,7 +6440,7 @@ cdef class Matrix(matrix1.Matrix): :: sage: M1 = matrix([[t*(t-1)*(t+1)],[t*(t-2)*(t+2)],[t]]) - sage: output1 = M1.weak_popov_form() + sage: output1 = M1.row_reduced_form() sage: output1 ( [0] [ 1 0 2*t^2 + 1] @@ -6450,7 +6455,7 @@ cdef class Matrix(matrix1.Matrix): :: sage: M2 = M1.change_ring(K) - sage: output2 = M2.weak_popov_form() + sage: output2 = M2.row_reduced_form() sage: output1 == output2 True sage: output1[0].base_ring() is K @@ -6469,7 +6474,7 @@ cdef class Matrix(matrix1.Matrix): sage: R. = QQ['t'] sage: M = matrix([[t^3 - t,t^2 - 2],[0,t]]).transpose() - sage: M.weak_popov_form() + sage: M.row_reduced_form() ( [ t -t^2] [ 1 -t] [t^2 - 2 t], [ 0 1], [2, 2] @@ -6482,7 +6487,7 @@ cdef class Matrix(matrix1.Matrix): sage: R. = GF(5)['t'] sage: K = FractionField(R) sage: M = matrix([[K(0),K(0)],[K(0),K(0)]]) - sage: M.weak_popov_form() + sage: M.row_reduced_form() ( [0 0] [1 0] [0 0], [0 1], [-Infinity, -Infinity] @@ -6494,7 +6499,7 @@ cdef class Matrix(matrix1.Matrix): sage: R. = QQ['t'] sage: M = matrix([[t,t,t],[0,0,t]], ascend=False) - sage: M.weak_popov_form() + sage: M.row_reduced_form() ( [t t t] [1 0] [0 0 t], [0 1], [1, 1] @@ -6506,7 +6511,7 @@ cdef class Matrix(matrix1.Matrix): :: sage: M = matrix([[1,0],[1,1]]) - sage: M.weak_popov_form() + sage: M.row_reduced_form() Traceback (most recent call last): ... TypeError: the coefficients of M must lie in a univariate @@ -6538,7 +6543,7 @@ cdef class Matrix(matrix1.Matrix): """ import sage.matrix.matrix_misc - return sage.matrix.matrix_misc.weak_popov_form(self) + return sage.matrix.matrix_misc.row_reduced_form(self,ascend) ########################################################################## # Functions for symmetries of a matrix under row and column permutations # diff --git a/src/sage/matrix/matrix_misc.py b/src/sage/matrix/matrix_misc.py index 7a5069cb7af..edb3ad029d6 100644 --- a/src/sage/matrix/matrix_misc.py +++ b/src/sage/matrix/matrix_misc.py @@ -25,7 +25,7 @@ def row_iterator(A): for i in xrange(A.nrows()): yield A.row(i) -def weak_popov_form(M,ascend=True): +def row_reduced_form(M,ascend=None): """ This function computes a weak Popov form of a matrix over a rational function field `k(x)`, for `k` a field. @@ -75,6 +75,10 @@ def weak_popov_form(M,ascend=True): more information. """ + if ascend is not None: + from sage.misc.superseded import deprecation + deprecation(16888, 'Keyword ascend is deprecated and should no longer be used.') + # determine whether M has polynomial or rational function coefficients R0 = M.base_ring() @@ -180,22 +184,24 @@ def weak_popov_form(M,ascend=True): # so continue onto next step of algorithm break - # sort the rows in order of degree - d = [] - from sage.rings.all import infinity - for i in range(len(r)): - d.append(max([e.degree() for e in r[i]])) - if d[i] < 0: - d[i] = -infinity - else: - d[i] -= den.degree() - - for i in range(len(r)): - for j in range(i+1,len(r)): - if (ascend and d[i] > d[j]) or (not ascend and d[i] < d[j]): - (r[i], r[j]) = (r[j], r[i]) - (d[i], d[j]) = (d[j], d[i]) - (N[i], N[j]) = (N[j], N[i]) - - # return reduced matrix and operations matrix - return (matrix(r)/den, matrix(N), d) + if ascend is not None: + # sort the rows in order of degree + d = [] + from sage.rings.all import infinity + for i in range(len(r)): + d.append(max([e.degree() for e in r[i]])) + if d[i] < 0: + d[i] = -infinity + else: + d[i] -= den.degree() + + for i in range(len(r)): + for j in range(i+1,len(r)): + if (ascend and d[i] > d[j]) or (not ascend and d[i] < d[j]): + (r[i], r[j]) = (r[j], r[i]) + (d[i], d[j]) = (d[j], d[i]) + (N[i], N[j]) = (N[j], N[i]) + + # return reduced matrix and operations matrix + return (matrix(r)/den, matrix(N), d) + return (matrix(r)/den) From 12755f174d1775e53ebf719612f5e984d7120677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=B6dinger?= Date: Thu, 28 Aug 2014 15:29:52 +0200 Subject: [PATCH 023/129] Reduced to be a renaming only. --- src/sage/matrix/matrix2.pyx | 8 +++--- src/sage/matrix/matrix_misc.py | 51 +++++++++++++++++----------------- 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 56022ec03c5..45d4182fe52 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -6387,12 +6387,12 @@ cdef class Matrix(matrix1.Matrix): extended.set_immutable() return extended - def weak_popov_form(self, ascend=None): + def weak_popov_form(self, ascend=True): from sage.misc.superseded import deprecation deprecation(16888, 'You can just call row_reduced_form() instead') - return self.row_reduced_form(ascend=True) + return self.row_reduced_form(ascend) - def row_reduced_form(self, ascend=None): + def row_reduced_form(self, ascend=True): """ This function computes a row reduced form of a matrix over a rational function field `k(x)`, for `k` a field. @@ -6543,7 +6543,7 @@ cdef class Matrix(matrix1.Matrix): """ import sage.matrix.matrix_misc - return sage.matrix.matrix_misc.row_reduced_form(self,ascend) + return sage.matrix.matrix_misc.row_reduced_form(self) ########################################################################## # Functions for symmetries of a matrix under row and column permutations # diff --git a/src/sage/matrix/matrix_misc.py b/src/sage/matrix/matrix_misc.py index edb3ad029d6..b07379a2512 100644 --- a/src/sage/matrix/matrix_misc.py +++ b/src/sage/matrix/matrix_misc.py @@ -25,7 +25,12 @@ def row_iterator(A): for i in xrange(A.nrows()): yield A.row(i) -def row_reduced_form(M,ascend=None): +def weak_popov_form(M,ascend=True): + from sage.misc.superseded import deprecation + deprecation(16888, 'You can just call row_reduced_form() instead') + return row_reduced_form(M,ascend) + +def row_reduced_form(M,ascend=True): """ This function computes a weak Popov form of a matrix over a rational function field `k(x)`, for `k` a field. @@ -75,10 +80,6 @@ def row_reduced_form(M,ascend=None): more information. """ - if ascend is not None: - from sage.misc.superseded import deprecation - deprecation(16888, 'Keyword ascend is deprecated and should no longer be used.') - # determine whether M has polynomial or rational function coefficients R0 = M.base_ring() @@ -184,24 +185,22 @@ def row_reduced_form(M,ascend=None): # so continue onto next step of algorithm break - if ascend is not None: - # sort the rows in order of degree - d = [] - from sage.rings.all import infinity - for i in range(len(r)): - d.append(max([e.degree() for e in r[i]])) - if d[i] < 0: - d[i] = -infinity - else: - d[i] -= den.degree() - - for i in range(len(r)): - for j in range(i+1,len(r)): - if (ascend and d[i] > d[j]) or (not ascend and d[i] < d[j]): - (r[i], r[j]) = (r[j], r[i]) - (d[i], d[j]) = (d[j], d[i]) - (N[i], N[j]) = (N[j], N[i]) - - # return reduced matrix and operations matrix - return (matrix(r)/den, matrix(N), d) - return (matrix(r)/den) + # sort the rows in order of degree + d = [] + from sage.rings.all import infinity + for i in range(len(r)): + d.append(max([e.degree() for e in r[i]])) + if d[i] < 0: + d[i] = -infinity + else: + d[i] -= den.degree() + + for i in range(len(r)): + for j in range(i+1,len(r)): + if (ascend and d[i] > d[j]) or (not ascend and d[i] < d[j]): + (r[i], r[j]) = (r[j], r[i]) + (d[i], d[j]) = (d[j], d[i]) + (N[i], N[j]) = (N[j], N[i]) + + # return reduced matrix and operations matrix + return (matrix(r)/den, matrix(N), d) From 42f8a7d4b2a1942a558d86cc31fd6106b8bed92f Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Tue, 9 Sep 2014 09:07:01 -0700 Subject: [PATCH 024/129] More cleanup work. --- .../hyperbolic_space/hyperbolic_coercion.py | 42 +- .../hyperbolic_space/hyperbolic_geodesic.py | 245 ++++-- .../hyperbolic_space/hyperbolic_interface.py | 14 +- .../hyperbolic_space/hyperbolic_isometry.py | 271 +++++- .../hyperbolic_space/hyperbolic_model.py | 820 +++++------------- .../hyperbolic_space/hyperbolic_point.py | 77 +- 6 files changed, 718 insertions(+), 751 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 2a78ee403c3..5a43b1b1104 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -66,6 +66,14 @@ def _call_(self, x): sage: psi(UHP.get_point(I)) (0, 0, 1) + It is an error to try to convert a boundary point to a model + that doesn't support boundary points:: + + sage: HyperbolicPlane.UHP.point_to_model(infinity, 'HM') + Traceback (most recent call last): + ... + NotImplementedError: boundary points are not implemented for the HM model + It is an error to try to convert a boundary point to a model that doesn't support boundary points:: @@ -116,7 +124,7 @@ def convert_isometry(self, x): sage: phi(PD.get_point(0.5+0.5*I)) # Wrong test... Point in UHP 2.00000000000000 + 1.00000000000000*I """ - return self.codomain().get_isometry(self.image_isometry(x._matrix)) + return self.codomain().get_isometry(self.image_isometry_matrix(x._matrix)) def __invert__(self): """ @@ -154,14 +162,12 @@ def image_coordinates(self, x): sage: phi = PD.coerce_map_from(UHP) sage: phi.image_coordinates(I) 0 - sage: phi.image_coorindates(I) - +Infinity """ if x == infinity: return I return (x - I)/(Integer(1) - I*x) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -192,7 +198,7 @@ def image_coordinates(self, x): return ((2*real(x))/(real(x)**2 + imag(x)**2 + 1), (real(x)**2 + imag(x)**2 - 1)/(real(x)**2 + imag(x)**2 + 1)) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -222,7 +228,7 @@ def image_coordinates(self, x): (real(x)**2 + imag(x)**2 - 1)/(2*imag(x)), (real(x)**2 + imag(x)**2 + 1)/(2*imag(x)))) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -253,12 +259,16 @@ def image_coordinates(self, x): 2.00000000000000 + 1.00000000000000*I sage: phi.image_coordinates(0) I + sage: phi.image_coordinates(I) + +Infinity + sage: phi.image_coordinates(-I) + 0 """ if x == I: return infinity return (x + I)/(Integer(1) + I*x) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -281,7 +291,7 @@ def image_coordinates(self, x): return (2*real(x)/(Integer(1) + real(x)**2 +imag(x)**2), 2*imag(x)/(Integer(1) + real(x)**2 + imag(x)**2)) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -308,7 +318,7 @@ def image_coordinates(self, x): (real(x)**2 + imag(x)**2 + 1)/(1 - real(x)**2 - imag(x)**2) )) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -338,6 +348,8 @@ def image_coordinates(self, x): sage: phi = UHP.coerce_map_from(KM) sage: phi.image_coordinates((0, 0)) I + sage: phi.image_coordinates((0, 1)) + +Infinity """ if tuple(x) == (0, 1): return infinity @@ -345,7 +357,7 @@ def image_coordinates(self, x): + I*(-(sqrt(-x[0]**2 -x[1]**2 + 1) - x[0]**2 - x[1]**2 + 1) / ((x[1] - 1)*sqrt(-x[0]**2 - x[1]**2 + 1) + x[1] - 1)) ) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -368,7 +380,7 @@ def image_coordinates(self, x): return ( x[0]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) + I*x[1]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) ) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -398,7 +410,7 @@ def image_coordinates(self, x): return (vector((2*x[0], 2*x[1], 1 + x[0]**2 + x[1]**2)) / (1 - x[0]**2 - x[1]**2)) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -431,7 +443,7 @@ def image_coordinates(self, x): return -((x[0]*x[2] + x[0]) + I*(x[2] + 1)) / ((x[1] - 1)*x[2] - x[0]**2 - x[1]**2 + x[1] - 1) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -453,7 +465,7 @@ def image_coordinates(self, x): """ return x[0]/(1 + x[2]) + I*(x[1]/(1 + x[2])) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. @@ -482,7 +494,7 @@ def image_coordinates(self, x): """ return (x[0]/(1 + x[2]), x[1]/(1 + x[2])) - def image_isometry(self, x): + def image_isometry_matrix(self, x): """ Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 4ccd1960f50..9f2dc39295f 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -85,7 +85,6 @@ class HyperbolicGeodesic(SageObject): sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I)) sage: g = UHP.get_geodesic(I, 2 + I) """ - _HMethods = HyperbolicAbstractMethods ##################### # "Private" Methods # @@ -110,34 +109,26 @@ def _cached_start(self): r""" The representation of the start point used for calculations. - For example, if the current model uses the HyperbolicMethodsUHP - class, then :meth:`_cached_start` will hold the upper half plane - representation of ``self.start()``. - EXAMPLES:: sage: HyperbolicPlane().PD().get_geodesic(0, 1/2)._cached_start I """ - return self.model().point_to_model(self.start().coordinates(), - self._HMethods.model_name()) + M = self._model.realization_of().a_realization() + return M(self.start()).coordinates() @lazy_attribute def _cached_end(self): r""" The representation of the end point used for calculations. - For example, if the current model uses the - :class:`HyperbolicMethodsUHP` class, then :meth:`_cached_end` will - hold the upper half plane representation of ``self.end()``. - EXAMPLES:: sage: HyperbolicPlane().PD().get_geodesic(0, 1/2)._cached_end 3/5*I + 4/5 """ - return self.model().point_to_model(self.end().coordinates(), - self._HMethods.model_name()) + M = self._model.realization_of().a_realization() + return M(self.end()).coordinates() @lazy_attribute def _cached_endpoints(self): @@ -170,12 +161,10 @@ def _complete(self): False sage: g.complete()._complete True - """ - if self.model().is_bounded(): - return (self.model().boundary_point_in_model(self.start().coordinates()) - and self.model().boundary_point_in_model(self.end().coordinates())) - else: - return False #All non-bounded geodesics start life incomplete. + """ + if self._model.is_bounded(): + return (self._start.is_boundary() and self._end.is_boundary()) + return False #All non-bounded geodesics start life incomplete. def _repr_(self): r""" @@ -196,7 +185,7 @@ def _repr_(self): Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ return "Geodesic in {0} from {1} to {2}".format(self._model.short_name(), - self.start().coordinates(), self.end().coordinates()) + self._start.coordinates(), self._end.coordinates()) def __eq__(self, other): r""" @@ -211,9 +200,9 @@ def __eq__(self, other): sage: g1 == g1 True """ - return (self.model() is other.model() - and self.start() == other.start() - and self.end() == other.end()) + return (self._model is other._model + and self._start == other._start + and self._end == other._end) ####################### # Setters and Getters # @@ -253,7 +242,7 @@ def endpoints(self): sage: g.endpoints() [Point in UHP I, Point in UHP 3*I] """ - return [self.start(), self.end()] + return [self._start, self._end] def model(self): r""" @@ -295,30 +284,26 @@ def model_name(self): """ return self.model().short_name() - def to_model(self, model_name): + def to_model(self, model): r""" Convert the current object to image in another model. INPUT: - - ``model_name`` -- a string representing the image model + - ``model`` -- the image model EXAMPLES:: sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).to_model('PD') Geodesic in PD from 0 to 1/3*I """ - from sage.geometry.hyperbolic_space.model_factory import ModelFactory - factory = ModelFactory.find_factory(model_name) - if not factory.get_model().is_bounded() and self.is_complete(): + if not model.is_bounded() and self.is_complete(): g = self.uncomplete() - return g.to_model(model_name).complete() - start = self.model().point_to_model(self.start().coordinates(), - model_name) - end = self.model().point_to_model(self.end().coordinates(), model_name) - g = factory.get_geodesic(start, end) - if not self.model().is_bounded() and factory.get_model().is_bounded() \ - and self.is_complete(): + return g.to_model(model).complete() + start = model(self._start) + end = model(self._end) + g = model.get_geodesic(start, end) + if not self._model.is_bounded() and model.is_bounded() and self.is_complete(): # Converting from a non-bounded model to a bounded model return g.complete() return g @@ -731,15 +716,9 @@ def perpendicular_bisector(self, **graphics_options): ... ValueError: perpendicular bisector is not defined for complete geodesics """ - if self.is_complete(): - raise ValueError("perpendicular bisector is not defined for " - "complete geodesics") - bisect_ends = self._HMethods.perpendicular_bisector( - *self._cached_endpoints) - M = self._HMethods.model() - bisect_ends = [M.point_to_model(k, self.model_name()) - for k in bisect_ends] - return self._model.get_geodesic(*bisect_ends, **graphics_options) + UHP = self._model.realization_of().UHP + P = self.to_model(UHP).perpendicular_bisector(**graphics_options) + return P.to_model(self._model) def midpoint(self, **graphics_options): r""" @@ -758,14 +737,11 @@ def midpoint(self, **graphics_options): sage: HyperbolicPlane().UHP().get_geodesic(0,2).midpoint() Traceback (most recent call last): ... - ValueError: midpoint not defined for complete geodesics + ValueError: midpoint is not defined for complete geodesics """ - if self.is_complete(): - raise ValueError("midpoint not defined for complete geodesics") - mid = self._HMethods.midpoint(*self._cached_endpoints, - **graphics_options) - mid = self._HMethods.model().point_to_model(mid, self.model_name()) - return self._model.get_point(mid) + UHP = self._model.realization_of().UHP + P = self.to_model(UHP).midpoint(**graphics_options) + return self._model(P) def dist(self, other): r""" @@ -797,20 +773,7 @@ def dist(self, other): sage: g.dist(p) +Infinity """ - if isinstance(other, HyperbolicGeodesic): - if not self.is_parallel(other): - return 0 - - elif self.is_ultra_parallel(other): - perp = self.common_perpendicular(other) - # Find where self and other intersect the common perp... - p = self.intersection(perp) - q = other.intersection(perp) - # and return their distance - return p.dist(q) - return self._HMethods.geod_dist_from_point(self._cached_start, - self._cached_end, - other._cached_coordinates) + return self._model.dist(self, other) def angle(self, other): r""" @@ -851,7 +814,7 @@ def angle(self, other): if not (self.is_complete() and other.is_complete()): try: # Make sure the segments intersect. - self.intersection (other) + self.intersection(other) except ValueError: print("Warning: Geodesic segments do not intersect. " "The angle between them is not defined.\n" @@ -870,9 +833,10 @@ def length(self): sage: g.length() arccosh(9/4) """ - end1, end2 = self.endpoints() - return end1.dist(end2) + return self._model._point_dist(self._start.coordinates(), self._end.coordinates()) +##################################################################### +## UHP geodesics class HyperbolicGeodesicUHP(HyperbolicGeodesic): r""" @@ -892,7 +856,29 @@ class HyperbolicGeodesicUHP(HyperbolicGeodesic): sage: g = UHP.get_geodesic(UHP.point(I), UHP.point(2 + I)) sage: g = UHP.get_geodesic(I, 2 + I) """ - _HMethods = HyperbolicMethodsUHP + def reflection_in(self, start, end): + r""" + Return the matrix of the involution fixing the geodesic through + ``start`` and ``end``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.reflection_in(0, 1) + [ 1 0] + [ 2 -1] + sage: HyperbolicMethodsUHP.reflection_in(I, 2*I) + [ 1 0] + [ 0 -1] + """ + x, y = [real(k) for k in self.boundary_points(start, end)] + if x == infinity: + M = matrix(2, [[1,-2*y],[0,-1]]) + elif y == infinity: + M = matrix(2, [[1,-2*x],[0,-1]]) + else: + M = matrix(2, [[(x+y)/(y-x),- 2*x*y/(y-x)], [2/(y-x), -(x+y)/(y-x)]]) + return self._model.get_isometry(M) def show(self, boundary=True, **options): r""" @@ -949,6 +935,125 @@ def show(self, boundary=True, **options): pic = bd_pic + pic return pic + def perpendicular_bisector(self, **graphics_options): #UHP + r""" + Return the perpendicular bisector of the hyperbolic geodesic with + endpoints ``start`` and ``end`` if that geodesic has finite length. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] + sage: g = [a, b] + sage: h = HyperbolicMethodsUHP.perpendicular_bisector(*g) + sage: bool(HyperbolicMethodsUHP.intersection(*(g + h))[0] - HyperbolicMethodsUHP.midpoint(*g) < 10**-9) + True + + Infinite geodesics cannot be bisected:: + + sage: HyperbolicMethodsUHP.perpendicular_bisector(0, 1) + Traceback (most recent call last): + ... + ValueError: the length must be finite + """ + if self.length() == infinity: + raise ValueError("the length must be finite") + d = self._dist_points(self._start.coordinates(), self._end.coordinates()) / 2 + end_1, end_2 = self.boundary_points(self._start, self._end) + S = self._to_std_geod(end_1, self._start, end_2) + T1 = matrix(2,[exp(d/2),0,0,exp(-d/2)])* + T2 = matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)]) + H = S.inverse() * (T1 * T2) * S + L = [self._model.get_point(mobius_transform(H, k)) for k in [end_1, end_2]] + return self._model.get_geodesic(L[0], L[1], **graphics_options) + + def midpoint(self): #UHP + r""" + Return the (hyperbolic) midpoint of ``self`` if . + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] + sage: g = [a, b] + sage: m = HyperbolicMethodsUHP.midpoint(*g) + sage: d1 =HyperbolicMethodsUHP.point_dist(m, g[0]) + sage: d2 = HyperbolicMethodsUHP.point_dist(m, g[1]) + sage: bool(abs(d1 - d2) < 10**-9) + True + + Infinite geodesics have no midpoint:: + + sage: HyperbolicMethodsUHP.midpoint(0, 2) + Traceback (most recent call last): + ... + ValueError: the length must be finite + """ + if self.length() == infinity: + raise ValueError("the length must be finite") + + start = self._start.coordinates() + end = self._end.coordinates() + d = self._dist_points(start, end) / 2 + end_1, end_2 = self.boundary_points(self._start, self._end) + S = self._to_std_geod(end_1, self._start, end_2) + T = matrix(2, [[exp(half_dist), 0], [0, 1]]) + M = S.inverse() * T * S + if ((real(start - end) < EPSILON) or + (abs(real(start - end)) < EPSILON and + imag(start - end) < EPSILON)): + end_p = start + else: + end_p = end + return mobius_transform(M, end_p) + + def angle(self, other): #UHP + r""" + Return the angle between any two given completed geodesics if + they intersect. + + INPUT: + + -``other`` -- a hyperbolic geodesic in the UHP model + + OUTPUT: + + - the angle in radians between the two given geodesics + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: numerical_approx(HyperbolicMethodsUHP.angle(2, 4, 3, 3 + I)) + 1.57079632679490 + + If the geodesics are identical, return angle 0:: + + sage: HyperbolicMethodsUHP.angle(2, 4, 2, 4) + 0 + """ + start2,end2 = other.endpoints() + (p_1,p_2) = sorted(self._model.boundary_points(start_1, end_1)) + (q_1,q_2) = sorted(self._model.boundary_points(start_2, end_2)) + # if the geodesics are equal, the angle between them is 0 + if (abs(p_1 - q_1) < EPSILON \ + and abs(p_2 - q_2) < EPSILON): + return 0 + elif p_2 != infinity: # geodesic not a straight line + # So we send it to the geodesic with endpoints [0, oo] + T = self._crossratio_matrix(p_1, (p_1+p_2)/2, p_2) + else: + # geodesic is a straight line, so we send it to the geodesic + # with endpoints [0,oo] + T = self._crossratio_matrix(p_1, p_1 +1, p_2) + # b_1 and b_2 are the endpoints of the image of other + b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] + # If other is now a straight line... + if (b_1 == infinity or b_2 == infinity): + # then since they intersect, they are equal + return 0 + else: + return real(arccos((b_1+b_2)/abs(b_2-b_1))) + class HyperbolicGeodesicPD(HyperbolicGeodesic): r""" diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index bb91c69720e..e16ca1e2caf 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -164,8 +164,20 @@ def _an_element_(self): """ return self(self.realization_of().PD().get_point(0)) + def dist(self, a, b): + """ + Return the distance between ``a`` and ``b``. + """ + R = self.realization_of().a_realization() + return R.dist(a, b) + class ElementMethods: - pass + # TODO: Move to a category of metric spaces + def dist(self, other): + """ + Return the distance between ``self`` and ``other``. + """ + return self.parent().dist(self, other) # TODO: Remove this class and move its doctests class HyperbolicUserInterface(UniqueRepresentation): diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index c9bc75dbedd..601c851d4fd 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -85,7 +85,6 @@ def __init__(self, model, A): [-1 0] """ model.isometry_test(A) - self._model = model self._matrix = A Morphism.__init__(self, Hom(model, model)) @@ -124,7 +123,7 @@ def _repr_(self): [1 0] [0 1] """ - return self._repr_type() + " in {0}\n{1}".format(self._model.short_name(), self.matrix()) + return self._repr_type() + " in {0}\n{1}".format(self.domain().short_name(), self._matrix) def _repr_type(self): r""" @@ -171,10 +170,10 @@ def __eq__(self, other): """ pos_matrix = bool(abs(self.matrix() - other.matrix()) < EPSILON) neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) - if self._model.is_isometry_group_projective(): - return self._model is other._model and (pos_matrix or neg_matrix) + if self.domain().is_isometry_group_projective(): + return self.domain() is other.domain() and (pos_matrix or neg_matrix) else: - return self._model is other._model and pos_matrix + return self.domain() is other.domain() and pos_matrix def __pow__(self, n): r""" @@ -187,7 +186,7 @@ def __pow__(self, n): [41 15] [30 11] """ - return self.__class__(self._model, self.matrix()**n) + return self.__class__(self.domain(), self._matrix**n) def __mul__(self, other): r""" @@ -223,10 +222,10 @@ def __mul__(self, other): raise TypeError("{0} and {1} are not in the same" "model".format(self, other)) if isinstance(other, HyperbolicIsometry): - return self.__class__(self._model, self.matrix()*other.matrix()) + return self.__class__(self.domain(), self._matrix*other._matrix) elif isinstance(other, HyperbolicPoint): return self._model.get_point(self.model().isometry_act_on_point( - self.matrix(), other.coordinates())) + self._matrix, other.coordinates())) elif isinstance(other, HyperbolicGeodesic): return self._model.get_geodesic(self*other.start(), self*other.end()) else: @@ -256,12 +255,12 @@ def _call_(self, other): Point in HM (0, -1, sqrt(2)). """ from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic - if self.model() is not other.model(): + if self.domain() is not other.domain(): raise TypeError("{0} is not in the {1} model".format(other, self.model_name())) if isinstance(other, HyperbolicGeodesic): - return self._model.get_geodesic(self(other.start()), self(other.end())) - return self._model.get_point(self.model().isometry_act_on_point( + return self.domain().get_geodesic(self(other.start()), self(other.end())) + return self.domain().get_point(self.model().isometry_act_on_point( self.matrix(), other.coordinates())) ####################### @@ -328,7 +327,7 @@ def model(self): sage: HyperbolicIsometryHM(identity_matrix(3)).model() """ - return self._model + return self.domain() def model_name(self): r""" @@ -349,7 +348,7 @@ def model_name(self): sage: HyperbolicIsometryHM(identity_matrix(3)).model_name() 'HM' """ - return self._model.short_name() + return self.domain().short_name() def to_model(self, other): r""" @@ -409,8 +408,8 @@ def to_model(self, other): [ 0 0 1] """ if isinstance(other, str): - other = getattr(self._model.realization_of(), other) - phi = other.coerce_map_from(self._model) + other = getattr(self.domain().realization_of(), other) + phi = other.coerce_map_from(self.domain()) return phi.convert_isometry(self) ################### @@ -463,7 +462,9 @@ def classification(self): sage: E.classification() 'reflection' """ - return self._HMethods.classification(self._cached_matrix) + R = self._model.realization_of().a_realization() + return self.to_model(R).classification() + #return self.to_model(R).classification(self._cached_matrix) def translation_length(self): r""" @@ -509,10 +510,10 @@ def axis(self, **graphics_options): ... ValueError: the isometry is not hyperbolic: axis is undefined """ - if self.classification() not in ( - ['hyperbolic', 'orientation-reversing hyperbolic']): + if self.classification() not in ['hyperbolic', + 'orientation-reversing hyperbolic']: raise ValueError("the isometry is not hyperbolic: axis is undefined") - return self._model.get_geodesic(*self.fixed_point_set()) + return self.domain().get_geodesic(*self.fixed_point_set()) def fixed_point_set(self, **graphics_options): r""" @@ -549,7 +550,7 @@ def fixed_point_set(self, **graphics_options): pts = self._HMethods.fixed_point_set(self._cached_matrix) pts = [self._HMethods.model().point_to_model(k, self.model_name()) for k in pts] - return [self._model.get_point(k, **graphics_options) for k in pts] + return [self.domain().get_point(k, **graphics_options) for k in pts] def fixed_geodesic(self, **graphics_options): r""" @@ -588,7 +589,7 @@ def repelling_fixed_point(self, **graphics_options): """ fp = self._HMethods.repelling_fixed_point(self._cached_matrix) fp = self._HMethods.model().point_to_model(fp, self.model_name()) - return self._model.get_point(fp) + return self.domain().get_point(fp) def attracting_fixed_point(self, **graphics_options): r""" @@ -608,7 +609,7 @@ def attracting_fixed_point(self, **graphics_options): """ fp = self._HMethods.attracting_fixed_point(self._cached_matrix) fp = self._HMethods.model().point_to_model(fp, self.model_name()) - return self._model.get_point(fp) + return self.domain().get_point(fp) def isometry_from_fixed_points(self, repel, attract): r""" @@ -633,12 +634,12 @@ def isometry_from_fixed_points(self, repel, attract): """ try: A = self._HMethods.isometry_from_fixed_points(repel._cached_coordinates, - attract._cached_coordinates) + attract._cached_coordinates) A = self._HMethods.model().isometry_to_model(A, self.model_name()) - return self._model.get_isometry(A) + return self.domain().get_isometry(A) except(AttributeError): - repel = self._model.get_point(repel) - attract = self._model.get_point(attract) + repel = self.domain().get_point(repel) + attract = self.domain().get_point(attract) return self.isometry_from_fixed_points(repel, attract) class HyperbolicIsometryUHP(HyperbolicIsometry): @@ -655,6 +656,224 @@ class HyperbolicIsometryUHP(HyperbolicIsometry): sage: A = HyperbolicIsometryUHP(identity_matrix(2)) """ + def orientation_preserving(self): + r""" + Return ``True`` if ``self`` is orientation preserving and ``False`` + otherwise. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = identity_matrix(2) + sage: HyperbolicMethodsUHP.orientation_preserving(A) + True + sage: B = matrix(2,[0,1,1,0]) + sage: HyperbolicMethodsUHP.orientation_preserving(B) + False + """ + return bool(self._matrix.det() > 0) + + def classification(self): + r""" + Classify the hyperbolic isometry as elliptic, parabolic, or + hyperbolic. + + A hyperbolic isometry fixes two points on the boundary of + hyperbolic space, a parabolic isometry fixes one point on the + boundary of hyperbolic space, and an elliptic isometry fixes + no points. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.classification(identity_matrix(2)) + 'identity' + + sage: HyperbolicMethodsUHP.classification(4*identity_matrix(2)) + 'identity' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[2,0,0,1/2])) + 'hyperbolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2, [0, 3, -1/3, 6])) + 'hyperbolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[1,1,0,1])) + 'parabolic' + + sage: HyperbolicMethodsUHP.classification(matrix(2,[-1,0,0,1])) + 'reflection' + """ + A = self._matrix.n() + A = A / (abs(A.det()).sqrt()) + tau = abs(A.trace()) + a = A.list() + if A.det() > 0: + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < + EPSILON) + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < + EPSILON) + if tf: + return 'identity' + if tau - 2 < -EPSILON: + return 'elliptic' + elif tau -2 > -EPSILON and tau - 2 < EPSILON: + return 'parabolic' + elif tau - 2 > EPSILON: + return 'hyperbolic' + else: + raise ValueError("something went wrong with classification:" + + " trace is {}".format(A.trace())) + else: #The isometry reverses orientation. + if tau < EPSILON: + return 'reflection' + else: + return 'orientation-reversing hyperbolic' + + def translation_length(self): + r""" + For hyperbolic elements, return the translation length; + otherwise, raise a ``ValueError``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.translation_length(matrix(2,[2,0,0,1/2])) + 2*arccosh(5/4) + + :: + + sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) + sage: p = exp(i*7*pi/8) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: Hp = mobius_transform(H, p) + sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) + True + """ + d = sqrt(self._matrix.det()**2) + tau = sqrt((self._matrix / sqrt(d)).trace()**2) + if self.classification() in ['hyperbolic', 'oriention-reversing hyperbolic']: + return 2 * arccosh(tau/2) + raise TypeError("translation length is only defined for hyperbolic transformations") + + def fixed_point_set(self): + r""" + Return the a list containing the fixed point set of + orientation-preserving isometries. + + OUTPUT: + + - a list of hyperbolic points + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) + sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(mobius_transform(H, p1) == p1) + True + sage: bool(mobius_transform(H, p2) == p2) + True + + sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) + Traceback (most recent call last): + ... + ValueError: the identity transformation fixes the entire hyperbolic plane + """ + d = sqrt(self._matrix.det()**2) + M = self._matrix / sqrt(d) + tau = M.trace()**2 + M_cls = self.classification() + if M_cls == 'identity': + raise ValueError("the identity transformation fixes the entire " + "hyperbolic plane") + if M_cls == 'parabolic': + if abs(M[1,0]) < EPSILON: + return [infinity] + else: + # boundary point + return [(M[0,0] - M[1,1]) / (2*M[1,0])] + elif M_cls == 'elliptic': + d = sqrt(tau - 4) + return [(M[0,0] - M[1,1] + sign(M[1,0])*d)/(2*M[1,0])] + elif M_cls == 'hyperbolic': + if M[1,0] != 0: #if the isometry doesn't fix infinity + d = sqrt(tau - 4) + p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0]) + p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0]) + return [p_1, p_2] + else: #else, it fixes infinity. + p_1 = M[0,1] / (M[1,1] - M[0,0]) + p_2 = infinity + return [p_1, p_2] + else: + # raise NotImplementedError("fixed point set not implemented for" + # " isometries of type {0}".format(M_cls)) + try: + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + except (IndexError): + M = M.change_ring(RDF) + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + if p[1] == 0: + p = infinity + else: + p = p[0] / p[1] + if q[1] == 0: + q = infinity + else: + q = q[0] / q[1] + pts = [p, q] + return [k for k in pts if imag(k) >= 0] + + def repelling_fixed_point(self): + r""" + Return the repelling fixed point; otherwise raise a ``ValueError``. + + OUTPUT: + + - a hyperbolic point + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = matrix(2,[4,0,0,1/4]) + sage: HyperbolicMethodsUHP.repelling_fixed_point(A) + 0 + """ + if self.classification() not in ['hyperbolic', + 'orientation-reversing hyperbolic']: + raise ValueError("repelling fixed point is defined only" + + "for hyperbolic isometries") + v = self._matrix.eigenmatrix_right()[1].column(1) + if v[1] == 0: + return infinity + return v[0] / v[1] + + def attracting_fixed_point(self): + r""" + Return the attracting fixed point; otherwise raise a ``ValueError``. + + OUTPUT: + + - a hyperbolic point + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: A = matrix(2,[4,0,0,1/4]) + sage: HyperbolicMethodsUHP.attracting_fixed_point(A) + +Infinity + """ + if self.classification() not in \ + ['hyperbolic', 'orientation-reversing hyperbolic']: + raise ValueError("Attracting fixed point is defined only" + + "for hyperbolic isometries.") + v = self._matrix.eigenmatrix_right()[1].column(0) + if v[1] == 0: + return infinity + return v[0] / v[1] + class HyperbolicIsometryPD(HyperbolicIsometry): r""" Create a hyperbolic isometry in the PD model. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index a62c90f74c9..43560229d16 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -111,6 +111,9 @@ lazy_import('sage.modules.free_module_element', 'vector') +##################################################################### +## Abstract model + class HyperbolicModel(Parent, UniqueRepresentation, BindableClass): r""" Abstract base class for hyperbolic models. @@ -140,13 +143,13 @@ def __init__(self, space, name, short_name, bounded, conformal, from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels Parent.__init__(self, category=HyperbolicModels(space)) - def _repr_(self): + def _repr_(self): #Abstract """ Return a string representation of ``self``. """ return "Hyperbolic plane in the {} model".format(self._name) - def _element_constructor_(self, x, is_boundary=None, **graphics_options): + def _element_constructor_(self, x, is_boundary=None, **graphics_options): #Abstract """ Construct an element of ``self``. """ @@ -154,7 +157,7 @@ def _element_constructor_(self, x, is_boundary=None, **graphics_options): Element = HyperbolicPoint - def name(self): + def name(self): #Abstract """ Return the name of this model. @@ -166,7 +169,7 @@ def name(self): """ return self._name - def short_name(self): + def short_name(self): #Abstract """ Return the short name of this model. @@ -178,7 +181,7 @@ def short_name(self): """ return self._short_name - def is_bounded(self): + def is_bounded(self): #Abstract """ Return ``True`` if ``self`` is a bounded model. @@ -190,7 +193,7 @@ def is_bounded(self): """ return self._bounded - def is_conformal(self): + def is_conformal(self): #Abstract """ Return ``True`` if ``self`` is a conformal model. @@ -202,7 +205,7 @@ def is_conformal(self): """ return self._conformal - def is_isometry_group_projective(self): + def is_isometry_group_projective(self): #Abstract """ Return ``True`` if the isometry group of ``self`` is projective. @@ -250,9 +253,9 @@ def point_test(self, p): #Abstract ... ValueError: -I + 2 is not a valid point in the UHP model """ - if not (cls.point_in_model(p) or cls.boundary_point_in_model(p)): + if not (self.point_in_model(p) or self.boundary_point_in_model(p)): error_string = "{0} is not a valid point in the {1} model" - raise ValueError(error_string.format(p, cls.short_name)) + raise ValueError(error_string.format(p, self._short_name)) def boundary_point_in_model(self, p): #Abstract r""" @@ -288,7 +291,7 @@ def bdry_point_test(self, p): #Abstract ... ValueError: I + 1 is not a valid boundary point in the UHP model """ - if not self._bounded or not cls.boundary_point_in_model(p): + if not self._bounded or not self.boundary_point_in_model(p): error_string = "{0} is not a valid boundary point in the {1} model" raise ValueError(error_string.format(p, self._short_name)) @@ -348,7 +351,7 @@ def isometry_test(self, A): #Abstract [I 1] [2 1] is not a valid isometry in the UHP model. """ - if not cls.isometry_in_model(A): + if not self.isometry_in_model(A): error_string = "\n{0} is not a valid isometry in the {1} model." raise ValueError(error_string.format(A, cls.short_name)) @@ -586,6 +589,134 @@ def random_isometry(self, preserve_orientation=True, **kwargs): A = UHP.random_isometry(preserve_orientation, **kwargs) return A.to_model(self) + ################ + # Dist methods # + ################ + + def dist(self, a, b): + r""" + Calculate the hyperbolic distance between ``a`` and ``b``. + + INPUT: + + - ``a``, ``b`` -- a point or geodesic + + OUTPUT: + + - the hyperbolic distance + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1.0 + I) + sage: p1.dist(p2) + 2.23230104635820 + + sage: p1 = HyperbolicPlane().PD().get_point(0) + sage: p2 = HyperbolicPlane().PD().get_point(I/2) + sage: p1.dist(p2) + arccosh(5/3) + + sage: UHP(p1).dist(UHP(p2)) + arccosh(5/3) + + sage: p1 = HyperbolicPlane().KM().get_point((0, 0)) + sage: p2 = HyperbolicPlane().KM().get_point((1/2, 1/2)) + sage: numerical_approx(p1.dist(p2)) + 0.881373587019543 + + sage: p1 = HyperbolicPlane().HM().get_point((0,0,1)) + sage: p2 = HyperbolicPlane().HM().get_point((1,0,sqrt(2))) + sage: numerical_approx(p1.dist(p2)) + 0.881373587019543 + + Distance between a point and itself is 0:: + + sage: p = HyperbolicPlane().UHP().get_point(47 + I) + sage: p.dist(p) + 0 + """ + if isinstance(a, HyperbolicGeodesic): + if isinstance(b, HyperbolicGeodesic): + if not a.is_parallel(b): + return 0 + + if a.is_ultra_parallel(b): + perp = a.common_perpendicular(b) + # Find where a and b intersect the common perp... + p = a.intersection(perp) + q = b.intersection(perp) + # ...and return their distance + return self.dist(p, q) + + raise NotImplementedError("can only compute distance between" + " ultra-parallel and interecting geodesics") + + # If only one is a geodesic, make sure it's b to make things easier + a,b = b,a + + if not isinstance(b, HyperbolicPoint): + raise TypeError("invalid input type") + + coords = lambda x: self(x).coordinates() + if isinstance(a, HyperbolicPoint): + return self._dist_points(coords(a), coords(b)) + elif isinstance(a, HyperbolicGeodesic): + return self._dist_geod_point(coords(a.start()), coords(a.end()), coords(b)) + raise TypeError("invalid input type") + + def _dist_points(self, p1, p2): + r""" + Compute the distance between two points. + + INPUT: + + - ``p1``, ``p2`` -- the coordinates of the points + + EXAMPLES:: + + sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) + 1.38629436111989 + """ + UHP = self.realization_of().UHP() + phi = UHP.coerce_map_from(self) + return UHP._dist_points(phi.image_coordinates(p1), phi.image_coordinates(p2)) + + def _dist_geod_point(self, start, end, p): + r""" + Return the hyperbolic distance from a given hyperbolic geodesic + and a hyperbolic point. + + INPUT: + + - ``start`` -- the start coordinates of the geodesic + - ``end`` -- the end coordinates of the geodesic + - ``p`` -- the coordinates of the point + + OUTPUT: + + - the hyperbolic distance + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) + arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) + + If `p` is a boundary point, the distance is infinity:: + + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) + +Infinity + """ + UHP = self.realization_of().UHP() + phi = lambda c: UHP.coerce_map_from(self).image_coordinates(c) + return UHP._dist_geod_point(phi(start), phi(end), phi(p)) + + +##################################################################### +## Specific models + class HyperbolicModelUHP(HyperbolicModel): r""" Upper Half Plane model. @@ -719,47 +850,6 @@ def isometry_in_model(self, A): #UHP sum([k in RR for k in A.list()]) == 4 and abs(A.det()) > -EPSILON) - def point_to_model(self, coordinates, model_name): #UHP - r""" - Convert ``coordinates`` from the current model to the model - specified in ``model_name``. - - INPUT: - - - ``coordinates`` -- the coordinates of a valid point in the - current model - - ``model_name`` -- a string denoting the model to be converted to - - OUTPUT: - - - the coordinates of a point in the ``short_name`` model - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.point_to_model(I, 'UHP') - I - sage: HyperbolicPlane.UHP.point_to_model(I, 'PD') - 0 - sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'KM') - (6/11, 9/11) - sage: HyperbolicPlane.UHP.point_to_model(3 + I, 'HM') - (3, 9/2, 11/2) - - It is an error to try to convert a boundary point to a model - that doesn't support boundary points:: - - sage: HyperbolicPlane.UHP.point_to_model(infinity, 'HM') - Traceback (most recent call last): - ... - NotImplementedError: boundary points are not implemented for the HM model - """ - p = coordinates - if (cls.boundary_point_in_model(p) and not - ModelFactory.find_model(model_name).is_bounded()): - raise NotImplementedError("boundary points are not implemented for" - " the {0} model".format(model_name)) - return cls.pt_conversion_dict[model_name](coordinates) - def isometry_to_model(self, A, model_name): # UHP r""" Convert ``A`` from the current model to the model specified in @@ -801,43 +891,80 @@ def get_background_graphic(self, **bdry_options): #UHP bd_max = bdry_options.get('bd_max', 5) return line(((bd_min, 0), (bd_max, 0)), color='black') - ################# - # Point Methods # - ################# + ################ + # Dist methods # + ################ - def point_dist(self, p1, p2): + def _dist_points(self, p1, p2): r""" Compute the distance between two points in the Upper Half Plane using the hyperbolic metric. + INPUT: + + - ``p1``, ``p2`` -- the coordinates of the points + EXAMPLES:: sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) 1.38629436111989 """ - cls.model().point_test(p1) - cls.model().point_test(p2) num = (real(p2) - real(p1))**2 + (imag(p2) - imag(p1))**2 denom = 2*imag(p1)*imag(p2) if denom == 0: return infinity return arccosh(1 + num/denom) - def symmetry_in(self, p): + def _dist_geod_point(self, start, end, p): r""" - Return the involutary isometry fixing the given point. + Return the hyperbolic distance from a given hyperbolic geodesic + and a hyperbolic point. + + INPUT: + + - ``start`` -- the start coordinates of the geodesic + - ``end`` -- the end coordinates of the geodesic + - ``p`` -- the coordinates of the point + + OUTPUT: + + - the hyperbolic distance EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.symmetry_in(3 + 2*I) - [ 3/2 -13/2] - [ 1/2 -3/2] + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) + arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) + + If `p` is a boundary point, the distance is infinity:: + + sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) + +Infinity """ - self.point_test(p) - x, y = real(p), imag(p) - if y > 0: - return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) + # Here is the trick for computing distance to a geodesic: + # find an isometry mapping the geodesic to the geodesic between + # 0 and infinity (so corresponding to the line imag(z) = 0. + # then any complex number is r exp(i*theta) in polar coordinates. + # the mutual perpendicular between this point and imag(z) = 0 + # intersects imag(z) = 0 at ri. So we calculate the distance + # between r exp(i*theta) and ri after we transform the original + # point. + (bd_1, bd_2) = self.boundary_points(start, end) + if bd_1 + bd_2 != infinity: + # Not a straight line + # Map the endpoints to 0 and infinity and the midpoint to 1. + T = self._crossratio_matrix(bd_1, (bd_1 + bd_2)/2, bd_2) + else: + # Is a straight line + # Map the endpoints to 0 and infinity and another endpoint + # to 1 + T = self._crossratio_matrix(bd_1, bd_1 + 1, bd_2) + x = mobius_transform(T, p) + return self._dist_points(x, abs(x)*I) + + ################# + # Point Methods # + ################# def random_point(self, **kwargs): r""" @@ -862,7 +989,7 @@ def random_point(self, **kwargs): # Geodesic Methods # #################### - def boundary_points(self, p1, p2): + def boundary_points(self, p1, p2): #UHP r""" Given two points ``p1`` and ``p2`` in the hyperbolic plane, determine the endpoints of the complete hyperbolic geodesic @@ -887,7 +1014,7 @@ def boundary_points(self, p1, p2): if p1 == p2: raise ValueError("{} and {} are not distinct".format(p1, p2)) [x1, x2] = [real(k) for k in [p1,p2]] - [y1,y2] = [imag(k) for k in [p1,p2]] + [y1, y2] = [imag(k) for k in [p1,p2]] # infinity is the first endpoint, so the other ideal endpoint # is just the real part of the second coordinate if p1 == infinity: @@ -904,31 +1031,7 @@ def boundary_points(self, p1, p2): r = sqrt((c - x1)**2 + y1**2) return [c-r, c + r] - def reflection_in(self, start, end): - r""" - Return the matrix of the involution fixing the geodesic through - ``start`` and ``end``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.reflection_in(0, 1) - [ 1 0] - [ 2 -1] - sage: HyperbolicMethodsUHP.reflection_in(I, 2*I) - [ 1 0] - [ 0 -1] - """ - x,y = [real(k) for k in self.boundary_points(start, end)] - if x == infinity: - M = matrix(2,[1,-2*y,0,-1]) - elif y == infinity: - M = matrix(2,[1,-2*x,0,-1]) - else: - M = matrix(2,[(x+y)/(y-x),-2*x*y/(y-x),2/(y-x),-(x+y)/(y-x)]) - return M - - def common_perpendicular(self, start_1, end_1, start_2, end_2): + def common_perpendicular(self, start_1, end_1, start_2, end_2): #UHP r""" Return the unique hyperbolic geodesic perpendicular to two given geodesics, if such a geodesic exists; otherwise raise a @@ -958,13 +1061,12 @@ def common_perpendicular(self, start_1, end_1, start_2, end_2): """ A = self.reflection_in(start_1, end_1) B = self.reflection_in(start_2, end_2) - C = A*B - if self.classification(C) != 'hyperbolic': - raise ValueError("geodesics intersect; " + - "no common perpendicular exists") - return self.fixed_point_set(C) + C = A * B + if C.classification() != 'hyperbolic': + raise ValueError("geodesics intersect; no common perpendicular exists") + return C.fixed_point_set() - def intersection(self, start_1, end_1, start_2, end_2): + def intersection(self, start_1, end_1, start_2, end_2): #UHP r""" Return the point of intersection of two complete geodesics (if such a point exists). @@ -1007,123 +1109,12 @@ def intersection(self, start_1, end_1, start_2, end_2): return end_1 A = self.reflection_in(start_1, end_1) B = self.reflection_in(start_2, end_2) - C = A*B - if self.classification(C) in ['hyperbolic', 'parabolic']: + C = A * B + if C.classification() in ['hyperbolic', 'parabolic']: raise ValueError("geodesics don't intersect") - return self.fixed_point_set(C) - - def perpendicular_bisector(self, start, end): - r""" - Return the perpendicular bisector of the hyperbolic geodesic - with endpoints start and end if that geodesic has finite length. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] - sage: g = [a, b] - sage: h = HyperbolicMethodsUHP.perpendicular_bisector(*g) - sage: bool(HyperbolicMethodsUHP.intersection(*(g + h))[0] - HyperbolicMethodsUHP.midpoint(*g) < 10**-9) - True - - Complete geodesics cannot be bisected:: - - sage: HyperbolicMethodsUHP.perpendicular_bisector(0, 1) - Traceback (most recent call last): - ... - ZeroDivisionError: input matrix must be nonsingular - """ - start, end = sorted((start, end)) - d = self.point_dist(start, end)/2 - end_1, end_2 = self.boundary_points(start, end) - T = (matrix(2,[exp(d/2),0,0,exp(-d/2)])* - matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)])) - S= self._to_std_geod(end_1, start, end_2) - H = S.inverse()*T*S - return [mobius_transform(H ,k) for k in [end_1, end_2]] - - def midpoint(self, start, end): - r""" - Return the (hyperbolic) midpoint of a hyperbolic line segment. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] - sage: g = [a, b] - sage: m = HyperbolicMethodsUHP.midpoint(*g) - sage: d1 =HyperbolicMethodsUHP.point_dist(m, g[0]) - sage: d2 = HyperbolicMethodsUHP.point_dist(m, g[1]) - sage: bool(abs(d1 - d2) < 10**-9) - True - - Complete geodesics have no midpoint:: - - sage: HyperbolicMethodsUHP.midpoint(0, 2) - Traceback (most recent call last): - ... - ZeroDivisionError: input matrix must be nonsingular - """ - half_dist = self.point_dist(start, end)/2 - end_1,end_2 = self.boundary_points(start, end) - S = cls._to_std_geod(end_1, start , end_2) - T = matrix(2,[exp(half_dist), 0, 0, 1]) - M = S.inverse()*T*S - if ((real(start - end) < EPSILON) or - (abs(real(start - end)) < EPSILON and - imag(start - end) < EPSILON)): - end_p = start - else: - end_p = end - end_p = mobius_transform (M, end_p) - return end_p - - def geod_dist_from_point(self, start, end, p): - r""" - Return the hyperbolic distance from a given hyperbolic geodesic - and a hyperbolic point. - - INPUT: - - - ``other`` -- a hyperbolic point in the same model - - OUTPUT: - - - the hyperbolic distance - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) - arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) - - If `p` is a boundary point, the distance is infinity:: - - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) - +Infinity - """ - # Here is the trick for computing distance to a geodesic: - # find an isometry mapping the geodesic to the geodesic between - # 0 and infinity (so corresponding to the line imag(z) = 0. - # then any complex number is r exp(i*theta) in polar coordinates. - # the mutual perpendicular between this point and imag(z) = 0 - # intersects imag(z) = 0 at ri. So we calculate the distance - # between r exp(i*theta) and ri after we transform the original - # point. - (bd_1, bd_2) = self.boundary_points(start, end) - if bd_1 + bd_2 != infinity: - # Not a straight line - # Map the endpoints to 0 and infinity and the midpoint to 1. - T = self._crossratio_matrix(bd_1, (bd_1 + bd_2)/2, bd_2) - else: - # Is a straight line - # Map the endpoints to 0 and infinity and another endpoint - # to 1 - T = self._crossratio_matrix(bd_1, bd_1 + 1, bd_2) - x = mobius_transform(T, p) - return self.point_dist(x, abs(x)*I) + return C.fixed_point_set() - def uncomplete(self, start, end): + def uncomplete(self, start, end): #UHP r""" Return starting and ending points of a geodesic whose completion is the geodesic starting at ``start`` and ending at ``end``. @@ -1150,284 +1141,11 @@ def uncomplete(self, start, end): p2 = mobius_transform(A, 3*I) return [p1, p2] - def angle(self, start_1, end_1, start_2, end_2): - r""" - Return the angle between any two given completed geodesics if - they intersect. - - INPUT: - - -``other`` -- a hyperbolic geodesic in the UHP model - - OUTPUT: - - - the angle in radians between the two given geodesics - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: numerical_approx(HyperbolicMethodsUHP.angle(2, 4, 3, 3 + I)) - 1.57079632679490 - - If the geodesics are identical, return angle 0:: - - sage: HyperbolicMethodsUHP.angle(2, 4, 2, 4) - 0 - """ - (p_1,p_2) = sorted(self.boundary_points(start_1, end_1)) - (q_1,q_2) = sorted(self.boundary_points(start_2, end_2)) - # if the geodesics are equal, the angle between them is 0 - if (abs(p_1 - q_1) < EPSILON \ - and abs(p_2 - q_2) < EPSILON): - return 0 - elif p_2 != infinity: # geodesic not a straight line - # So we send it to the geodesic with endpoints [0, oo] - T = self._crossratio_matrix(p_1, (p_1+p_2)/2, p_2) - else: - # geodesic is a straight line, so we send it to the geodesic - # with endpoints [0,oo] - T = self._crossratio_matrix(p_1, p_1 +1, p_2) - # b_1 and b_2 are the endpoints of the image of other - b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] - # If other is now a straight line... - if (b_1 == infinity or b_2 == infinity): - # then since they intersect, they are equal - return 0 - else: - return real(arccos((b_1+b_2)/abs(b_2-b_1))) - #################### # Isometry Methods # #################### - # Move to HyperbolicIsometryUHP - def orientation_preserving(self, M): - r""" - Return ``True`` if ``self`` is orientation preserving and ``False`` - otherwise. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = identity_matrix(2) - sage: HyperbolicMethodsUHP.orientation_preserving(A) - True - sage: B = matrix(2,[0,1,1,0]) - sage: HyperbolicMethodsUHP.orientation_preserving(B) - False - """ - return bool(M.det() > 0) - - # Move to HyperbolicIsometryUHP - def classification(self, M): - r""" - Classify the hyperbolic isometry as elliptic, parabolic, or - hyperbolic. - - A hyperbolic isometry fixes two points on the boundary of - hyperbolic space, a parabolic isometry fixes one point on the - boundary of hyperbolic space, and an elliptic isometry fixes - no points. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.classification(identity_matrix(2)) - 'identity' - - sage: HyperbolicMethodsUHP.classification(4*identity_matrix(2)) - 'identity' - - sage: HyperbolicMethodsUHP.classification(matrix(2,[2,0,0,1/2])) - 'hyperbolic' - - sage: HyperbolicMethodsUHP.classification(matrix(2, [0, 3, -1/3, 6])) - 'hyperbolic' - - sage: HyperbolicMethodsUHP.classification(matrix(2,[1,1,0,1])) - 'parabolic' - - sage: HyperbolicMethodsUHP.classification(matrix(2,[-1,0,0,1])) - 'reflection' - """ - A = M.n() - A = A / (abs(A.det()).sqrt()) - tau = abs(A.trace()) - a = A.list() - if A.det() > 0: - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < - EPSILON) - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < - EPSILON) - if tf: - return 'identity' - if tau - 2 < -EPSILON: - return 'elliptic' - elif tau -2 > -EPSILON and tau - 2 < EPSILON: - return 'parabolic' - elif tau - 2 > EPSILON: - return 'hyperbolic' - else: - raise ValueError("something went wrong with classification:" + - " trace is {}".format(A.trace())) - else: #The isometry reverses orientation. - if tau < EPSILON: - return 'reflection' - else: - return 'orientation-reversing hyperbolic' - - # Move to HyperbolicIsometryUHP - def translation_length(self, M): - r""" - For hyperbolic elements, return the translation length; - otherwise, raise a ``ValueError``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.translation_length(matrix(2,[2,0,0,1/2])) - 2*arccosh(5/4) - - :: - - sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) - sage: p = exp(i*7*pi/8) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: Hp = mobius_transform(H, p) - sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) - True - """ - d = sqrt(M.det()**2) - tau = sqrt((M / sqrt(d)).trace()**2) - if self.classification(M) in ['hyperbolic', 'oriention-reversing hyperbolic']: - return 2*arccosh(tau/2) - raise TypeError("translation length is only defined for hyperbolic" - " transformations") - - # Move to HyperbolicIsometryUHP - def fixed_point_set(self, M): - r""" - Return the a list containing the fixed point set of - orientation-preserving isometries. - - OUTPUT: - - - a list of hyperbolic points - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) - sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: bool(mobius_transform(H, p1) == p1) - True - sage: bool(mobius_transform(H, p2) == p2) - True - - sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) - Traceback (most recent call last): - ... - ValueError: the identity transformation fixes the entire hyperbolic plane - """ - d = sqrt(M.det()**2) - M = M / sqrt(d) - tau = M.trace()**2 - M_cls = self.classification(M) - if M_cls == 'identity': - raise ValueError("the identity transformation fixes the entire " - "hyperbolic plane") - if M_cls == 'parabolic': - if abs(M[1,0]) < EPSILON: - return [infinity] - else: - # boundary point - return [(M[0,0] - M[1,1])/(2*M[1,0])] - elif M_cls=='elliptic': - d = sqrt(tau - 4) - return [(M[0,0] - M[1,1] + sign(M[1,0])*d)/(2*M[1,0])] - elif M_cls == 'hyperbolic': - if M[1,0]!= 0: #if the isometry doesn't fix infinity - d = sqrt(tau - 4) - p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0]) - p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0]) - return [p_1, p_2] - else: #else, it fixes infinity. - p_1 = M[0,1]/(M[1,1]-M[0,0]) - p_2 = infinity - return [p_1, p_2] - else: - # raise NotImplementedError("fixed point set not implemented for" - # " isometries of type {0}".format(M_cls)) - try: - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] - except (IndexError): - M = M.change_ring(RDF) - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] - if p[1] == 0: - p = infinity - else: - p = p[0]/p[1] - if q[1] == 0: - q = infinity - else: - q = q[0]/q[1] - pts = [p, q] - return [k for k in pts if imag(k) >= 0] - - # Move to HyperbolicIsometryUHP - def repelling_fixed_point(self, M): - r""" - For a hyperbolic isometry, return the repelling fixed point; - otherwise raise a ``ValueError``. - - OUTPUT: - - - a hyperbolic point - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = matrix(2,[4,0,0,1/4]) - sage: HyperbolicMethodsUHP.repelling_fixed_point(A) - 0 - """ - if self.classification(M) not in ['hyperbolic', - 'orientation-reversing hyperbolic']: - raise ValueError("repelling fixed point is defined only" + - "for hyperbolic isometries") - v = M.eigenmatrix_right()[1].column(1) - if v[1] == 0: - return infinity - return v[0]/v[1] - - # Move to HyperbolicIsometryUHP - def attracting_fixed_point(self, M): - r""" - For a hyperbolic isometry, return the attracting fixed point; - otherwise raise a ``ValueError``. - - OUTPUT: - - - a hyperbolic point - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = matrix(2,[4,0,0,1/4]) - sage: HyperbolicMethodsUHP.attracting_fixed_point(A) - +Infinity - """ - if self.classification(M) not in \ - ['hyperbolic', 'orientation-reversing hyperbolic']: - raise ValueError("Attracting fixed point is defined only" + - "for hyperbolic isometries.") - v = M.eigenmatrix_right()[1].column(0) - if v[1] == 0: - return infinity - return v[0]/v[1] - - def isometry_from_fixed_points(self, repel, attract): + def isometry_from_fixed_points(self, repel, attract): # UHP r""" Given two fixed points ``repel`` and ``attract`` as complex numbers return a hyperbolic isometry with ``repel`` as repelling @@ -1458,9 +1176,9 @@ def isometry_from_fixed_points(self, repel, attract): else: A = self._mobius_sending([repel, attract, infinity], [repel, attract, max(repel, attract) + 1]) - return A + return self.get_isometry(A) - def random_isometry(self, preserve_orientation=True, **kwargs): + def random_isometry(self, preserve_orientation=True, **kwargs): #UHP r""" Return a random isometry in the Upper Half Plane model. @@ -1501,7 +1219,8 @@ def random_isometry(self, preserve_orientation=True, **kwargs): # Helping Methods # ################### - def _to_std_geod(self, start, p, end): + @staticmethod + def _to_std_geod(start, p, end): #UHP r""" Given the points of a geodesic in hyperbolic space, return the hyperbolic isometry that sends that geodesic to the geodesic @@ -1520,10 +1239,35 @@ def _to_std_geod(self, start, p, end): sage: bool(mobius_transform(A, p_3) == infinity) True """ - B = matrix(2, [1, 0, 0, -I]) - return B * self._crossratio_matrix(start, p, end) + B = matrix(2, [[1, 0], [0, -I]]) + return B * HyperbolicModelUHP._crossratio_matrix(start, p, end) - def _crossratio_matrix(self, p_0, p_1, p_2): + @staticmethod + def _mobius_sending(z, w): #UHP + r""" + Given two lists ``z`` and ``w`` of three points each in + `\mathbb{CP}^1`, return the linear fractional transformation + taking the points in ``z`` to the points in ``w``. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) + True + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) + True + sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) + True + """ + if len(z) != 3 or len(w) != 3: + raise TypeError("mobius_sending requires each list to be three points long") + A = HyperbolicModelUHP._crossratio_matrix(z[0],z[1],z[2]) + B = HyperbolicModelUHP._crossratio_matrix(w[0],w[1],w[2]) + return B.inverse() * A + + @staticmethod + def _crossratio_matrix(p_0, p_1, p_2): #UHP r""" Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine coordinates, return the linear fractional transformation taking @@ -1565,32 +1309,6 @@ def _crossratio_matrix(self, p_0, p_1, p_2): return matrix(2,[p_1 - p_2, (p_1 - p_2)*(-p_0), p_1 - p_0, ( p_1 - p_0)*(-p_2)]) - def _mobius_sending(self, list1, list2): - r""" - Given two lists ``list1``, ``list2``, of three points each in - `\mathbb{CP}^1`, return the linear fractional transformation - taking the points in ``list1`` to the points in ``list2``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) - True - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) - True - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) - True - """ - if len(list1) != 3 or len(list2) != 3: - raise TypeError("mobius_sending requires each list to be three points long") - pl = list1 + list2 - z = pl[0:3] - w = pl[3:6] - A = self._crossratio_matrix(z[0],z[1],z[2]) - B = self._crossratio_matrix(w[0],w[1],w[2]) - return B.inverse() * A - class HyperbolicModelPD(HyperbolicModel): r""" Poincaré Disk Model. @@ -1705,36 +1423,6 @@ def isometry_in_model(self, A): #PD # Orientation preserving and reversing return PD_preserve_orientation(A) or PD_preserve_orientation(I*A) - def point_to_model(self, coordinates, model_name): #PD - r""" - Convert ``coordinates`` from the current model to the model - specified in ``model_name``. - - INPUT: - - - ``coordinates`` -- the coordinates of a valid point in the - current model - - ``model_name`` -- a string denoting the model to be converted to - - OUTPUT: - - - the coordinates of a point in the ``short_name`` model - - EXAMPLES:: - - sage: HyperbolicPlane.PD.point_to_model(0, 'UHP') - I - - sage: HyperbolicPlane.PD.point_to_model(I, 'UHP') - +Infinity - - sage: HyperbolicPlane.PD.point_to_model(-I, 'UHP') - 0 - """ - if model_name == 'UHP' and coordinates == I: - return infinity - return super(HyperbolicModelPD, cls).point_to_model(coordinates, model_name) - def isometry_to_model(self, A, model_name): #PD r""" Convert ``A`` from the current model to the model specified in @@ -1892,36 +1580,6 @@ def isometry_in_model(self, A): #KM return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON) - def point_to_model(self, coordinates, model_name): #KM - r""" - Convert ``coordinates`` from the current model to the model - specified in ``model_name``. - - INPUT: - - - ``coordinates`` -- the coordinates of a valid point in the - current model - - ``model_name`` -- a string denoting the model to be converted to - - OUTPUT: - - - the coordinates of a point in the ``short_name`` model - - EXAMPLES:: - - sage: HyperbolicPlane.KM.point_to_model((0, 0), 'UHP') - I - - sage: HyperbolicPlane.KM.point_to_model((0, 0), 'HM') - (0, 0, 1) - - sage: HyperbolicPlane.KM.point_to_model((0,1), 'UHP') - +Infinity - """ - if model_name == 'UHP' and tuple(coordinates) == (0,1): - return infinity - return super(HyperbolicModelKM, cls).point_to_model(coordinates, model_name) - def get_background_graphic(self, **bdry_options): #KM r""" Return a graphic object that makes the model easier to visualize. diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 89e6795f789..d2acc695b6c 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -179,9 +179,6 @@ def __init__(self, model, coordinates, is_boundary, check=True, **graphics_optio def _cached_coordinates(self): r""" The representation of the current point used for calculations. - For example, if the current model uses the HyperbolicMethodsUHP - class, then ``_cached_coordinates`` will hold the upper half plane - representation of ``self.coordinates()``. EXAMPLES:: @@ -189,8 +186,9 @@ def _cached_coordinates(self): sage: A._cached_coordinates I """ - return self.parent().point_to_model(self.coordinates(), - self._HMethods.model_name()) + R = self.parent().realization_of().a_realization() + return R(self).coordinates() + def _repr_(self): r""" Return a string representation of ``self``. @@ -404,59 +402,6 @@ def graphics_options(self): # Methods implemented in _HMethods # #################################### - def dist(self, other): - r""" - Calculate the hyperbolic distance between two points in the same - model. - - INPUT: - - - ``other`` -- a hyperbolic point in the same model as ``self`` - - OUTPUT: - - - the hyperbolic distance - - EXAMPLES:: - - sage: UHP = HyperbolicPlane().UHP() - sage: p1 = UHP.get_point(5 + 7*I) - sage: p2 = UHP.get_point(1.0 + I) - sage: p1.dist(p2) - 2.23230104635820 - - sage: p1 = HyperbolicPlane().PD().get_point(0) - sage: p2 = HyperbolicPlane().PD().get_point(I/2) - sage: p1.dist(p2) - arccosh(5/3) - - sage: UHP(p1).dist(UHP(p2)) - arccosh(5/3) - - sage: p1 = HyperbolicPlane().KM().get_point((0, 0)) - sage: p2 = HyperbolicPlane().KM().get_point((1/2, 1/2)) - sage: numerical_approx(p1.dist(p2)) - 0.881373587019543 - - sage: p1 = HyperbolicPlane().HM().get_point((0,0,1)) - sage: p2 = HyperbolicPlane().HM().get_point((1,0,sqrt(2))) - sage: numerical_approx(p1.dist(p2)) - 0.881373587019543 - - Distance between a point and itself is 0:: - - sage: p = HyperbolicPlane().UHP().get_point(47 + I) - sage: p.dist(p) - 0 - """ - tmp_other = other.to_model(self.model_name()) - if isinstance(other, HyperbolicPoint): - return self._HMethods.point_dist(self._cached_coordinates, tmp_other._cached_coordinates) - elif isinstance(other, HyperbolicGeodesic): - return self._HMethods.geod_dist_from_point( - *(other._cached_endpoints + self._cached_coordinates - )) - def symmetry_in(self): r""" Return the involutary isometry fixing the given point. @@ -502,6 +447,7 @@ def symmetry_in(self): sage: A*A == HyperbolicPlane().UHP().isometry(identity_matrix(2)) True """ + R = self.parent().realization_of().a_realization() A = self._HMethods.symmetry_in(self._cached_coordinates) A = self._HMethods.model().isometry_to_model(A, self.model_name()) return self.parent().get_isometry(A) @@ -567,6 +513,21 @@ class HyperbolicPointUHP(HyperbolicPoint): Boundary point in UHP 1 """ + def symmetry_in(self): + r""" + Return the involutary isometry fixing the given point. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: HyperbolicMethodsUHP.symmetry_in(3 + 2*I) + [ 3/2 -13/2] + [ 1/2 -3/2] + """ + p = self._coordinates + x, y = real(p), imag(p) + if y > 0: + return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) def show(self, boundary=True, **options): r""" From 872e453d3b274ee165e9041b4ba0d5751f377f7c Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 13 Sep 2014 22:36:14 -0700 Subject: [PATCH 025/129] Finished refactoring and most doctest pass. --- .../hyperbolic_space/hyperbolic_coercion.py | 75 +- .../hyperbolic_space/hyperbolic_geodesic.py | 711 +++++++----- .../hyperbolic_space/hyperbolic_interface.py | 330 +----- .../hyperbolic_space/hyperbolic_isometry.py | 738 +++++++----- .../hyperbolic_space/hyperbolic_methods.py | 109 -- .../hyperbolic_space/hyperbolic_model.py | 1031 ++++++----------- .../hyperbolic_space/hyperbolic_point.py | 294 +++-- 7 files changed, 1437 insertions(+), 1851 deletions(-) delete mode 100644 src/sage/geometry/hyperbolic_space/hyperbolic_methods.py diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 5a43b1b1104..60ece3bc488 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -18,18 +18,14 @@ # http://www.gnu.org/licenses/ #*********************************************************************** -from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint -from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometry from sage.categories.morphism import Morphism from sage.symbolic.pynac import I from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector -from sage.rings.all import RR from sage.rings.integer import Integer from sage.rings.infinity import infinity -#from sage.functions.other import real, imag, sqrt +from sage.functions.other import real, imag, sqrt from sage.misc.lazy_import import lazy_import -lazy_import('sage.functions.other', ['real','imag','sqrt']) lazy_import('sage.misc.misc', 'attrcall') class HyperbolicModelCoercion(Morphism): @@ -64,15 +60,15 @@ def _call_(self, x): Point in UHP 2.00000000000000 + 1.00000000000000*I sage: psi = HM.coerce_map_from(UHP) sage: psi(UHP.get_point(I)) - (0, 0, 1) + Point in HM (0, 0, 1) It is an error to try to convert a boundary point to a model that doesn't support boundary points:: - sage: HyperbolicPlane.UHP.point_to_model(infinity, 'HM') + sage: psi(UHP.get_point(infinity)) Traceback (most recent call last): ... - NotImplementedError: boundary points are not implemented for the HM model + NotImplementedError: boundary points are not implemented for the Hyperboloid Model It is an error to try to convert a boundary point to a model that doesn't support boundary points:: @@ -83,17 +79,17 @@ def _call_(self, x): NotImplementedError: boundary points are not implemented for the Hyperboloid Model """ C = self.codomain() - bdry = False - if C.is_bounded(): - if self.domain().is_bounded(): - bdry = x.is_boundary() - else: - bdry = C.boundary_point_in_model(x) - elif self.domain().is_bounded() and x.is_boundary(): + if not C.is_bounded() and self.domain().is_bounded() and x.is_boundary(): raise NotImplementedError("boundary points are not implemented for" " the {0}".format(C.name())) - return C.element_class(C, self.image_coordinates(x.coordinates()), bdry, - check=False, **x.graphics_options()) + + y = self.image_coordinates(x.coordinates()) + if self.domain().is_bounded(): + bdry = x.is_boundary() + else: + bdry = C.boundary_point_in_model(y) + + return C.element_class(C, y, bdry, check=False, **x.graphics_options()) def convert_geodesic(self, x): """ @@ -119,12 +115,17 @@ def convert_isometry(self, x): EXAMPLES:: sage: UHP = HyperbolicPlane().UHP() - sage: PD = HyperbolicPlane().PD() - sage: phi = UHP.coerce_map_from(PD) - sage: phi(PD.get_point(0.5+0.5*I)) # Wrong test... - Point in UHP 2.00000000000000 + 1.00000000000000*I + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(UHP) + sage: I2 = UHP.get_isometry(identity_matrix(2)) + sage: phi.convert_isometry(I2) + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1] """ - return self.codomain().get_isometry(self.image_isometry_matrix(x._matrix)) + C = self.codomain() + return C._Isometry(C, self.image_isometry_matrix(x._matrix), check=False) def __invert__(self): """ @@ -165,7 +166,7 @@ def image_coordinates(self, x): """ if x == infinity: return I - return (x - I)/(Integer(1) - I*x) + return (x - I) / (Integer(1) - I*x) def image_isometry_matrix(self, x): """ @@ -173,8 +174,15 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: phi = PD.coerce_map_from(UHP) + sage: phi.image_isometry_matrix(identity_matrix(2)) + [1 0] + [0 1] """ - return matrix(2,[1,-I,-I,1]) * x * matrix(2,[1,I,I,1])/Integer(2) + return matrix([[1,-I],[-I,1]]) * x * matrix([[1,I],[I,1]])/Integer(2) class CoercionUHPtoKM(HyperbolicModelCoercion): """ @@ -273,9 +281,22 @@ def image_isometry_matrix(self, x): Return the image of the matrix of the hyperbolic isometry ``x`` under ``self``. - EXAMPLES:: - """ - return matrix(2,[1,I,I,1]) * x * matrix(2,[1,-I,-I,1])/Integer(2) + EXAMPLES: + + We check that orientation-reversing isometries behave as they + should:: + + sage: PD = HyperbolicPlane().PD() + sage: UHP = HyperbolicPlane().UHP() + sage: phi = UHP.coerce_map_from(PD) + sage: phi.image_isometry_matrix(matrix([[0,I],[I,0]])) + [ 0 -1] + [-1 0] + """ + from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD + if not HyperbolicIsometryPD._orientation_preserving(x): + x = I*x + return matrix([[1,I],[I,1]]) * x * matrix([[1,-I],[-I,1]]) / Integer(2) class CoercionPDtoKM(HyperbolicModelCoercion): """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 9f2dc39295f..a23b45151af 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -44,27 +44,21 @@ from sage.structure.sage_object import SageObject from sage.symbolic.pynac import I -from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute -from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.rings.infinity import infinity from sage.rings.all import CC, RR from sage.symbolic.constants import pi +from sage.modules.free_module_element import vector +from sage.matrix.constructor import matrix +from sage.functions.other import real, imag, sqrt +from sage.functions.trig import sin, cos, arccos +from sage.functions.log import exp +from sage.functions.hyperbolic import sinh, cosh, arcsinh -lazy_import('sage.functions.other', 'real') -lazy_import('sage.functions.other', 'imag') - -lazy_import('sage.functions.trig', 'cos') -lazy_import('sage.functions.trig', 'sin') -lazy_import('sage.plot.line', 'line') -lazy_import('sage.modules.free_module_element', 'vector') -lazy_import('sage.functions.other','sqrt') -lazy_import('sage.functions.hyperbolic', 'cosh') -lazy_import('sage.functions.hyperbolic', 'sinh') -lazy_import('sage.functions.hyperbolic', 'arcsinh') +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', - ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) +from sage.misc.lazy_import import lazy_import +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'mobius_transform') class HyperbolicGeodesic(SageObject): r""" @@ -81,9 +75,17 @@ class HyperbolicGeodesic(SageObject): EXAMPLES:: - sage: UHP = HyperbolicPlane().UHP() - sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I)) - sage: g = UHP.get_geodesic(I, 2 + I) + sage: HyperbolicPlane().UHP().get_geodesic(1, 0) + Geodesic in UHP from 1 to 0 + + sage: HyperbolicPlane().PD().get_geodesic(1, 0) + Geodesic in PD from 1 to 0 + + sage: HyperbolicPlane().KM().get_geodesic((0,1/2), (1/2, 0)) + Geodesic in KM from (0, 1/2) to (1/2, 0) + + sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (0,1, sqrt(2))) + Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) """ ##################### @@ -105,43 +107,18 @@ def __init__(self, model, start, end, **graphics_options): self._graphics_options = graphics_options @lazy_attribute - def _cached_start(self): - r""" - The representation of the start point used for calculations. - - EXAMPLES:: - - sage: HyperbolicPlane().PD().get_geodesic(0, 1/2)._cached_start - I - """ - M = self._model.realization_of().a_realization() - return M(self.start()).coordinates() - - @lazy_attribute - def _cached_end(self): - r""" - The representation of the end point used for calculations. - - EXAMPLES:: - - sage: HyperbolicPlane().PD().get_geodesic(0, 1/2)._cached_end - 3/5*I + 4/5 - """ - M = self._model.realization_of().a_realization() - return M(self.end()).coordinates() - - @lazy_attribute - def _cached_endpoints(self): + def _cached_geodesic(self): r""" - The representation of the endpoints used for calculations. + The representation of the geodesic used for calculations. EXAMPLES:: sage: A = HyperbolicPlane().PD().get_geodesic(0, 1/2) - sage: A._cached_endpoints - [I, 3/5*I + 4/5] + sage: A._cached_geodesic + Geodesic in UHP from I to 3/5*I + 4/5 """ - return [self._cached_start, self._cached_end] + M = self._model.realization_of().a_realization() + return self.to_model(M) @lazy_attribute def _complete(self): @@ -200,6 +177,8 @@ def __eq__(self, other): sage: g1 == g1 True """ + if not isinstance(other, HyperbolicGeodesic): + return False return (self._model is other._model and self._start == other._start and self._end == other._end) @@ -251,39 +230,19 @@ def model(self): EXAMPLES:: sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).model() - + Hyperbolic plane in the Upper Half Plane Model model sage: HyperbolicPlane().PD().get_geodesic(0, I/2).model() - + Hyperbolic plane in the Poincare Disk Model model sage: HyperbolicPlane().KM().get_geodesic((0, 0), (0, 1/2)).model() - + Hyperbolic plane in the Klein Disk Model model sage: HyperbolicPlane().HM().get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model() - + Hyperbolic plane in the Hyperboloid Model model """ return self._model - def model_name(self): - r""" - Return the short name of the hyperbolic model. - - EXAMPLES:: - - sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).model_name() - 'UHP' - - sage: HyperbolicPlane().PD().get_geodesic(0, I/2).model_name() - 'PD' - - sage: HyperbolicPlane().KM().get_geodesic((0, 0), (0, 1/2)).model_name() - 'KM' - - sage: HyperbolicPlane().HM().get_geodesic((0, 0, 1), (0, 1, sqrt(2))).model_name() - 'HM' - """ - return self.model().short_name() - def to_model(self, model): r""" Convert the current object to image in another model. @@ -294,18 +253,20 @@ def to_model(self, model): EXAMPLES:: - sage: HyperbolicPlane().UHP().get_geodesic(I, 2*I).to_model('PD') + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: UHP.get_geodesic(I, 2*I).to_model(PD) + Geodesic in PD from 0 to 1/3*I + sage: UHP.get_geodesic(I, 2*I).to_model('PD') Geodesic in PD from 0 to 1/3*I """ - if not model.is_bounded() and self.is_complete(): - g = self.uncomplete() - return g.to_model(model).complete() + if isinstance(model, str): + model = getattr(self._model.realization_of(), model)() + if not model.is_bounded() and self.length() == infinity: + raise NotImplementedError("cannot convert to an unbounded model") start = model(self._start) end = model(self._end) g = model.get_geodesic(start, end) - if not self._model.is_bounded() and model.is_bounded() and self.is_complete(): - # Converting from a non-bounded model to a bounded model - return g.complete() return g def graphics_options(self): @@ -404,7 +365,7 @@ def is_asymptotically_parallel(self, other): return ((self != other) and ((p1 in [q1, q2]) or (p2 in [q1,q2])) and self.model() is other.model()) - def is_ultra_parallel(self,other): + def is_ultra_parallel(self, other): r""" Return ``True`` if ``self`` and ``other`` are ultra parallel and ``False`` otherwise. @@ -434,8 +395,9 @@ def is_ultra_parallel(self,other): sage: g.is_ultra_parallel(g) False """ - [R_self, R_other] = [k.reflection_in() for k in [self,other]] - return (R_self*R_other).classification() == 'hyperbolic' + A = self.reflection_involution() + B = other.reflection_involution() + return (A * B).classification() == 'hyperbolic' def is_parallel(self, other): r""" @@ -472,12 +434,9 @@ def is_parallel(self, other): sage: g.is_parallel(g) False """ - [R_self, R_other] = [k.reflection_in() for k in [self,other]] - return (R_self*R_other).classification() in ['parabolic', 'hyperbolic'] - - ################################### - # Methods implemented in _HMethods # - ################################### + A = self.reflection_involution() + B = other.reflection_involution() + return (A * B).classification() in ['parabolic', 'hyperbolic'] def ideal_endpoints(self): r""" @@ -501,19 +460,16 @@ def ideal_endpoints(self): ... NotImplementedError: boundary points are not implemented in the HM model """ - if not self.model().is_bounded(): + if not self._model.is_bounded(): raise NotImplementedError("boundary points are not implemented in the " - + "{0} model".format(self.model_name())) + + "{0} model".format(self._model.short_name())) if self.is_complete(): return self.endpoints() - ends = self._HMethods.boundary_points(*self._cached_endpoints) - ends = [self._HMethods.model().point_to_model(k, self.model_name()) for - k in ends] - return [self._model.get_bdry_point(k) for k in ends] + return [self._model(k) for k in self._cached_geodesic.ideal_endpoints()] def complete(self): r""" - Return the ideal endpoints in bounded models. Raise a + Return the geodesic with ideal endpoints in bounded models. Raise a ``NotImplementedError`` in models that are not bounded. EXAMPLES:: @@ -542,64 +498,37 @@ def complete(self): g._complete = True return g - def uncomplete(self): - r""" - Return a geodesic segment whose completion is the same as that - of ``self``. - - EXAMPLES:: - - sage: g = HyperbolicPlane().UHP().get_geodesic(I, 2 + 3*I) - sage: g.uncomplete() - Geodesic in UHP from I to 3*I + 2 - - sage: g.uncomplete().complete() == g.complete() - True - - sage: h = HyperbolicPlane().UHP().get_geodesic(2, 3) - sage: h.uncomplete().complete() - Geodesic in UHP from 2 to 3 - """ - if not self.is_complete(): - return self - ends = self._HMethods.uncomplete(*self._cached_endpoints) - ends = [self._HMethods.model().point_to_model(k, self.model_name()) - for k in ends] - return self._model.get_geodesic(*ends) - - def reflection_in(self): + def reflection_involution(self): r""" Return the involution fixing ``self``. EXAMPLES:: sage: H = HyperbolicPlane() - sage: H.UHP().get_geodesic(2,4).reflection_in() + sage: H.UHP().get_geodesic(2,4).reflection_involution() Isometry in UHP [ 3 -8] [ 1 -3] - sage: H.PD().get_geodesic0, I).reflection_in() + sage: H.PD().get_geodesic(0, I).reflection_involution() Isometry in PD [ 0 -1] [ 1 0] - sage: H.HM().get_geodesic((0,0), (0,1)).reflection_in() + sage: H.KM().get_geodesic((0,0), (0,1)).reflection_involution() Isometry in KM [-1 0 0] [ 0 1 0] [ 0 0 1] - sage: A = H.HM().get_geodesic((0,0,1), (1,0, n(sqrt(2)))).reflection_in() + sage: A = H.HM().get_geodesic((0,0,1), (1,0, n(sqrt(2)))).reflection_involution() sage: B = diagonal_matrix([1, -1, 1]) sage: bool((B - A.matrix()).norm() < 10**-9) True """ - A = self._HMethods.reflection_in(*self._cached_endpoints) - A = self._HMethods.model().isometry_to_model(A, self.model_name()) - return self._model.get_isometry(A) + return self._cached_geodesic.reflection_involution().to_model(self._model) - def common_perpendicular(self, other, **graphics_options): + def common_perpendicular(self, other): r""" Return the unique hyperbolic geodesic perpendicular to two given geodesics, if such a geodesic exists. If none exists, raise a @@ -628,99 +557,64 @@ def common_perpendicular(self, other, **graphics_options): sage: g.common_perpendicular(h) Traceback (most recent call last): ... - ValueError: geodesics intersect, no common perpendicular exists + ValueError: geodesics intersect; no common perpendicular exists """ if not self.is_parallel(other): - raise ValueError('geodesics intersect, no common perpendicular exists') - perp_ends = self._HMethods.common_perpendicular( - *(self._cached_endpoints + other._cached_endpoints)) - M = self._HMethods.model() - perp_ends = [M.point_to_model(k, self.model_name()) - for k in perp_ends] - return self._model.get_geodesic(*perp_ends, **graphics_options) - - def intersection(self, other, **graphics_options): + raise ValueError('geodesics intersect; no common perpendicular exists') + return self._cached_geodesic.common_perpendicular(other).to_model(self._model) + + def intersection(self, other): r""" Return the point of intersection of two geodesics (if such a point exists). - The option ``as_complete`` determines whether we test for the - completed geodesics to intersect, or just the segments. - INPUT: - ``other`` -- a hyperbolic geodesic in the same model as ``self`` OUTPUT: - - a hyperbolic point + - a hyperbolic point or geodesic EXAMPLES:: - sage: UHP = HyperbolicPlane().UHP() - sage: g = UHP.get_geodesic(3,5) - sage: h = UHP.get_geodesic(4,7) - sage: g.intersection(h) - Point in UHP 2/3*sqrt(-2) + 13/3 - - If the given geodesics do not intersect, raise an error:: - - sage: g = UHP.get_geodesic(4,5) - sage: h = UHP.get_geodesic(5,7) - sage: g.intersection(h) - Traceback (most recent call last): - ... - ValueError: geodesics don't intersect - - If the given geodesics are identical, return that geodesic:: - - sage: g = UHP.get_geodesic(4+I,18*I) - sage: h = UHP.get_geodesic4+I,18*I) - sage: g.intersection(h) - Geodesic in UHP from I + 4 to 18*I + sage: PD = HyperbolicPlane().PD() """ if self == other: return self elif self.is_parallel(other): raise ValueError("geodesics don't intersect") - inters = self._HMethods.intersection(*(self._cached_endpoints + - other._cached_endpoints)) + inters = self._cached_geodesic.intersection(other) if len(inters) == 2: return self elif len(inters) == 1: - inters = self._HMethods.model().point_to_model(inters[0], - self.model_name()) - return self._model.get_point(inters, **graphics_options) - else: - raise ValueError("can't calculate the intersection of" - "{1} and {2}".format(self, other)) - + return [self._model(inters[0])] + return [] - def perpendicular_bisector(self, **graphics_options): + def perpendicular_bisector(self): r""" Return the perpendicular bisector of ``self`` if ``self`` has finite length. Here distance is hyperbolic distance. EXAMPLES:: - sage: g = HyperbolicPlane().UHP().random_geodesic() + sage: g = HyperbolicPlane().PD().random_geodesic() sage: h = g.perpendicular_bisector() sage: bool(h.intersection(g).coordinates() - g.midpoint().coordinates() < 10**-9) True Complete geodesics cannot be bisected:: - sage: g = HyperbolicPlane().UHP().get_geodesic(0, 1) + sage: g = HyperbolicPlane().PD().get_geodesic(0, 1) sage: g.perpendicular_bisector() Traceback (most recent call last): ... - ValueError: perpendicular bisector is not defined for complete geodesics + ValueError: the length must be finite """ - UHP = self._model.realization_of().UHP - P = self.to_model(UHP).perpendicular_bisector(**graphics_options) + P = self._cached_geodesic.perpendicular_bisector() return P.to_model(self._model) - def midpoint(self, **graphics_options): + def midpoint(self): r""" Return the (hyperbolic) midpoint of a hyperbolic line segment. @@ -737,10 +631,10 @@ def midpoint(self, **graphics_options): sage: HyperbolicPlane().UHP().get_geodesic(0,2).midpoint() Traceback (most recent call last): ... - ValueError: midpoint is not defined for complete geodesics + ValueError: the length must be finite """ - UHP = self._model.realization_of().UHP - P = self.to_model(UHP).midpoint(**graphics_options) + UHP = self._model.realization_of().a_realization() + P = self.to_model(UHP).midpoint() return self._model(P) def dist(self, other): @@ -780,6 +674,8 @@ def angle(self, other): Return the angle between any two given geodesics if they intersect. + INPUT: + - ``other`` -- a hyperbolic geodesic in the same model as ``self`` OUTPUT: @@ -788,40 +684,15 @@ def angle(self, other): EXAMPLES:: - sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4) - sage: h = HyperbolicPlane().UHP().get_geodesic(3, 3+I) + sage: PD = HyperbolicPlane().PD() + sage: g = PD.get_geodesic(3/5*I + 4/5, 15/17*I + 8/17) + sage: h = PD.get_geodesic(4/5*I + 3/5, 9/13*I + 6/13) sage: g.angle(h) 1/2*pi - - It is an error to ask for the angle of two geodesics that do not - intersect:: - - sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4) - sage: h = HyperbolicPlane().UHP().get_geodesic(5, 7) - sage: g.angle(h) - Traceback (most recent call last): - ... - ValueError: geodesics do not intersect - - If the geodesics are identical, return angle 0:: - - sage: g = HyperbolicPlane().UHP().get_geodesic(2, 4) - sage: g.angle(g) - 0 """ if self.is_parallel(other): raise ValueError("geodesics do not intersect") - if not (self.is_complete() and other.is_complete()): - try: - # Make sure the segments intersect. - self.intersection(other) - except ValueError: - print("Warning: Geodesic segments do not intersect. " - "The angle between them is not defined.\n" - "Returning the angle between their completions.") - return self.complete().angle(other.complete()) - return self._HMethods.angle(*(self._cached_endpoints + - other._cached_endpoints)) + return self._cached_geodesic.angle(other) def length(self): r""" @@ -833,7 +704,7 @@ def length(self): sage: g.length() arccosh(9/4) """ - return self._model._point_dist(self._start.coordinates(), self._end.coordinates()) + return self._model._dist_points(self._start.coordinates(), self._end.coordinates()) ##################################################################### ## UHP geodesics @@ -844,44 +715,47 @@ class HyperbolicGeodesicUHP(HyperbolicGeodesic): INPUT: - - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a point - in hyperbolic space representing the start of the geodesic + - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the start of the geodesic - - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point - in hyperbolic space representing the end of the geodesic + - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the end of the geodesic EXAMPLES:: sage: UHP = HyperbolicPlane().UHP() - sage: g = UHP.get_geodesic(UHP.point(I), UHP.point(2 + I)) + sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I)) sage: g = UHP.get_geodesic(I, 2 + I) """ - def reflection_in(self, start, end): + def reflection_involution(self): r""" - Return the matrix of the involution fixing the geodesic through - ``start`` and ``end``. + Return the isometry of the involution fixing the geodesic ``self``. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.reflection_in(0, 1) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_geodesic(0, 1).reflection_involution() + Isometry in UHP [ 1 0] [ 2 -1] - sage: HyperbolicMethodsUHP.reflection_in(I, 2*I) + sage: UHP.get_geodesic(I, 2*I).reflection_involution() + Isometry in UHP [ 1 0] [ 0 -1] """ - x, y = [real(k) for k in self.boundary_points(start, end)] + x, y = [real(k.coordinates()) for k in self.ideal_endpoints()] if x == infinity: - M = matrix(2, [[1,-2*y],[0,-1]]) + M = matrix([[1,-2*y],[0,-1]]) elif y == infinity: - M = matrix(2, [[1,-2*x],[0,-1]]) + M = matrix([[1,-2*x],[0,-1]]) else: - M = matrix(2, [[(x+y)/(y-x),- 2*x*y/(y-x)], [2/(y-x), -(x+y)/(y-x)]]) + M = matrix([[(x+y)/(y-x),- 2*x*y/(y-x)], [2/(y-x), -(x+y)/(y-x)]]) return self._model.get_isometry(M) def show(self, boundary=True, **options): r""" + Plot ``self``. + EXAMPLES:: sage: HyperbolicPlane().UHP().get_geodesic(0, 1).show() @@ -901,6 +775,7 @@ def show(self, boundary=True, **options): elif end_2 == CC(infinity): end_2 = (real(end_1), (imag(end_1) + 10)) end_1 = (real(end_1),imag(end_1)) + from sage.plot.line import line pic = line((end_1,end_2), **opts) if boundary: cent = min(bd_1,bd_2) @@ -935,37 +810,170 @@ def show(self, boundary=True, **options): pic = bd_pic + pic return pic - def perpendicular_bisector(self, **graphics_options): #UHP + def ideal_endpoints(self): r""" - Return the perpendicular bisector of the hyperbolic geodesic with - endpoints ``start`` and ``end`` if that geodesic has finite length. + Determine the ideal (boundary) endpoints of the complete + hyperbolic geodesic corresponding to ``self``. + + OUTPUT: + + - a list of 2 boundary points EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] - sage: g = [a, b] - sage: h = HyperbolicMethodsUHP.perpendicular_bisector(*g) - sage: bool(HyperbolicMethodsUHP.intersection(*(g + h))[0] - HyperbolicMethodsUHP.midpoint(*g) < 10**-9) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_geodesic(I, 2*I).ideal_endpoints() + [Boundary point in UHP 0, + Boundary point in UHP +Infinity] + sage: UHP.get_geodesic(1 + I, 2 + 4*I).ideal_endpoints() + [Boundary point in UHP -sqrt(65) + 9, + Boundary point in UHP sqrt(65) + 9] + """ + start = self._start.coordinates() + end = self._end.coordinates() + [x1, x2] = [real(k) for k in [start, end]] + [y1, y2] = [imag(k) for k in [start, end]] + # infinity is the first endpoint, so the other ideal endpoint + # is just the real part of the second coordinate + if start == infinity: + return [self._model.get_point(start), self._model.get_point(x2)] + # Same idea as above + if end == infinity: + return [self._model.get_point(x1), self._model.get_point(end)] + # We could also have a vertical line with two interior points + if x1 == x2: + return [self._model.get_point(x1), self._model.get_point(infinity)] + # Otherwise, we have a semicircular arc in the UHP + c = ((x1+x2)*(x2-x1) + (y1+y2)*(y2-y1)) / (2*(x2-x1)) + r = sqrt((c - x1)**2 + y1**2) + return [self._model.get_point(c - r), self._model.get_point(c + r)] + + def common_perpendicular(self, other): + r""" + Return the unique hyperbolic geodesic perpendicular to ``self`` + and ``other``, if such a geodesic exists; otherwise raise a + ``ValueError``. + + INPUT: + + - ``other`` -- a hyperbolic geodesic in current model + + OUTPUT: + + - a hyperbolic geodesic + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(2, 3) + sage: h = UHP.get_geodesic(4, 5) + sage: g.common_perpendicular(h) + Geodesic in UHP from 1/2*sqrt(3) + 7/2 to -1/2*sqrt(3) + 7/2 + + It is an error to ask for the common perpendicular of two + intersecting geodesics:: + + sage: g = UHP.get_geodesic(2, 4) + sage: h = UHP.get_geodesic(3, infinity) + sage: g.common_perpendicular(h) + Traceback (most recent call last): + ... + ValueError: geodesics intersect; no common perpendicular exists + """ + # Make sure both are in the same model + if other._model is not self._model: + other = other.to_model(self._model) + + A = self.reflection_involution() + B = other.reflection_involution() + C = A * B + if C.classification() != 'hyperbolic': + raise ValueError("geodesics intersect; no common perpendicular exists") + return C.fixed_point_set() + + def intersection(self, other): + r""" + Return the point of intersection of ``self`` and ``other`` + (if such a point exists). + + INPUT: + + - ``other`` -- a hyperbolic geodesic in the current model + + OUTPUT: + + - a list of hyperbolic points or a hyperbolic geodesic + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(3, 5) + sage: h = UHP.get_geodesic(4, 7) + sage: g.intersection(h) + [Point in UHP 2/3*sqrt(-2) + 13/3] + + If the given geodesics do not intersect, the function returns an + empty list:: + + sage: g = UHP.get_geodesic(4, 5) + sage: h = UHP.get_geodesic(5, 7) + sage: g.intersection(h) + [] + + If the given geodesics are identical, return that + geodesic:: + + sage: g = UHP.get_geodesic(4 + I, 18*I) + sage: h = UHP.get_geodesic(4 + I, 18*I) + sage: g.intersection(h) + [Boundary point in UHP -1/8*sqrt(114985) - 307/8, + Boundary point in UHP 1/8*sqrt(114985) - 307/8] + """ + start_1, end_1 = sorted(self.ideal_endpoints(), key=str) + start_2, end_2 = sorted(other.ideal_endpoints(), key=str) + if start_1 == start_2 and end_1 == end_2: # Unoriented geods are same + return [start_1, end_1] + if start_1 == start_2: + return start_1 + elif end_1 == end_2: + return end_1 + A = self.reflection_involution() + B = other.reflection_involution() + C = A * B + if C.classification() in ['hyperbolic', 'parabolic']: + return [] + return C.fixed_point_set() + + def perpendicular_bisector(self): #UHP + r""" + Return the perpendicular bisector of the hyperbolic geodesic ``self`` + if that geodesic has finite length. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.random_geodesic() + sage: h = g.perpendicular_bisector() + sage: c = lambda x: x.coordinates() + sage: bool(c(g.intersection(h).start()) - c(g.midpoint()) < 10**-9) True Infinite geodesics cannot be bisected:: - sage: HyperbolicMethodsUHP.perpendicular_bisector(0, 1) + sage: UHP.get_geodesic(0, 1).perpendicular_bisector() Traceback (most recent call last): ... ValueError: the length must be finite """ if self.length() == infinity: raise ValueError("the length must be finite") - d = self._dist_points(self._start.coordinates(), self._end.coordinates()) / 2 - end_1, end_2 = self.boundary_points(self._start, self._end) - S = self._to_std_geod(end_1, self._start, end_2) - T1 = matrix(2,[exp(d/2),0,0,exp(-d/2)])* - T2 = matrix(2,[cos(pi/4),-sin(pi/4),sin(pi/4),cos(pi/4)]) - H = S.inverse() * (T1 * T2) * S - L = [self._model.get_point(mobius_transform(H, k)) for k in [end_1, end_2]] - return self._model.get_geodesic(L[0], L[1], **graphics_options) + start = self._start.coordinates() + d = self._model._dist_points(start, self._end.coordinates()) / 2 + S = self.complete()._to_std_geod(start) + T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]]) + T2 = matrix([[cos(pi/4), -sin(pi/4)], [sin(pi/4), cos(pi/4)]]) + H = self._model.get_isometry(S.inverse() * (T1 * T2) * S) + return self._model.get_geodesic(H(self._start), H(self._end)) def midpoint(self): #UHP r""" @@ -973,18 +981,17 @@ def midpoint(self): #UHP EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: a, b = [HyperbolicMethodsUHP.random_point() for k in range(2)] - sage: g = [a, b] - sage: m = HyperbolicMethodsUHP.midpoint(*g) - sage: d1 =HyperbolicMethodsUHP.point_dist(m, g[0]) - sage: d2 = HyperbolicMethodsUHP.point_dist(m, g[1]) + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.random_geodesic() + sage: m = g.midpoint() + sage: d1 = UHP.dist(m, g.start()) + sage: d2 = UHP.dist(m, g.end()) sage: bool(abs(d1 - d2) < 10**-9) True Infinite geodesics have no midpoint:: - sage: HyperbolicMethodsUHP.midpoint(0, 2) + sage: UHP.get_geodesic(0, 2).midpoint() Traceback (most recent call last): ... ValueError: the length must be finite @@ -994,18 +1001,17 @@ def midpoint(self): #UHP start = self._start.coordinates() end = self._end.coordinates() - d = self._dist_points(start, end) / 2 - end_1, end_2 = self.boundary_points(self._start, self._end) - S = self._to_std_geod(end_1, self._start, end_2) - T = matrix(2, [[exp(half_dist), 0], [0, 1]]) + d = self._model._dist_points(start, end) / 2 + S = self.complete()._to_std_geod(start) + T = matrix([[exp(d), 0], [0, 1]]) M = S.inverse() * T * S - if ((real(start - end) < EPSILON) or - (abs(real(start - end)) < EPSILON and - imag(start - end) < EPSILON)): + if ((real(start - end) < EPSILON) + or (abs(real(start - end)) < EPSILON + and imag(start - end) < EPSILON)): end_p = start else: end_p = end - return mobius_transform(M, end_p) + return self._model.get_point(mobius_transform(M, end_p)) def angle(self, other): #UHP r""" @@ -1022,50 +1028,152 @@ def angle(self, other): #UHP EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: numerical_approx(HyperbolicMethodsUHP.angle(2, 4, 3, 3 + I)) + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(2, 4) + sage: h = UHP.get_geodesic(3, 3 + I) + sage: g.angle(h) + 1/2*pi + sage: numerical_approx(g.angle(h)) 1.57079632679490 If the geodesics are identical, return angle 0:: - sage: HyperbolicMethodsUHP.angle(2, 4, 2, 4) + sage: g.angle(g) 0 + + It is an error to ask for the angle of two geodesics that do not + intersect:: + + sage: g = UHP.get_geodesic(2, 4) + sage: h = UHP.get_geodesic(5, 7) + sage: g.angle(h) + Traceback (most recent call last): + ... + ValueError: geodesics do not intersect """ - start2,end2 = other.endpoints() - (p_1,p_2) = sorted(self._model.boundary_points(start_1, end_1)) - (q_1,q_2) = sorted(self._model.boundary_points(start_2, end_2)) + if self.is_parallel(other): + raise ValueError("geodesics do not intersect") + # Make sure the segments are complete or intersect + if (not (self.is_complete() and other.is_complete()) \ + and not self.intersection(other)): + print("Warning: Geodesic segments do not intersect. " + "The angle between them is not defined.\n" + "Returning the angle between their completions.") + + # Make sure both are in the same model + if other._model is not self._model: + other = other.to_model(self._model) + + (p1, p2) = sorted([k.coordinates() for k in self.ideal_endpoints()], key=str) + (q1, q2) = sorted([k.coordinates() for k in other.ideal_endpoints()], key=str) # if the geodesics are equal, the angle between them is 0 - if (abs(p_1 - q_1) < EPSILON \ - and abs(p_2 - q_2) < EPSILON): + if (abs(p1 - q1) < EPSILON + and abs(p2 - q2) < EPSILON): return 0 - elif p_2 != infinity: # geodesic not a straight line + elif p2 != infinity: # geodesic not a straight line # So we send it to the geodesic with endpoints [0, oo] - T = self._crossratio_matrix(p_1, (p_1+p_2)/2, p_2) + T = HyperbolicGeodesicUHP._crossratio_matrix(p1, (p1+p2)/2, p2) else: # geodesic is a straight line, so we send it to the geodesic # with endpoints [0,oo] - T = self._crossratio_matrix(p_1, p_1 +1, p_2) - # b_1 and b_2 are the endpoints of the image of other - b_1, b_2 = [mobius_transform(T, k) for k in [q_1, q_2]] + T = HyperbolicGeodesicUHP._crossratio_matrix(p1, p1+1, p2) + # b1 and b2 are the endpoints of the image of other + b1, b2 = [mobius_transform(T, k) for k in [q1, q2]] # If other is now a straight line... - if (b_1 == infinity or b_2 == infinity): + if (b1 == infinity or b2 == infinity): # then since they intersect, they are equal return 0 - else: - return real(arccos((b_1+b_2)/abs(b_2-b_1))) + return real( arccos((b1+b2) / abs(b2-b1)) ) + + ################## + # Helper methods # + ################## + def _to_std_geod(self, p): + r""" + Given the coordinates of a geodesic in hyperbolic space, return the + hyperbolic isometry that sends that geodesic to the geodesic + through 0 and infinity that also sends the point ``p`` to `i`. + + INPUT: + + - ``p`` -- the coordinates of the + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: (p1, p2, p3) = [UHP.random_point() for k in range(3)] + sage: A = UHP.get_geodesic(p1, p3)._to_std_geod(p2.coordinates()) + sage: A = UHP.get_isometry(A) + sage: bool(abs(A(p1).coordinates()) < 10**-9) + True + sage: bool(abs(A(p2).coordinates() - I) < 10**-9) + True + sage: bool(A(p3).coordinates() == infinity) + True + """ + B = matrix([[1, 0], [0, -I]]) + s = self._start.coordinates() + e = self._end.coordinates() + return B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e) + + @staticmethod + def _crossratio_matrix(p0, p1, p2): #UHP + r""" + Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine + coordinates, return the linear fractional transformation taking + the elements of `p` to `0`,`1`, and `\infty'. + + INPUT: + + - a list of three distinct elements of three distinct elements + of `\mathbb{CP}^1` in affine coordinates; that is, each element + must be a complex number, `\infty`, or symbolic. + + OUTPUT: + + - an element of `\GL(2,\CC)` + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesicUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform + sage: UHP = HyperbolicPlane().UHP() + sage: (p1, p2, p3) = [UHP.random_point().coordinates() for k in range(3)] + sage: A = HyperbolicGeodesicUHP._crossratio_matrix(p1, p2, p3) + sage: bool(abs(mobius_transform(A, p1) < 10**-9)) + True + sage: bool(abs(mobius_transform(A, p2) - 1) < 10**-9) + True + sage: bool(mobius_transform(A, p3) == infinity) + True + sage: (x,y,z) = var('x,y,z'); HyperbolicGeodesicUHP._crossratio_matrix(x,y,z) + [ y - z -x*(y - z)] + [ -x + y (x - y)*z] + """ + if p0 == infinity: + return matrix([[0, -(p1 - p2)], [-1, p2]]) + elif p1 == infinity: + return matrix([[1, -p0], [1, -p2]]) + elif p2 == infinity: + return matrix([[1, -p0], [0, p1 - p0]]) + return matrix([[p1 - p2, (p1 - p2)*(-p0)], + [p1 - p0, (p1 - p0)*(-p2)]]) + +##################################################################### +## Other geodesics class HyperbolicGeodesicPD(HyperbolicGeodesic): r""" - Create a geodesic in the Poincare disk model. + A geodesic in the Poincare disk model. INPUT: - - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a - point in hyperbolic space representing the start of the geodesic + - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the start of the geodesic - - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point - in hyperbolic space representing the end of the geodesic + - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the end of the geodesic EXAMPLES:: @@ -1073,10 +1181,10 @@ class HyperbolicGeodesicPD(HyperbolicGeodesic): sage: g = PD.get_geodesic(PD.get_point(I), PD.get_point(I/2)) sage: g = PD.get_geodesic(I, I/2) """ - _HMethods = HyperbolicMethodsUHP - def show(self, boundary=True, **options): r""" + Plot ``self``. + EXAMPLES:: sage: HyperbolicPlane().PD().get_geodesic(0, 1).show() @@ -1088,6 +1196,7 @@ def show(self, boundary=True, **options): bd_1, bd_2 = [CC(k.coordinates()) for k in self.ideal_endpoints()] # Check to see if it's a line if bool (real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))**2 < EPSILON: + from sage.plot.line import line pic = line([(real(bd_1),imag(bd_1)),(real(bd_2),imag(bd_2))], **opts) else: @@ -1127,26 +1236,26 @@ def show(self, boundary=True, **options): class HyperbolicGeodesicKM(HyperbolicGeodesic): r""" - Create a geodesic in the Klein disk model. + A geodesic in the Klein disk model. INPUT: - - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a - point in hyperbolic space representing the start of the geodesic + - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the start of the geodesic - - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point - in hyperbolic space representing the end of the geodesic + - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the end of the geodesic EXAMPLES:: sage: KM = HyperbolicPlane().KM() - sage: g = KM.get_geodesic(KM.point((0,1)), KM.point((0,1/2))) + sage: g = KM.get_geodesic(KM.get_point((0,1)), KM.get_point((0,1/2))) sage: g = KM.get_geodesic((0,1), (0,1/2)) """ - _HMethods = HyperbolicMethodsUHP - def show(self, boundary=True, **options): r""" + Plot ``self``. + EXAMPLES:: sage: HyperbolicPlane().KM().get_geodesic((0,0), (1,0)).show() @@ -1164,27 +1273,27 @@ def show(self, boundary=True, **options): class HyperbolicGeodesicHM(HyperbolicGeodesic): r""" - Create a geodesic in the hyperboloid model. + A geodesic in the hyperboloid model. INPUT: - - ``start`` -- a :class:`HyperbolicPoint` or coordinates of a - point in hyperbolic space representing the start of the geodesic + - ``start`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the start of the geodesic - - ``end`` -- a :class:`HyperbolicPoint` or coordinates of a point - in hyperbolic space representing the end of the geodesic + - ``end`` -- a :class:`HyperbolicPoint` in hyperbolic space + representing the end of the geodesic EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: HM = HyperbolicPlane().HM() - sage: g = HM.get_geodesic(HM.point((0, 0, 1)), HM.point((0,1,sqrt(2)))) + sage: g = HM.get_geodesic(HM.get_point((0, 0, 1)), HM.get_point((0,1,sqrt(2)))) sage: g = HM.get_geodesic((0, 0, 1), (0, 1, sqrt(2))) """ - _HMethods = HyperbolicMethodsUHP - def show(self, show_hyperboloid=True, **graphics_options): r""" + Plot ``self``. + EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * @@ -1212,7 +1321,7 @@ def show(self, show_hyperboloid=True, **graphics_options): hyperbola = cosh(x)*v1 + sinh(x)*v2 endtime = arcsinh(v2_ldot_u2) from sage.plot.plot3d.all import parametric_plot3d - pic = parametric_plot3d(hyperbola,(x,0, endtime),**graphics_options) + pic = parametric_plot3d(hyperbola,(x,0, endtime), **graphics_options) if show_hyperboloid: bd_pic = self._model.get_background_graphic() pic = bd_pic + pic diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index e16ca1e2caf..7278a95b512 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -13,16 +13,31 @@ UHP (upper half plane model), PD (Poincaré disk model), KM (Klein disk model), and HM (hyperboloid model). +.. NOTE:: + + All of the current models of 2 dimensional hyperbolic space + use the upper half plane model for their computations. This can + lead to some problems, such as long coordinate strings for symbolic + points. For example, the vector ``(1, 0, sqrt(2))`` defines a point + in the hyperboloid model. Performing mapping this point to the upper + half plane and performing computations there may return with vector + whose components are unsimplified strings have several ``sqrt(2)``'s. + Presently, this drawback is outweighed by the rapidity with which new + models can be implemented. + AUTHORS: - Greg Laun (2013): Initial version. +- Rania Amer, Jean-Philippe Burelle, Bill Goldman, Zach Groton, + Jeremy Lent, Leila Vaden, Derrick Wigglesworth (2011): many of the + methods spread across the files. EXAMPLES:: - sage: HyperbolicPlane.UHP.point(2 + I) + sage: HyperbolicPlane().UHP().get_point(2 + I) Point in UHP I + 2 - sage: HyperbolicPlane.PD.point(1/2 + I/2) + sage: HyperbolicPlane().PD().get_point(1/2 + I/2) Point in PD 1/2*I + 1/2 """ @@ -39,9 +54,10 @@ #*********************************************************************** from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent +from sage.misc.abstract_method import abstract_method +from sage.misc.lazy_attribute import lazy_attribute from sage.categories.sets_cat import Sets from sage.categories.realizations import Realizations, Category_realization_of_parent -from sage.misc.lazy_attribute import lazy_attribute from sage.geometry.hyperbolic_space.hyperbolic_model import ( HyperbolicModelUHP, HyperbolicModelPD, HyperbolicModelHM, HyperbolicModelKM) @@ -49,6 +65,12 @@ def HyperbolicSpace(n): """ Return ``n`` dimensional hyperbolic space. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicSpace + sage: HyperbolicSpace(2) + Hyperbolic plane """ if n == 2: return HyperbolicPlane() @@ -68,18 +90,35 @@ class HyperbolicPlane(Parent, UniqueRepresentation): def __init__(self): """ Initialize ``self``. + + EXAMPLES:: + + sage: H = HyperbolicPlane() + sage: TestSuite(H).run() """ Parent.__init__(self, category=Sets().WithRealizations()) + self.a_realization() # We create a realization so at least one is known def _repr_(self): """ Return a string representation of ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane() + Hyperbolic plane """ return "Hyperbolic plane" def a_realization(self): """ Return a realization of ``self``. + + EXAMPLES:: + + sage: H = HyperbolicPlane() + sage: H.a_realization() + Hyperbolic plane in the Upper Half Plane Model model """ return self.UHP() @@ -140,8 +179,7 @@ def super_categories(self): sage: H = HyperbolicPlane() sage: models = HyperbolicModels(H) sage: models.super_categories() - [Category of finite dimensional algebras with basis over Rational Field, - Category of realizations of Descent algebra of 4 over Rational Field] + [Category of sets, Category of realizations of Hyperbolic plane] """ return [Sets(), Realizations(self.base())] @@ -164,280 +202,32 @@ def _an_element_(self): """ return self(self.realization_of().PD().get_point(0)) + # TODO: Move to a category of metric spaces once created + @abstract_method def dist(self, a, b): """ Return the distance between ``a`` and ``b``. + + EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: PD.dist(PD.get_point(0), PD.get_point(I/2)) + arccosh(5/3) """ - R = self.realization_of().a_realization() - return R.dist(a, b) class ElementMethods: - # TODO: Move to a category of metric spaces + # TODO: Move to a category of metric spaces once created def dist(self, other): """ Return the distance between ``self`` and ``other``. - """ - return self.parent().dist(self, other) - -# TODO: Remove this class and move its doctests -class HyperbolicUserInterface(UniqueRepresentation): - r""" - Abstract base class for hyperbolic interfaces. These provide a user - interface for interacting with models of hyperbolic geometry without - having the interface dictate the class structure. - """ - @classmethod - def is_bounded(cls): - r""" - Return ``True`` if the model is bounded and ``False`` otherwise. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.is_bounded() - True - sage: HyperbolicPlane.PD.is_bounded() - True - sage: HyperbolicPlane.KM.is_bounded() - True - sage: HyperbolicPlane.HM.is_bounded() - False - """ - return cls.HModel.bounded - - @classmethod - def point(cls, p, **kwargs): - r""" - Return a :class:`HyperbolicPoint` object in the current - model with coordinates ``p``. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.point(0) - Boundary point in UHP 0 - - sage: HyperbolicPlane.PD.point(I/2) - Point in PD 1/2*I - - sage: HyperbolicPlane.KM.point((0,1)) - Boundary point in KM (0, 1) - - sage: HyperbolicPlane.HM.point((0,0,1)) - Point in HM (0, 0, 1) - """ - return cls.HFactory.get_point(p, **kwargs) - - @classmethod - def point_in_model(cls, p): - r""" - Return ``True`` if ``p`` gives the coordinates of a point in the - interior of hyperbolic space in the model. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.point_in_model(I) - True - sage: HyperbolicPlane.UHP.point_in_model(0) # Not interior point. - False - sage: HyperbolicPlane.HM.point_in_model((0,1, sqrt(2))) - True - """ - return cls.HModel.point_in_model(p) - - @classmethod - def boundary_point_in_model(cls, p): - r""" - Return ``True`` if ``p`` gives the coordinates of a point on the - ideal boundary of hyperbolic space in the current model. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.boundary_point_in_model(0) - True - sage: HyperbolicPlane.UHP.boundary_point_in_model(I) # Not boundary point - False - """ - return cls.HModel.boundary_point_in_model(p) - - @classmethod - def isometry_in_model(cls, A): - r""" - Return ``True`` if the matrix ``A`` acts isometrically on - hyperbolic space in the current model. - - EXAMPLES:: - - sage: A = matrix(2,[10,0,0,10]) # det(A) is not 1 - sage: HyperbolicPlane.UHP.isometry_in_model(A) # But A acts isometrically - True - """ - return cls.HModel.isometry_in_model(A) - - @classmethod - def geodesic(cls, start, end, **kwargs): - r""" - Return an oriented :class:`HyperbolicGeodesic` object in the - current model that starts at ``start`` and ends at ``end``. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.geodesic(1, 0) - Geodesic in UHP from 1 to 0 - - sage: HyperbolicPlane.PD.geodesic(1, 0) - Geodesic in PD from 1 to 0 - - sage: HyperbolicPlane.KM.geodesic((0,1/2), (1/2, 0)) - Geodesic in KM from (0, 1/2) to (1/2, 0) - - sage: HyperbolicPlane.HM.geodesic((0,0,1), (0,1, sqrt(2))) - Geodesic in HM from (0, 0, 1) to (0, 1, sqrt(2)) - """ - return cls.HFactory.get_geodesic(start, end, **kwargs) - - @classmethod - def isometry(cls, A): - r""" - Return an :class:`HyperbolicIsometry` object in the current model - that coincides with (in the case of linear isometry groups) or lifts - to (in the case of projective isometry groups) the matrix ``A``. - - EXAMPLES:: - - sage: HyperbolicPlane.UHP.isometry(identity_matrix(2)) - Isometry in UHP - [1 0] - [0 1] - - sage: HyperbolicPlane.PD.isometry(identity_matrix(2)) - Isometry in PD - [1 0] - [0 1] - - sage: HyperbolicPlane.KM.isometry(identity_matrix(3)) - Isometry in KM - [1 0 0] - [0 1 0] - [0 0 1] - - sage: HyperbolicPlane.HM.isometry(identity_matrix(3)) - Isometry in HM - [1 0 0] - [0 1 0] - [0 0 1] - """ - return cls.HFactory.get_isometry(A) - - @classmethod - def random_point(cls, **kwargs): - r""" - Return a random point in the current model. - - EXAMPLES:: - - sage: p = HyperbolicPlane.UHP.random_point() - """ - return cls.HPoint.random_element(**kwargs) - - @classmethod - def random_geodesic(cls, **kwargs): - r""" - Return a random geodesic in the current model. - - EXAMPLES:: - - sage: p = HyperbolicPlane.UHP.random_geodesic() - """ - return cls.HGeodesic.random_element(**kwargs) - - @classmethod - def random_isometry(cls, **kwargs): - r""" - Return a random isometry in the current model. - - EXAMPLES:: - - sage: p = HyperbolicPlane.UHP.random_isometry() - """ - return cls.HIsometry.random_element(**kwargs) - - @classmethod - def isometry_from_fixed_points(cls, p1, p2): - r""" - Given two ideal boundary points ``p1`` and ``p2``, return an - isometry of hyperbolic type that fixes ``p1`` and ``p2``. - - INPUT: - - - ``p1``, ``p2`` -- points in the ideal boundary of hyperbolic - space either as coordinates or as :class:`HyperbolicPoint`. - - OUTPUT: - - - a :class:`HyperbolicIsometry` in the current model whose - classification is hyperbolic that fixes ``p1`` and ``p2`` - - EXAMPLES:: - - sage: UHP = HyperbolicPlane.UHP - sage: UHP.isometry_from_fixed_points(0, 4) - Isometry in UHP - [ -1 0] - [-1/5 -1/5] - sage: UHP.isometry_from_fixed_points(UHP.point(0), UHP.point(4)) - Isometry in UHP - [ -1 0] - [-1/5 -1/5] - """ - return cls.HIsometry.isometry_from_fixed_points(cls.point(p1), - cls.point(p2)) - @classmethod - def dist(cls, a, b): - r""" - Return the hyperbolic distance between points ``a`` and ``b``. - - INPUT: - - - ``a`` -- a hyperbolic point - - ``b`` -- a hyperbolic point - - EXAMPLES:: - - sage: UHP = HyperbolicPlane.UHP - sage: UHP.dist(UHP.point(I), UHP.point(2*I)) - arccosh(5/4) - sage: HyperbolicPlane.UHP.dist(I, 2*I) - arccosh(5/4) - """ - try: - return a.dist(b) - except(AttributeError): - return cls.point(a).dist(cls.point(b)) - - @classmethod - def isometry_to_model(cls, A, model): - r""" - Return the image of ``A`` in the isometry group of the model - given by ``model``. - - INPUT: - - - ``A`` -- a matrix acting isometrically on the current model - - ``model`` -- the name of an implemented model of hyperbolic - space of the same dimension + EXAMPLES:: - EXAMPLES:: + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1 + I) + sage: p1.dist(p2) + arccosh(33/7) + """ + return self.parent().dist(self, other) - sage: UHP = HyperbolicPlane.UHP - sage: I2 = identity_matrix(2) - sage: UHP.isometry_to_model(I2, 'HM') - [1 0 0] - [0 1 0] - [0 0 1] - sage: UHP.isometry_to_model(UHP.isometry(I2), 'PD') - [1 0] - [0 1] - """ - if isinstance(A, HyperbolicIsometry): - A = A.matrix() - return cls.HModel.isometry_to_model(A, model) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 601c851d4fd..c3e74125055 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -20,11 +20,12 @@ We can construct isometries in the upper half plane model, abbreviated UHP for convenience:: - sage: HyperbolicPlane.UHP.isometry(matrix(2,[1,2,3,4])) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_isometry(matrix(2,[1,2,3,4])) Isometry in UHP [1 2] [3 4] - sage: A = HyperbolicPlane.UHP.isometry(matrix(2,[0,1,1,0])) + sage: A = UHP.get_isometry(matrix(2,[0,1,1,0])) sage: A.inverse() Isometry in UHP [0 1] @@ -42,14 +43,18 @@ from sage.categories.homset import Hom from sage.categories.morphism import Morphism -from sage.misc.lazy_import import lazy_import from sage.misc.lazy_attribute import lazy_attribute -lazy_import('sage.modules.free_module_element', 'vector') +from sage.matrix.constructor import matrix +from sage.modules.free_module_element import vector +from sage.symbolic.pynac import I from sage.rings.infinity import infinity -from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.misc.latex import latex -from sage.rings.all import CC -from sage.functions.other import real, imag +from sage.rings.all import CC, RDF +from sage.functions.other import real, imag, sqrt +from sage.functions.all import cos, sin, arccosh, arccos, sign + +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON +from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic class HyperbolicIsometry(Morphism): r""" @@ -63,49 +68,49 @@ class HyperbolicIsometry(Morphism): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(identity_matrix(2)) - sage: B = HyperbolicIsometryHM(identity_matrix(3)) + sage: HyperbolicPlane().HM().get_isometry(identity_matrix(3)) + Isometry in HM + [1 0 0] + [0 1 0] + [0 0 1] """ ##################### # "Private" Methods # ##################### - def __init__(self, model, A): + def __init__(self, model, A, check=True): r""" See :class:`HyperbolicIsometry` for full documentation. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(matrix(2, [0,1,-1,0])) - Isometry in UHP - [ 0 1] - [-1 0] + sage: A = HyperbolicPlane().UHP().get_isometry(matrix(2, [0,1,-1,0])) + sage: TestSuite(A).run(skip="_test_category") """ - model.isometry_test(A) + if check: + model.isometry_test(A) self._matrix = A Morphism.__init__(self, Hom(model, model)) @lazy_attribute - def _cached_matrix(self): + def _cached_isometry(self): r""" The representation of the current isometry used for calculations. For example, if the current model uses the - HyperbolicMethodsUHP class, then _cached_matrix will hold the - `SL(2,\RR)` representation of ``self.matrix()``. + upper half plane, then ``_cached_isometry`` will + hold the `SL(2,\RR)` representation of ``self.matrix()``. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryHM(identity_matrix(3)) - sage: A._cached_matrix + sage: A = HyperbolicPlane().HM().get_isometry(identity_matrix(3)) + sage: A._cached_isometry + Isometry in UHP [1 0] [0 1] """ - return self.model().isometry_to_model(self.matrix(), - self._HMethods.model().short_name) + R = self.domain().realization_of().a_realization() + return self.to_model(R) def _repr_(self): r""" @@ -117,8 +122,7 @@ def _repr_(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(identity_matrix(2)) + sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) Isometry in UHP [1 0] [0 1] @@ -128,6 +132,12 @@ def _repr_(self): def _repr_type(self): r""" Return the type of morphism. + + EXAMPLES:: + + sage: A = HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) + sage: A._repr_type() + 'Isometry' """ return "Isometry" @@ -135,15 +145,14 @@ def _latex_(self): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicPlane.UHP.isometry(identity_matrix(2)) - sage: B = HyperbolicPlane.HM.isometry(identity_matrix(3)) + sage: A = HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) sage: latex(A) \pm \left(\begin{array}{rr} 1 & 0 \\ 0 & 1 \end{array}\right) + sage: B = HyperbolicPlane().HM().get_isometry(identity_matrix(3)) sage: latex(B) \left(\begin{array}{rrr} 1 & 0 & 0 \\ @@ -151,7 +160,7 @@ def _latex_(self): 0 & 0 & 1 \end{array}\right) """ - if self.model().isometry_group_is_projective: + if self.domain().is_isometry_group_projective(): return "\pm " + latex(self._matrix) else: return latex(self._matrix) @@ -162,12 +171,13 @@ def __eq__(self, other): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(identity_matrix(2)) - sage: B = HyperbolicIsometryUHP(-identity_matrix(2)) + sage: A = HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) + sage: B = HyperbolicPlane().UHP().get_isometry(-identity_matrix(2)) sage: A == B True """ + if not isinstance(other, HyperbolicIsometry): + return False pos_matrix = bool(abs(self.matrix() - other.matrix()) < EPSILON) neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) if self.domain().is_isometry_group_projective(): @@ -179,8 +189,7 @@ def __pow__(self, n): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(matrix(2,[3,1,2,1])) + sage: A = HyperbolicPlane().UHP().get_isometry(matrix(2,[3,1,2,1])) sage: A**3 Isometry in UHP [41 15] @@ -192,76 +201,66 @@ def __mul__(self, other): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) - sage: B = HyperbolicIsometryUHP(Matrix(2,[3,1,1,2])) - sage: B*A + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(Matrix(2,[5,2,1,2])) + sage: B = UHP.get_isometry(Matrix(2,[3,1,1,2])) + sage: B * A Isometry in UHP [16 8] [ 7 6] - sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) - sage: p = HyperbolicPlane.UHP.point(2 + I) - sage: A*p + sage: A = UHP.get_isometry(Matrix(2,[5,2,1,2])) + sage: p = UHP.get_point(2 + I) + sage: A * p Point in UHP 8/17*I + 53/17 - sage: g = HyperbolicPlane.UHP.geodesic(2 + I,4 + I) - sage: A*g + sage: g = UHP.get_geodesic(2 + I, 4 + I) + sage: A * g Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37 sage: A = diagonal_matrix([1, -1, 1]) - sage: A = HyperbolicIsometryHM(A) - sage: A.orientation_preserving() + sage: A = HyperbolicPlane().HM().get_isometry(A) + sage: A.preserves_orientation() False - sage: p = HyperbolicPlane.HM.point((0, 1, sqrt(2))) - sage: A*p + sage: p = HyperbolicPlane().HM().get_point((0, 1, sqrt(2))) + sage: A * p Point in HM (0, -1, sqrt(2)) """ - from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic - from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint - if self.model_name() != other.model_name(): - raise TypeError("{0} and {1} are not in the same" - "model".format(self, other)) if isinstance(other, HyperbolicIsometry): - return self.__class__(self.domain(), self._matrix*other._matrix) - elif isinstance(other, HyperbolicPoint): - return self._model.get_point(self.model().isometry_act_on_point( - self._matrix, other.coordinates())) - elif isinstance(other, HyperbolicGeodesic): - return self._model.get_geodesic(self*other.start(), self*other.end()) - else: - NotImplementedError("multiplication is not defined between a " - "hyperbolic isometry and {0}".format(other)) + other = other.to_model(self.codomain()) + return self.__class__(self.codomain(), self._matrix*other._matrix) + from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint + if isinstance(other, HyperbolicPoint): + return self(other) + if isinstance(other, HyperbolicGeodesic): + return self.codomain().get_geodesic(self(other.start()), self(other.end())) + + raise NotImplementedError("multiplication is not defined between a " + "hyperbolic isometry and {0}".format(other)) - def _call_(self, other): + def _call_(self, p): r""" EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(Matrix(2,[5,2,1,2])) - sage: p = UHP.point(2 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(Matrix(2,[5,2,1,2])) + sage: p = UHP.get_point(2 + I) sage: A(p) - Point in UHP 8/17*I + 53/17. - - sage: g = UHP.geodesic(2 + I,4 + I) - sage: A (g) - Geodesic in UHP from 8/17*I + 53/17 to 8/37*I + 137/37. + Point in UHP 8/17*I + 53/17 sage: A = diagonal_matrix([1, -1, 1]) - sage: A = HyperbolicIsometryHM(A) - sage: A.orientation_preserving() + sage: A = HyperbolicPlane().HM().get_isometry(A) + sage: A.preserves_orientation() False - sage: p = HM.point((0, 1, sqrt(2))) + sage: p = HyperbolicPlane().HM().get_point((0, 1, sqrt(2))) sage: A(p) - Point in HM (0, -1, sqrt(2)). - """ - from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic - if self.domain() is not other.domain(): - raise TypeError("{0} is not in the {1} model".format(other, self.model_name())) + Point in HM (0, -1, sqrt(2)) - if isinstance(other, HyperbolicGeodesic): - return self.domain().get_geodesic(self(other.start()), self(other.end())) - return self.domain().get_point(self.model().isometry_act_on_point( - self.matrix(), other.coordinates())) + sage: I2 = UHP.get_isometry(identity_matrix(2)) + sage: p = UHP.random_point() + sage: bool(UHP.dist(I2(p), p) < 10**-9) + True + """ + return self.codomain().get_point(self._matrix * vector(p._coordinates)) ####################### # Setters and Getters # @@ -279,8 +278,8 @@ def matrix(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(-identity_matrix(2)).matrix() + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_isometry(-identity_matrix(2)).matrix() [-1 0] [ 0 -1] """ @@ -292,19 +291,27 @@ def inverse(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(matrix(2,[4,1,3,2])) + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(matrix(2,[4,1,3,2])) sage: B = A.inverse() - sage: A*B == HyperbolicIsometryUHP(identity_matrix(2)) + sage: A*B == UHP.get_isometry(identity_matrix(2)) True """ - return self.__class__(self._model, self.matrix().inverse()) + return self.__class__(self.domain(), self.matrix().inverse()) __invert__ = inverse def is_identity(self): """ Return ``True`` if ``self`` is the identity isometry. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_isometry(matrix(2,[4,1,3,2])).is_identity() + False + sage: UHP.get_isometry(identity_matrix(2)).is_identity() + True """ return self._matrix.is_one() @@ -314,42 +321,20 @@ def model(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(identity_matrix(2)).model() - + sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2)).model() + Hyperbolic plane in the Upper Half Plane Model model - sage: HyperbolicIsometryPD(identity_matrix(2)).model() - + sage: HyperbolicPlane().PD().get_isometry(identity_matrix(2)).model() + Hyperbolic plane in the Poincare Disk Model model - sage: HyperbolicIsometryKM(identity_matrix(3)).model() - + sage: HyperbolicPlane().KM().get_isometry(identity_matrix(3)).model() + Hyperbolic plane in the Klein Disk Model model - sage: HyperbolicIsometryHM(identity_matrix(3)).model() - + sage: HyperbolicPlane().HM().get_isometry(identity_matrix(3)).model() + Hyperbolic plane in the Hyperboloid Model model """ return self.domain() - def model_name(self): - r""" - Return the short name of the hyperbolic model. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: HyperbolicIsometryUHP(identity_matrix(2)).model_name() - 'UHP' - - sage: HyperbolicIsometryPD(identity_matrix(2)).model_name() - 'PD' - - sage: HyperbolicIsometryKM(identity_matrix(3)).model_name() - 'KM' - - sage: HyperbolicIsometryHM(identity_matrix(3)).model_name() - 'HM' - """ - return self.domain().short_name() - def to_model(self, other): r""" Convert the current object to image in another model. @@ -366,49 +351,53 @@ def to_model(self, other): sage: KM = H.KM() sage: HM = H.HM() - sage: I = UHP.get_isometry(identity_matrix(2)) - sage: I.to_model(HM) + sage: A = UHP.get_isometry(identity_matrix(2)) + sage: A.to_model(HM) Isometry in HM [1 0 0] [0 1 0] [0 0 1] - sage: I.to_model('HM') + sage: A.to_model('HM') Isometry in HM [1 0 0] [0 1 0] [0 0 1] - sage: I = PD.isometry(matrix(2,[I, 0, 0, -I])) - sage: I.to_model(UHP) + sage: A = PD.get_isometry(matrix([[I, 0], [0, -I]])) + sage: A.to_model(UHP) + Isometry in UHP [ 0 1] [-1 0] - - sage: I.to_model(HM) + sage: A.to_model(HM) + Isometry in HM [-1 0 0] [ 0 -1 0] [ 0 0 1] - - sage: Ito_model(KM) + sage: A.to_model(KM) + Isometry in KM [-1 0 0] [ 0 -1 0] [ 0 0 1] - sage: J = HM.isosometry(diagonal_matrix([-1, -1, 1])) - sage: J.to_model(UHP) + sage: A = HM.get_isometry(diagonal_matrix([-1, -1, 1])) + sage: A.to_model('UHP') + Isometry in UHP [ 0 -1] [ 1 0] - - sage: J.to_model(PD) + sage: A.to_model('PD') + Isometry in PD [-I 0] [ 0 I] - - sage: J.to_model(KM) + sage: A.to_model('KM') + Isometry in KM [-1 0 0] [ 0 -1 0] [ 0 0 1] """ if isinstance(other, str): - other = getattr(self.domain().realization_of(), other) + other = getattr(self.domain().realization_of(), other)() + if other is self.domain(): + return self phi = other.coerce_map_from(self.domain()) return phi.convert_isometry(self) @@ -416,26 +405,22 @@ def to_model(self, other): # Boolean Methods # ################### - def orientation_preserving(self): + def preserves_orientation(self): r""" Return ``True`` if ``self`` is orientation preserving and ``False`` otherwise. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(identity_matrix(2)) - sage: A.orientation_preserving() + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(identity_matrix(2)) + sage: A.preserves_orientation() True - sage: B = HyperbolicIsometryUHP(matrix(2,[0,1,1,0])) - sage: B.orientation_preserving() + sage: B = UHP.get_isometry(matrix(2,[0,1,1,0])) + sage: B.preserves_orientation() False """ - return self._HMethods.orientation_preserving(self._cached_matrix) - - #################################### - # Methods implemented in _HMethods # - #################################### + return self._cached_isometry.preserves_orientation() def classification(self): r""" @@ -449,22 +434,20 @@ def classification(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) + sage: UHP = HyperbolicPlane().UHP() + sage: H = UHP.get_isometry(matrix(2,[2,0,0,1/2])) sage: H.classification() 'hyperbolic' - sage: P = HyperbolicIsometryUHP(matrix(2,[1,1,0,1])) + sage: P = UHP.get_isometry(matrix(2,[1,1,0,1])) sage: P.classification() 'parabolic' - sage: E = HyperbolicIsometryUHP(matrix(2,[-1,0,0,1])) + sage: E = UHP.get_isometry(matrix(2,[-1,0,0,1])) sage: E.classification() 'reflection' """ - R = self._model.realization_of().a_realization() - return self.to_model(R).classification() - #return self.to_model(R).classification(self._cached_matrix) + return self._cached_isometry.classification() def translation_length(self): r""" @@ -473,38 +456,38 @@ def translation_length(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) + sage: UHP = HyperbolicPlane().UHP() + sage: H = UHP.get_isometry(matrix(2,[2,0,0,1/2])) sage: H.translation_length() 2*arccosh(5/4) :: - sage: f_1 = HyperbolicPlane.UHP.point(-1) - sage: f_2 = HyperbolicPlane.UHP.point(1) - sage: H = HyperbolicIsometryUHP.isometry_from_fixed_points(f_1, f_2) - sage: p = HyperbolicPlane.UHP.point(exp(i*7*pi/8)) + sage: f_1 = UHP.get_point(-1) + sage: f_2 = UHP.get_point(1) + sage: H = UHP.isometry_from_fixed_points(f_1, f_2) + sage: p = UHP.get_point(exp(i*7*pi/8)) sage: bool((p.dist(H*p) - H.translation_length()) < 10**-9) True """ - return self._HMethods.translation_length(self._cached_matrix) + return self._cached_isometry.translation_length() - def axis(self, **graphics_options): + def axis(self): r""" For a hyperbolic isometry, return the axis of the transformation; otherwise raise a ``ValueError``. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: H = HyperbolicIsometryUHP(matrix(2,[2,0,0,1/2])) + sage: UHP = HyperbolicPlane().UHP() + sage: H = UHP.get_isometry(matrix(2,[2,0,0,1/2])) sage: H.axis() Geodesic in UHP from 0 to +Infinity It is an error to call this function on an isometry that is not hyperbolic:: - sage: P = HyperbolicIsometryUHP(matrix(2,[1,4,0,1])) + sage: P = UHP.get_isometry(matrix(2,[1,4,0,1])) sage: P.axis() Traceback (most recent call last): ... @@ -513,65 +496,64 @@ def axis(self, **graphics_options): if self.classification() not in ['hyperbolic', 'orientation-reversing hyperbolic']: raise ValueError("the isometry is not hyperbolic: axis is undefined") - return self.domain().get_geodesic(*self.fixed_point_set()) + return self.fixed_point_set() - def fixed_point_set(self, **graphics_options): + def fixed_point_set(self): r""" Return the a list containing the fixed point set of orientation- preserving isometries. OUTPUT: - - a list of hyperbolic points + - a list of hyperbolic points or a hyperbolic geodesic EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: H = HyperbolicIsometryUHP(matrix(2, [-2/3,-1/3,-1/3,-2/3])) - sage: (p1,p2) = H.fixed_point_set() - sage: H*p1 == p1 + sage: KM = HyperbolicPlane().KM() + sage: H = KM.get_isometry(matrix([[5/3,0,4/3], [0,1,0], [4/3,0,5/3]])) + sage: g = H.fixed_point_set(); g + Geodesic in KM from (1, 0) to (-1, 0) + sage: H(g.start()) == g.start() True - sage: H*p2 == p2 + sage: H(g.end()) == g.end() True - sage: A = HyperbolicIsometryUHP(matrix(2,[0,1,1,0])) - sage: A.orientation_preserving() + sage: A = KM.get_isometry(matrix([[1,0,0], [0,-1,0], [0,0,1]])) + sage: A.preserves_orientation() False sage: A.fixed_point_set() - [Boundary point in UHP 1, Boundary point in UHP -1] + Geodesic in KM from (1, 0) to (-1, 0) :: - sage: B = HyperbolicIsometryUHP(identity_matrix(2)) + sage: B = KM.get_isometry(identity_matrix(3)) sage: B.fixed_point_set() Traceback (most recent call last): ... ValueError: the identity transformation fixes the entire hyperbolic plane """ - pts = self._HMethods.fixed_point_set(self._cached_matrix) - pts = [self._HMethods.model().point_to_model(k, self.model_name()) - for k in pts] - return [self.domain().get_point(k, **graphics_options) for k in pts] + M = self.domain() + pts = self._cached_isometry.fixed_point_set() + if isinstance(pts, HyperbolicGeodesic): + return pts.to_model(M) + return [M(k) for k in pts] - def fixed_geodesic(self, **graphics_options): + def fixed_geodesic(self): r""" If ``self`` is a reflection in a geodesic, return that geodesic. EXAMPLES:: - sage: A = HyperbolicPlane.UHP.isometry(matrix(2, [0, 1, 1, 0])) + sage: A = HyperbolicPlane().PD().get_isometry(matrix([[0, 1], [1, 0]])) sage: A.fixed_geodesic() - Geodesic in UHP from 1 to -1 + Geodesic in PD from -1 to 1 """ - fps = self._HMethods.fixed_point_set(self._cached_matrix) - if len(fps) < 2: + fps = self._cached_isometry.fixed_point_set() + if not isinstance(fps, HyperbolicGeodesic): raise ValueError("isometries of type {0}".format(self.classification()) - + " don't fix geodesics") - from sage.geometry.hyperbolic_space.model_factory import ModelFactory - fact = ModelFactory.find_factory(self._HMethods.model_name()) - geod = fact.get_geodesic(fps[0], fps[1]) - return geod.to_model(self.model_name()) + + " do not fix geodesics") + return fps.to_model(self.domain()) - def repelling_fixed_point(self, **graphics_options): + def repelling_fixed_point(self): r""" For a hyperbolic isometry, return the attracting fixed point; otherwise raise a ``ValueError``. @@ -582,16 +564,15 @@ def repelling_fixed_point(self, **graphics_options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(Matrix(2,[4,0,0,1/4])) sage: A.repelling_fixed_point() Boundary point in UHP 0 """ - fp = self._HMethods.repelling_fixed_point(self._cached_matrix) - fp = self._HMethods.model().point_to_model(fp, self.model_name()) + fp = self._cached_isometry.repelling_fixed_point() return self.domain().get_point(fp) - def attracting_fixed_point(self, **graphics_options): + def attracting_fixed_point(self): r""" For a hyperbolic isometry, return the attracting fixed point; otherwise raise a `ValueError``. @@ -602,46 +583,14 @@ def attracting_fixed_point(self, **graphics_options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: A = HyperbolicIsometryUHP(Matrix(2,[4,0,0,1/4])) + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(Matrix(2,[4,0,0,1/4])) sage: A.attracting_fixed_point() Boundary point in UHP +Infinity """ - fp = self._HMethods.attracting_fixed_point(self._cached_matrix) - fp = self._HMethods.model().point_to_model(fp, self.model_name()) + fp = self._cached_isometry.attracting_fixed_point() return self.domain().get_point(fp) - def isometry_from_fixed_points(self, repel, attract): - r""" - Given two fixed points ``repel`` and ``attract`` as hyperbolic - points return a hyperbolic isometry with ``repel`` as repelling - fixed point and ``attract`` as attracting fixed point. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import * - sage: p, q = [HyperbolicPlane.UHP.point(k) for k in [2 + I, 3 + I]] - sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) - Traceback (most recent call last): - ... - ValueError: fixed points of hyperbolic elements must be ideal - - sage: p, q = [HyperbolicPlane.UHP.point(k) for k in [2, 0]] - sage: HyperbolicIsometryUHP.isometry_from_fixed_points(p, q) - Isometry in UHP - [ -1 0] - [-1/3 -1/3] - """ - try: - A = self._HMethods.isometry_from_fixed_points(repel._cached_coordinates, - attract._cached_coordinates) - A = self._HMethods.model().isometry_to_model(A, self.model_name()) - return self.domain().get_isometry(A) - except(AttributeError): - repel = self.domain().get_point(repel) - attract = self.domain().get_point(attract) - return self.isometry_from_fixed_points(repel, attract) - class HyperbolicIsometryUHP(HyperbolicIsometry): r""" Create a hyperbolic isometry in the UHP model. @@ -652,23 +601,38 @@ class HyperbolicIsometryUHP(HyperbolicIsometry): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryUHP - sage: A = HyperbolicIsometryUHP(identity_matrix(2)) + sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) + Isometry in UHP + [1 0] + [0 1] """ + def _call_(self, p): + r""" + Return image of ``p`` under the action of ``self``. + + EXAMPLES:: - def orientation_preserving(self): + sage: UHP = HyperbolicPlane().UHP() + sage: I2 = UHP.get_isometry(identity_matrix(2)) + sage: p = UHP.random_point() + sage: bool(UHP.dist(I2(p), p) < 10**-9) + True + """ + return self.codomain().get_point(mobius_transform(self._matrix, p.coordinates())) + + def preserves_orientation(self): r""" Return ``True`` if ``self`` is orientation preserving and ``False`` otherwise. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: UHP = HyperbolicPlane().UHP() sage: A = identity_matrix(2) - sage: HyperbolicMethodsUHP.orientation_preserving(A) + sage: UHP.get_isometry(A).preserves_orientation() True sage: B = matrix(2,[0,1,1,0]) - sage: HyperbolicMethodsUHP.orientation_preserving(B) + sage: UHP.get_isometry(B).preserves_orientation() False """ return bool(self._matrix.det() > 0) @@ -685,23 +649,23 @@ def classification(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.classification(identity_matrix(2)) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_isometry(identity_matrix(2)).classification() 'identity' - sage: HyperbolicMethodsUHP.classification(4*identity_matrix(2)) + sage: UHP.get_isometry(4*identity_matrix(2)).classification() 'identity' - sage: HyperbolicMethodsUHP.classification(matrix(2,[2,0,0,1/2])) + sage: UHP.get_isometry(matrix(2,[2,0,0,1/2])).classification() 'hyperbolic' - sage: HyperbolicMethodsUHP.classification(matrix(2, [0, 3, -1/3, 6])) + sage: UHP.get_isometry(matrix(2, [0, 3, -1/3, 6])).classification() 'hyperbolic' - sage: HyperbolicMethodsUHP.classification(matrix(2,[1,1,0,1])) + sage: UHP.get_isometry(matrix(2,[1,1,0,1])).classification() 'parabolic' - sage: HyperbolicMethodsUHP.classification(matrix(2,[-1,0,0,1])) + sage: UHP.get_isometry(matrix(2,[-1,0,0,1])).classification() 'reflection' """ A = self._matrix.n() @@ -709,26 +673,21 @@ def classification(self): tau = abs(A.trace()) a = A.list() if A.det() > 0: - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < - EPSILON) - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < - EPSILON) + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < EPSILON) if tf: return 'identity' if tau - 2 < -EPSILON: return 'elliptic' - elif tau -2 > -EPSILON and tau - 2 < EPSILON: + if tau -2 > -EPSILON and tau - 2 < EPSILON: return 'parabolic' - elif tau - 2 > EPSILON: + if tau - 2 > EPSILON: return 'hyperbolic' - else: - raise ValueError("something went wrong with classification:" + - " trace is {}".format(A.trace())) - else: #The isometry reverses orientation. - if tau < EPSILON: - return 'reflection' - else: - return 'orientation-reversing hyperbolic' + raise ValueError("something went wrong with classification:" + + " trace is {}".format(A.trace())) + # Otherwise The isometry reverses orientation + if tau < EPSILON: + return 'reflection' + return 'orientation-reversing hyperbolic' def translation_length(self): r""" @@ -737,17 +696,16 @@ def translation_length(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.translation_length(matrix(2,[2,0,0,1/2])) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_isometry(matrix(2,[2,0,0,1/2])).translation_length() 2*arccosh(5/4) :: - sage: H = HyperbolicMethodsUHP.isometry_from_fixed_points(-1,1) - sage: p = exp(i*7*pi/8) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: Hp = mobius_transform(H, p) - sage: bool((HyperbolicMethodsUHP.point_dist(p, Hp) - HyperbolicMethodsUHP.translation_length(H)) < 10**-9) + sage: H = UHP.isometry_from_fixed_points(-1,1) + sage: p = UHP.get_point(exp(i*7*pi/8)) + sage: Hp = H(p) + sage: bool((UHP.dist(p, Hp) - H.translation_length()) < 10**-9) True """ d = sqrt(self._matrix.det()**2) @@ -758,25 +716,33 @@ def translation_length(self): def fixed_point_set(self): r""" - Return the a list containing the fixed point set of + Return the a list or geodesic containing the fixed point set of orientation-preserving isometries. OUTPUT: - - a list of hyperbolic points + - a list of hyperbolic points or a hyperbolic geodesic EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: H = matrix(2, [-2/3,-1/3,-1/3,-2/3]) - sage: (p1,p2) = HyperbolicMethodsUHP.fixed_point_set(H) - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: bool(mobius_transform(H, p1) == p1) + sage: UHP = HyperbolicPlane().UHP() + sage: H = UHP.get_isometry(matrix(2, [-2/3,-1/3,-1/3,-2/3])) + sage: g = H.fixed_point_set(); g + Geodesic in UHP from -1 to 1 + sage: H(g.start()) == g.start() True - sage: bool(mobius_transform(H, p2) == p2) + sage: H(g.end()) == g.end() True + sage: A = UHP.get_isometry(matrix(2,[0,1,1,0])) + sage: A.preserves_orientation() + False + sage: A.fixed_point_set() + Geodesic in UHP from 1 to -1 + + :: - sage: HyperbolicMethodsUHP.fixed_point_set(identity_matrix(2)) + sage: B = UHP.get_isometry(identity_matrix(2)) + sage: B.fixed_point_set() Traceback (most recent call last): ... ValueError: the identity transformation fixes the entire hyperbolic plane @@ -788,43 +754,51 @@ def fixed_point_set(self): if M_cls == 'identity': raise ValueError("the identity transformation fixes the entire " "hyperbolic plane") + + pt = self.domain().get_point if M_cls == 'parabolic': if abs(M[1,0]) < EPSILON: - return [infinity] + return [pt(infinity)] else: # boundary point - return [(M[0,0] - M[1,1]) / (2*M[1,0])] + return [pt( (M[0,0] - M[1,1]) / (2*M[1,0]) )] elif M_cls == 'elliptic': d = sqrt(tau - 4) - return [(M[0,0] - M[1,1] + sign(M[1,0])*d)/(2*M[1,0])] + return [pt( (M[0,0] - M[1,1] + sign(M[1,0])*d) / (2*M[1,0]) )] elif M_cls == 'hyperbolic': if M[1,0] != 0: #if the isometry doesn't fix infinity d = sqrt(tau - 4) p_1 = (M[0,0] - M[1,1]+d) / (2*M[1,0]) p_2 = (M[0,0] - M[1,1]-d) / (2*M[1,0]) - return [p_1, p_2] - else: #else, it fixes infinity. - p_1 = M[0,1] / (M[1,1] - M[0,0]) - p_2 = infinity - return [p_1, p_2] + return self.domain().get_geodesic(pt(p_1), pt(p_2)) + #else, it fixes infinity. + p_1 = M[0,1] / (M[1,1] - M[0,0]) + p_2 = infinity + return self.domain().get_geodesic(pt(p_1), pt(p_2)) + + try: + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + except IndexError: + M = M.change_ring(RDF) + p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] + + pts = [] + if p[1] == 0: + pts.append(infinity) else: - # raise NotImplementedError("fixed point set not implemented for" - # " isometries of type {0}".format(M_cls)) - try: - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] - except (IndexError): - M = M.change_ring(RDF) - p, q = [M.eigenvectors_right()[k][1][0] for k in range(2)] - if p[1] == 0: - p = infinity - else: - p = p[0] / p[1] - if q[1] == 0: - q = infinity - else: - q = q[0] / q[1] - pts = [p, q] - return [k for k in pts if imag(k) >= 0] + p = p[0] / p[1] + if imag(p) >= 0: + pts.append(p) + if q[1] == 0: + pts.append(infinity) + else: + q = q[0] / q[1] + if imag(q) >= 0: + pts.append(q) + pts = [pt(k) for k in pts] + if len(pts) == 2: + return self.domain().get_geodesic(*pts) + return pts def repelling_fixed_point(self): r""" @@ -836,10 +810,10 @@ def repelling_fixed_point(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: UHP = HyperbolicPlane().UHP() sage: A = matrix(2,[4,0,0,1/4]) - sage: HyperbolicMethodsUHP.repelling_fixed_point(A) - 0 + sage: UHP.get_isometry(A).repelling_fixed_point() + Boundary point in UHP 0 """ if self.classification() not in ['hyperbolic', 'orientation-reversing hyperbolic']: @@ -847,8 +821,8 @@ def repelling_fixed_point(self): "for hyperbolic isometries") v = self._matrix.eigenmatrix_right()[1].column(1) if v[1] == 0: - return infinity - return v[0] / v[1] + return self.domain().get_point(infinity) + return self.domain().get_point(v[0] / v[1]) def attracting_fixed_point(self): r""" @@ -860,10 +834,10 @@ def attracting_fixed_point(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP + sage: UHP = HyperbolicPlane().UHP() sage: A = matrix(2,[4,0,0,1/4]) - sage: HyperbolicMethodsUHP.attracting_fixed_point(A) - +Infinity + sage: UHP.get_isometry(A).attracting_fixed_point() + Boundary point in UHP +Infinity """ if self.classification() not in \ ['hyperbolic', 'orientation-reversing hyperbolic']: @@ -871,8 +845,8 @@ def attracting_fixed_point(self): "for hyperbolic isometries.") v = self._matrix.eigenmatrix_right()[1].column(0) if v[1] == 0: - return infinity - return v[0] / v[1] + return self.domain().get_point(infinity) + return self.domain().get_point(v[0] / v[1]) class HyperbolicIsometryPD(HyperbolicIsometry): r""" @@ -884,9 +858,63 @@ class HyperbolicIsometryPD(HyperbolicIsometry): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD - sage: A = HyperbolicIsometryPD(identity_matrix(2)) + sage: HyperbolicPlane().PD().get_isometry(identity_matrix(2)) + Isometry in PD + [1 0] + [0 1] """ + def _call_(self, p): + r""" + Return image of ``p`` under the action of ``self``. + + EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: I2 = PD.get_isometry(identity_matrix(2)) + sage: q = PD.random_point() + sage: bool(PD.dist(I2(q), q) < 10**-9) + True + """ + _image = mobius_transform(self._matrix, p.coordinates()) + if not self.preserves_orientation(): + _image = mobius_transform(I*matrix([[0,1],[1,0]]), _image) + return self.codomain().get_point(_image) + + def preserves_orientation(self): + """ + Return ``True`` if ``self`` preserves orientation and ``False`` + otherwise. + + EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: PD.get_isometry(matrix([[-I, 0], [0, I]])).preserves_orientation() + True + sage: PD.get_isometry(matrix([[0, I], [I, 0]])).preserves_orientation() + False + """ + return bool(self._matrix.det() > 0) and HyperbolicIsometryPD._orientation_preserving(self._matrix) + + @staticmethod + def _orientation_preserving(A): + r""" + For a matrix ``A`` of a PD isometry, determine if it preserves + orientation. + + This test is more involved than just checking the sign of + the determinant. + + EXAMPLES:: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD + sage: orient = HyperbolicIsometryPD._orientation_preserving + sage: orient(matrix([[-I, 0], [0, I]])) + True + sage: orient(matrix([[0, I], [I, 0]])) + False + """ + return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() + and abs(A[0][0]) - abs(A[0][1]) != 0) class HyperbolicIsometryKM(HyperbolicIsometry): r""" @@ -898,21 +926,93 @@ class HyperbolicIsometryKM(HyperbolicIsometry): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryKM - sage: A = HyperbolicIsometryKM(identity_matrix(3)) + sage: HyperbolicPlane().KM().get_isometry(identity_matrix(3)) + Isometry in KM + [1 0 0] + [0 1 0] + [0 0 1] """ + def _call_(self, p): + r""" + Return image of ``p`` under the action of ``self``. + + EXAMPLES:: -class HyperbolicIsometryHM(HyperbolicIsometry): + sage: KM = HyperbolicPlane().KM() + sage: I3 = KM.get_isometry(identity_matrix(3)) + sage: v = KM.random_point() + sage: bool(KM.dist(I3(v), v) < 10**-9) + True + """ + v = self._matrix * vector(list(p.coordinates()) + [1]) + if v[2] == 0: + return self.codomain().get_point(infinity) + return self.codomain().get_point(v[0:2] / v[2]) + +##################################################################### +## Helper functions + +def mobius_transform(A, z): r""" - Create a hyperbolic isometry in the HM model. + Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex + plane return the mobius transformation action of ``A`` on ``z``. INPUT: - - a matrix in `SO(2,1)` + - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers + - ``z`` -- a complex number or infinity + + OUTPUT: + + - a complex number or infinity EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryHM - sage: A = HyperbolicIsometryHM(identity_matrix(3)) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform + sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) + 2/109*I + 43/109 + sage: y = var('y') + sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) + x + I*y + + The matrix must be square and `2 \times 2`:: + + sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + sage: mobius_transform(identity_matrix(3),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring + + The matrix can be symbolic or can be a matrix over the real + or complex numbers, but must be invertible:: + + sage: (a,b,c,d) = var('a,b,c,d'); + sage: mobius_transform(matrix(2,[a,b,c,d]),I) + (I*a + b)/(I*c + d) + + sage: mobius_transform(matrix(2,[0,0,0,0]),I) + Traceback (most recent call last): + ... + TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring """ + if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: + (a,b,c,d) = A.list() + if z == infinity: + if c == 0: + return infinity + return a/c + if a*d - b*c < 0: + w = z.conjugate() # Reverses orientation + else: + w = z + if c*z + d == 0: + return infinity + return (a*w + b) / (c*w + d) + + raise TypeError("A must be an invertible 2x2 matrix over the" + " complex numbers or a symbolic ring") diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py b/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py deleted file mode 100644 index 635f3d2937b..00000000000 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_methods.py +++ /dev/null @@ -1,109 +0,0 @@ -r""" -Hyperbolic Methods - -This module should not be used directly by users. It is provided for -developers of Sage. - -This module implements computational methods for some models of -hyperbolic space. The methods may operate on points, geodesics, or -isometries of hyperbolic space. However, instead of taking -HyperbolicPoint, HyperbolicGeodesic, or HyperbolicIsometry objects as -input, they instead take the coordinates of points, the endpoints of -geodesics, or matrices. Similarly, they output coordinates or matrices -rather than Hyperbolic objects. - -The methods are factored out of the :class:`HyperbolicPoint`, -:class:`HyperbolicGeodesic`, and :class:`HyperbolicIsometry` classes -to allow the implementation of additional -models of hyperbolic space with minimal work. For example, to implement -a model of 2-dimensional hyperbolic space, as long as one provides an -isometry of that model with the upper half plane, one can use the upper -half plane methods to do computations. This prevents, for example, -having to work out an efficient algorithm for computing the midpoint of -a geodesic in every model. Isometries are implemented in the -HyperbolicModel module, and it is primarily in that model that new code -must be added to implement a new model of hyperbolic space. - -In practice, all of the current models of 2 dimensional hyperbolic space -use the upper half plane model for their computations. This can lead to -some problems, such as long coordinate strings for symbolic points. For -example, the vector ``(1, 0, sqrt(2))`` defines a point in the hyperboloid -model. Performing mapping this point to the upper half plane and -performing computations there may return with vector whose components -are unsimplified strings have several ``sqrt(2)``'s. Presently, this -drawback is outweighed by the rapidity with which new models can be -implemented. - -AUTHORS: - -- Greg Laun (2013): Refactoring, rewrites, all docstrings. -- Rania Amer (2011): some UHP and PD methods. -- Jean-Philippe Burelle (2011): some UHP and PD methods. -- Zach Groton (2011): some UHP and PD methods. -- Greg Laun (2011): some UHP and PD methods. -- Jeremy Lent (2011): some UHP and PD methods. -- Leila Vaden (2011): some UHP and PD methods. -- Derrick Wigglesworth (2011): some UHP and PD methods. -- Bill Goldman (2011): many UHP and PD methods, implemented in Mathematica. -""" - -#*********************************************************************** -# -# Copyright (C) 2013 Greg Laun -# -# -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#*********************************************************************** -from sage.structure.unique_representation import UniqueRepresentation -from sage.misc.lazy_import import lazy_import -from sage.symbolic.pynac import I -from sage.functions.all import exp, cos, sin, arccosh, arccos, sqrt, sign -from sage.functions.all import imag, real -from sage.matrix.all import matrix -from sage.rings.all import Integer, RR, RDF, infinity - - -class HyperbolicAbstractMethods(UniqueRepresentation): - r""" - The abstract base class for hyperbolic methods. Primarily serving - as a list of methods that must be implemented. - """ - HModel = HyperbolicModel - - @classmethod - def model(cls): - r""" - Return the class of the underlying hyperbolic model. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.model() - - """ - return cls.HModel - - @classmethod - def model_name(cls): - r""" - Return the short name of the underlying hyperbolic model. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.model_name() - 'UHP' - """ - return cls.HModel.short_name - - -class HyperbolicMethodsUHP(HyperbolicAbstractMethods): - r""" - Hyperbolic methods for the UHP model of hyperbolic space. - """ - HModel = HyperbolicModelUHP - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 43560229d16..b5cbc329488 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -31,11 +31,11 @@ We illustrate how the classes in this module encode data by comparing the upper half plane (UHP), Poincaré disk (PD) and hyperboloid (HM) -models. First we import:: +models. First we create:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP as U - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD as P - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelHM as H + sage: U = HyperbolicPlane().UHP() + sage: P = HyperbolicPlane().PD() + sage: H = HyperbolicPlane().HM() We note that the UHP and PD models are bounded while the HM model is not:: @@ -48,9 +48,9 @@ The isometry groups of UHP and PD are projective, while that of HM is linear: - sage: U.isometry_group_is_projective + sage: U.is_isometry_group_projective() True - sage: H.isometry_group_is_projective + sage: H.is_isometry_group_projective() False The models are responsible for determining if the coordinates of points @@ -86,22 +86,23 @@ from sage.misc.bindable_class import BindableClass from sage.misc.lazy_import import lazy_import from sage.functions.other import imag, real, sqrt -from sage.symbolic.constants import pi -from sage.functions.log import exp -from sage.functions.all import cos, sin, arccosh, arccos, sign +from sage.functions.all import arccosh from sage.rings.all import CC, RR, RDF from sage.rings.integer import Integer from sage.rings.infinity import infinity +from sage.symbolic.constants import pi from sage.symbolic.pynac import I -from sage.matrix.all import matrix +from sage.matrix.constructor import matrix from sage.categories.homset import Hom -from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON -from sage.geometry.hyperbolic_space.hyperbolic_point import HyperbolicPoint, HyperbolicPointUHP + +from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON, LORENTZ_GRAM +from sage.geometry.hyperbolic_space.hyperbolic_point import ( + HyperbolicPoint, HyperbolicPointUHP) from sage.geometry.hyperbolic_space.hyperbolic_isometry import ( - HyperbolicIsometryUHP, HyperbolicIsometryPD, - HyperbolicIsometryKM, HyperbolicIsometryHM) + HyperbolicIsometry, HyperbolicIsometryUHP, + HyperbolicIsometryPD, HyperbolicIsometryKM, mobius_transform) from sage.geometry.hyperbolic_space.hyperbolic_geodesic import ( - HyperbolicGeodesicUHP, HyperbolicGeodesicPD, + HyperbolicGeodesic, HyperbolicGeodesicUHP, HyperbolicGeodesicPD, HyperbolicGeodesicKM, HyperbolicGeodesicHM) from sage.geometry.hyperbolic_space.hyperbolic_coercion import ( CoercionUHPtoPD, CoercionUHPtoKM, CoercionUHPtoHM, @@ -118,20 +119,25 @@ class HyperbolicModel(Parent, UniqueRepresentation, BindableClass): r""" Abstract base class for hyperbolic models. """ - #name = "abstract hyperbolic space" - #short_name = "Abstract" - #bounded = False - #conformal = False - #dimension = 0 - #isometry_group = None - #isometry_group_is_projective = False - #pt_conversion_dict = {} - #isom_conversion_dict = {} + Element = HyperbolicPoint + _Geodesic = HyperbolicGeodesic + _Isometry = HyperbolicIsometry def __init__(self, space, name, short_name, bounded, conformal, dimension, isometry_group, isometry_group_is_projective): """ Initialize ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: TestSuite(UHP).run() + sage: PD = HyperbolicPlane().PD() + sage: TestSuite(PD).run() + sage: KM = HyperbolicPlane().KM() + sage: TestSuite(KM).run() + sage: HM = HyperbolicPlane().HM() + sage: TestSuite(HM).run() """ self._name = name self._short_name = short_name @@ -146,17 +152,26 @@ def __init__(self, space, name, short_name, bounded, conformal, def _repr_(self): #Abstract """ Return a string representation of ``self``. + + EXAMPLES:: + + sage: HyperbolicPlane().UHP() + Hyperbolic plane in the Upper Half Plane Model model """ return "Hyperbolic plane in the {} model".format(self._name) def _element_constructor_(self, x, is_boundary=None, **graphics_options): #Abstract """ Construct an element of ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP(2 + I) + Point in UHP I + 2 """ return self.get_point(x, is_boundary, **graphics_options) - Element = HyperbolicPoint - def name(self): #Abstract """ Return the name of this model. @@ -169,7 +184,7 @@ def name(self): #Abstract """ return self._name - def short_name(self): #Abstract + def short_name(self): """ Return the short name of this model. @@ -181,19 +196,24 @@ def short_name(self): #Abstract """ return self._short_name - def is_bounded(self): #Abstract + def is_bounded(self): """ Return ``True`` if ``self`` is a bounded model. EXAMPLES:: - sage: UHP = HyperbolicPlane().UHP() - sage: UHP.is_bounded() + sage: HyperbolicPlane().UHP().is_bounded() + True + sage: HyperbolicPlane().PD().is_bounded() + True + sage: HyperbolicPlane().KM().is_bounded() True + sage: HyperbolicPlane().HM().is_bounded() + False """ return self._bounded - def is_conformal(self): #Abstract + def is_conformal(self): """ Return ``True`` if ``self`` is a conformal model. @@ -205,7 +225,7 @@ def is_conformal(self): #Abstract """ return self._conformal - def is_isometry_group_projective(self): #Abstract + def is_isometry_group_projective(self): """ Return ``True`` if the isometry group of ``self`` is projective. @@ -217,10 +237,10 @@ def is_isometry_group_projective(self): #Abstract """ return self._isometry_group_is_projective - def point_in_model(self, p): #Abstract + def point_in_model(self, p): r""" - Return ``True`` if the point is in the given model and ``False`` - otherwise. + Return ``True`` if the point ``p`` is in the interiror of the + given model and ``False`` otherwise. INPUT: @@ -232,9 +252,9 @@ def point_in_model(self, p): #Abstract EXAMPLES:: - sage: HyperbolicPlane.UHP.point_in_model(I) + sage: HyperbolicPlane().UHP().point_in_model(I) True - sage: HyperbolicPlane.UHP.point_in_model(-I) + sage: HyperbolicPlane().UHP().point_in_model(-I) False """ return True @@ -247,8 +267,8 @@ def point_test(self, p): #Abstract EXAMPLES:: sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP - sage: HyperbolicModelUHP.point_test(2 + I) - sage: HyperbolicModelUHP.point_test(2 - I) + sage: HyperbolicPlane().UHP().point_test(2 + I) + sage: HyperbolicPlane().UHP().point_test(2 - I) Traceback (most recent call last): ... ValueError: -I + 2 is not a valid point in the UHP model @@ -272,7 +292,7 @@ def boundary_point_in_model(self, p): #Abstract EXAMPLES:: - sage: HyperbolicPlane.UHP.boundary_point_in_model(I) + sage: HyperbolicPlane().UHP().boundary_point_in_model(I) False """ return True @@ -284,9 +304,8 @@ def bdry_point_test(self, p): #Abstract EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP - sage: HyperbolicModelUHP.bdry_point_test(2) - sage: HyperbolicModelUHP.bdry_point_test(1 + I) + sage: HyperbolicPlane().UHP().bdry_point_test(2) + sage: HyperbolicPlane().UHP().bdry_point_test(1 + I) Traceback (most recent call last): ... ValueError: I + 1 is not a valid boundary point in the UHP model @@ -310,52 +329,36 @@ def isometry_in_model(self, A): #Abstract EXAMPLES:: - sage: HyperbolicPlane.UHP.isometry_in_model(identity_matrix(2)) + sage: HyperbolicPlane().UHP().isometry_in_model(identity_matrix(2)) True - sage: HyperbolicPlane.UHP.isometry_in_model(identity_matrix(3)) + sage: HyperbolicPlane().UHP().isometry_in_model(identity_matrix(3)) False """ return True - def isometry_act_on_point(self, A, p): #Abstract - r""" - Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action `A \cdot p`. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP - sage: I2 = identity_matrix(2) - sage: p = HyperbolicPlane.UHP.random_point().coordinates() - sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) - True - """ - return A * vector(p) - def isometry_test(self, A): #Abstract r""" - Test whether an isometry is in the model. + Test whether an isometry ``A`` is in the model. If the isometry is in the model, do nothing. Otherwise, raise a ``ValueError``. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP - sage: HyperbolicModelUHP.isometry_test(identity_matrix(2)) - sage: HyperbolicModelUHP.isometry_test(matrix(2, [I,1,2,1])) + sage: HyperbolicPlane().UHP().isometry_test(identity_matrix(2)) + sage: HyperbolicPlane().UHP().isometry_test(matrix(2, [I,1,2,1])) Traceback (most recent call last): ... ValueError: [I 1] - [2 1] is not a valid isometry in the UHP model. + [2 1] is not a valid isometry in the UHP model """ if not self.isometry_in_model(A): - error_string = "\n{0} is not a valid isometry in the {1} model." - raise ValueError(error_string.format(A, cls.short_name)) + error_string = "\n{0} is not a valid isometry in the {1} model" + raise ValueError(error_string.format(A, self._short_name)) - def get_point(self, coordinates, is_boundary=None, **graphics_options): #Abstract + def get_point(self, coordinates, is_boundary=None, **graphics_options): r""" Return a point in ``self``. @@ -376,7 +379,6 @@ def get_point(self, coordinates, is_boundary=None, **graphics_options): #Abstrac We can create an interior point via the coordinates:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * sage: HyperbolicPlane().UHP().get_point(2*I) Point in UHP 2*I @@ -385,18 +387,12 @@ def get_point(self, coordinates, is_boundary=None, **graphics_options): #Abstrac sage: HyperbolicPlane().UHP().get_point(23) Boundary point in UHP 23 - Or we can create both types of points:: - - sage: HyperbolicPlane().UHP().get_point(p) - Point in UHP 2*I - - sage: HyperbolicPlane().UHP().get_point(q) - Boundary point in UHP 23 + However we cannot create points outside of our model:: sage: HyperbolicPlane().UHP().get_point(12 - I) Traceback (most recent call last): ... - ValueError: -I + 12 is neither an interior nor boundary point in the UHP model + ValueError: -I + 12 is not a valid point in the UHP model :: @@ -458,6 +454,14 @@ def get_geodesic(self, start, end=None, **graphics_options): #Abstract sage: HyperbolicPlane().HM().get_geodesic((0,0,1), (1,0, sqrt(2))) Geodesic in HM from (0, 0, 1) to (1, 0, sqrt(2)) + + TESTS:: + + sage: UHP = HyperbolicPlane().UHP() + sage: g = UHP.get_geodesic(UHP.get_point(I), UHP.get_point(2 + I)) + sage: h = UHP.get_geodesic(I, 2 + I) + sage: g == h + True """ if end is None: if isinstance(start, HyperbolicGeodesic): @@ -471,28 +475,28 @@ def get_geodesic(self, start, end=None, **graphics_options): #Abstract def get_isometry(self, A): r""" - Return an isometry in the appropriate model given the matrix. + Return an isometry in ``self`` from the matrix ``A`` in the + isometry group of ``self``. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: HyperbolicFactoryUHP.get_isometry(identity_matrix(2)) + sage: HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) Isometry in UHP [1 0] [0 1] - sage: HyperbolicFactoryPD.get_isometry(identity_matrix(2)) + sage: HyperbolicPlane().PD().get_isometry(identity_matrix(2)) Isometry in PD [1 0] [0 1] - sage: HyperbolicFactoryKM.get_isometry(identity_matrix(3)) + sage: HyperbolicPlane().KM().get_isometry(identity_matrix(3)) Isometry in KM [1 0 0] [0 1 0] [0 0 1] - sage: HyperbolicFactoryHM.get_isometry(identity_matrix(3)) + sage: HyperbolicPlane().HM().get_isometry(identity_matrix(3)) Isometry in HM [1 0 0] [0 1 0] @@ -517,24 +521,27 @@ def random_element(self, **kwargs): sage: bool((p.coordinates().imag()) > 0) True - sage: p = HyperbolicPointPD.random_element() - sage: HyperbolicPlane.PD.point_in_model(p.coordinates()) + sage: p = HyperbolicPlane().PD().random_element() + sage: HyperbolicPlane().PD().point_in_model(p.coordinates()) True - sage: p = HyperbolicPointKM.random_element() - sage: HyperbolicPlane.KM.point_in_model(p.coordinates()) + sage: p = HyperbolicPlane().KM().random_element() + sage: HyperbolicPlane().KM().point_in_model(p.coordinates()) True - sage: p = HyperbolicPointHM.random_element().coordinates() + sage: p = HyperbolicPlane().HM().random_element().coordinates() sage: bool((p[0]**2 + p[1]**2 - p[2]**2 - 1) < 10**-8) True """ - return self.random_point(**kwds) + return self.random_point(**kwargs) - def random_point(self, **kwds): - """ + def random_point(self, **kwargs): + r""" Return a random point of ``self``. + The points are uniformly distributed over the rectangle + `[-10, 10] \times [0, 10 i]` in the upper half plane model. + EXAMPLES:: sage: p = HyperbolicPlane().UHP().random_point() @@ -546,21 +553,23 @@ def random_point(self, **kwds): sage: PD.point_in_model(p.coordinates()) True """ - UHP = self.realization_of().UHP() - return self(UHP.random_point(**kwargs)) + R = self.realization_of().a_realization() + return self(R.random_point(**kwargs)) def random_geodesic(self, **kwargs): r""" Return a random hyperbolic geodesic. + Return the geodesic between two random points. + EXAMPLES:: - sage: h = HyperbolicPlane().UHP().random_geodesic() + sage: h = HyperbolicPlane().PD().random_geodesic() sage: bool((h.endpoints()[0].coordinates()).imag() >= 0) True """ - UHP = self.realization_of().UHP() - g_ends = [UHP.random_point(**kwargs) for k in range(2)] + R = self.realization_of().a_realization() + g_ends = [R.random_point(**kwargs) for k in range(2)] return self.get_geodesic(self(g_ends[0]), self(g_ends[1])) def random_isometry(self, preserve_orientation=True, **kwargs): @@ -578,15 +587,15 @@ def random_isometry(self, preserve_orientation=True, **kwargs): EXAMPLES:: - sage: A = HyperbolicPlane().UHP().random_isometry() - sage: A.orientation_preserving() + sage: A = HyperbolicPlane().PD().random_isometry() + sage: A.preserves_orientation() True - sage: B = HyperbolicPlane().UHP().random_isometry(preserve_orientation=False) - sage: B.orientation_preserving() + sage: B = HyperbolicPlane().PD().random_isometry(preserve_orientation=False) + sage: B.preserves_orientation() False """ - UHP = self.realization_of().UHP() - A = UHP.random_isometry(preserve_orientation, **kwargs) + R = self.realization_of().a_realization() + A = R.random_isometry(preserve_orientation, **kwargs) return A.to_model(self) ################ @@ -610,33 +619,50 @@ def dist(self, a, b): sage: UHP = HyperbolicPlane().UHP() sage: p1 = UHP.get_point(5 + 7*I) sage: p2 = UHP.get_point(1.0 + I) - sage: p1.dist(p2) + sage: UHP.dist(p1, p2) 2.23230104635820 - sage: p1 = HyperbolicPlane().PD().get_point(0) - sage: p2 = HyperbolicPlane().PD().get_point(I/2) - sage: p1.dist(p2) + sage: PD = HyperbolicPlane().PD() + sage: p1 = PD.get_point(0) + sage: p2 = PD.get_point(I/2) + sage: PD.dist(p1, p2) arccosh(5/3) sage: UHP(p1).dist(UHP(p2)) arccosh(5/3) - sage: p1 = HyperbolicPlane().KM().get_point((0, 0)) - sage: p2 = HyperbolicPlane().KM().get_point((1/2, 1/2)) - sage: numerical_approx(p1.dist(p2)) + sage: KM = HyperbolicPlane().KM() + sage: p1 = KM.get_point((0, 0)) + sage: p2 = KM.get_point((1/2, 1/2)) + sage: numerical_approx(KM.dist(p1, p2)) 0.881373587019543 - sage: p1 = HyperbolicPlane().HM().get_point((0,0,1)) - sage: p2 = HyperbolicPlane().HM().get_point((1,0,sqrt(2))) - sage: numerical_approx(p1.dist(p2)) + sage: HM = HyperbolicPlane().HM() + sage: p1 = HM.get_point((0,0,1)) + sage: p2 = HM.get_point((1,0,sqrt(2))) + sage: numerical_approx(HM.dist(p1, p2)) 0.881373587019543 Distance between a point and itself is 0:: - sage: p = HyperbolicPlane().UHP().get_point(47 + I) - sage: p.dist(p) + sage: p = UHP.get_point(47 + I) + sage: UHP.dist(p, p) 0 + + Points on the boundary are infinitely far from interior points:: + + sage: UHP.get_point(3).dist(UHP.get_point(I)) + +Infinity + + TESTS:: + + sage: UHP.dist(UHP.get_point(I), UHP.get_point(2*I)) + arccosh(5/4) + sage: UHP.dist(I, 2*I) + arccosh(5/4) """ + coords = lambda x: self(x).coordinates() + if isinstance(a, HyperbolicGeodesic): if isinstance(b, HyperbolicGeodesic): if not a.is_parallel(b): @@ -648,7 +674,7 @@ def dist(self, a, b): p = a.intersection(perp) q = b.intersection(perp) # ...and return their distance - return self.dist(p, q) + return self._dist_points(coords(p), coords(q)) raise NotImplementedError("can only compute distance between" " ultra-parallel and interecting geodesics") @@ -656,15 +682,11 @@ def dist(self, a, b): # If only one is a geodesic, make sure it's b to make things easier a,b = b,a - if not isinstance(b, HyperbolicPoint): - raise TypeError("invalid input type") + if isinstance(b, HyperbolicGeodesic): + (p, q) = b.ideal_endpoints() + return self._dist_geod_point(coords(p), coords(q), coords(a)) - coords = lambda x: self(x).coordinates() - if isinstance(a, HyperbolicPoint): - return self._dist_points(coords(a), coords(b)) - elif isinstance(a, HyperbolicGeodesic): - return self._dist_geod_point(coords(a.start()), coords(a.end()), coords(b)) - raise TypeError("invalid input type") + return self._dist_points(coords(a), coords(b)) def _dist_points(self, p1, p2): r""" @@ -676,12 +698,12 @@ def _dist_points(self, p1, p2): EXAMPLES:: - sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) - 1.38629436111989 + sage: HyperbolicPlane().PD()._dist_points(3/5*I, 0) + arccosh(17/8) """ - UHP = self.realization_of().UHP() - phi = UHP.coerce_map_from(self) - return UHP._dist_points(phi.image_coordinates(p1), phi.image_coordinates(p2)) + R = self.realization_of().a_realization() + phi = R.coerce_map_from(self) + return R._dist_points(phi.image_coordinates(p1), phi.image_coordinates(p2)) def _dist_geod_point(self, start, end, p): r""" @@ -700,22 +722,49 @@ def _dist_geod_point(self, start, end, p): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) + sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, 1/2 + I/2, 0) arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) If `p` is a boundary point, the distance is infinity:: - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) + sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, 1/2 + I/2, 12/13*I + 5/13) +Infinity """ - UHP = self.realization_of().UHP() - phi = lambda c: UHP.coerce_map_from(self).image_coordinates(c) - return UHP._dist_geod_point(phi(start), phi(end), phi(p)) + R = self.realization_of().a_realization() + phi = lambda c: R.coerce_map_from(self).image_coordinates(c) + return R._dist_geod_point(phi(start), phi(end), phi(p)) + #################### + # Isometry methods # + #################### + + def isometry_from_fixed_points(self, repel, attract): + r""" + Given two fixed points ``repel`` and ``attract`` as hyperbolic + points return a hyperbolic isometry with ``repel`` as repelling + fixed point and ``attract`` as attracting fixed point. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: p, q = PD.get_point(1/2 + I/2), PD.get_point(6/13 + 9/13*I) + sage: PD.isometry_from_fixed_points(p, q) + Traceback (most recent call last): + ... + ValueError: fixed points of hyperbolic elements must be ideal + + sage: p, q = PD.get_point(4/5 + 3/5*I), PD.get_point(-I) + sage: PD.isometry_from_fixed_points(p, q) + Isometry in PD + [ 1/6*I - 2/3 -1/3*I - 1/6] + [ 1/3*I - 1/6 -1/6*I - 2/3] + """ + R = self.realization_of().a_realization() + return R.isometry_from_fixed_points(R(repel), R(attract)).to_model(self) ##################################################################### -## Specific models +## Upper half plane model class HyperbolicModelUHP(HyperbolicModel): r""" @@ -763,32 +812,39 @@ def _coerce_map_from_(self, X): return CoercionHMtoUHP(Hom(X, self)) return super(HyperbolicModelUHP, self)._coerce_map_from_(X) - def point_in_model(self, p): #UHP + def point_in_model(self, p): r""" Check whether a complex number lies in the open upper half plane. EXAMPLES:: - sage: HyperbolicPlane.UHP.point_in_model(1 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.point_in_model(1 + I) True - sage: HyperbolicPlane.UHP.point_in_model(infinity) + sage: UHP.point_in_model(infinity) False - sage: HyperbolicPlane.UHP.point_in_model(CC(infinity)) + sage: UHP.point_in_model(CC(infinity)) False - sage: HyperbolicPlane.UHP.point_in_model(RR(infinity)) + sage: UHP.point_in_model(RR(infinity)) False - sage: HyperbolicPlane.UHP.point_in_model(1) + sage: UHP.point_in_model(1) False - sage: HyperbolicPlane.UHP.point_in_model(12) + sage: UHP.point_in_model(12) False - sage: HyperbolicPlane.UHP.point_in_model(1 - I) + sage: UHP.point_in_model(1 - I) False - sage: HyperbolicPlane.UHP.point_in_model(-2*I) + sage: UHP.point_in_model(-2*I) + False + sage: UHP.point_in_model(I) + True + sage: UHP.point_in_model(0) # Not interior point False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() return bool(imag(CC(p)) > 0) - def boundary_point_in_model(self, p): #UHP + def boundary_point_in_model(self, p): r""" Check whether a complex number is a real number or ``\infty``. In the ``UHP.model_name_name``, this is the ideal boundary of @@ -796,42 +852,34 @@ def boundary_point_in_model(self, p): #UHP EXAMPLES:: - sage: HyperbolicPlane.UHP.boundary_point_in_model(1 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.boundary_point_in_model(1 + I) False - sage: HyperbolicPlane.UHP.boundary_point_in_model(infinity) + sage: UHP.boundary_point_in_model(infinity) True - sage: HyperbolicPlane.UHP.boundary_point_in_model(CC(infinity)) + sage: UHP.boundary_point_in_model(CC(infinity)) True - sage: HyperbolicPlane.UHP.boundary_point_in_model(RR(infinity)) + sage: UHP.boundary_point_in_model(RR(infinity)) True - sage: HyperbolicPlane.UHP.boundary_point_in_model(1) + sage: UHP.boundary_point_in_model(1) True - sage: HyperbolicPlane.UHP.boundary_point_in_model(12) + sage: UHP.boundary_point_in_model(12) True - sage: HyperbolicPlane.UHP.boundary_point_in_model(1 - I) + sage: UHP.boundary_point_in_model(1 - I) False - sage: HyperbolicPlane.UHP.boundary_point_in_model(-2*I) + sage: UHP.boundary_point_in_model(-2*I) + False + sage: UHP.boundary_point_in_model(0) + True + sage: UHP.boundary_point_in_model(I) False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() im = abs(imag(CC(p)).n()) return bool( (im < EPSILON) or (p == infinity) ) - def isometry_act_on_point(self, A, p): #UHP - r""" - Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action `A \cdot p`. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP - sage: I2 = identity_matrix(2) - sage: p = HyperbolicPlane.UHP.random_point().coordinates() - sage: bool(norm(HyperbolicModelUHP.isometry_act_on_point(I2, p) - p) < 10**-9) - True - """ - return mobius_transform(A, p) - - def isometry_in_model(self, A): #UHP + def isometry_in_model(self, A): r""" Check that ``A`` acts as an isometry on the upper half plane. That is, ``A`` must be an invertible `2 \times 2` matrix with real @@ -839,52 +887,35 @@ def isometry_in_model(self, A): #UHP EXAMPLES:: + sage: UHP = HyperbolicPlane().UHP() sage: A = matrix(2,[1,2,3,4]) - sage: HyperbolicPlane.UHP.isometry_in_model(A) + sage: UHP.isometry_in_model(A) True sage: B = matrix(2,[I,2,4,1]) - sage: HyperbolicPlane.UHP.isometry_in_model(B) + sage: UHP.isometry_in_model(B) False - """ - return bool(A.ncols() == 2 and A.nrows() == 2 and - sum([k in RR for k in A.list()]) == 4 and - abs(A.det()) > -EPSILON) - - def isometry_to_model(self, A, model_name): # UHP - r""" - Convert ``A`` from the current model to the model specified in - ``model_name``. - - INPUT: - - - ``A`` -- a matrix in the current model - - ``model_name`` -- a string denoting the model to be converted to - - OUTPUT: - - the coordinates of a point in the ``short_name`` model + An example of a matrix `A` such that `\det(A) \neq 1`, but the `A` + acts isometrically:: - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP - sage: HyperbolicModelUHP.isometry_to_model(matrix(2,[0, 1, 1, 0]),'PD') - [0 I] - [I 0] + sage: C = matrix(2,[10,0,0,10]) + sage: UHP.isometry_in_model(C) + True """ - cls.isometry_test(A) - if A.det() < 0 and model_name == 'PD': - return cls.isom_conversion_dict[model_name](I * A) - return cls.isom_conversion_dict[model_name](A) + if isinstance(A, HyperbolicIsometry): + return True + return bool(A.ncols() == 2 and A.nrows() == 2 and + sum([k in RR for k in A.list()]) == 4 and + abs(A.det()) > -EPSILON) - def get_background_graphic(self, **bdry_options): #UHP + def get_background_graphic(self, **bdry_options): r""" Return a graphic object that makes the model easier to visualize. For the upper half space, the background object is the ideal boundary. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryUHP.get_background_graphic() + sage: hp = HyperbolicPlane().UHP().get_background_graphic() """ from sage.plot.line import line bd_min = bdry_options.get('bd_min', -5) @@ -906,11 +937,11 @@ def _dist_points(self, p1, p2): EXAMPLES:: - sage: HyperbolicMethodsUHP.point_dist(4.0*I,I) + sage: HyperbolicPlane().UHP()._dist_points(4.0*I, I) 1.38629436111989 """ num = (real(p2) - real(p1))**2 + (imag(p2) - imag(p1))**2 - denom = 2*imag(p1)*imag(p2) + denom = 2 * imag(p1) * imag(p2) if denom == 0: return infinity return arccosh(1 + num/denom) @@ -922,8 +953,8 @@ def _dist_geod_point(self, start, end, p): INPUT: - - ``start`` -- the start coordinates of the geodesic - - ``end`` -- the end coordinates of the geodesic + - ``start`` -- the start ideal point coordinates of the geodesic + - ``end`` -- the end ideal point coordinates of the geodesic - ``p`` -- the coordinates of the point OUTPUT: @@ -932,13 +963,12 @@ def _dist_geod_point(self, start, end, p): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, I) + sage: HyperbolicPlane().UHP()._dist_geod_point(2, 2 + I, I) arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) If `p` is a boundary point, the distance is infinity:: - sage: HyperbolicMethodsUHP.geod_dist_from_point(2, 2 + I, 5) + sage: HyperbolicPlane().UHP()._dist_geod_point(2, 2 + I, 5) +Infinity """ # Here is the trick for computing distance to a geodesic: @@ -949,16 +979,14 @@ def _dist_geod_point(self, start, end, p): # intersects imag(z) = 0 at ri. So we calculate the distance # between r exp(i*theta) and ri after we transform the original # point. - (bd_1, bd_2) = self.boundary_points(start, end) - if bd_1 + bd_2 != infinity: - # Not a straight line + if start + end != infinity: + # Not a straight line: # Map the endpoints to 0 and infinity and the midpoint to 1. - T = self._crossratio_matrix(bd_1, (bd_1 + bd_2)/2, bd_2) + T = HyperbolicGeodesicUHP._crossratio_matrix(start, (start + end)/2, end) else: - # Is a straight line - # Map the endpoints to 0 and infinity and another endpoint - # to 1 - T = self._crossratio_matrix(bd_1, bd_1 + 1, bd_2) + # Is a straight line: + # Map the endpoints to 0 and infinity and another endpoint to 1. + T = HyperbolicGeodesicUHP._crossratio_matrix(start, start + 1, end) x = mobius_transform(T, p) return self._dist_points(x, abs(x)*I) @@ -973,179 +1001,24 @@ def random_point(self, **kwargs): EXAMPLES:: - sage: p = HyperbolicPlane().UHP().random_point() + sage: p = HyperbolicPlane().UHP().random_point().coordinates() sage: bool((p.imag()) > 0) True """ - # TODO use **kwargs to allow these to be set + # TODO: use **kwargs to allow these to be set real_min = -10 real_max = 10 imag_min = 0 imag_max = 10 - return RR.random_element(min = real_min ,max=real_max) +\ - I*RR.random_element(min = imag_min,max = imag_max) - - #################### - # Geodesic Methods # - #################### - - def boundary_points(self, p1, p2): #UHP - r""" - Given two points ``p1`` and ``p2`` in the hyperbolic plane, - determine the endpoints of the complete hyperbolic geodesic - through ``p1`` and ``p2``. - - INPUT: - - - ``p1``, ``p2`` -- points in the hyperbolic plane - - OUTPUT: - - - a list of boundary points - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.boundary_points(I, 2*I) - [0, +Infinity] - sage: HyperbolicMethodsUHP.boundary_points(1 + I, 2 + 4*I) - [-sqrt(65) + 9, sqrt(65) + 9] - """ - if p1 == p2: - raise ValueError("{} and {} are not distinct".format(p1, p2)) - [x1, x2] = [real(k) for k in [p1,p2]] - [y1, y2] = [imag(k) for k in [p1,p2]] - # infinity is the first endpoint, so the other ideal endpoint - # is just the real part of the second coordinate - if p1 == infinity: - return [p1, x2] - # Same idea as above - elif p2 == infinity: - return [x1, p2] - # We could also have a vertical line with two interior points - elif x1 == x2: - return [x1, infinity] - # Otherwise, we have a semicircular arc in the UHP - else: - c = ((x1+x2)*(x2-x1)+(y1+y2)*(y2-y1))/(2*(x2-x1)) - r = sqrt((c - x1)**2 + y1**2) - return [c-r, c + r] - - def common_perpendicular(self, start_1, end_1, start_2, end_2): #UHP - r""" - Return the unique hyperbolic geodesic perpendicular to two given - geodesics, if such a geodesic exists; otherwise raise a - ``ValueError``. - - INPUT: - - - ``other`` -- a hyperbolic geodesic in current model - - OUTPUT: - - - a hyperbolic geodesic - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.common_perpendicular(2, 3, 4, 5) - [1/2*sqrt(3) + 7/2, -1/2*sqrt(3) + 7/2] - - It is an error to ask for the common perpendicular of two - intersecting geodesics:: - - sage: HyperbolicMethodsUHP.common_perpendicular(2, 4, 3, infinity) - Traceback (most recent call last): - ... - ValueError: geodesics intersect; no common perpendicular exists - """ - A = self.reflection_in(start_1, end_1) - B = self.reflection_in(start_2, end_2) - C = A * B - if C.classification() != 'hyperbolic': - raise ValueError("geodesics intersect; no common perpendicular exists") - return C.fixed_point_set() - - def intersection(self, start_1, end_1, start_2, end_2): #UHP - r""" - Return the point of intersection of two complete geodesics - (if such a point exists). - - INPUT: - - - ``other`` -- a hyperbolic geodesic in the current model - - OUTPUT: - - - a hyperbolic point - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.intersection(3, 5, 4, 7) - [2/3*sqrt(-2) + 13/3] - - If the given geodesics do not intersect, the function raises an - error:: - - sage: HyperbolicMethodsUHP.intersection(4, 5, 5, 7) - Traceback (most recent call last): - ... - ValueError: geodesics don't intersect - - If the given geodesics are identical, return that - geodesic:: - - sage: HyperbolicMethodsUHP.intersection(4 + I, 18*I, 4 + I, 18*I) - [-1/8*sqrt(114985) - 307/8, 1/8*sqrt(114985) - 307/8] - """ - start_1, end_1 = sorted(self.boundary_points(start_1, end_1)) - start_2, end_2 = sorted(self.boundary_points(start_2, end_2)) - if start_1 == start_2 and end_1 == end_2: # Unoriented geods are same - return [start_1, end_1] - if start_1 == start_2: - return start_1 - elif end_1 == end_2: - return end_1 - A = self.reflection_in(start_1, end_1) - B = self.reflection_in(start_2, end_2) - C = A * B - if C.classification() in ['hyperbolic', 'parabolic']: - raise ValueError("geodesics don't intersect") - return C.fixed_point_set() - - def uncomplete(self, start, end): #UHP - r""" - Return starting and ending points of a geodesic whose completion - is the geodesic starting at ``start`` and ending at ``end``. - - INPUT: - - - ``start`` -- a real number or infinity - - ``end`` -- a real number or infinity - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.uncomplete(0,1) - [3/10*I + 1/10, 3/10*I + 9/10] - """ - if start + end == infinity: - p = min(real(start), real(end)) + I - else: - center = (real(start) + real(end))/Integer(2) - radius = abs(real(end) - center) - p = center + radius*I - A = self._to_std_geod(start, p, end).inverse() - p1 = mobius_transform(A, I/Integer(3)) - p2 = mobius_transform(A, 3*I) - return [p1, p2] + p = RR.random_element(min=real_min ,max=real_max) \ + + I*RR.random_element(min=imag_min, max=imag_max) + return self.get_point(p) #################### # Isometry Methods # #################### - def isometry_from_fixed_points(self, repel, attract): # UHP + def isometry_from_fixed_points(self, repel, attract): r""" Given two fixed points ``repel`` and ``attract`` as complex numbers return a hyperbolic isometry with ``repel`` as repelling @@ -1153,16 +1026,34 @@ def isometry_from_fixed_points(self, repel, attract): # UHP EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2 + I, 3 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.isometry_from_fixed_points(2 + I, 3 + I) Traceback (most recent call last): ... ValueError: fixed points of hyperbolic elements must be ideal - sage: HyperbolicMethodsUHP.isometry_from_fixed_points(2, 0) + sage: UHP.isometry_from_fixed_points(2, 0) + Isometry in UHP [ -1 0] [-1/3 -1/3] + + TESTS:: + + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.isometry_from_fixed_points(0, 4) + Isometry in UHP + [ -1 0] + [-1/5 -1/5] + sage: UHP.isometry_from_fixed_points(UHP.get_point(0), UHP.get_point(4)) + Isometry in UHP + [ -1 0] + [-1/5 -1/5] """ + if isinstance(repel, HyperbolicPoint): + repel = repel._coordinates + if isinstance(attract, HyperbolicPoint): + attract = attract._coordinates + if imag(repel) + imag(attract) > EPSILON: raise ValueError("fixed points of hyperbolic elements must be ideal") repel = real(repel) @@ -1178,7 +1069,7 @@ def isometry_from_fixed_points(self, repel, attract): # UHP [repel, attract, max(repel, attract) + 1]) return self.get_isometry(A) - def random_isometry(self, preserve_orientation=True, **kwargs): #UHP + def random_isometry(self, preserve_orientation=True, **kwargs): r""" Return a random isometry in the Upper Half Plane model. @@ -1193,11 +1084,10 @@ def random_isometry(self, preserve_orientation=True, **kwargs): #UHP EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: A = HyperbolicMethodsUHP.random_isometry() - sage: B = HyperbolicMethodsUHP.random_isometry(preserve_orientation=False) - sage: B.det() < 0 - True + sage: A = HyperbolicPlane().UHP().random_isometry() + sage: B = HyperbolicPlane().UHP().random_isometry(preserve_orientation=False) + sage: B.preserves_orientation() + False """ [a,b,c,d] = [RR.random_element() for k in range(4)] while abs(a*d - b*c) < EPSILON: @@ -1205,43 +1095,16 @@ def random_isometry(self, preserve_orientation=True, **kwargs): #UHP M = matrix(RDF, 2,[a,b,c,d]) M = M / (M.det()).abs().sqrt() if M.det() > 0: - if preserve_orientation: - return M - else: - return M*matrix(2,[0,1,1,0]) - else: - if preserve_orientation: - return M*matrix(2,[0,1,1,0]) - else: - return M + if not preserve_orientation: + M = M * matrix(2,[0,1,1,0]) + elif preserve_orientation: + M = M * matrix(2,[0,1,1,0]) + return self._Isometry(self, M, check=False) ################### # Helping Methods # ################### - @staticmethod - def _to_std_geod(start, p, end): #UHP - r""" - Given the points of a geodesic in hyperbolic space, return the - hyperbolic isometry that sends that geodesic to the geodesic - through 0 and infinity that also sends the point ``p`` to ``I``. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] - sage: A = HyperbolicMethodsUHP._to_std_geod(p_1, p_2, p_3) - sage: bool(abs(mobius_transform(A, p_1)) < 10**-9) - True - sage: bool(abs(mobius_transform(A, p_2) - I) < 10**-9) - True - sage: bool(mobius_transform(A, p_3) == infinity) - True - """ - B = matrix(2, [[1, 0], [0, -I]]) - return B * HyperbolicModelUHP._crossratio_matrix(start, p, end) - @staticmethod def _mobius_sending(z, w): #UHP r""" @@ -1251,63 +1114,23 @@ def _mobius_sending(z, w): #UHP EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) + sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelUHP + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform + sage: bool(abs(mobius_transform(HyperbolicModelUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),1) - 3 + I) < 10^-4) True - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) + sage: bool(abs(mobius_transform(HyperbolicModelUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),2) - 5*I) < 10^-4) True - sage: bool(abs(mobius_transform(HyperbolicMethodsUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) + sage: bool(abs(mobius_transform(HyperbolicModelUHP._mobius_sending([1,2,infinity],[3 - I, 5*I,-12]),infinity) + 12) < 10^-4) True """ if len(z) != 3 or len(w) != 3: raise TypeError("mobius_sending requires each list to be three points long") - A = HyperbolicModelUHP._crossratio_matrix(z[0],z[1],z[2]) - B = HyperbolicModelUHP._crossratio_matrix(w[0],w[1],w[2]) + A = HyperbolicGeodesicUHP._crossratio_matrix(z[0],z[1],z[2]) + B = HyperbolicGeodesicUHP._crossratio_matrix(w[0],w[1],w[2]) return B.inverse() * A - @staticmethod - def _crossratio_matrix(p_0, p_1, p_2): #UHP - r""" - Given three points (the list `p`) in `\mathbb{CP}^{1}` in affine - coordinates, return the linear fractional transformation taking - the elements of `p` to `0`,`1`, and `\infty'. - - INPUT: - - - a list of three distinct elements of three distinct elements - of `\mathbb{CP}^1` in affine coordinates; that is, each element - must be a complex number, `\infty`, or symbolic. - - OUTPUT: - - - an element of `\GL(2,\CC)` - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import mobius_transform - sage: (p_1, p_2, p_3) = [HyperbolicMethodsUHP.random_point() for k in range(3)] - sage: A = HyperbolicMethodsUHP._crossratio_matrix(p_1, p_2, p_3) - sage: bool(abs(mobius_transform(A, p_1) < 10**-9)) - True - sage: bool(abs(mobius_transform(A, p_2) - 1) < 10**-9) - True - sage: bool(mobius_transform(A, p_3) == infinity) - True - sage: (x,y,z) = var('x,y,z'); HyperbolicMethodsUHP._crossratio_matrix(x,y,z) - [ y - z -x*(y - z)] - [ -x + y (x - y)*z] - """ - if p_0 == infinity: - return matrix(2,[0,-(p_1 - p_2), -1, p_2]) - elif p_1 == infinity: - return matrix(2,[1, -p_0,1,-p_2]) - elif p_2 == infinity: - return matrix(2,[1,-p_0, 0, p_1 - p_0]) - else: - return matrix(2,[p_1 - p_2, (p_1 - p_2)*(-p_0), p_1 - p_0, - ( p_1 - p_0)*(-p_2)]) +##################################################################### +## Poincare disk model class HyperbolicModelPD(HyperbolicModel): r""" @@ -1355,59 +1178,43 @@ def _coerce_map_from_(self, X): return CoercionHMtoPD(Hom(X, self)) return super(HyperbolicModelPD, self)._coerce_map_from_(X) - def point_in_model(self, p): #PD + def point_in_model(self, p): r""" Check whether a complex number lies in the open unit disk. EXAMPLES:: - sage: HyperbolicPlane.PD.point_in_model(1.00) + sage: PD = HyperbolicPlane().PD() + sage: PD.point_in_model(1.00) False - - sage: HyperbolicPlane.PD.point_in_model(1/2 + I/2) + sage: PD.point_in_model(1/2 + I/2) True - - sage: HyperbolicPlane.PD.point_in_model(1 + .2*I) + sage: PD.point_in_model(1 + .2*I) False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() return bool(abs(CC(p)) < 1) - def boundary_point_in_model(self, p): #PD + def boundary_point_in_model(self, p): r""" Check whether a complex number lies in the open unit disk. EXAMPLES:: - sage: HyperbolicPlane.PD.boundary_point_in_model(1.00) + sage: PD = HyperbolicPlane().PD() + sage: PD.boundary_point_in_model(1.00) True - - sage: HyperbolicPlane.PD.boundary_point_in_model(1/2 + I/2) + sage: PD.boundary_point_in_model(1/2 + I/2) False - - sage: HyperbolicPlane.PD.boundary_point_in_model(1 + .2*I) + sage: PD.boundary_point_in_model(1 + .2*I) False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() return bool(abs(abs(CC(p))- 1) < EPSILON) - def isometry_act_on_point(self, A, p): #PD - r""" - Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action `A \cdot p`. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelPD - sage: I2 = identity_matrix(2) - sage: q = HyperbolicPlane.PD.random_point().coordinates() - sage: bool(norm(HyperbolicModelPD.isometry_act_on_point(I2, q) - q) < 10**-9) - True - """ - _image = mobius_transform(A, p) - if not PD_preserve_orientation(A): - return mobius_transform(I*matrix(2,[0,1,1,0]), _image) - return _image - - def isometry_in_model(self, A): #PD + def isometry_in_model(self, A): r""" Check if the given matrix ``A`` is in the group `U(1,1)`. @@ -1415,56 +1222,30 @@ def isometry_in_model(self, A): #PD sage: z = [CC.random_element() for k in range(2)]; z.sort(key=abs) sage: A = matrix(2,[z[1], z[0],z[0].conjugate(),z[1].conjugate()]) - sage: HyperbolicPlane.PD.isometry_in_model(A) + sage: HyperbolicPlane().PD().isometry_in_model(A) True """ + if isinstance(A, HyperbolicIsometry): + return True # alpha = A[0][0] # beta = A[0][1] # Orientation preserving and reversing - return PD_preserve_orientation(A) or PD_preserve_orientation(I*A) - - def isometry_to_model(self, A, model_name): #PD - r""" - Convert ``A`` from the current model to the model specified in - ``model_name``. - - INPUT: - - - ``A`` -- a matrix in the current model - - ``model_name`` -- a string denoting the model to be converted to + return (HyperbolicIsometryPD._orientation_preserving(A) + or HyperbolicIsometryPD._orientation_preserving(I*A)) - OUTPUT: - - - the coordinates of a point in the ``short_name`` model - - EXAMPLES: - - We check that orientation-reversing isometries behave as they - should:: - - sage: HyperbolicPlane.PD.isometry_to_model(matrix(2,[0,I,I,0]),'UHP') - [ 0 -1] - [-1 0] - """ - cls.isometry_test(A) - # Check for orientation-reversing isometries. - if (not PD_preserve_orientation(A) and model_name == 'UHP'): - return cls.isom_conversion_dict[model_name](I*A) - return cls.isom_conversion_dict[model_name](A) - - def get_background_graphic(self, **bdry_options): #PD + def get_background_graphic(self, **bdry_options): r""" Return a graphic object that makes the model easier to visualize. For the Poincare disk, the background object is the ideal boundary. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryPD.get_background_graphic() + sage: circ = HyperbolicPlane().PD().get_background_graphic() """ from sage.plot.circle import circle return circle((0,0), 1, axes=False, color='black') + ##################################################################### ## Klein disk model @@ -1513,82 +1294,66 @@ def _coerce_map_from_(self, X): return CoercionHMtoKM(Hom(X, self)) return super(HyperbolicModelKM, self)._coerce_map_from_(X) - def point_in_model(self, p): #KM + def point_in_model(self, p): r""" Check whether a point lies in the open unit disk. EXAMPLES:: - sage: HyperbolicPlane.KM.point_in_model((1,0)) + sage: KM = HyperbolicPlane().KM() + sage: KM.point_in_model((1, 0)) False - - sage: HyperbolicPlane.KM.point_in_model((1/2 , 1/2)) + sage: KM.point_in_model((1/2, 1/2)) True - - sage: HyperbolicPlane.KM.point_in_model((1 , .2)) + sage: KM.point_in_model((1, .2)) False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() return len(p) == 2 and bool(p[0]**2 + p[1]**2 < 1) - def boundary_point_in_model(self, p): #KM + def boundary_point_in_model(self, p): r""" Check whether a point lies in the unit circle, which corresponds to the ideal boundary of the hyperbolic plane in the Klein model. EXAMPLES:: - sage: HyperbolicPlane.KM.boundary_point_in_model((1,0)) + sage: KM = HyperbolicPlane().KM() + sage: KM.boundary_point_in_model((1, 0)) True - - sage: HyperbolicPlane.KM.boundary_point_in_model((1/2 , 1/2)) + sage: KM.boundary_point_in_model((1/2, 1/2)) False - - sage: HyperbolicPlane.KM.boundary_point_in_model((1 , .2)) + sage: KM.boundary_point_in_model((1, .2)) False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() return len(p) == 2 and bool(abs(p[0]**2 + p[1]**2 - 1) < EPSILON) - def isometry_act_on_point(self, A, p): #KM - r""" - Given an isometry ``A`` and a point ``p`` in the current model, - return image of ``p`` unduer the action `A \cdot p`. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import HyperbolicModelKM - sage: I3 = identity_matrix(3) - sage: v = vector(HyperbolicPlane.KM.random_point().coordinates()) - sage: bool(norm(HyperbolicModelKM.isometry_act_on_point(I3, v) - v) < 10**-9) - True - """ - v = A*vector((list(p) + [1])) - if v[2] == 0: - return infinity - return v[0:2]/v[2] - - def isometry_in_model(self, A): #KM + def isometry_in_model(self, A): r""" Check if the given matrix ``A`` is in the group `SO(2,1)`. EXAMPLES:: - sage: A = matrix(3,[1, 0, 0, 0, 17/8, 15/8, 0, 15/8, 17/8]) - sage: HyperbolicPlane.KM.isometry_in_model(A) + sage: A = matrix(3, [[1, 0, 0], [0, 17/8, 15/8], [0, 15/8, 17/8]]) + sage: HyperbolicPlane().KM().isometry_in_model(A) True """ - from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM - return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < - EPSILON) + if isinstance(A, HyperbolicIsometry): + return True + return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 + < EPSILON) - def get_background_graphic(self, **bdry_options): #KM + def get_background_graphic(self, **bdry_options): r""" Return a graphic object that makes the model easier to visualize. For the Klein model, the background object is the ideal boundary. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryKM.get_background_graphic() + sage: circ = HyperbolicPlane().KM().get_background_graphic() """ from sage.plot.circle import circle return circle((0,0), 1, axes=False, color='black') @@ -1601,7 +1366,6 @@ class HyperbolicModelHM(HyperbolicModel): Hyperboloid Model. """ _Geodesic = HyperbolicGeodesicHM - _Isometry = HyperbolicIsometryHM def __init__(self, space): """ @@ -1615,7 +1379,7 @@ def __init__(self, space): HyperbolicModel.__init__(self, space, name="Hyperboloid Model", short_name="HM", bounded=False, conformal=True, dimension=2, - isometry_group="SO(2, 1)", isometry_group_is_projective=True) + isometry_group="SO(2, 1)", isometry_group_is_projective=False) def _coerce_map_from_(self, X): """ @@ -1641,54 +1405,55 @@ def _coerce_map_from_(self, X): return CoercionKMtoHM(Hom(X, self)) return super(HyperbolicModelHM, self)._coerce_map_from_(X) - def point_in_model(self, p): #HM + def point_in_model(self, p): r""" Check whether a complex number lies in the hyperboloid. EXAMPLES:: - sage: HyperbolicPlane.HM.point_in_model((0,0,1)) + sage: HM = HyperbolicPlane().HM() + sage: HM.point_in_model((0,0,1)) True - - sage: HyperbolicPlane.HM.point_in_model((1,0,sqrt(2))) + sage: HM.point_in_model((1,0,sqrt(2))) True - - sage: HyperbolicPlane.HM.point_in_model((1,2,1)) + sage: HM.point_in_model((1,2,1)) False """ + if isinstance(p, HyperbolicPoint): + return p.is_boundary() return len(p) == 3 and bool(p[0]**2 + p[1]**2 - p[2]**2 + 1 < EPSILON) - def boundary_point_in_model(self, p): #HM + def boundary_point_in_model(self, p): r""" Return ``False`` since the Hyperboloid model has no boundary points. EXAMPLES:: - sage: HyperbolicPlane.HM.boundary_point_in_model((0,0,1)) + sage: HM = HyperbolicPlane().HM() + sage: HM.boundary_point_in_model((0,0,1)) False - - sage: HyperbolicPlane.HM.boundary_point_in_model((1,0,sqrt(2))) + sage: HM.boundary_point_in_model((1,0,sqrt(2))) False - - sage: HyperbolicPlane.HM.boundary_point_in_model((1,2,1)) + sage: HM.boundary_point_in_model((1,2,1)) False """ return False - def isometry_in_model(self, A): #HM + def isometry_in_model(self, A): r""" Test that the matrix ``A`` is in the group `SO(2,1)^+`. EXAMPLES:: sage: A = diagonal_matrix([1,1,-1]) - sage: HyperbolicPlane.HM.isometry_in_model(A) + sage: HyperbolicPlane().HM().isometry_in_model(A) True """ - from sage.geometry.hyperbolic_space.hyperbolic_constants import LORENTZ_GRAM + if isinstance(A, HyperbolicIsometry): + return True return bool((A*LORENTZ_GRAM*A.transpose() - LORENTZ_GRAM).norm()**2 < EPSILON) - def get_background_graphic(self, **bdry_options): #HM + def get_background_graphic(self, **bdry_options): r""" Return a graphic object that makes the model easier to visualize. For the hyperboloid model, the background object is the hyperboloid @@ -1696,8 +1461,7 @@ def get_background_graphic(self, **bdry_options): #HM EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_factory import * - sage: circ = HyperbolicFactoryPD.get_background_graphic() + sage: H = HyperbolicPlane().HM().get_background_graphic() """ hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1) z_height = bdry_options.get('z_height', 7.0) @@ -1708,88 +1472,3 @@ def get_background_graphic(self, **bdry_options): #HM return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max), (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) -##################################################################### -## Helper functions - -def PD_preserve_orientation(A): - r""" - For a PD isometry, determine if it preserves orientation. - This test is more more involved than just checking the sign - of the determinant, and it is used a few times in this file. - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_model import PD_preserve_orientation as orient - sage: orient(matrix(2, [-I, 0, 0, I])) - True - sage: orient(matrix(2, [0, I, I, 0])) - False - """ - return bool(A[1][0] == A[0][1].conjugate() and A[1][1] == A[0][0].conjugate() - and abs(A[0][0]) - abs(A[0][1]) != 0) - -def mobius_transform(A, z): - r""" - Given a matrix ``A`` in `GL(2, \CC)` and a point ``z`` in the complex - plane return the mobius transformation action of ``A`` on ``z``. - - INPUT: - - - ``A`` -- a `2 \times 2` invertible matrix over the complex numbers - - ``z`` -- a complex number or infinity - - OUTPUT: - - - a complex number or infinity - - EXAMPLES:: - - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import mobius_transform - sage: mobius_transform(matrix(2,[1,2,3,4]),2 + I) - 2/109*I + 43/109 - sage: y = var('y') - sage: mobius_transform(matrix(2,[1,0,0,1]),x + I*y) - x + I*y - - The matrix must be square and `2 \times 2`:: - - sage: mobius_transform(matrix([[3,1,2],[1,2,5]]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - - sage: mobius_transform(identity_matrix(3),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - - The matrix can be symbolic or can be a matrix over the real - or complex numbers, but must be invertible:: - - sage: (a,b,c,d) = var('a,b,c,d'); - sage: mobius_transform(matrix(2,[a,b,c,d]),I) - (I*a + b)/(I*c + d) - - sage: mobius_transform(matrix(2,[0,0,0,0]),I) - Traceback (most recent call last): - ... - TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring - """ - if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: - (a,b,c,d) = A.list() - if z == infinity: - if c == 0: - return infinity - return a/c - if a*d - b*c < 0: - w = z.conjugate() # Reverses orientation - else: - w = z - if c*z + d == 0: - return infinity - else: - return (a*w + b)/(c*w + d) - else: - raise TypeError("A must be an invertible 2x2 matrix over the" - " complex numbers or a symbolic ring") - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index d2acc695b6c..ad395892073 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -23,21 +23,31 @@ We can construct points in the upper half plane model, abbreviated UHP for convenience:: - sage: HyperbolicPlane.UHP.point(2 + I) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP.get_point(2 + I) Point in UHP I + 2 - sage: g = HyperbolicPlane.UHP.point(3 + I) - sage: g.dist(HyperbolicPlane.UHP.point(I)) + sage: g = UHP.get_point(3 + I) + sage: g.dist(UHP.get_point(I)) arccosh(11/2) We can also construct boundary points in the upper half plane model:: - sage: HyperbolicPlane.UHP.point(3) + sage: UHP.get_point(3) Boundary point in UHP 3 -Points on the boundary are infinitely far from interior points:: +Some more examples:: - sage: HyperbolicPlane.UHP.point(3).dist(HyperbolicPlane.UHP.point(I)) - +Infinity + sage: HyperbolicPlane().UHP().get_point(0) + Boundary point in UHP 0 + + sage: HyperbolicPlane().PD().get_point(I/2) + Point in PD 1/2*I + + sage: HyperbolicPlane().KM().get_point((0,1)) + Boundary point in KM (0, 1) + + sage: HyperbolicPlane().HM().get_point((0,0,1)) + Point in HM (0, 0, 1) """ #*********************************************************************** @@ -53,18 +63,14 @@ from sage.symbolic.pynac import I from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex -from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometry +from sage.matrix.matrix import is_Matrix +from sage.matrix.constructor import matrix +from sage.modules.free_module_element import vector from sage.rings.infinity import infinity from sage.rings.all import RR, CC +from sage.functions.other import real, imag -from sage.misc.lazy_import import lazy_import -lazy_import('sage.functions.other', 'real') -lazy_import('sage.modules.free_module_element', 'vector') - -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_methods', - ['HyperbolicAbstractMethods', 'HyperbolicMethodsUHP']) -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_geodesic', 'HyperbolicGeodesic') - +from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometry class HyperbolicPoint(Element): r""" @@ -137,10 +143,38 @@ class HyperbolicPoint(Element): It is an error to specify an interior point of hyperbolic space as a boundary point:: - sage: HyperbolicPlane().UHP().get_point(0.2 + 0.3*I, boundary=True) + sage: HyperbolicPlane().UHP().get_point(0.2 + 0.3*I, is_boundary=True) Traceback (most recent call last): ... ValueError: 0.200000000000000 + 0.300000000000000*I is not a valid boundary point in the UHP model + + TESTS: + + In the PD model, the coordinates of a point are in the unit disk + in the complex plane `\CC`:: + + sage: HyperbolicPlane().PD().get_point(0) + Point in PD 0 + sage: HyperbolicPlane().PD().get_point(1) + Boundary point in PD 1 + + In the KM model, the coordinates of a point are in the unit disk + in the real plane `\RR^2`:: + + sage: HyperbolicPlane().KM().get_point((0,0)) + Point in KM (0, 0) + sage: HyperbolicPlane().KM().get_point((1,0)) + Boundary point in KM (1, 0) + + In the HM model, the coordinates of a poi nt are on the + hyperboloidgiven by `x^2 + y^2 - z^2 = -1`:: + + sage: HyperbolicPlane().HM().get_point((0,0,1)) + Point in HM (0, 0, 1) + sage: HyperbolicPlane().HM().get_point((1,0,0), is_boundary=True) + Traceback (most recent call last): + ... + NotImplementedError: boundary points are not implemented in the HM model """ def __init__(self, model, coordinates, is_boundary, check=True, **graphics_options): r""" @@ -175,20 +209,6 @@ def __init__(self, model, coordinates, is_boundary, check=True, **graphics_optio # "Private" Methods # ##################### - @lazy_attribute - def _cached_coordinates(self): - r""" - The representation of the current point used for calculations. - - EXAMPLES:: - - sage: A = HyperbolicPlane().HM().get_point((0, 0, 1)) - sage: A._cached_coordinates - I - """ - R = self.parent().realization_of().a_realization() - return R(self).coordinates() - def _repr_(self): r""" Return a string representation of ``self``. @@ -273,10 +293,10 @@ def __eq__(self, other): sage: p1 == p2 True """ - return (self.parent() is other.parent() + return (isinstance(other, HyperbolicPoint) + and self.parent() is other.parent() and bool(self._coordinates == other._coordinates)) - # TODO: Add a test that this works with isometries def __rmul__(self, other): r""" Implement the action of matrices on points of hyperbolic space. @@ -284,18 +304,24 @@ def __rmul__(self, other): EXAMPLES:: sage: A = matrix(2, [0, 1, 1, 0]) - sage: A * HyperbolicPlane.UHP.point(2 + I) + sage: A = HyperbolicPlane().UHP().get_isometry(A) + sage: A * HyperbolicPlane().UHP().get_point(2 + I) Point in UHP 1/5*I + 2/5 + + We also lift matrices into isometries:: + sage: B = diagonal_matrix([-1, -1, 1]) - sage: B * HyperbolicPlane.HM.point((0, 1, sqrt(2))) + sage: B = HyperbolicPlane().HM().get_isometry(B) + sage: B * HyperbolicPlane().HM().get_point((0, 1, sqrt(2))) Point in HM (0, -1, sqrt(2)) """ - from sage.matrix.matrix import is_Matrix - if is_Matrix(other): + if isinstance(other, HyperbolicIsometry): + return other(self) + elif is_Matrix(other): + # TODO: Currently the __mul__ from the matrices gets called first + # and returns an error instead of calling this method A = self.parent().get_isometry(other) - return A * self - elif isinstance(other, HyperbolicIsometry): - return other * self + return A(self) else: raise TypeError("unsupported operand type(s) for *:" "{0} and {1}".format(self, other)) @@ -330,21 +356,41 @@ def model(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * sage: HyperbolicPlane().UHP().get_point(I).model() - + Hyperbolic plane in the Upper Half Plane Model model sage: HyperbolicPlane().PD().get_point(0).model() - + Hyperbolic plane in the Poincare Disk Model model sage: HyperbolicPlane().KM().get_point((0,0)).model() - + Hyperbolic plane in the Klein Disk Model model sage: HyperbolicPlane().HM().get_point((0,0,1)).model() - + Hyperbolic plane in the Hyperboloid Model model """ return self.parent() + def to_model(self, model): + """ + Convert ``self`` to the ``model``. + + INPUT: + + - ``other`` -- (a string representing) the image model + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: PD = HyperbolicPlane().PD() + sage: PD.get_point(1/2+I/2).to_model(UHP) + Point in UHP I + 2 + sage: PD.get_point(1/2+I/2).to_model('UHP') + Point in UHP I + 2 + """ + if isinstance(model, str): + model = getattr(self.parent().realization_of(), model)() + return model(self) + def is_boundary(self): """ Return ``True`` if ``self`` is a boundary point. @@ -368,8 +414,7 @@ def update_graphics(self, update=False, **options): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = HyperbolicPointUHP(I); p.graphics_options() + sage: p = HyperbolicPlane().UHP().get_point(I); p.graphics_options() {} sage: p.update_graphics(color = "red"); p.graphics_options() @@ -391,65 +436,59 @@ def graphics_options(self): EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_point import * - sage: p = HyperbolicPointUHP(2 + I, color="red") + sage: p = HyperbolicPlane().UHP().get_point(2 + I, color="red") sage: p.graphics_options() {'color': 'red'} """ return self._graphics_options - #################################### - # Methods implemented in _HMethods # - #################################### - - def symmetry_in(self): + def symmetry_involution(self): r""" Return the involutary isometry fixing the given point. EXAMPLES:: sage: z = HyperbolicPlane().UHP().get_point(3 + 2*I) - sage: z.symmetry_in() + sage: z.symmetry_involution() Isometry in UHP [ 3/2 -13/2] [ 1/2 -3/2] - sage: HyperbolicPlane().UHP().get_point(I).symmetry_in() + sage: HyperbolicPlane().UHP().get_point(I).symmetry_involution() Isometry in UHP [ 0 -1] [ 1 0] - sage: HyperbolicPlane().PD().get_point(0).symmetry_in() + sage: HyperbolicPlane().PD().get_point(0).symmetry_involution() Isometry in PD [-I 0] [ 0 I] - sage: HyperbolicPlane().KM().get_point((0, 0)).symmetry_in() + sage: HyperbolicPlane().KM().get_point((0, 0)).symmetry_involution() Isometry in KM [-1 0 0] [ 0 -1 0] [ 0 0 1] - sage: HyperbolicPlane().HM().get_point((0,0,1)).symmetry_in() + sage: HyperbolicPlane().HM().get_point((0,0,1)).symmetry_involution() Isometry in HM [-1 0 0] [ 0 -1 0] [ 0 0 1] sage: p = HyperbolicPlane().UHP().random_element() - sage: A = p.symmetry_in() + sage: A = p.symmetry_involution() sage: A*p == p True - sage: A.orientation_preserving() + sage: A.preserves_orientation() True - sage: A*A == HyperbolicPlane().UHP().isometry(identity_matrix(2)) + sage: A*A == HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) True """ R = self.parent().realization_of().a_realization() - A = self._HMethods.symmetry_in(self._cached_coordinates) - A = self._HMethods.model().isometry_to_model(A, self.model_name()) + A = R(self).symmetry_involution() return self.parent().get_isometry(A) ########### @@ -458,6 +497,8 @@ def symmetry_in(self): def show(self, boundary=True, **options): r""" + Plot ``self``. + EXAMPLES:: sage: HyperbolicPlane().PD().get_point(0).show() @@ -472,6 +513,9 @@ def show(self, boundary=True, **options): opts.update(self.graphics_options()) opts.update(options) + from sage.plot.point import point + from sage.misc.functional import numerical_approx + if self._bdry: # It is a boundary point p = numerical_approx(p) pic = point((p, 0), **opts) @@ -479,21 +523,17 @@ def show(self, boundary=True, **options): bd_pic = self._model.get_background_graphic(bd_min = p - 1, bd_max = p + 1) pic = bd_pic + pic - return pic - - # It is an interior point - from sage.misc.functional import numerical_approx - if p in RR: - p = CC(p) - elif hasattr(p, 'iteritems') or hasattr(p, '__iter__'): - p = map(numerical_approx, p) - else: - p = numerical_approx(p) - from sage.plot.point import point - pic = point(p, **opts) - if boundary: - bd_pic = self.parent().get_background_graphic() - pic = bd_pic + pic + else: # It is an interior point + if p in RR: + p = CC(p) + elif hasattr(p, 'iteritems') or hasattr(p, '__iter__'): + p = [numerical_approx(k) for k in p] + else: + p = numerical_approx(p) + pic = point(p, **opts) + if boundary: + bd_pic = self.parent().get_background_graphic() + pic = bd_pic + pic return pic class HyperbolicPointUHP(HyperbolicPoint): @@ -511,103 +551,59 @@ class HyperbolicPointUHP(HyperbolicPoint): sage: HyperbolicPlane().UHP().get_point(1) Boundary point in UHP 1 - """ - def symmetry_in(self): + def symmetry_involution(self): r""" Return the involutary isometry fixing the given point. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_methods import HyperbolicMethodsUHP - sage: HyperbolicMethodsUHP.symmetry_in(3 + 2*I) + sage: HyperbolicPlane().UHP().get_point(3 + 2*I).symmetry_involution() + Isometry in UHP [ 3/2 -13/2] [ 1/2 -3/2] """ p = self._coordinates x, y = real(p), imag(p) if y > 0: - return matrix(2,[x/y,-(x**2/y) - y,1/y,-(x/y)]) + M = matrix([[x/y, -(x**2/y) - y], [1/y, -(x/y)]]) + return self.parent().get_isometry(M) + raise ValueError("cannot determine the isometry of a boundary point") def show(self, boundary=True, **options): r""" + Plot ``self``. + EXAMPLES:: sage: HyperbolicPlane().UHP().get_point(I).show() - sage: HyperbolicPlane().UHP().get_point(0).show() sage: HyperbolicPlane().UHP().get_point(infinity).show() Traceback (most recent call last): ... NotImplementedError: can't draw the point infinity """ - # FIXME: Something didn't get put into the UHP point's show() properly - opts = dict([('axes', False),('aspect_ratio',1)]) + p = self.coordinates() + if p == infinity: + raise NotImplementedError("can't draw the point infinity") + opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) opts.update(options) from sage.misc.functional import numerical_approx - p = self.coordinates() + 0*I - p = numerical_approx(p) + p = numerical_approx(p + 0*I) from sage.plot.point import point - pic = point(p, **opts) - if boundary: - cent = real(p) - bd_pic = self.parent().get_background_graphic(bd_min = cent - 1, - bd_max = cent + 1) - pic = bd_pic + pic + if self._bdry: + pic = point((p, 0), **opts) + if boundary: + bd_pic = self.parent().get_background_graphic(bd_min = p - 1, + bd_max = p + 1) + pic = bd_pic + pic + else: + pic = point(p, **opts) + if boundary: + cent = real(p) + bd_pic = self.parent().get_background_graphic(bd_min = cent - 1, + bd_max = cent + 1) + pic = bd_pic + pic return pic -class HyperbolicPointPD(HyperbolicPoint): - r""" - Create a point in the PD model. - - INPUT: - - - the coordinates of a point in the unit disk in the complex plane `\CC` - - EXAMPLES:: - - sage: HyperbolicPlane().PD().get_point(0) - Point in PD 0 - - sage: HyperbolicPlane().PD().get_point(1) - Boundary point in PD 1 - """ - -class HyperbolicPointKM(HyperbolicPoint): - r""" - Create a point in the KM model. - - INPUT: - - - the coordinates of a point in the unit disk in the real plane `\RR^2` - - EXAMPLES:: - - sage: HyperbolicPlane().KM().get_point((0,0)) - Point in KM (0, 0) - - sage: HyperbolicPlane().KM().get_point((1,0)) - Boundary point in KM (1, 0) - """ - -class HyperbolicPointHM(HyperbolicPoint): - r""" - Create a point in the HM model. - - INPUT: - - - the coordinates of a point in the hyperboloid given - by `x^2 + y^2 - z^2 = -1` - - EXAMPLES:: - - sage: HyperbolicPlane().HM().get_point((0,0,1)) - Point in HM (0, 0, 1) - - sage: HyperbolicPlane().HM().get_point((1,0,0), is_boundary=True) - Traceback (most recent call last): - ... - NotImplementedError: boundary points are not implemented in the HM model - """ - From 37287f35acfced2696dff3126e36cba8578a96f4 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sat, 13 Sep 2014 23:26:24 -0700 Subject: [PATCH 026/129] Some more doctest and bug fixes. --- .../hyperbolic_space/hyperbolic_coercion.py | 2 + .../hyperbolic_space/hyperbolic_geodesic.py | 9 ++-- .../hyperbolic_space/hyperbolic_isometry.py | 42 ++++++++++++++++--- .../hyperbolic_space/hyperbolic_model.py | 14 ++++--- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 60ece3bc488..ef26d64d539 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -182,6 +182,8 @@ def image_isometry_matrix(self, x): [1 0] [0 1] """ + if x.det() < 0: + x = I * x return matrix([[1,-I],[-I,1]]) * x * matrix([[1,I],[I,1]])/Integer(2) class CoercionUHPtoKM(HyperbolicModelCoercion): diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index a23b45151af..25860a181bf 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -833,20 +833,21 @@ def ideal_endpoints(self): end = self._end.coordinates() [x1, x2] = [real(k) for k in [start, end]] [y1, y2] = [imag(k) for k in [start, end]] + M = self._model # infinity is the first endpoint, so the other ideal endpoint # is just the real part of the second coordinate if start == infinity: - return [self._model.get_point(start), self._model.get_point(x2)] + return [M.get_point(start), M.get_point(x2)] # Same idea as above if end == infinity: - return [self._model.get_point(x1), self._model.get_point(end)] + return [M.get_point(x1), M.get_point(end)] # We could also have a vertical line with two interior points if x1 == x2: - return [self._model.get_point(x1), self._model.get_point(infinity)] + return [M.get_point(x1), M.get_point(infinity)] # Otherwise, we have a semicircular arc in the UHP c = ((x1+x2)*(x2-x1) + (y1+y2)*(y2-y1)) / (2*(x2-x1)) r = sqrt((c - x1)**2 + y1**2) - return [self._model.get_point(c - r), self._model.get_point(c + r)] + return [M.get_point(c - r), M.get_point(c + r)] def common_perpendicular(self, other): r""" diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index c3e74125055..391b4f0f632 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -41,6 +41,7 @@ # http://www.gnu.org/licenses/ #*********************************************************************** +from copy import copy from sage.categories.homset import Hom from sage.categories.morphism import Morphism from sage.misc.lazy_attribute import lazy_attribute @@ -90,7 +91,8 @@ def __init__(self, model, A, check=True): """ if check: model.isometry_test(A) - self._matrix = A + self._matrix = copy(A) # Make a copy of the potentially mutable matrix + self._matrix.set_immutable() # Make it immutable Morphism.__init__(self, Hom(model, model)) @lazy_attribute @@ -171,19 +173,49 @@ def __eq__(self, other): EXAMPLES:: - sage: A = HyperbolicPlane().UHP().get_isometry(identity_matrix(2)) - sage: B = HyperbolicPlane().UHP().get_isometry(-identity_matrix(2)) + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(identity_matrix(2)) + sage: B = UHP.get_isometry(-identity_matrix(2)) sage: A == B True + + sage: HM = HyperbolicPlane().HM() + sage: A = HM.random_isometry() + sage: A == A + True """ if not isinstance(other, HyperbolicIsometry): return False pos_matrix = bool(abs(self.matrix() - other.matrix()) < EPSILON) - neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) if self.domain().is_isometry_group_projective(): + neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) return self.domain() is other.domain() and (pos_matrix or neg_matrix) + return self.domain() is other.domain() and pos_matrix + + def __hash__(self): + """ + Return the hash of ``self``. + + EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: A = UHP.get_isometry(identity_matrix(2)) + sage: B = UHP.get_isometry(-identity_matrix(2)) + sage: hash(A) == hash(B) + True + + sage: HM = HyperbolicPlane().HM() + sage: A = HM.random_isometry() + sage: hash(A) == hash(A) + True + """ + if self.domain().is_isometry_group_projective(): + # Special care must be taken for projective groups + m = matrix(self._matrix.nrows(), map(abs, self._matrix.list())) + m.set_immutable() else: - return self.domain() is other.domain() and pos_matrix + m = self._matrix + return hash((self.domain(), self.codomain(), m)) def __pow__(self, n): r""" diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index b5cbc329488..b3a5b3df60d 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -712,8 +712,8 @@ def _dist_geod_point(self, start, end, p): INPUT: - - ``start`` -- the start coordinates of the geodesic - - ``end`` -- the end coordinates of the geodesic + - ``start`` -- the start ideal point coordinates of the geodesic + - ``end`` -- the end ideal point coordinates of the geodesic - ``p`` -- the coordinates of the point OUTPUT: @@ -722,15 +722,16 @@ def _dist_geod_point(self, start, end, p): EXAMPLES:: - sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, 1/2 + I/2, 0) + sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, I, 0) arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) If `p` is a boundary point, the distance is infinity:: - sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, 1/2 + I/2, 12/13*I + 5/13) + sage: HyperbolicPlane().PD()._dist_geod_point(3/5*I + 4/5, I, 12/13*I + 5/13) +Infinity """ R = self.realization_of().a_realization() + assert R is not self phi = lambda c: R.coerce_map_from(self).image_coordinates(c) return R._dist_geod_point(phi(start), phi(end), phi(p)) @@ -963,12 +964,13 @@ def _dist_geod_point(self, start, end, p): EXAMPLES:: - sage: HyperbolicPlane().UHP()._dist_geod_point(2, 2 + I, I) + sage: UHP = HyperbolicPlane().UHP() + sage: UHP._dist_geod_point(2, infinity, I) arccosh(1/10*sqrt(5)*((sqrt(5) - 1)^2 + 4) + 1) If `p` is a boundary point, the distance is infinity:: - sage: HyperbolicPlane().UHP()._dist_geod_point(2, 2 + I, 5) + sage: HyperbolicPlane().UHP()._dist_geod_point(2, infinity, 5) +Infinity """ # Here is the trick for computing distance to a geodesic: From 77a3d853acb50c68e7c9ebc9aaae79aa9f63f759 Mon Sep 17 00:00:00 2001 From: Ben Salisbury Date: Thu, 25 Sep 2014 13:28:37 -0400 Subject: [PATCH 027/129] Code added --- .../categories/highest_weight_crystals.py | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index e3744cb7157..022504a989a 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -375,7 +375,55 @@ def iter_by_deg(gens): class ElementMethods: - pass + def all_paths(self,index_set=None,start=[]): + r""" + Return all paths to the highest weight from ``self`` with respect + to `index_set`. + + INPUT: + + - ``self`` -- an element of a crystal + + - ``index_set`` -- a subset of the index set of ``self` + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux("A2") + sage: b0 = B.highest_weight_vector() + sage: b = b0.f_string([1,2,1,2]) + sage: L = b.all_paths() + sage: L + + sage: list(L) + [[2, 1, 2, 1], [2, 2, 1, 1]] + + sage: Y = crystals.infinity.GeneralizedYoungWalls(3) + sage: y0 = Y.highest_weight_vector() + sage: y = y0.f_string([0,1,2,3,2,1,0]) + sage: list(y.all_paths()) + [[0, 1, 2, 3, 2, 1, 0], + [0, 1, 3, 2, 2, 1, 0], + [0, 3, 1, 2, 2, 1, 0], + [0, 3, 2, 1, 1, 0, 2], + [0, 3, 2, 1, 1, 2, 0]] + + sage: B = crystals.Tableaux("A3",shape=[4,2,1]) + sage: b0 = B.highest_weight_vector() + sage: b = b0.f_string([1,1,2,3]) + sage: list(b.all_paths()) + [[1, 3, 2, 1], [3, 1, 2, 1], [3, 2, 1, 1]] + """ + if index_set is None: + index_set = self.index_set() + hw = True + for i in index_set: + next = self.e(i) + if next is not None: + for x in next.all_paths(index_set,start+[i]): + yield x + hw = False + if hw: + yield start class TensorProducts(TensorProductsCategory): """ From b4f472f37c8ecca84e828db548a1d9a022da9836 Mon Sep 17 00:00:00 2001 From: Ben Salisbury Date: Thu, 25 Sep 2014 13:59:44 -0400 Subject: [PATCH 028/129] Moved code from hw category to crystals category --- src/sage/categories/crystals.py | 48 ++++++++++++++++++ .../categories/highest_weight_crystals.py | 49 +------------------ 2 files changed, 49 insertions(+), 48 deletions(-) diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py index 1e68177bde3..f7d4daff436 100644 --- a/src/sage/categories/crystals.py +++ b/src/sage/categories/crystals.py @@ -1163,6 +1163,54 @@ def to_lowest_weight(self, index_set = None): return [lw[0], [i] + lw[1]] return [self, []] + def all_paths(self,index_set=None,start=[]): + r""" + Return all paths to the highest weight from ``self`` with respect + to `index_set`. + + INPUT: + + - ``self`` -- an element of a crystal + + - ``index_set`` -- a subset of the index set of ``self` + + EXAMPLES:: + + sage: B = crystals.infinity.Tableaux("A2") + sage: b0 = B.highest_weight_vector() + sage: b = b0.f_string([1,2,1,2]) + sage: L = b.all_paths() + sage: list(L) + [[2, 1, 2, 1], [2, 2, 1, 1]] + + sage: Y = crystals.infinity.GeneralizedYoungWalls(3) + sage: y0 = Y.highest_weight_vector() + sage: y = y0.f_string([0,1,2,3,2,1,0]) + sage: list(y.all_paths()) + [[0, 1, 2, 3, 2, 1, 0], + [0, 1, 3, 2, 2, 1, 0], + [0, 3, 1, 2, 2, 1, 0], + [0, 3, 2, 1, 1, 0, 2], + [0, 3, 2, 1, 1, 2, 0]] + + sage: B = crystals.Tableaux("A3",shape=[4,2,1]) + sage: b0 = B.highest_weight_vector() + sage: b = b0.f_string([1,1,2,3]) + sage: list(b.all_paths()) + [[1, 3, 2, 1], [3, 1, 2, 1], [3, 2, 1, 1]] + """ + if index_set is None: + index_set = self.index_set() + hw = True + for i in index_set: + next = self.e(i) + if next is not None: + for x in next.all_paths(index_set,start+[i]): + yield x + hw = False + if hw: + yield start + def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both"): r""" Construct the subcrystal generated by ``self`` using `e_i` and/or diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index 022504a989a..8719b352ce7 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -375,55 +375,8 @@ def iter_by_deg(gens): class ElementMethods: - def all_paths(self,index_set=None,start=[]): - r""" - Return all paths to the highest weight from ``self`` with respect - to `index_set`. - - INPUT: - - - ``self`` -- an element of a crystal - - - ``index_set`` -- a subset of the index set of ``self` + pass - EXAMPLES:: - - sage: B = crystals.infinity.Tableaux("A2") - sage: b0 = B.highest_weight_vector() - sage: b = b0.f_string([1,2,1,2]) - sage: L = b.all_paths() - sage: L - - sage: list(L) - [[2, 1, 2, 1], [2, 2, 1, 1]] - - sage: Y = crystals.infinity.GeneralizedYoungWalls(3) - sage: y0 = Y.highest_weight_vector() - sage: y = y0.f_string([0,1,2,3,2,1,0]) - sage: list(y.all_paths()) - [[0, 1, 2, 3, 2, 1, 0], - [0, 1, 3, 2, 2, 1, 0], - [0, 3, 1, 2, 2, 1, 0], - [0, 3, 2, 1, 1, 0, 2], - [0, 3, 2, 1, 1, 2, 0]] - - sage: B = crystals.Tableaux("A3",shape=[4,2,1]) - sage: b0 = B.highest_weight_vector() - sage: b = b0.f_string([1,1,2,3]) - sage: list(b.all_paths()) - [[1, 3, 2, 1], [3, 1, 2, 1], [3, 2, 1, 1]] - """ - if index_set is None: - index_set = self.index_set() - hw = True - for i in index_set: - next = self.e(i) - if next is not None: - for x in next.all_paths(index_set,start+[i]): - yield x - hw = False - if hw: - yield start class TensorProducts(TensorProductsCategory): """ From d8f518ff48c8be2ea73f2b51e067af9e6c87b4bf Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Sun, 28 Sep 2014 23:26:20 +0200 Subject: [PATCH 029/129] Update to SageManifolds v0.6 --- src/sage/tensor/modules/comp.py | 362 +++++++++++++----- .../tensor/modules/finite_rank_free_module.py | 9 +- src/sage/tensor/modules/free_module_tensor.py | 299 +++++++++------ .../tensor/modules/tensor_with_indices.py | 87 ++++- 4 files changed, 523 insertions(+), 234 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 57482896d60..1bdb2acbabb 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -1163,7 +1163,7 @@ def __div__(self, other): result._comp[ind] = val / other return result - def self_contract(self, pos1, pos2): + def trace(self, pos1, pos2): r""" Index contraction. @@ -1185,7 +1185,7 @@ def self_contract(self, pos1, pos2): sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 2) sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] - sage: c.self_contract(0,1) + sage: c.trace(0,1) 15 sage: c[0,0] + c[1,1] + c[2,2] # check 15 @@ -1200,7 +1200,7 @@ def self_contract(self, pos1, pos2): (0, 1, 0), (0, 0, 1) ] - sage: s = a.self_contract(0,1) ; s # contraction on the first two indices + sage: s = a.trace(0,1) ; s # contraction on the first two indices 1-index components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1210,11 +1210,11 @@ def self_contract(self, pos1, pos2): [-15, 30, 45] sage: [sum(a[j,j,i] for j in range(3)) for i in range(3)] # check [-15, 30, 45] - sage: s = a.self_contract(0,2) ; s[:] # contraction on the first and last indices + sage: s = a.trace(0,2) ; s[:] # contraction on the first and last indices [28, 32, 36] sage: [sum(a[j,i,j] for j in range(3)) for i in range(3)] # check [28, 32, 36] - sage: s = a.self_contract(1,2) ; s[:] # contraction on the last two indices + sage: s = a.trace(1,2) ; s[:] # contraction on the last two indices [12, 24, 36] sage: [sum(a[i,j,j] for j in range(3)) for i in range(3)] # check [12, 24, 36] @@ -1250,22 +1250,27 @@ def self_contract(self, pos1, pos2): result[[ind_res]] += val return result - - def contract(self, pos1, other, pos2): + def contract(self, *args): r""" - Index contraction with another instance of :class:`Components`. + Contraction on one or many indices with another instance of + :class:`Components`. INPUT: - - ``pos1`` -- position of the first index (in ``self``) for the - contraction (with the convention position=0 for the first slot) + - ``pos1`` -- positions of the indices in ``self`` involved in the + contraction; ``pos1`` must be a sequence of integers, with 0 standing + for the first index position, 1 for the second one, etc. If ``pos1`` + is not provided, a single contraction on the last index position of + ``self`` is assumed - ``other`` -- the set of components to contract with - - ``pos2`` -- position of the second index (in ``other``) for the - contraction (with the convention position=0 for the first slot) + - ``pos2`` -- positions of the indices in ``other`` involved in the + contraction, with the same conventions as for ``pos1``. If ``pos2`` + is not provided, a single contraction on the first index position of + ``other`` is assumed OUTPUT: - - set of components resulting from the (pos1, pos2) contraction + - set of components resulting from the contraction EXAMPLES: @@ -1291,10 +1296,28 @@ def contract(self, pos1, other, pos2): [12, 24, 36] sage: [sum(a[j]*b[i,j] for j in range(3)) for i in range(3)] # check [12, 24, 36] + + Contraction on 2 indices:: + + sage: c = a*b ; c + 3-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s = c.contract(1,2, b, 0,1) ; s + 1-index components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: s[:] + [-285, 570, 855] + sage: [sum(sum(c[i,j,k]*b[j,k] for k in range(3)) for j in range(3)) for i in range(3)] # check + [-285, 570, 855] - Consistency check with :meth:`self_contract`:: + Consistency check with :meth:`trace`:: - sage: a[:] = (1,2,-3) sage: b = a*a ; b # the tensor product of a with itself fully symmetric 2-indices components w.r.t. [ (1, 0, 0), @@ -1302,27 +1325,189 @@ def contract(self, pos1, other, pos2): (0, 0, 1) ] sage: b[:] - [ 1 2 -3] - [ 2 4 -6] - [-3 -6 9] - sage: b.self_contract(0,1) + [ 1 -2 -3] + [-2 4 6] + [-3 6 9] + sage: b.trace(0,1) 14 - sage: a.contract(0, a, 0) == b.self_contract(0,1) + sage: a.contract(0, a, 0) == b.trace(0,1) True """ - if not isinstance(other, Components): - raise TypeError("For the contraction, other must be an instance " + - "of Components.") - if pos1 < 0 or pos1 > self._nid - 1: - raise IndexError("pos1 out of range.") - if pos2 < 0 or pos2 > other._nid - 1: - raise IndexError("pos2 out of range.") - return (self*other).self_contract(pos1, - pos2+self._nid) - #!# the above is correct (in particular the symmetries are delt by - # self_contract()), but it is not optimal (unnecessary terms are - # evaluated when performing the tensor product self*other) + # + # Treatment of the input + # + nargs = len(args) + for i, arg in enumerate(args): + if isinstance(arg, Components): + other = arg + it = i + break + else: + raise TypeError("A set of components must be provided in the " + + "argument list.") + if it == 0: + pos1 = (self._nid - 1,) + else: + pos1 = args[:it] + if it == nargs-1: + pos2 = (0,) + else: + pos2 = args[it+1:] + ncontr = len(pos1) # number of contractions + if len(pos2) != ncontr: + raise TypeError("Different number of indices for the contraction.") + if other._frame != self._frame: + raise TypeError("The two sets of components are not defined on " + + "the same frame.") + if other._sindex != self._sindex: + raise TypeError("The two sets of components do not have the " + + "same starting index.") + contractions = [(pos1[i], pos2[i]) for i in range(ncontr)] + res_nid = self._nid + other._nid - 2*ncontr + # + # Special case of a scalar result + # + if res_nid == 0: + # To generate the indices tuples (of size ncontr) involved in the + # the contraction, we create an empty instance of Components with + # ncontr indices and call the method index_generator() on it: + comp_for_contr = Components(self._ring, self._frame, ncontr, + start_index=self._sindex) + res = 0 + for ind in comp_for_contr.index_generator(): + res += self[[ind]] * other[[ind]] + return res + # + # Positions of self and other indices in the result + # (None = the position is involved in a contraction and therefore + # does not appear in the final result) + # + pos_s = [None for i in range(self._nid)] # initialization + pos_o = [None for i in range(other._nid)] # initialization + shift = 0 + for pos in range(self._nid): + for contract_pair in contractions: + if pos == contract_pair[0]: + shift += 1 + break + else: + pos_s[pos] = pos - shift + for pos in range(other._nid): + for contract_pair in contractions: + if pos == contract_pair[1]: + shift += 1 + break + else: + pos_o[pos] = self._nid + pos - shift + rev_s = [pos_s.index(i) for i in range(self._nid-ncontr)] + rev_o = [pos_o.index(i) for i in range(self._nid-ncontr, res_nid)] + # + # Determination of the symmetries of the result + # + max_len_sym = 0 # maximum length of symmetries in the result + max_len_antisym = 0 # maximum length of antisymmetries in the result + if res_nid > 1: # no need to search for symmetries if res_nid == 1 + if isinstance(self, CompWithSym): + s_sym = self._sym + s_antisym = self._antisym + else: + s_sym = [] + s_antisym = [] + if isinstance(other, CompWithSym): + o_sym = other._sym + o_antisym = other._antisym + else: + o_sym = [] + o_antisym = [] + # print "s_sym, s_antisym: ", s_sym, s_antisym + # print "o_sym, o_antisym: ", o_sym, o_antisym + res_sym = [] + res_antisym = [] + for isym in s_sym: + r_isym = [] + for pos in isym: + if pos_s[pos] is not None: + r_isym.append(pos_s[pos]) + if len(r_isym) > 1: + res_sym.append(r_isym) + max_len_sym = max(max_len_sym, len(r_isym)) + for isym in s_antisym: + r_isym = [] + for pos in isym: + if pos_s[pos] is not None: + r_isym.append(pos_s[pos]) + if len(r_isym) > 1: + res_antisym.append(r_isym) + max_len_antisym = max(max_len_antisym, len(r_isym)) + for isym in o_sym: + r_isym = [] + for pos in isym: + if pos_o[pos] is not None: + r_isym.append(pos_o[pos]) + if len(r_isym) > 1: + res_sym.append(r_isym) + max_len_sym = max(max_len_sym, len(r_isym)) + for isym in o_antisym: + r_isym = [] + for pos in isym: + if pos_o[pos] is not None: + r_isym.append(pos_o[pos]) + if len(r_isym) > 1: + res_antisym.append(r_isym) + max_len_antisym = max(max_len_antisym, len(r_isym)) + # print "res_sym: ", res_sym + # print "res_antisym: ", res_antisym + # print "max_len_sym: ", max_len_sym + # print "max_len_antisym: ", max_len_antisym + # + # Construction of the result object in view of the remaining symmetries: + # + if max_len_sym == 0 and max_len_antisym == 0: + res = Components(self._ring, self._frame, res_nid, + start_index=self._sindex, + output_formatter=self._output_formatter) + elif max_len_sym == res_nid: + res = CompFullySym(self._ring, self._frame, res_nid, + start_index=self._sindex, + output_formatter=self._output_formatter) + elif max_len_antisym == res_nid: + res = CompFullyAntiSym(self._ring, self._frame, res_nid, + start_index=self._sindex, + output_formatter=self._output_formatter) + else: + res = CompWithSym(self._ring, self._frame, res_nid, + start_index=self._sindex, + output_formatter=self._output_formatter, + sym=res_sym, antisym=res_antisym) + # + # Performing the contraction + # + # To generate the indices tuples (of size ncontr) involved in the + # the contraction, we create an empty instance of Components with + # ncontr indices and call the method index_generator() on it: + comp_for_contr = Components(self._ring, self._frame, ncontr, + start_index=self._sindex) + shift_o = self._nid - ncontr + for ind in res.non_redundant_index_generator(): + ind_s = [None for i in range(self._nid)] # initialization + ind_o = [None for i in range(other._nid)] # initialization + for i, pos in enumerate(rev_s): + ind_s[pos] = ind[i] + for i, pos in enumerate(rev_o): + ind_o[pos] = ind[shift_o+i] + sm = 0 + for ind_c in comp_for_contr.index_generator(): + ic = 0 + for pos_s, pos_o in contractions: + k = ind_c[ic] + ind_s[pos_s] = k + ind_o[pos_o] = k + ic += 1 + sm += self[[ind_s]] * other[[ind_o]] + res[[ind]] = sm + return res + def index_generator(self): r""" @@ -1399,13 +1584,13 @@ def non_redundant_index_generator(self): yield ind - def symmetrize(self, pos=None): + def symmetrize(self, *pos): r""" Symmetrization over the given index positions INPUT: - - ``pos`` -- (default: None) tuple of index positions involved in the + - ``pos`` -- list of index positions involved in the symmetrization (with the convention position=0 for the first slot); if none, the symmetrization is performed over all the indices @@ -1434,7 +1619,7 @@ def symmetrize(self, pos=None): [4 5 6] [3 5 7] [7 8 9], [5 7 9] ) - sage: c.symmetrize() == c.symmetrize((0,1)) + sage: c.symmetrize() == c.symmetrize(0,1) True Full symmetrization of 3-indices components:: @@ -1461,12 +1646,12 @@ def symmetrize(self, pos=None): ....: print s[i,j,k] == (c[i,j,k]+c[i,k,j]+c[j,k,i]+c[j,i,k]+c[k,i,j]+c[k,j,i])/6, ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: c.symmetrize() == c.symmetrize((0,1,2)) + sage: c.symmetrize() == c.symmetrize(0,1,2) True Partial symmetrization of 3-indices components:: - sage: s = c.symmetrize((0,1)) ; s # symmetrization on the first two indices + sage: s = c.symmetrize(0,1) ; s # symmetrization on the first two indices 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1486,7 +1671,7 @@ def symmetrize(self, pos=None): ....: print s[i,j,k] == (c[i,j,k]+c[j,i,k])/2, ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s = c.symmetrize([1,2]) ; s # symmetrization on the last two indices + sage: s = c.symmetrize(1,2) ; s # symmetrization on the last two indices 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1506,7 +1691,7 @@ def symmetrize(self, pos=None): ....: print s[i,j,k] == (c[i,j,k]+c[i,k,j])/2, ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s = c.symmetrize((0,2)) ; s # symmetrization on the first and last indices + sage: s = c.symmetrize(0,2) ; s # symmetrization on the first and last indices 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1529,7 +1714,7 @@ def symmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup - if pos is None: + if not pos: pos = range(self._nid) else: if len(pos) < 2: @@ -1558,16 +1743,15 @@ def symmetrize(self, pos=None): return result - def antisymmetrize(self, pos=None): + def antisymmetrize(self, *pos): r""" Antisymmetrization over the given index positions INPUT: - - ``pos`` -- (default: None) tuple of index positions involved in the - antisymmetrization (with the convention position=0 for the first - slot); if none, the antisymmetrization is performed over all the - indices + - ``pos`` -- list of index positions involved in the antisymmetrization + (with the convention position=0 for the first slot); if none, the + antisymmetrization is performed over all the indices OUTPUT: @@ -1594,7 +1778,7 @@ def antisymmetrize(self, pos=None): [4 5 6] [ 1 0 -1] [7 8 9], [ 2 1 0] ) - sage: c.antisymmetrize() == c.antisymmetrize((0,1)) + sage: c.antisymmetrize() == c.antisymmetrize(0,1) True Full antisymmetrization of 3-indices components:: @@ -1620,12 +1804,12 @@ def antisymmetrize(self, pos=None): ....: for k in range(3): ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6, True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: c.symmetrize() == c.symmetrize((0,1,2)) + sage: c.symmetrize() == c.symmetrize(0,1,2) True Partial antisymmetrization of 3-indices components:: - sage: s = c.antisymmetrize((0,1)) ; s # antisymmetrization on the first two indices + sage: s = c.antisymmetrize(0,1) ; s # antisymmetrization on the first two indices 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1645,7 +1829,7 @@ def antisymmetrize(self, pos=None): ....: print s[i,j,k] == (c[i,j,k]-c[j,i,k])/2, ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s = c.antisymmetrize((1,2)) ; s # antisymmetrization on the last two indices + sage: s = c.antisymmetrize(1,2) ; s # antisymmetrization on the last two indices 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1665,7 +1849,7 @@ def antisymmetrize(self, pos=None): ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j])/2, ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s = c.antisymmetrize((0,2)) ; s # antisymmetrization on the first and last indices + sage: s = c.antisymmetrize(0,2) ; s # antisymmetrization on the first and last indices 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1688,16 +1872,16 @@ def antisymmetrize(self, pos=None): The order of index positions in the argument does not matter:: - sage: c.antisymmetrize((1,0)) == c.antisymmetrize((0,1)) + sage: c.antisymmetrize(1,0) == c.antisymmetrize(0,1) True - sage: c.antisymmetrize((2,1)) == c.antisymmetrize((1,2)) + sage: c.antisymmetrize(2,1) == c.antisymmetrize(1,2) True - sage: c.antisymmetrize((2,0)) == c.antisymmetrize((0,2)) + sage: c.antisymmetrize(2,0) == c.antisymmetrize(0,2) True """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup - if pos is None: + if not pos: pos = range(self._nid) else: if len(pos) < 2: @@ -2369,7 +2553,7 @@ def __mul__(self, other): return result - def self_contract(self, pos1, pos2): + def trace(self, pos1, pos2): r""" Index contraction, taking care of the symmetries. @@ -2392,7 +2576,7 @@ def self_contract(self, pos1, pos2): sage: V = VectorSpace(QQ, 3) sage: a = CompFullySym(QQ, V.basis(), 2) sage: a[:] = [[1,2,3],[2,4,5],[3,5,6]] - sage: a.self_contract(0,1) + sage: a.trace(0,1) 11 sage: a[0,0] + a[1,1] + a[2,2] 11 @@ -2401,7 +2585,7 @@ def self_contract(self, pos1, pos2): sage: b = CompFullyAntiSym(QQ, V.basis(), 2) sage: b[0,1], b[0,2], b[1,2] = (3, -2, 1) - sage: b.self_contract(0,1) # must be zero by antisymmetry + sage: b.trace(0,1) # must be zero by antisymmetry 0 @@ -2415,7 +2599,7 @@ def self_contract(self, pos1, pos2): (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (1, 2) - sage: s = c.self_contract(0,1) ; s + sage: s = c.trace(0,1) ; s 1-index components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2425,7 +2609,7 @@ def self_contract(self, pos1, pos2): [-28, 2, 8] sage: [sum(v[k]*b[k,i] for k in range(3)) for i in range(3)] # check [-28, 2, 8] - sage: s = c.self_contract(1,2) ; s + sage: s = c.trace(1,2) ; s 1-index components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2439,10 +2623,10 @@ def self_contract(self, pos1, pos2): (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (0, 1) - sage: s = c.self_contract(0,1) + sage: s = c.trace(0,1) sage: s[:] # is zero by antisymmetry [0, 0, 0] - sage: s = c.self_contract(1,2) ; s[:] + sage: s = c.trace(1,2) ; s[:] [28, -2, -8] sage: [sum(b[i,k]*v[k] for k in range(3)) for i in range(3)] # check [28, -2, -8] @@ -2455,7 +2639,7 @@ def self_contract(self, pos1, pos2): (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) - sage: s = c.self_contract(0,1) ; s # the symmetry on (0,1) is lost: + sage: s = c.trace(0,1) ; s # the symmetry on (0,1) is lost: fully antisymmetric 2-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2467,7 +2651,7 @@ def self_contract(self, pos1, pos2): [ 22 -11 0] sage: [[sum(c[k,k,i,j] for k in range(3)) for j in range(3)] for i in range(3)] # check [[0, 33, -22], [-33, 0, 11], [22, -11, 0]] - sage: s = c.self_contract(1,2) ; s # both symmetries are lost by this contraction + sage: s = c.trace(1,2) ; s # both symmetries are lost by this contraction 2-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2682,13 +2866,13 @@ def non_redundant_index_generator(self): ind[pos] = si ret = 1 - def symmetrize(self, pos=None): + def symmetrize(self, *pos): r""" Symmetrization over the given index positions INPUT: - - ``pos`` -- (default: None) list of index positions involved in the + - ``pos`` -- list of index positions involved in the symmetrization (with the convention position=0 for the first slot); if none, the symmetrization is performed over all the indices @@ -2706,7 +2890,7 @@ def symmetrize(self, pos=None): sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 3) sage: c[:] = [[[1,2,3], [4,5,6], [7,8,9]], [[10,11,12], [13,14,15], [16,17,18]], [[19,20,21], [22,23,24], [25,26,27]]] - sage: cs = c.symmetrize((0,1)) ; cs + sage: cs = c.symmetrize(0,1) ; cs 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2727,7 +2911,7 @@ def symmetrize(self, pos=None): [[29/3, 14, 55/3], [14, 55/3, 68/3], [55/3, 68/3, 27]]]) sage: s == c.symmetrize() # should be true True - sage: s1 = cs.symmetrize((0,1)) ; s1 # should return a copy of cs + sage: s1 = cs.symmetrize(0,1) ; s1 # should return a copy of cs 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2738,7 +2922,7 @@ def symmetrize(self, pos=None): Let us now start with a symmetry on the last two indices:: - sage: cs1 = c.symmetrize((1,2)) ; cs1 + sage: cs1 = c.symmetrize(1,2) ; cs1 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2757,13 +2941,13 @@ def symmetrize(self, pos=None): the index positions (1,2) a set of components that is symmetric w.r.t. the index positions (0,1):: - sage: cs = c.symmetrize((0,1)) ; cs + sage: cs = c.symmetrize(0,1) ; cs 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1) - sage: css = cs.symmetrize((1,2)) + sage: css = cs.symmetrize(1,2) sage: css # the symmetry (0,1) has been lost: 3-indices components w.r.t. [ (1, 0, 0), @@ -2794,7 +2978,7 @@ def symmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (1, 2, 3) - sage: a1 = a.symmetrize((0,1)) ; a1 # the symmetry (1,2,3) has been reduced to (2,3): + sage: a1 = a.symmetrize(0,1) ; a1 # the symmetry (1,2,3) has been reduced to (2,3): 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2824,7 +3008,7 @@ def symmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (2, 3) - sage: s = c.symmetrize((0,1)) ; s # symmetrization on the first two indices + sage: s = c.symmetrize(0,1) ; s # symmetrization on the first two indices 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2840,7 +3024,7 @@ def symmetrize(self, pos=None): ] sage: s == 0 # the full symmetrization results in zero due to the antisymmetry on the last two indices True - sage: s = c.symmetrize((2,3)) ; s + sage: s = c.symmetrize(2,3) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2848,7 +3032,7 @@ def symmetrize(self, pos=None): ], with symmetry on the index positions (2, 3) sage: s == 0 # must be zero since the symmetrization has been performed on the antisymmetric indices True - sage: s = c.symmetrize((0,2)) ; s + sage: s = c.symmetrize(0,2) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2870,14 +3054,14 @@ def symmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (1, 2, 3) - sage: s = c.symmetrize((0,1)) ; s + sage: s = c.symmetrize(0,1) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) sage: # Note that the antisymmetry on (1, 2, 3) has been reduced to (2, 3) only - sage: s = c.symmetrize((1,2)) ; s + sage: s = c.symmetrize(1,2) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -2888,7 +3072,7 @@ def symmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup - if pos is None: + if not pos: pos = range(self._nid) else: if len(pos) < 2: @@ -2983,15 +3167,15 @@ def symmetrize(self, pos=None): return result - def antisymmetrize(self, pos=None): + def antisymmetrize(self, *pos): r""" Antisymmetrization over the given index positions INPUT: - - ``pos`` -- (default: None) list of index positions involved in the - antisymmetrization (with the convention position=0 for the first slot); - if none, the antisymmetrization is performed over all the indices + - ``pos`` -- list of index positions involved in the antisymmetrization + (with the convention position=0 for the first slot); if none, the + antisymmetrization is performed over all the indices OUTPUT: @@ -3038,7 +3222,7 @@ def antisymmetrize(self, pos=None): Antisymmetrization over already antisymmetric indices does not change anything:: - sage: s1 = s.antisymmetrize((1,2)) ; s1 + sage: s1 = s.antisymmetrize(1,2) ; s1 fully antisymmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3046,7 +3230,7 @@ def antisymmetrize(self, pos=None): ] sage: s1 == s True - sage: c1 = c.antisymmetrize((1,2)) ; c1 + sage: c1 = c.antisymmetrize(1,2) ; c1 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3057,7 +3241,7 @@ def antisymmetrize(self, pos=None): But in general, antisymmetrization may alter previous antisymmetries:: - sage: c2 = c.antisymmetrize((0,1)) ; c2 # the antisymmetry (2,3) is lost: + sage: c2 = c.antisymmetrize(0,1) ; c2 # the antisymmetry (2,3) is lost: 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3071,7 +3255,7 @@ def antisymmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (0, 1, 2) - sage: s = c.antisymmetrize((1,3)) ; s + sage: s = c.antisymmetrize(1,3) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3093,7 +3277,7 @@ def antisymmetrize(self, pos=None): (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1) - sage: s = c.antisymmetrize((2,3)) ; s + sage: s = c.antisymmetrize(2,3) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3120,7 +3304,7 @@ def antisymmetrize(self, pos=None): Similarly, the partial antisymmetrization on the first two indices results in zero:: - sage: s = c.antisymmetrize((0,1)) ; s + sage: s = c.antisymmetrize(0,1) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3131,7 +3315,7 @@ def antisymmetrize(self, pos=None): The partial antisymmetrization on the positions (0,2) destroys the symmetry on (0,1):: - sage: s = c.antisymmetrize((0,2)) ; s + sage: s = c.antisymmetrize(0,2) ; s 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3148,7 +3332,7 @@ def antisymmetrize(self, pos=None): """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup - if pos is None: + if not pos: pos = range(self._nid) else: if len(pos) < 2: diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 42d02c461a8..9a42d6100f5 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -629,8 +629,13 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, elif tensor_type==(1,1): return FreeModuleEndomorphism(self, name=name, latex_name=latex_name) - elif tensor_type[0]==0 and tensor_type[1]>1 and antisym is not None: - if len(antisym)==tensor_type[1]: + elif tensor_type[0]==0 and tensor_type[1]>1 and antisym is not None\ + and antisym !=[]: + if isinstance(antisym, list): + antisym0 = antisym[0] + else: + antisym0 = antisym + if len(antisym0)==tensor_type[1]: return FreeModuleAltForm(self, tensor_type[1], name=name, latex_name=latex_name) else: diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index cc111d44196..9c14c8290b4 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -1474,24 +1474,25 @@ def __call__(self, *args): res._latex_name = res_latex return res - def self_contract(self, pos1, pos2): + def trace(self, pos1=0, pos2=1): r""" - Contraction on two slots of the tensor. + Trace (contraction) on two slots of the tensor. INPUT: - - ``pos1`` -- position of the first index for the contraction, with the - convention ``pos1=0`` for the first slot - - ``pos2`` -- position of the second index for the contraction, with - the same convention as for ``pos1``. + - ``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`` OUTPUT: - - tensor resulting from the (pos1, pos2) contraction + - tensor or scalar resulting from the (pos1, pos2) contraction EXAMPLES: - Contraction on the two slots of a type-(1,1) tensor:: + Trace of a type-(1,1) tensor:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e @@ -1499,12 +1500,14 @@ def self_contract(self, pos1, pos2): sage: a = M.tensor((1,1), name='a') ; a endomorphism a on the rank-3 free module M over the Integer Ring sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] - sage: a.self_contract(0,1) # contraction of slot 0 with slot 1 + sage: a.trace() + 15 + sage: a.trace(0,1) # equivalent to above (contraction of slot 0 with slot 1) 15 - sage: a.self_contract(1,0) # the order of the slots does not matter + sage: a.trace(1,0) # the order of the slots does not matter 15 - Instead of the explicit call to the method :meth:`self_contract`, one + 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 square brackets:: @@ -1528,7 +1531,7 @@ def self_contract(self, pos1, pos2): sage: b = M.tensor((2,0), name='b') ; b type-(2,0) tensor b on the rank-3 free module M over the Integer Ring sage: b[:] = [[1,2,3], [4,5,6], [7,8,9]] - sage: b.self_contract(0,1) + sage: b.trace(0,1) Traceback (most recent call last): ... IndexError: Contraction on two contravariant indices is not allowed. @@ -1543,7 +1546,7 @@ def self_contract(self, pos1, pos2): sage: # by construction, t is a tensor field antisymmetric w.r.t. its last two slots: sage: t.symmetries() no symmetry; antisymmetry: (2, 3) - sage: s = t.self_contract(0,1) ; s # contraction on the first two slots + sage: s = t.trace(0,1) ; s # contraction on the first two slots alternating form of degree 2 on the rank-3 free module M over the Integer Ring sage: s.symmetries() # the antisymmetry is preserved no symmetry; antisymmetry: (0, 1) @@ -1553,7 +1556,7 @@ def self_contract(self, pos1, pos2): [-30 -15 0] sage: s == 15*b # check True - sage: s = t.self_contract(0,2) ; s # contraction on the first and third slots + sage: s = t.trace(0,2) ; s # contraction on the first and third slots type-(0,2) tensor on the rank-3 free module M over the Integer Ring sage: s.symmetries() # the antisymmetry has been destroyed by the above contraction: no symmetry; no antisymmetry @@ -1564,25 +1567,25 @@ def self_contract(self, pos1, pos2): sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) for j in M.irange()] for i in M.irange()] ) # check True - Use of index notation instead of :meth:`self_contract`:: + Use of index notation instead of :meth:`trace`:: - sage: t['^k_kij'] == t.self_contract(0,1) + sage: t['^k_kij'] == t.trace(0,1) True - sage: t['^k_{kij}'] == t.self_contract(0,1) # LaTeX notation + sage: t['^k_{kij}'] == t.trace(0,1) # LaTeX notation True - sage: t['^k_ikj'] == t.self_contract(0,2) + sage: t['^k_ikj'] == t.trace(0,2) True - sage: t['^k_ijk'] == t.self_contract(0,3) + sage: t['^k_ijk'] == t.trace(0,3) True Index symbols not involved in the contraction may be replaced by dots:: - sage: t['^k_k..'] == t.self_contract(0,1) + sage: t['^k_k..'] == t.trace(0,1) True - sage: t['^k_.k.'] == t.self_contract(0,2) + sage: t['^k_.k.'] == t.trace(0,2) True - sage: t['^k_..k'] == t.self_contract(0,3) + sage: t['^k_..k'] == t.trace(0,3) True """ @@ -1600,7 +1603,7 @@ def self_contract(self, pos1, pos2): basis = self._fmodule._def_basis else: # a basis is picked arbitrarily: basis = self.pick_a_basis() - resu_comp = self._components[basis].self_contract(pos1, pos2) + resu_comp = self._components[basis].trace(pos1, pos2) if self._tensor_rank == 2: # result is a scalar return resu_comp else: @@ -1608,20 +1611,25 @@ def self_contract(self, pos1, pos2): def contract(self, *args): r""" - Contraction with another tensor. + Contraction on one or more indices with another tensor. INPUT: - - ``pos1`` -- position of the first index (in ``self``) for the - contraction; if not given, the last index position is assumed + - ``pos1`` -- positions of the indices in ``self`` involved in the + contraction; ``pos1`` must be a sequence of integers, with 0 standing + for the first index position, 1 for the second one, etc. If ``pos1`` + is not provided, a single contraction on the last index position of + ``self`` is assumed - ``other`` -- the tensor to contract with - - ``pos2`` -- position of the second index (in ``other``) for the - contraction; if not given, the first index position is assumed - + - ``pos2`` -- positions of the indices in ``other`` involved in the + contraction, with the same conventions as for ``pos1``. If ``pos2`` + is not provided, a single contraction on the first index position of + ``other`` is assumed + OUTPUT: - - tensor resulting from the (pos1, pos2) contraction of ``self`` with - ``other`` + - tensor resulting from the contraction at the positions ``pos1`` and + ``pos2`` of ``self`` with ``other`` EXAMPLES: @@ -1718,7 +1726,7 @@ def contract(self, *args): sage: a.contract(0, b) Traceback (most recent call last): ... - TypeError: Contraction not possible: the two index positions are both contravariant. + TypeError: Contraction on two contravariant indices not permitted. In the present case, performing the contraction is identical to applying the endomorphism to the module element:: @@ -1782,64 +1790,97 @@ def contract(self, *args): sage: s1 == s # ... and yields the same result as previously: True + The contraction can be performed on more than a single index; for + instance a 2-indices contraction of a type-(2,1) tensor with a + type-(1,2) one is:: + + sage: a # a is a tensor of type-(2,1) + type-(2,1) tensor on the rank-3 free module M over the Integer Ring + sage: b = M([1,-1,2])*b ; b # a tensor of type (1,2) + type-(1,2) tensor on the rank-3 free module M over the Integer Ring + sage: s = a.contract(1,2,b,1,0) ; s # the double contraction + endomorphism on the rank-3 free module M over the Integer Ring + sage: s[:] + [ -36 30 15] + [-252 210 105] + [-204 170 85] + sage: s == a['^.k_l']*b['^l_k.'] # the same thing in index notation + True + """ + # + # Treatment of the input + # nargs = len(args) - if nargs == 1: - pos1 = self._tensor_rank - 1 - other = args[0] - pos2 = 0 - elif nargs == 2: - if isinstance(args[0], FreeModuleTensor): - pos1 = self._tensor_rank - 1 - other = args[0] - pos2 = args[1] - else: - pos1 = args[0] - other = args[1] - pos2 = 0 - elif nargs == 3: - pos1 = args[0] - other = args[1] - pos2 = args[2] + for i, arg in enumerate(args): + if isinstance(arg, FreeModuleTensor): + other = arg + it = i + break else: - raise TypeError("Wrong number of arguments in contract(): " + - str(nargs) + - " arguments provided, while between 1 and 3 are expected.") - if not isinstance(other, FreeModuleTensor): - raise TypeError("For the contraction, other must be a tensor ") + raise TypeError("A tensor must be provided in the argument list.") + if it == 0: + pos1 = (self._tensor_rank - 1,) + else: + pos1 = args[:it] + if it == nargs-1: + pos2 = (0,) + else: + pos2 = args[it+1:] + ncontr = len(pos1) # number of contractions + if len(pos2) != ncontr: + raise TypeError("Different number of indices for the contraction.") k1, l1 = self._tensor_type k2, l2 = other._tensor_type - if pos1 < k1 and pos2 < k2: - raise TypeError("Contraction not possible: the two index " + - "positions are both contravariant.") - if pos1 >= k1 and pos2 >= k2: - raise TypeError("Contraction not possible: the two index " + - "positions are both covavariant.") + for i in range(ncontr): + p1 = pos1[i] + p2 = pos2[i] + if p1 < k1 and p2 < k2: + raise TypeError("Contraction on two contravariant indices " + + "not permitted.") + if p1 >= k1 and p2 >= k2: + raise TypeError("Contraction on two covariant indices " + + "not permitted.") + # + # Contraction at the component level + # basis = self.common_basis(other) if basis is None: raise ValueError("No common basis for the contraction.") - cmp_res = self._components[basis].contract(pos1, - other._components[basis], pos2) - # reordering of the indices to have all contravariant indices first: - if k2 > 1: - if pos1 < k1: - cmp_res = cmp_res.swap_adjacent_indices(k1-1, k1+l1-1, k1+l1+k2-1) - else: - cmp_res = cmp_res.swap_adjacent_indices(k1, k1+l1-1, k1+l1+k2-2) - type_res = (k1+k2-1, l1+l2-1) - if type_res == (0, 0): - return cmp_res # scalar case - else: - return self._fmodule.tensor_from_comp(type_res, cmp_res) - - - def symmetrize(self, pos=None, basis=None): + args = pos1 + (other._components[basis],) + pos2 + cmp_res = self._components[basis].contract(*args) + if self._tensor_rank + other._tensor_rank - 2*ncontr == 0: + # Case of scalar output: + return cmp_res + # + # Reordering of the indices to have all contravariant indices first: + # + nb_cov_s = 0 # Number of covariant indices of self not involved in the + # contraction + for pos in range(k1,k1+l1): + if pos not in pos1: + nb_cov_s += 1 + nb_con_o = 0 # Number of contravariant indices of other not involved + # in the contraction + for pos in range(0,k2): + if pos not in pos2: + nb_con_o += 1 + if nb_cov_s != 0 and nb_con_o !=0: + # some reodering is necessary: + p2 = k1 + l1 - ncontr + p1 = p2 - nb_cov_s + p3 = p2 + nb_con_o + cmp_res = cmp_res.swap_adjacent_indices(p1, p2, p3) + type_res = (k1+k2-ncontr, l1+l2-ncontr) + return self._fmodule.tensor_from_comp(type_res, cmp_res) + + def symmetrize(self, *pos, **kwargs): r""" Symmetrization over some arguments. INPUT: - - ``pos`` -- (default: None) list of argument positions involved in the + - ``pos`` -- list of argument positions involved in the symmetrization (with the convention position=0 for the first argument); if none, the symmetrization is performed over all the arguments @@ -1904,7 +1945,7 @@ def symmetrize(self, pos=None, basis=None): sage: t = M.tensor((0,3)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] - sage: s = t.symmetrize((0,1)) ; s # (0,1) = the first two arguments + sage: s = t.symmetrize(0,1) ; s # (0,1) = the first two arguments type-(0,3) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry @@ -1919,23 +1960,23 @@ def symmetrize(self, pos=None, basis=None): ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[j,i,k]), ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s.symmetrize((0,1)) == s # another test + sage: s.symmetrize(0,1) == s # another test True Again the index notation can be used:: - sage: t['_(ij)k'] == t.symmetrize((0,1)) + sage: t['_(ij)k'] == t.symmetrize(0,1) True - sage: t['_(..).'] == t.symmetrize((0,1)) # no index name + sage: t['_(..).'] == t.symmetrize(0,1) # no index name True - sage: t['_{(ij)k}'] == t.symmetrize((0,1)) # LaTeX notation + sage: t['_{(ij)k}'] == t.symmetrize(0,1) # LaTeX notation True - sage: t['_{(..).}'] == t.symmetrize((0,1)) # this also allowed + sage: t['_{(..).}'] == t.symmetrize(0,1) # this also allowed True Symmetrization of a tensor of type (0,3) on the first and last arguments:: - sage: s = t.symmetrize((0,2)) ; s # (0,2) = first and last arguments + sage: s = t.symmetrize(0,2) ; s # (0,2) = first and last arguments type-(0,3) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 2); no antisymmetry @@ -1949,12 +1990,12 @@ def symmetrize(self, pos=None, basis=None): ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[k,j,i]), ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s.symmetrize((0,2)) == s # another test + sage: s.symmetrize(0,2) == s # another test True Symmetrization of a tensor of type (0,3) on the last two arguments:: - sage: s = t.symmetrize((1,2)) ; s # (1,2) = the last two arguments + sage: s = t.symmetrize(1,2) ; s # (1,2) = the last two arguments type-(0,3) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (1, 2); no antisymmetry @@ -1969,16 +2010,16 @@ def symmetrize(self, pos=None, basis=None): ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[i,k,j]), ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s.symmetrize((1,2)) == s # another test + sage: s.symmetrize(1,2) == s # another test True Use of the index notation:: - sage: t['_i(jk)'] == t.symmetrize((1,2)) + sage: t['_i(jk)'] == t.symmetrize(1,2) True - sage: t['_.(..)'] == t.symmetrize((1,2)) + sage: t['_.(..)'] == t.symmetrize(1,2) True - sage: t['_{i(jk)}'] == t.symmetrize((1,2)) # LaTeX notation + sage: t['_{i(jk)}'] == t.symmetrize(1,2) # LaTeX notation True Full symmetrization of a tensor of type (0,3):: @@ -2012,38 +2053,38 @@ def symmetrize(self, pos=None, basis=None): sage: t = M.tensor((1,2)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] - sage: s = t.symmetrize((0,1)) + sage: s = t.symmetrize(0,1) Traceback (most recent call last): ... TypeError: 0 is a contravariant position, while 1 is a covariant position; symmetrization is meaningfull only on tensor arguments of the same type. - sage: s = t.symmetrize((1,2)) # OK: both 1 and 2 are covariant positions + sage: s = t.symmetrize(1,2) # OK: both 1 and 2 are covariant positions The order of positions does not matter:: - sage: t.symmetrize((2,1)) == t.symmetrize((1,2)) + sage: t.symmetrize(2,1) == t.symmetrize(1,2) True Use of the index notation:: - sage: t['^i_(jk)'] == t.symmetrize((1,2)) + sage: t['^i_(jk)'] == t.symmetrize(1,2) True - sage: t['^._(..)'] == t.symmetrize((1,2)) + sage: t['^._(..)'] == t.symmetrize(1,2) True The character '^' can be skipped, the character '_' being sufficient to separate contravariant indices from covariant ones:: - sage: t['i_(jk)'] == t.symmetrize((1,2)) + sage: t['i_(jk)'] == t.symmetrize(1,2) True The LaTeX notation can be employed:: - sage: t['^{i}_{(jk)}'] == t.symmetrize((1,2)) + sage: t['^{i}_{(jk)}'] == t.symmetrize(1,2) True """ - if pos is None: + if not pos: pos = range(self._tensor_rank) # check whether the symmetrization is possible: pos_cov = self._tensor_type[0] # first covariant position @@ -2064,19 +2105,21 @@ def symmetrize(self, pos=None, basis=None): str(pos[k]) + " is a contravariant position; \n" "symmetrization is meaningfull only on tensor " + "arguments of the same type.") - if basis is None: + if 'basis' in kwargs: + basis = kwargs['basis'] + else: basis = self.pick_a_basis() - res_comp = self._components[basis].symmetrize(pos) + res_comp = self._components[basis].symmetrize(*pos) return self._fmodule.tensor_from_comp(self._tensor_type, res_comp) - def antisymmetrize(self, pos=None, basis=None): + def antisymmetrize(self, *pos, **kwargs): r""" Antisymmetrization over some arguments. INPUT: - - ``pos`` -- (default: None) list of argument positions involved in the + - ``pos`` -- list of argument positions involved in the antisymmetrization (with the convention position=0 for the first argument); if none, the antisymmetrization is performed over all the arguments @@ -2116,7 +2159,7 @@ def antisymmetrize(self, pos=None, basis=None): True True True True True True True True True sage: s.antisymmetrize() == s # another test True - sage: t.antisymmetrize() == t.antisymmetrize((0,1)) + sage: t.antisymmetrize() == t.antisymmetrize(0,1) True Antisymmetrization of a tensor of type (0,3) over the first two @@ -2124,7 +2167,7 @@ def antisymmetrize(self, pos=None, basis=None): sage: t = M.tensor((0,3)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] - sage: s = t.antisymmetrize((0,1)) ; s # (0,1) = the first two arguments + sage: s = t.antisymmetrize(0,1) ; s # (0,1) = the first two arguments type-(0,3) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) @@ -2139,9 +2182,9 @@ def antisymmetrize(self, pos=None, basis=None): ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[j,i,k]), ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s.antisymmetrize((0,1)) == s # another test + sage: s.antisymmetrize(0,1) == s # another test True - sage: s.symmetrize((0,1)) == 0 # of course + sage: s.symmetrize(0,1) == 0 # of course True Instead of invoking the method :meth:`antisymmetrize`, one can use @@ -2170,7 +2213,7 @@ def antisymmetrize(self, pos=None, basis=None): Antisymmetrization of a tensor of type (0,3) over the first and last arguments:: - sage: s = t.antisymmetrize((0,2)) ; s # (0,2) = first and last arguments + sage: s = t.antisymmetrize(0,2) ; s # (0,2) = first and last arguments type-(0,3) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 2) @@ -2185,17 +2228,17 @@ def antisymmetrize(self, pos=None, basis=None): ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[k,j,i]), ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s.antisymmetrize((0,2)) == s # another test + sage: s.antisymmetrize(0,2) == s # another test True - sage: s.symmetrize((0,2)) == 0 # of course + sage: s.symmetrize(0,2) == 0 # of course True - sage: s.symmetrize((0,1)) == 0 # no reason for this to hold + sage: s.symmetrize(0,1) == 0 # no reason for this to hold False Antisymmetrization of a tensor of type (0,3) over the last two arguments:: - sage: s = t.antisymmetrize((1,2)) ; s # (1,2) = the last two arguments + sage: s = t.antisymmetrize(1,2) ; s # (1,2) = the last two arguments type-(0,3) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (1, 2) @@ -2210,15 +2253,15 @@ def antisymmetrize(self, pos=None, basis=None): ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[i,k,j]), ....: True True True True True True True True True True True True True True True True True True True True True True True True True True True - sage: s.antisymmetrize((1,2)) == s # another test + sage: s.antisymmetrize(1,2) == s # another test True - sage: s.symmetrize((1,2)) == 0 # of course + sage: s.symmetrize(1,2) == 0 # of course True The index notation can be used instead of the explicit call to :meth:`antisymmetrize`:: - sage: t['_i[jk]'] == t.antisymmetrize((1,2)) + sage: t['_i[jk]'] == t.antisymmetrize(1,2) True Full antisymmetrization of a tensor of type (0,3):: @@ -2240,13 +2283,13 @@ def antisymmetrize(self, pos=None, basis=None): True True True True True True True True True True True True True True True True True True True True True True True True True True True sage: s.antisymmetrize() == s # another test True - sage: s.symmetrize((0,1)) == 0 # of course + sage: s.symmetrize(0,1) == 0 # of course True - sage: s.symmetrize((0,2)) == 0 # of course + sage: s.symmetrize(0,2) == 0 # of course True - sage: s.symmetrize((1,2)) == 0 # of course + sage: s.symmetrize(1,2) == 0 # of course True - sage: t.antisymmetrize() == t.antisymmetrize((0,1,2)) + sage: t.antisymmetrize() == t.antisymmetrize(0,1,2) True The index notation can be used instead of the explicit call to @@ -2265,32 +2308,32 @@ def antisymmetrize(self, pos=None, basis=None): sage: t = M.tensor((1,2)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] - sage: s = t.antisymmetrize((0,1)) + sage: s = t.antisymmetrize(0,1) Traceback (most recent call last): ... TypeError: 0 is a contravariant position, while 1 is a covariant position; antisymmetrization is meaningfull only on tensor arguments of the same type. - sage: s = t.antisymmetrize((1,2)) # OK: both 1 and 2 are covariant positions + sage: s = t.antisymmetrize(1,2) # OK: both 1 and 2 are covariant positions The order of positions does not matter:: - sage: t.antisymmetrize((2,1)) == t.antisymmetrize((1,2)) + sage: t.antisymmetrize(2,1) == t.antisymmetrize(1,2) True Again, the index notation can be used:: - sage: t['^i_[jk]'] == t.antisymmetrize((1,2)) + sage: t['^i_[jk]'] == t.antisymmetrize(1,2) True - sage: t['^i_{[jk]}'] == t.antisymmetrize((1,2)) # LaTeX notation + sage: t['^i_{[jk]}'] == t.antisymmetrize(1,2) # LaTeX notation True The character '^' can be skipped:: - sage: t['i_[jk]'] == t.antisymmetrize((1,2)) + sage: t['i_[jk]'] == t.antisymmetrize(1,2) True """ - if pos is None: + if not pos: pos = range(self._tensor_rank) # check whether the antisymmetrization is possible: pos_cov = self._tensor_type[0] # first covariant position @@ -2311,9 +2354,11 @@ def antisymmetrize(self, pos=None, basis=None): str(pos[k]) + " is a contravariant position; \n" "antisymmetrization is meaningfull only on tensor " + "arguments of the same type.") - if basis is None: + if 'basis' in kwargs: + basis = kwargs['basis'] + else: basis = self.pick_a_basis() - res_comp = self._components[basis].antisymmetrize(pos) + res_comp = self._components[basis].antisymmetrize(*pos) return self._fmodule.tensor_from_comp(self._tensor_type, res_comp) diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py index c3feeebbddd..bd78756fef4 100644 --- a/src/sage/tensor/modules/tensor_with_indices.py +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -81,13 +81,13 @@ class TensorWithIndices(SageObject): type-(2,2) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry - sage: s == t.symmetrize((0,1)) + sage: s == t.symmetrize(0,1) True The letters denoting the indices can be chosen freely; since they carry no information, they can even be replaced by dots:: - sage: t['^(..)_..'] == t.symmetrize((0,1)) + sage: t['^(..)_..'] == t.symmetrize(0,1) True Similarly, for an antisymmetrization:: @@ -96,14 +96,14 @@ class TensorWithIndices(SageObject): type-(2,2) tensor on the rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (2, 3) - sage: s == t.antisymmetrize((2,3)) + sage: s == t.antisymmetrize(2,3) True Another example of an operation indicated by indices is a contraction:: sage: s = t['^ki_kj'] ; s # contraction on the repeated index k endomorphism on the rank-3 free module M over the Rational Field - sage: s == t.self_contract(0,2) + sage: s == t.trace(0,2) True Indices not involved in the contraction may be replaced by dots:: @@ -125,6 +125,30 @@ class TensorWithIndices(SageObject): sage: t['^{ik}_{jl}']*b['_{mk}'] == s # LaTeX notation True + Contraction on two indices:: + + sage: s = a['^kl']*b['_kl'] ; s + 105 + sage: s == a.contract(0,1, b, 0,1) + True + + Some minimal arithmetics:: + + sage: 2*a['^ij'] + X^ij + sage: (2*a['^ij'])._tensor == 2*a + True + sage: 2*t['ij_kl'] + X^ij_kl + sage: +a['^ij'] + +a^ij + sage: +t['ij_kl'] + +t^ij_kl + sage: -a['^ij'] + -a^ij + sage: -t['ij_kl'] + -t^ij_kl + """ def __init__(self, tensor, indices): self._tensor = tensor # may be changed below @@ -150,7 +174,7 @@ def __init__(self, tensor, indices): if con.find('(', sym1+1) != -1 or '[' in con: raise NotImplementedError("Multiple symmetries are not " + "treated yet.") - self._tensor = self._tensor.symmetrize(range(sym1, sym2+1)) + self._tensor = self._tensor.symmetrize(*(range(sym1, sym2+1))) self._changed = True # self does no longer contain the original tensor con = con.replace('(','').replace(')','') if '[' in con: @@ -159,7 +183,7 @@ def __init__(self, tensor, indices): if con.find('[', sym1+1) != -1 or '(' in con: raise NotImplementedError("Multiple symmetries are not " + "treated yet.") - self._tensor = self._tensor.antisymmetrize(range(sym1, sym2+1)) + self._tensor = self._tensor.antisymmetrize(*(range(sym1, sym2+1))) self._changed = True # self does no longer contain the original tensor con = con.replace('[','').replace(']','') if len(con) != self._tensor._tensor_type[0]: @@ -184,7 +208,8 @@ def __init__(self, tensor, indices): "treated yet.") csym1 = sym1 + self._tensor._tensor_type[0] csym2 = sym2 + self._tensor._tensor_type[0] - self._tensor = self._tensor.symmetrize(range(csym1, csym2+1)) + self._tensor = self._tensor.symmetrize( + *(range(csym1, csym2+1))) self._changed = True # self does no longer contain the original # tensor cov = cov.replace('(','').replace(')','') @@ -196,7 +221,8 @@ def __init__(self, tensor, indices): "treated yet.") csym1 = sym1 + self._tensor._tensor_type[0] csym2 = sym2 + self._tensor._tensor_type[0] - self._tensor = self._tensor.antisymmetrize(range(csym1, csym2+1)) + self._tensor = self._tensor.antisymmetrize( + *(range(csym1, csym2+1))) self._changed = True # self does no longer contain the original # tensor cov = cov.replace('[','').replace(']','') @@ -215,12 +241,12 @@ def __init__(self, tensor, indices): pos2 = self._tensor._tensor_type[0] + self._cov.index(ind) contraction_pairs.append((pos1, pos2)) if len(contraction_pairs) > 1: - raise NotImplementedError("Multiple contractions are not " + + raise NotImplementedError("Multiple self-contractions are not " + "implemented yet.") if len(contraction_pairs) == 1: pos1 = contraction_pairs[0][0] pos2 = contraction_pairs[0][1] - self._tensor = self._tensor.self_contract(pos1, pos2) + self._tensor = self._tensor.trace(pos1, pos2) self._changed = True # self does no longer contain the original # tensor ind = self._con[pos1] @@ -282,11 +308,40 @@ def __mul__(self, other): if contraction_pairs == []: # No contraction is performed: the tensor product is returned return self._tensor * other._tensor - if len(contraction_pairs) > 1: - raise NotImplementedError("Multiple contractions are not " + - "implemented yet.") - pos1 = contraction_pairs[0][0] - pos2 = contraction_pairs[0][1] - return self._tensor.contract(pos1, other._tensor, pos2) + ncontr = len(contraction_pairs) + pos1 = [contraction_pairs[i][0] for i in range(ncontr)] + pos2 = [contraction_pairs[i][1] for i in range(ncontr)] + args = pos1 + [other._tensor] + pos2 + return self._tensor.contract(*args) + def __rmul__(self, other): + r""" + Multiplication on the left by ``other``. + """ + return TensorWithIndices(other*self._tensor, + self._con + '_' + self._cov) + + def __pos__(self): + r""" + Unary plus operator. + + OUTPUT: + + - an exact copy of ``self`` + + """ + return TensorWithIndices(+self._tensor, + self._con + '_' + self._cov) + + def __neg__(self): + r""" + Unary minus operator. + + OUTPUT: + + - - ``self`` + + """ + return TensorWithIndices(-self._tensor, + self._con + '_' + self._cov) From c612e31909f31f2dedd641e6fa10fee94635ea8c Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Mon, 29 Sep 2014 16:30:48 -0400 Subject: [PATCH 030/129] Fixed two or three test failures in hyperbolic_geodesic.py. --- .../geometry/hyperbolic_space/hyperbolic_geodesic.py | 11 +++++++---- .../geometry/hyperbolic_space/hyperbolic_model.py | 4 ++-- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 25860a181bf..516b8359973 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -528,7 +528,7 @@ def reflection_involution(self): """ return self._cached_geodesic.reflection_involution().to_model(self._model) - def common_perpendicular(self, other): + def common_perpendicula(self, other): r""" Return the unique hyperbolic geodesic perpendicular to two given geodesics, if such a geodesic exists. If none exists, raise a @@ -600,7 +600,7 @@ def perpendicular_bisector(self): sage: g = HyperbolicPlane().PD().random_geodesic() sage: h = g.perpendicular_bisector() - sage: bool(h.intersection(g).coordinates() - g.midpoint().coordinates() < 10**-9) + sage: bool(h.intersection(g)[0].coordinates() - g.midpoint().coordinates() < 10**-9) True Complete geodesics cannot be bisected:: @@ -956,7 +956,7 @@ def perpendicular_bisector(self): #UHP sage: g = UHP.random_geodesic() sage: h = g.perpendicular_bisector() sage: c = lambda x: x.coordinates() - sage: bool(c(g.intersection(h).start()) - c(g.midpoint()) < 10**-9) + sage: bool(c(g.intersection(h)[0]) - c(g.midpoint()) < 10**-9) True Infinite geodesics cannot be bisected:: @@ -973,7 +973,10 @@ def perpendicular_bisector(self): #UHP S = self.complete()._to_std_geod(start) T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]]) T2 = matrix([[cos(pi/4), -sin(pi/4)], [sin(pi/4), cos(pi/4)]]) - H = self._model.get_isometry(S.inverse() * (T1 * T2) * S) + isom_mtrx = S.inverse() * (T1 * T2) * S # We need to clean this matrix up. + if (isom_mtrx - isom_mtrx.conjugate()).norm() < 2**-9: # Imaginary part is small. + isom_mtrx = (isom_mtrx + isom_mtrx.conjugate()) / 2 # Set it to its real part. + H = self._model.get_isometry(isom_mtrx) return self._model.get_geodesic(H(self._start), H(self._end)) def midpoint(self): #UHP diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index b3a5b3df60d..628942370e5 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -671,8 +671,8 @@ def dist(self, a, b): if a.is_ultra_parallel(b): perp = a.common_perpendicular(b) # Find where a and b intersect the common perp... - p = a.intersection(perp) - q = b.intersection(perp) + p = a.intersection(perp)[0] + q = b.intersection(perp)[0] # ...and return their distance return self._dist_points(coords(p), coords(q)) From 222c2ad0611bc609c7a24f934a8fe6cef5cc6792 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Mon, 29 Sep 2014 20:59:09 -0400 Subject: [PATCH 031/129] All tests now pass for hyperbolic_geodesic.py. In particular, _to_std_geod tests are fixed. --- .../hyperbolic_space/hyperbolic_geodesic.py | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 516b8359973..2fb92beca53 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1106,20 +1106,26 @@ def _to_std_geod(self, p): EXAMPLES:: sage: UHP = HyperbolicPlane().UHP() - sage: (p1, p2, p3) = [UHP.random_point() for k in range(3)] - sage: A = UHP.get_geodesic(p1, p3)._to_std_geod(p2.coordinates()) + sage: (p1, p2) = [UHP.random_point() for k in range(2)] + sage: g = UHP.get_geodesic(p1, p2) + sage: A = g._to_std_geod(g.midpoint().coordinates()) # Send midpoint to I. sage: A = UHP.get_isometry(A) - sage: bool(abs(A(p1).coordinates()) < 10**-9) + sage: [s, e]= g.complete().endpoints() + sage: bool(abs(A(s).coordinates()) < 10**-9) True - sage: bool(abs(A(p2).coordinates() - I) < 10**-9) + sage: bool(abs(A(g.midpoint()).coordinates() - I) < 10**-9) True - sage: bool(A(p3).coordinates() == infinity) + sage: bool(A(e).coordinates() == infinity) True """ B = matrix([[1, 0], [0, -I]]) - s = self._start.coordinates() - e = self._end.coordinates() - return B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e) + [s, e]= [k.coordinates() for k in self.complete().endpoints()] + # outmat below will be returned after we normalize the determinant. + outmat = B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e) + outmat = outmat/outmat.det().sqrt() + if abs(outmat - outmat.conjugate()) < 10**-9: # Small imaginary part. + outmat = (outmat + outmat.conjugate()) / 2 # Set it equal to its real part. + return outmat @staticmethod def _crossratio_matrix(p0, p1, p2): #UHP From b558754ec2a6ca610ed14d1831e35002384c483c Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Tue, 30 Sep 2014 11:38:22 -0400 Subject: [PATCH 032/129] Fixed a bug in isometry_from_fixed_points. --- src/sage/geometry/hyperbolic_space/hyperbolic_model.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 628942370e5..4198fc739de 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -749,6 +749,13 @@ def isometry_from_fixed_points(self, repel, attract): sage: UHP = HyperbolicPlane().UHP() sage: PD = HyperbolicPlane().PD() + sage: PD.isometry_from_fixed_points(-i, i) + Isometry in PD + [ 3/4 1/4*I] + [-1/4*I 3/4] + + :: + sage: p, q = PD.get_point(1/2 + I/2), PD.get_point(6/13 + 9/13*I) sage: PD.isometry_from_fixed_points(p, q) Traceback (most recent call last): @@ -762,7 +769,8 @@ def isometry_from_fixed_points(self, repel, attract): [ 1/3*I - 1/6 -1/6*I - 2/3] """ R = self.realization_of().a_realization() - return R.isometry_from_fixed_points(R(repel), R(attract)).to_model(self) + return R.isometry_from_fixed_points(R(self(repel)), R(self(attract))).to_model(self) + ##################################################################### ## Upper half plane model From 45392f410cf0167d30400acf14365dbe1a1c7772 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Thu, 2 Oct 2014 23:43:32 -0400 Subject: [PATCH 033/129] Partial progress toward getting reflection_involution working properly with coercion UHP -> PD. --- .../hyperbolic_space/hyperbolic_coercion.py | 6 ++-- .../hyperbolic_space/hyperbolic_geodesic.py | 35 +++++++++++++++---- .../hyperbolic_space/hyperbolic_isometry.py | 3 -- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index ef26d64d539..887510ebe03 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -183,7 +183,8 @@ def image_isometry_matrix(self, x): [0 1] """ if x.det() < 0: - x = I * x +# x = I * x + return matrix([[1,-I],[-I,1]]) * x * matrix([[1,I],[I,1]]).conjugate()/Integer(2) return matrix([[1,-I],[-I,1]]) * x * matrix([[1,I],[I,1]])/Integer(2) class CoercionUHPtoKM(HyperbolicModelCoercion): @@ -297,7 +298,8 @@ def image_isometry_matrix(self, x): """ from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD if not HyperbolicIsometryPD._orientation_preserving(x): - x = I*x +# x = I*x + return matrix([[1,I],[I,1]]) * x * matrix([[1,-I],[-I,1]]).conjugate() / Integer(2) return matrix([[1,I],[I,1]]) * x * matrix([[1,-I],[-I,1]]) / Integer(2) class CoercionPDtoKM(HyperbolicModelCoercion): diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 2fb92beca53..edcd551c68f 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -505,26 +505,48 @@ def reflection_involution(self): EXAMPLES:: sage: H = HyperbolicPlane() - sage: H.UHP().get_geodesic(2,4).reflection_involution() + sage: gU = H.UHP().get_geodesic(2,4) + sage: RU = gU.reflection_involution(); RU Isometry in UHP [ 3 -8] [ 1 -3] - sage: H.PD().get_geodesic(0, I).reflection_involution() + sage: RU*gU == gU + True + + sage: gP = H.PD().get_geodesic(0, I) + sage: RP = gP.reflection_involution(); RP Isometry in PD - [ 0 -1] - [ 1 0] + [ 0 I] + [-I 0] + + sage: RP*gP == gP + True - sage: H.KM().get_geodesic((0,0), (0,1)).reflection_involution() + sage: gK = H.KM().get_geodesic((0,0), (0,1)) + sage: RK = gK.reflection_involution(); RK Isometry in KM [-1 0 0] [ 0 1 0] [ 0 0 1] + sage: RK*gK == gK + True + sage: A = H.HM().get_geodesic((0,0,1), (1,0, n(sqrt(2)))).reflection_involution() sage: B = diagonal_matrix([1, -1, 1]) sage: bool((B - A.matrix()).norm() < 10**-9) True + + The above tests go through the Upper Half Plane. It remains to + test that the matrices in the models do what we intend. + + :: + + sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform + sage: R = H.PD().get_geodesic(-1,1).reflection_involution() + sage: mobius_transform(R.matrix(), 0) == 0 + True """ return self._cached_geodesic.reflection_involution().to_model(self._model) @@ -734,7 +756,8 @@ def reflection_involution(self): EXAMPLES:: sage: UHP = HyperbolicPlane().UHP() - sage: UHP.get_geodesic(0, 1).reflection_involution() + sage: g1 = UHP.get_geodesic(0, 1) + sage: .reflection_involution() Isometry in UHP [ 1 0] [ 2 -1] diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 391b4f0f632..6632325c382 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -908,8 +908,6 @@ def _call_(self, p): True """ _image = mobius_transform(self._matrix, p.coordinates()) - if not self.preserves_orientation(): - _image = mobius_transform(I*matrix([[0,1],[1,0]]), _image) return self.codomain().get_point(_image) def preserves_orientation(self): @@ -1044,7 +1042,6 @@ def mobius_transform(A, z): if c*z + d == 0: return infinity return (a*w + b) / (c*w + d) - raise TypeError("A must be an invertible 2x2 matrix over the" " complex numbers or a symbolic ring") From 1f8d109f32f891e450905d75fe6bf081382316eb Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Fri, 3 Oct 2014 10:33:37 -0400 Subject: [PATCH 034/129] May need to roll this one back. Uncertain if it's an improvement. --- .../hyperbolic_space/hyperbolic_geodesic.py | 1 + .../hyperbolic_space/hyperbolic_isometry.py | 13 +++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index edcd551c68f..184b6f5efe4 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -543,6 +543,7 @@ def reflection_involution(self): :: + sage: H = HyperbolicPlane() # Remove before submitting XXX. sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform sage: R = H.PD().get_geodesic(-1,1).reflection_involution() sage: mobius_transform(R.matrix(), 0) == 0 diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 6632325c382..49a94977e90 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -186,11 +186,14 @@ def __eq__(self, other): """ if not isinstance(other, HyperbolicIsometry): return False - pos_matrix = bool(abs(self.matrix() - other.matrix()) < EPSILON) + test_matrix = bool((self.matrix() - other.matrix()).norm() < EPSILON) if self.domain().is_isometry_group_projective(): - neg_matrix = bool(abs(self.matrix() + other.matrix()) < EPSILON) - return self.domain() is other.domain() and (pos_matrix or neg_matrix) - return self.domain() is other.domain() and pos_matrix + A,B = self.matrix(), other.matrix() # Rename for simplicity + m = self.matrix().ncols() + A = A/sqrt(A.det(), m) # Normalized to have determinant 1 + B = B/sqrt(B.det(), m) + test_matrix = bool( (A - B).norm() < EPSILON or (A + B).norm() < EPSILON ) + return self.domain() is other.domain() and test_matrix def __hash__(self): """ @@ -908,6 +911,8 @@ def _call_(self, p): True """ _image = mobius_transform(self._matrix, p.coordinates()) + # if not self.preserves_orientation(): + # _image = mobius_transform(I*matrix([[0,1],[1,0]]), _image) return self.codomain().get_point(_image) def preserves_orientation(self): From 78209179e32bead65ec5f3de09c343dc1d7ed297 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20M=C3=B6dinger?= Date: Tue, 7 Oct 2014 12:50:04 +0200 Subject: [PATCH 035/129] Fixed the doctest of matrix/matrix_misc.py. --- src/sage/matrix/matrix_misc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/matrix/matrix_misc.py b/src/sage/matrix/matrix_misc.py index b07379a2512..4e65a64ff7a 100644 --- a/src/sage/matrix/matrix_misc.py +++ b/src/sage/matrix/matrix_misc.py @@ -68,7 +68,7 @@ def row_reduced_form(M,ascend=True): sage: R. = GF(3)['t'] sage: K = FractionField(R) sage: import sage.matrix.matrix_misc - sage: sage.matrix.matrix_misc.weak_popov_form(matrix([[(t-1)^2/t],[(t-1)]])) + sage: sage.matrix.matrix_misc.row_reduced_form(matrix([[(t-1)^2/t],[(t-1)]])) ( [ 0] [ t 2*t + 1] [(2*t + 1)/t], [ 1 2], [-Infinity, 0] @@ -76,7 +76,7 @@ def row_reduced_form(M,ascend=True): NOTES: - See docstring for weak_popov_form method of matrices for + See docstring for row_reduced_form method of matrices for more information. """ From 45aaa975d406ade603a15a34f34a9790936e42ab Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Wed, 8 Oct 2014 21:43:43 +0200 Subject: [PATCH 036/129] Add doctests and improve documentation in tensor/modules - Add doctests to all underscore methods in sage/tensor/modules - Move import of sage/tensor/modules/all.py from sage/all.py to sage/tensor/all.py - Remove the (spurious) mentions "This file was autogenerated" in rst files in doc/en/tensors_free_module (actually these files are not autogenerated) --- src/doc/en/tensors_free_module/comp.rst | 3 - .../finite_rank_free_module.rst | 3 - .../free_module_alt_form.rst | 3 - .../tensors_free_module/free_module_basis.rst | 3 - .../free_module_tensor.rst | 3 - .../free_module_tensor_spec.rst | 3 - .../tensor_free_module.rst | 3 - .../tensor_with_indices.rst | 3 - src/sage/all.py | 1 - src/sage/tensor/all.py | 1 + src/sage/tensor/modules/comp.py | 723 ++++++++++++++++-- .../tensor/modules/finite_rank_free_module.py | 64 +- src/sage/tensor/modules/format_utilities.py | 89 ++- .../tensor/modules/free_module_alt_form.py | 138 +++- src/sage/tensor/modules/free_module_basis.py | 176 +++++ src/sage/tensor/modules/free_module_tensor.py | 457 ++++++++++- .../tensor/modules/free_module_tensor_spec.py | 178 ++++- src/sage/tensor/modules/tensor_free_module.py | 82 +- .../tensor/modules/tensor_with_indices.py | 127 ++- 19 files changed, 1972 insertions(+), 88 deletions(-) diff --git a/src/doc/en/tensors_free_module/comp.rst b/src/doc/en/tensors_free_module/comp.rst index 5edc877457f..25434b1677d 100644 --- a/src/doc/en/tensors_free_module/comp.rst +++ b/src/doc/en/tensors_free_module/comp.rst @@ -5,9 +5,6 @@ Components ========== -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.comp :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/finite_rank_free_module.rst b/src/doc/en/tensors_free_module/finite_rank_free_module.rst index c327216f0fd..21fb348db1c 100644 --- a/src/doc/en/tensors_free_module/finite_rank_free_module.rst +++ b/src/doc/en/tensors_free_module/finite_rank_free_module.rst @@ -5,9 +5,6 @@ Free modules of finite rank =========================== -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.finite_rank_free_module :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/free_module_alt_form.rst b/src/doc/en/tensors_free_module/free_module_alt_form.rst index 8c481f2dd81..4ecc124777d 100644 --- a/src/doc/en/tensors_free_module/free_module_alt_form.rst +++ b/src/doc/en/tensors_free_module/free_module_alt_form.rst @@ -5,9 +5,6 @@ Alternating forms on free modules ================================= -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.free_module_alt_form :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/free_module_basis.rst b/src/doc/en/tensors_free_module/free_module_basis.rst index 0fdc17da7a5..9313780235f 100644 --- a/src/doc/en/tensors_free_module/free_module_basis.rst +++ b/src/doc/en/tensors_free_module/free_module_basis.rst @@ -5,9 +5,6 @@ Bases of free modules ===================== -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.free_module_basis :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/free_module_tensor.rst b/src/doc/en/tensors_free_module/free_module_tensor.rst index 128eaf03085..2f221ee7671 100644 --- a/src/doc/en/tensors_free_module/free_module_tensor.rst +++ b/src/doc/en/tensors_free_module/free_module_tensor.rst @@ -5,9 +5,6 @@ Tensors on free modules ======================= -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.free_module_tensor :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst index a11fd6cb08e..01dee654b85 100644 --- a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst +++ b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst @@ -5,9 +5,6 @@ Tensors of type (1,1) on free modules ===================================== -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.free_module_tensor_spec :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/tensor_free_module.rst b/src/doc/en/tensors_free_module/tensor_free_module.rst index eb3dc6994a3..295f44458ce 100644 --- a/src/doc/en/tensors_free_module/tensor_free_module.rst +++ b/src/doc/en/tensors_free_module/tensor_free_module.rst @@ -5,9 +5,6 @@ Tensor products of free modules =============================== -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.tensor_free_module :members: :undoc-members: diff --git a/src/doc/en/tensors_free_module/tensor_with_indices.rst b/src/doc/en/tensors_free_module/tensor_with_indices.rst index 8d9430be73a..4efeca0fc72 100644 --- a/src/doc/en/tensors_free_module/tensor_with_indices.rst +++ b/src/doc/en/tensors_free_module/tensor_with_indices.rst @@ -5,9 +5,6 @@ Index notation for tensors ========================== -.. This file has been autogenerated. - - .. automodule:: sage.tensor.modules.tensor_with_indices :members: :undoc-members: diff --git a/src/sage/all.py b/src/sage/all.py index 71ff7cdb880..2d779282248 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -168,7 +168,6 @@ sage.misc.lazy_import.lazy_import('sage.sandpiles.all', '*', globals()) from sage.tensor.all import * -from sage.tensor.modules.all import * from sage.matroids.all import * diff --git a/src/sage/tensor/all.py b/src/sage/tensor/all.py index 3567cc0428c..4cb8a34eb8a 100644 --- a/src/sage/tensor/all.py +++ b/src/sage/tensor/all.py @@ -1,3 +1,4 @@ from coordinate_patch import CoordinatePatch from differential_forms import DifferentialForms from differential_form_element import DifferentialForm, wedge +from modules.all import * diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 1bdb2acbabb..49750e61a29 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -1,17 +1,17 @@ r""" -Components +Components as indexed sets of ring elements. The class :class:`Components` is a technical class to take in charge the -storage of some ring elements that represent the components of -a "mathematical entity" with respect to some "frame". -Examples of "entity/frame" are "vector/vector-space basis" or -"vector field/vector frame on some manifold". More generally, the components +storage and manipulation of **indexed elements of a commutative ring** that represent the +components of some "mathematical entity" with respect to some "frame". +Examples of *entity/frame* are *vector/vector-space basis* or +*vector field/vector frame on some manifold*. More generally, the components can be those of a tensor on a free module or those of a tensor field on a manifold. They can also be non-tensorial quantities, like connection coefficients or structure coefficients of a vector frame. -The individual components are assumed to belong to a given ring and are -labelled by *indices*, i.e. tuple of integers. +The individual components are assumed to belong to a given commutative ring +and are labelled by *indices*, which are *tuples of integers*. The following operations are implemented on components with respect to a given frame: @@ -21,22 +21,24 @@ * tensor product * contraction -Various subclasses of the class :class:`Components` are +Various subclasses of class :class:`Components` are -* :class:`CompWithSym` for storing components with symmetries (symmetric and/or - antisymmetric indices) +* :class:`CompWithSym` for components with symmetries or antisymmetries w.r.t. + index permutations - * :class:`CompFullySym` for storing fully symmetric components + * :class:`CompFullySym` for fully symmetric components w.r.t. index + permutations * :class:`KroneckerDelta` for the Kronecker delta symbol - * :class:`CompFullyAntiSym` for storing fully antisymmetric components + * :class:`CompFullyAntiSym` for fully antisymmetric components w.r.t. index + permutations AUTHORS: - Eric Gourgoulhon, Michal Bejger (2014): initial version - Joris Vankerschaver (2010): for the idea of storing only the non-zero - components as dictionaries, which keys are the component indices (see + components as dictionaries, whose keys are the component indices (see class :class:`~sage.tensor.differential_form_element.DifferentialForm`) EXAMPLES: @@ -240,7 +242,8 @@ class :class:`~sage.tensor.differential_form_element.DifferentialForm`) class Components(SageObject): r""" - Class for storing components with respect to a given "frame". + Indexed set of ring elements forming some components with respect + to a given "frame". The "frame" can be a basis of some vector space or a vector frame on some manifold (i.e. a field of bases). @@ -250,12 +253,12 @@ class Components(SageObject): INPUT: - - ``ring`` -- ring in which each component takes its value + - ``ring`` -- commutative ring in which each component takes its value - ``frame`` -- frame with respect to which the components are defined; - whatever type ``frame`` is, it should have some method ``__len__()`` + whatever type ``frame`` is, it should have a method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size of a single index range - - ``nb_indices`` -- number of indices labeling the components + - ``nb_indices`` -- number of integer indices labeling the components - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. @@ -477,6 +480,14 @@ class Components(SageObject): """ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): + r""" + TEST:: + + sage: from sage.tensor.modules.comp import Components + sage: Components(ZZ, [1,2,3], 2) + 2-indices components w.r.t. [1, 2, 3] + + """ # For efficiency, no test is performed regarding the type and range of # the arguments: self._ring = ring @@ -485,11 +496,20 @@ def __init__(self, ring, frame, nb_indices, start_index=0, self._dim = len(frame) self._sindex = start_index self._output_formatter = output_formatter - self._comp = {} # the dictionary of components, with the indices as keys + self._comp = {} # the dictionary of components, with the index tuples + # as keys def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._repr_() + '2-indices components w.r.t. [1, 2, 3]' + """ description = str(self._nid) if self._nid == 1: @@ -507,6 +527,13 @@ def _new_instance(self): This method must be redefined by derived classes of :class:`Components`. + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._new_instance() + 2-indices components w.r.t. [1, 2, 3] + """ return Components(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) @@ -549,6 +576,18 @@ def _del_zeros(self): r""" Deletes all the zeros in the dictionary :attr:`_comp` + NB: The use case of this method must be rare because zeros are not + stored in :attr:`_comp`. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._comp = {(0,1): 3, (0,2): 0, (1,2): -5, (2,2): 0} # enforcing zero storage + sage: c._del_zeros() + sage: c._comp + {(0, 1): 3, (1, 2): -5} + """ # The zeros are first searched; they are deleted in a second stage, to # avoid changing the dictionary while it is read @@ -571,7 +610,30 @@ def _check_indices(self, indices): OUTPUT: - a tuple containing valid indices - + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._check_indices((0,1)) + (0, 1) + sage: c._check_indices([0,1]) + (0, 1) + sage: c._check_indices([2,1]) + (2, 1) + sage: c._check_indices([2,3]) + Traceback (most recent call last): + ... + IndexError: Index out of range: 3 not in [0,2] + sage: c._check_indices(1) + Traceback (most recent call last): + ... + TypeError: Wrong number of indices: 2 expected, while 1 are provided. + sage: c._check_indices([1,2,3]) + Traceback (most recent call last): + ... + TypeError: Wrong number of indices: 2 expected, while 3 are provided. + """ if isinstance(indices, (int, Integer)): ind = (indices,) @@ -606,6 +668,30 @@ def __getitem__(self, args): - the component corresponding to ``args`` or, if ``args`` = ``:``, the full list of components, in the form ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices object, a matrix is returned). + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c[1,2] # unset components are zero + 0 + sage: c.__getitem__((1,2)) + 0 + sage: c.__getitem__([1,2]) + 0 + sage: c[1,2] = -4 + sage: c[1,2] + -4 + sage: c.__getitem__((1,2)) + -4 + sage: c[:] + [ 0 0 0] + [ 0 0 -4] + [ 0 0 0] + sage: c.__getitem__(slice(None)) + [ 0 0 0] + [ 0 0 -4] + [ 0 0 0] """ no_format = self._output_formatter is None @@ -665,7 +751,27 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): of it if ``ind_slice`` == ``[a:b]`` (1-D case), in the form ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices object, a matrix is returned). - + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c[0,1], c[1,2] = 5, -4 + sage: c._get_list(slice(None)) + [ 0 5 0] + [ 0 0 -4] + [ 0 0 0] + sage: v = Components(ZZ, [1,2,3], 1) + sage: v[:] = 4, 5, 6 + sage: v._get_list(slice(None)) + [4, 5, 6] + sage: v._get_list(slice(0,1)) + [4] + sage: v._get_list(slice(0,2)) + [4, 5] + sage: v._get_list(slice(2,3)) + [6] + """ from sage.matrix.constructor import matrix si = self._sindex @@ -707,6 +813,23 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): def _gen_list(self, ind, no_format=True, format_type=None): r""" Recursive function to generate the list of values + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c[0,1], c[1,2] = 5, -4 + sage: c._gen_list([]) + [[0, 5, 0], [0, 0, -4], [0, 0, 0]] + sage: c._gen_list([0]) + [0, 5, 0] + sage: c._gen_list([1]) + [0, 0, -4] + sage: c._gen_list([2]) + [0, 0, 0] + sage: c._gen_list([0,1]) + 5 + """ if len(ind) == self._nid: if no_format: @@ -730,8 +853,28 @@ def __setitem__(self, args, value): self is a 1-index object) ; if [:] is provided, all the components are set. - ``value`` -- the value to be set or a list of values if ``args`` - == ``[:]`` - + == ``[:]`` (slice(None)) + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c.__setitem__((0,1), -4) + sage: c[:] + [ 0 -4 0] + [ 0 0 0] + [ 0 0 0] + sage: c[0,1] = -4 + sage: c[:] + [ 0 -4 0] + [ 0 0 0] + [ 0 0 0] + sage: c.__setitem__(slice(None), [[0, 1, 2], [3, 4, 5], [6, 7, 8]]) + sage: c[:] + [0 1 2] + [3 4 5] + [6 7 8] + """ format_type = None # default value, possibly redefined below if isinstance(args, list): # case of [[...]] syntax @@ -781,11 +924,22 @@ def _set_list(self, ind_slice, format_type, values): INPUT: - ``ind_slice`` -- a slice object + - ``format_type`` -- format possibly used to construct a ring element - ``values`` -- list of values for the components : the full list if ``ind_slice`` == ``[:]``, in the form ``T[i][j]...`` for the component `T_{ij...}`. In the 1-D case, ``ind_slice`` can be a slice of the full list, in the form ``[a:b]`` - + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._set_list(slice(None), None, [[0, 1, 2], [3, 4, 5], [6, 7, 8]]) + sage: c[:] + [0 1 2] + [3 4 5] + [6 7 8] + """ si = self._sindex nsi = si + self._dim @@ -814,6 +968,27 @@ def _set_list(self, ind_slice, format_type, values): def _set_value_list(self, ind, format_type, val): r""" Recursive function to set a list of values to self + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c._set_value_list([], None, [[1,2,3], [4,5,6], [7,8,9]]) + sage: c[:] + [1 2 3] + [4 5 6] + [7 8 9] + sage: c._set_value_list([0], None, [-1,-2,-3]) + sage: c[:] + [-1 -2 -3] + [ 4 5 6] + [ 7 8 9] + sage: c._set_value_list([2,1], None, -8) + sage: c[:] + [-1 -2 -3] + [ 4 5 6] + [ 7 -8 9] + """ if len(ind) == self._nid: if format_type is not None: @@ -952,6 +1127,26 @@ def __eq__(self, other): - True if ``self`` is equal to ``other``, or False otherwise + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 2) + sage: c.__eq__(0) # uninitialized components are zero + True + sage: c[0,1], c[1,2] = 5, -4 + sage: c.__eq__(0) + False + sage: c1 = Components(ZZ, [1,2,3], 2) + sage: c1[0,1] = 5 + sage: c.__eq__(c1) + False + sage: c1[1,2] = -4 + sage: c.__eq__(c1) + True + sage: v = Components(ZZ, [1,2,3], 1) + sage: c.__eq__(v) + False + """ if isinstance(other, (int, Integer)): # other is 0 if other == 0: @@ -984,6 +1179,21 @@ def __ne__(self, other): - True if ``self`` is different from ``other``, or False otherwise + EXAMPLES: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 1) + sage: c.__ne__(0) # uninitialized components are zero + False + sage: c1 = Components(ZZ, [1,2,3], 1) + sage: c.__ne__(c1) # c and c1 are both zero + False + sage: c[0] = 4 + sage: c.__ne__(0) + True + sage: c.__ne__(c1) + True + """ return not self.__eq__(other) @@ -994,7 +1204,21 @@ def __pos__(self): OUTPUT: - an exact copy of ``self`` - + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 1) + sage: c[:] = 5, 0, -4 + sage: a = c.__pos__() ; a + 1-index components w.r.t. [1, 2, 3] + sage: a[:] + [5, 0, -4] + sage: a == +c + True + sage: a == c + True + """ return self.copy() @@ -1005,7 +1229,19 @@ def __neg__(self): OUTPUT: - the opposite of the components represented by ``self`` - + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: c = Components(ZZ, [1,2,3], 1) + sage: c[:] = 5, 0, -4 + sage: a = c.__neg__() ; a + 1-index components w.r.t. [1, 2, 3] + sage: a[:] + [-5, 0, 4] + sage: a == -c + True + """ result = self._new_instance() for ind, val in self._comp.iteritems(): @@ -1025,6 +1261,20 @@ def __add__(self, other): - components resulting from the addition of ``self`` and ``other`` + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: a = Components(ZZ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: b = Components(ZZ, [1,2,3], 1) + sage: b[:] = 4, 5, 6 + sage: s = a.__add__(b) ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [5, 5, 3] + sage: s == a+b + True + """ if other == 0: return +self @@ -1049,7 +1299,25 @@ def __add__(self, other): def __radd__(self, other): r""" - Addition on the left with ``other``. + Reflected addition (addition on the right to `other``) + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: a = Components(ZZ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: b = Components(ZZ, [1,2,3], 1) + sage: b[:] = 4, 5, 6 + sage: s = a.__radd__(b) ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [5, 5, 3] + sage: s == a+b + True + sage: s = 0 + a ; s + 1-index components w.r.t. [1, 2, 3] + sage: s == a + True """ return self.__add__(other) @@ -1067,6 +1335,20 @@ def __sub__(self, other): - components resulting from the subtraction of ``other`` from ``self`` + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: a = Components(ZZ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: b = Components(ZZ, [1,2,3], 1) + sage: b[:] = 4, 5, 6 + sage: s = a.__sub__(b) ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [-3, -5, -9] + sage: s == a - b + True + """ if other == 0: return +self @@ -1075,7 +1357,27 @@ def __sub__(self, other): def __rsub__(self, other): r""" - Subtraction from ``other``. + Reflected subtraction (subtraction from ``other``). + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: a = Components(ZZ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: b = Components(ZZ, [1,2,3], 1) + sage: b[:] = 4, 5, 6 + sage: s = a.__rsub__(b) ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [3, 5, 9] + sage: s == b - a + True + sage: s = 0 - a ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [-1, 0, 3] + sage: s == -a + True """ return (-self).__add__(other) @@ -1093,6 +1395,22 @@ def __mul__(self, other): - the tensor product of ``self`` by ``other`` + EXAMPLES:: + + sage: from sage.tensor.modules.comp import Components + sage: a = Components(ZZ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: b = Components(ZZ, [1,2,3], 1) + sage: b[:] = 4, 5, 6 + sage: s = a.__mul__(b) ; s + 2-indices components w.r.t. [1, 2, 3] + sage: s[:] + [ 4 5 6] + [ 0 0 0] + [-12 -15 -18] + sage: s == a*b + True + """ if not isinstance(other, Components): raise TypeError("The second argument for the tensor product " + @@ -1136,8 +1454,22 @@ def __mul__(self, other): def __rmul__(self, other): r""" - Multiplication on the left by ``other``. + Reflected multiplication (multiplication on the left by ``other``). + + EXAMPLES:: + sage: from sage.tensor.modules.comp import Components + sage: a = Components(ZZ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: s = a.__rmul__(2) ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [2, 0, -6] + sage: s == 2*a + True + sage: a.__rmul__(0) == 0 + True + """ if isinstance(other, Components): raise NotImplementedError("Left tensor product not implemented.") @@ -1153,7 +1485,21 @@ def __rmul__(self, other): def __div__(self, other): r""" Division (by a scalar). + + EXAMPLES:: + sage: from sage.tensor.modules.comp import Components + sage: a = Components(QQ, [1,2,3], 1) + sage: a[:] = 1, 0, -3 + sage: s = a.__div__(3) ; s + 1-index components w.r.t. [1, 2, 3] + sage: s[:] + [1/3, 0, -1] + sage: s == a/3 + True + sage: 3*s == a + True + """ if isinstance(other, Components): raise NotImplementedError("Division by an object of type " + @@ -1917,8 +2263,9 @@ def antisymmetrize(self, *pos): class CompWithSym(Components): r""" - Class for storing components with respect to a given "frame", taking into - account symmetries or antisymmetries among the indices. + Indexed set of ring elements forming some components with respect to a + given "frame", with symmetries or antisymmetries regarding permutations + of the indices. The "frame" can be a basis of some vector space or a vector frame on some manifold (i.e. a field of bases). @@ -1927,12 +2274,12 @@ class CompWithSym(Components): Subclasses of :class:`CompWithSym` are - * :class:`CompFullySym` for storing fully symmetric components. - * :class:`CompFullyAntiSym` for storing fully antisymmetric components. + * :class:`CompFullySym` for fully symmetric components. + * :class:`CompFullyAntiSym` for fully antisymmetric components. INPUT: - - ``ring`` -- ring in which each component takes its value + - ``ring`` -- commutative ring in which each component takes its value - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size @@ -2110,10 +2457,18 @@ class CompWithSym(Components): ) sage: e + d == d + e True - + """ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None, sym=None, antisym=None): + r""" + TEST:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + 4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + + """ Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) self._sym = [] @@ -2158,6 +2513,17 @@ def __init__(self, ring, frame, nb_indices, start_index=0, def _repr_(self): r""" String representation of the object. + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) + sage: c._repr_() + '4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1)' + sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + sage: c._repr_() + '4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3)' + """ description = str(self._nid) if self._nid == 1: @@ -2178,6 +2544,13 @@ def _new_instance(self): Creates a :class:`CompWithSym` instance w.r.t. the same frame, and with the same number of indices and the same symmetries + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) + sage: a = c._new_instance() ; a + 4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1) + """ return CompWithSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter, self._sym, self._antisym) @@ -2204,7 +2577,20 @@ def _ordered_indices(self, indices): that corresponding to `ind` * `s=-1` if the value corresponding to ``indices`` is the opposite of that corresponding to `ind` - + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + sage: c._ordered_indices([0,1,1,2]) + (1, (0, 1, 1, 2)) + sage: c._ordered_indices([1,0,1,2]) + (1, (0, 1, 1, 2)) + sage: c._ordered_indices([0,1,2,1]) + (-1, (0, 1, 1, 2)) + sage: c._ordered_indices([0,1,2,2]) + (0, None) + """ from sage.combinat.permutation import Permutation ind = list(self._check_indices(indices)) @@ -2255,7 +2641,23 @@ def __getitem__(self, args): - the component corresponding to ``args`` or, if ``args`` = ``:``, the full list of components, in the form ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices object, a matrix is returned). - + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + sage: c.__getitem__((0,1,1,2)) # uninitialized components are zero + 0 + sage: c[0,1,1,2] = 5 + sage: c.__getitem__((0,1,1,2)) + 5 + sage: c.__getitem__((1,0,1,2)) + 5 + sage: c.__getitem__((0,1,2,1)) + -5 + sage: c[0,1,2,1] + -5 + """ no_format = self._output_formatter is None format_type = None # default value, possibly redefined below @@ -2322,7 +2724,27 @@ def __setitem__(self, args, value): are set. - ``value`` -- the value to be set or a list of values if ``args`` == ``[:]`` - + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: c = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) + sage: c.__setitem__((1,2), 5) + sage: c[:] + [0 0 0] + [0 0 5] + [0 5 0] + sage: c = CompWithSym(ZZ, [1,2,3], 2, antisym=(0,1)) + sage: c.__setitem__((1,2), 5) + sage: c[:] + [ 0 0 0] + [ 0 0 5] + [ 0 -5 0] + sage: c.__setitem__((2,2), 5) + Traceback (most recent call last): + ... + ValueError: By antisymmetry, the component cannot have a nonzero value for the indices (2, 2) + """ format_type = None # default value, possibly redefined below if isinstance(args, list): # case of [[...]] syntax @@ -2454,6 +2876,30 @@ def __add__(self, other): - components resulting from the addition of ``self`` and ``other`` + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: a = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) + sage: a[0,1], a[1,2] = 4, 5 + sage: b = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) + sage: b[0,1], b[2,2] = 2, -3 + sage: s = a.__add__(b) ; s # the symmetry is kept + 2-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1) + sage: s[:] + [ 0 6 0] + [ 6 0 5] + [ 0 5 -3] + sage: s == a + b + True + sage: c = CompWithSym(ZZ, [1,2,3], 2, antisym=(0,1)) + sage: c[0,1], c[0,2] = 3, 7 + sage: s = a.__add__(c) ; s # the symmetry is lost + 2-indices components w.r.t. [1, 2, 3] + sage: s[:] + [ 0 7 7] + [ 1 0 5] + [-7 5 0] + """ if other == 0: return +self @@ -2523,7 +2969,34 @@ def __mul__(self, other): OUTPUT: - the tensor product of ``self`` by ``other`` + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompWithSym + sage: a = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) + sage: a[0,1], a[1,2] = 4, 5 + sage: b = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) + sage: b[0,1], b[2,2] = 2, -3 + sage: s = a.__mul__(b) ; s + 4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1), with symmetry on the index positions (2, 3) + sage: s[1,0,0,1] + 8 + sage: s[1,0,0,1] == a[1,0] * b[0,1] + True + sage: s == a*b + True + sage: c = CompWithSym(ZZ, [1,2,3], 2, antisym=(0,1)) + sage: c[0,1], c[0,2] = 3, 7 + sage: s = a.__mul__(c) ; s + 4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + sage: s[1,0,2,0] + -28 + sage: s[1,0,2,0] == a[1,0] * c[2,0] + True + sage: s == a*c + True + """ if not isinstance(other, Components): raise TypeError("The second argument for the tensor product " + @@ -3436,8 +3909,9 @@ def antisymmetrize(self, *pos): class CompFullySym(CompWithSym): r""" - Class for storing fully symmetric components with respect to a given - "frame". + Indexed set of ring elements forming some components with respect to a + given "frame" that are fully symmetric with respect to any permutation + of the indices. The "frame" can be a basis of some vector space or a vector frame on some manifold (i.e. a field of bases). @@ -3445,7 +3919,7 @@ class CompFullySym(CompWithSym): INPUT: - - ``ring`` -- ring in which each component takes its value + - ``ring`` -- commutative ring in which each component takes its value - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size @@ -3564,12 +4038,28 @@ class CompFullySym(CompWithSym): """ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): + r""" + TEST:: + + sage: from sage.tensor.modules.comp import CompFullySym + sage: CompFullySym(ZZ, (1,2,3), 2) + fully symmetric 2-indices components w.r.t. (1, 2, 3) + + """ CompWithSym.__init__(self, ring, frame, nb_indices, start_index, output_formatter, sym=range(nb_indices)) def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import CompFullySym + sage: c = CompFullySym(ZZ, (1,2,3), 4) + sage: c._repr_() + 'fully symmetric 4-indices components w.r.t. (1, 2, 3)' + """ return "fully symmetric " + str(self._nid) + "-indices" + \ " components w.r.t. " + str(self._frame) @@ -3579,6 +4069,13 @@ def _new_instance(self): Creates a :class:`CompFullySym` instance w.r.t. the same frame, and with the same number of indices. + EXAMPLE:: + + sage: from sage.tensor.modules.comp import CompFullySym + sage: c = CompFullySym(ZZ, (1,2,3), 4) + sage: c._new_instance() + fully symmetric 4-indices components w.r.t. (1, 2, 3) + """ return CompFullySym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) @@ -3598,6 +4095,20 @@ def __getitem__(self, args): - the component corresponding to ``args`` or, if ``args`` = ``:``, the full list of components, in the form ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices object, a matrix is returned). + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompFullySym + sage: c = CompFullySym(ZZ, (1,2,3), 2) + sage: c[0,1] = 4 + sage: c.__getitem__((0,1)) + 4 + sage: c.__getitem__((1,0)) + 4 + sage: c.__getitem__(slice(None)) + [0 4 0] + [4 0 0] + [0 0 0] """ no_format = self._output_formatter is None @@ -3654,6 +4165,26 @@ def __setitem__(self, args, value): are set. - ``value`` -- the value to be set or a list of values if ``args`` == ``[:]`` + + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompFullySym + sage: c = CompFullySym(ZZ, (1,2,3), 2) + sage: c.__setitem__((0,1), 4) + sage: c[:] + [0 4 0] + [4 0 0] + [0 0 0] + sage: c.__setitem__((2,1), 5) + sage: c[:] + [0 4 0] + [4 0 5] + [0 5 0] + sage: c.__setitem__(slice(None), [[1, 2, 3], [2, 4, 5], [3, 5, 6]]) + sage: c[:] + [1 2 3] + [2 4 5] + [3 5 6] """ format_type = None # default value, possibly redefined below @@ -3703,6 +4234,33 @@ def __add__(self, other): - components resulting from the addition of ``self`` and ``other`` + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompFullySym + sage: a = CompFullySym(ZZ, (1,2,3), 2) + sage: a[0,1], a[1,2] = 4, 5 + sage: b = CompFullySym(ZZ, (1,2,3), 2) + sage: b[0,1], b[2,2] = 2, -3 + sage: s = a.__add__(b) ; s # the symmetry is kept + fully symmetric 2-indices components w.r.t. (1, 2, 3) + sage: s[:] + [ 0 6 0] + [ 6 0 5] + [ 0 5 -3] + sage: s == a + b + True + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: c = CompFullyAntiSym(ZZ, (1,2,3), 2) + sage: c[0,1], c[0,2] = 3, 7 + sage: s = a.__add__(c) ; s # the symmetry is lost + 2-indices components w.r.t. (1, 2, 3) + sage: s[:] + [ 0 7 7] + [ 1 0 5] + [-7 5 0] + sage: s == a + c + True + """ if other == 0: return +self @@ -3731,8 +4289,9 @@ def __add__(self, other): class CompFullyAntiSym(CompWithSym): r""" - Class for storing fully antisymmetric components with respect to a given - "frame". + Indexed set of ring elements forming some components with respect to a + given "frame" that are fully antisymmetric with respect to any permutation + of the indices. The "frame" can be a basis of some vector space or a vector frame on some manifold (i.e. a field of bases). @@ -3740,7 +4299,7 @@ class CompFullyAntiSym(CompWithSym): INPUT: - - ``ring`` -- ring in which each component takes its value + - ``ring`` -- commutative ring in which each component takes its value - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size @@ -3864,12 +4423,28 @@ class CompFullyAntiSym(CompWithSym): """ def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): + r""" + TEST:: + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: CompFullyAntiSym(ZZ, (1,2,3), 2) + fully antisymmetric 2-indices components w.r.t. (1, 2, 3) + + """ CompWithSym.__init__(self, ring, frame, nb_indices, start_index, output_formatter, antisym=range(nb_indices)) def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: c = CompFullyAntiSym(ZZ, (1,2,3), 4) + sage: c._repr_() + 'fully antisymmetric 4-indices components w.r.t. (1, 2, 3)' + """ return "fully antisymmetric " + str(self._nid) + "-indices" + \ " components w.r.t. " + str(self._frame) @@ -3878,6 +4453,13 @@ def _new_instance(self): r""" Creates a :class:`CompFullyAntiSym` instance w.r.t. the same frame, and with the same number of indices. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: c = CompFullyAntiSym(ZZ, (1,2,3), 4) + sage: c._new_instance() + fully antisymmetric 4-indices components w.r.t. (1, 2, 3) """ return CompFullyAntiSym(self._ring, self._frame, self._nid, self._sindex, @@ -3897,6 +4479,33 @@ def __add__(self, other): - components resulting from the addition of ``self`` and ``other`` + EXAMPLES:: + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: a = CompFullyAntiSym(ZZ, (1,2,3), 2) + sage: a[0,1], a[1,2] = 4, 5 + sage: b = CompFullyAntiSym(ZZ, (1,2,3), 2) + sage: b[0,1], b[0,2] = 2, -3 + sage: s = a.__add__(b) ; s # the antisymmetry is kept + fully antisymmetric 2-indices components w.r.t. (1, 2, 3) + sage: s[:] + [ 0 6 -3] + [-6 0 5] + [ 3 -5 0] + sage: s == a + b + True + sage: from sage.tensor.modules.comp import CompFullySym + sage: c = CompFullySym(ZZ, (1,2,3), 2) + sage: c[0,1], c[0,2] = 3, 7 + sage: s = a.__add__(c) ; s # the antisymmetry is lost + 2-indices components w.r.t. (1, 2, 3) + sage: s[:] + [ 0 7 7] + [-1 0 5] + [ 7 -5 0] + sage: s == a + c + True + """ if other == 0: return +self @@ -3929,7 +4538,7 @@ class KroneckerDelta(CompFullySym): INPUT: - - ``ring`` -- ring in which each component takes its value + - ``ring`` -- commutative ring in which each component takes its value - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size @@ -3983,6 +4592,14 @@ class KroneckerDelta(CompFullySym): """ def __init__(self, ring, frame, start_index=0, output_formatter=None): + r""" + TEST:: + + sage: from sage.tensor.modules.comp import KroneckerDelta + sage: KroneckerDelta(ZZ, (1,2,3)) + Kronecker delta of size 3x3 + + """ CompFullySym.__init__(self, ring, frame, 2, start_index, output_formatter) for i in range(self._sindex, self._dim + self._sindex): @@ -3991,6 +4608,14 @@ def __init__(self, ring, frame, start_index=0, output_formatter=None): def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import KroneckerDelta + sage: d = KroneckerDelta(ZZ, (1,2,3)) + sage: d._repr_() + 'Kronecker delta of size 3x3' + """ n = str(self._dim) return "Kronecker delta of size " + n + "x" + n @@ -3998,6 +4623,16 @@ def _repr_(self): def __setitem__(self, args, value): r""" Should not be used (the components of a Kronecker delta are constant) + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import KroneckerDelta + sage: d = KroneckerDelta(ZZ, (1,2,3)) + sage: d.__setitem__((0,0), 1) + Traceback (most recent call last): + ... + NotImplementedError: The components of a Kronecker delta cannot be changed. + """ raise NotImplementedError("The components of a Kronecker delta " + "cannot be changed.") diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 9a42d6100f5..bba6e2899cd 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -343,7 +343,7 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): sage: v.view(e, format_spec=10) # 10 bits of precision 0.33 e_0 - 2.0 e_2 - All the tests from the suite for the category + All tests from the suite for the category :class:`~sage.categories.modules.Modules` are passed:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') @@ -376,6 +376,15 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, output_formatter=None): + r""" + TEST:: + + sage: FiniteRankFreeModule(ZZ, 3, name='M') + rank-3 free module M over the Integer Ring + + See :class:`FiniteRankFreeModule` for documentation / doctests. + + """ if not ring.is_commutative(): raise TypeError("The module base ring must be commutative.") Parent.__init__(self, base=ring, category=Modules(ring)) @@ -407,6 +416,22 @@ def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None): r""" Construct an element of the module + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: v = M._element_constructor_(comp=[1,0,-2], basis=e, name='v') ; v + element v of the rank-3 free module M over the Integer Ring + sage: v.view() + v = e_0 - 2 e_2 + sage: v == M([1,0,-2]) + True + sage: v = M._element_constructor_(0) ; v + element zero of the rank-3 free module M over the Integer Ring + sage: v = M._element_constructor_() ; v + element of the rank-3 free module M over the Integer Ring + """ if comp == 0: return self._zero_element @@ -417,7 +442,21 @@ def _element_constructor_(self, comp=[], basis=None, name=None, def _an_element_(self): r""" - Construct some (unamed) element of the module + Construct some (unamed) element of the module. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: v = M._an_element_() ; v + element of the rank-3 free module M over the Integer Ring + sage: v.view() + e_0 + e_1 + e_2 + sage: v == M.an_element() + True + sage: v.parent() + rank-3 free module M over the Integer Ring + """ resu = self.element_class(self) if self._def_basis is not None: @@ -432,6 +471,13 @@ def _an_element_(self): def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M._repr_() + 'rank-3 free module M over the Integer Ring' + """ description = "rank-" + str(self._rank) + " free module " if self._name is not None: @@ -1109,6 +1155,20 @@ def sym_bilinear_form(self, name=None, latex_name=None): def _latex_(self): r""" LaTeX representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M._latex_() + 'M' + sage: latex(M) + M + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') + sage: M1._latex_() + '\\mathcal{M}' + sage: latex(M1) + \mathcal{M} + """ if self._latex_name is None: return r'\mbox{' + str(self) + r'}' diff --git a/src/sage/tensor/modules/format_utilities.py b/src/sage/tensor/modules/format_utilities.py index f435ea4a9e4..07098c28b27 100644 --- a/src/sage/tensor/modules/format_utilities.py +++ b/src/sage/tensor/modules/format_utilities.py @@ -166,6 +166,20 @@ def format_mul_txt(name1, operator, name2): Helper function for text-formatted names of results of multiplication or tensor product. + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import format_mul_txt + sage: format_mul_txt('a', '*', 'b') + 'a*b' + sage: format_mul_txt('a+b', '*', 'c') + '(a+b)*c' + sage: format_mul_txt('a', '*', 'b+c') + 'a*(b+c)' + sage: format_mul_txt('a+b', '*', 'c+d') + '(a+b)*(c+d)' + sage: format_mul_txt(None, '*', 'b') + sage: format_mul_txt('a', '*', None) + """ if name1 is None or name2 is None: return None @@ -181,6 +195,20 @@ def format_mul_latex(name1, operator, name2): Helper function for LaTeX names of results of multiplication or tensor product. + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import format_mul_latex + sage: format_mul_latex('a', '*', 'b') + 'a*b' + sage: format_mul_latex('a+b', '*', 'c') + '\\left(a+b\\right)*c' + sage: format_mul_latex('a', '*', 'b+c') + 'a*\\left(b+c\\right)' + sage: format_mul_latex('a+b', '*', 'c+d') + '\\left(a+b\\right)*\\left(c+d\\right)' + sage: format_mul_latex(None, '*', 'b') + sage: format_mul_latex('a', '*', None) + """ if name1 is None or name2 is None: return None @@ -195,6 +223,17 @@ def format_unop_txt(operator, name): r""" Helper function for text-formatted names of results of unary operator. + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import format_unop_txt + sage: format_unop_txt('-', 'a') + '-a' + sage: format_unop_txt('-', 'a+b') + '-(a+b)' + sage: format_unop_txt('-', '(a+b)') + '-(a+b)' + sage: format_unop_txt('-', None) + """ if name is None: return None @@ -208,6 +247,17 @@ def format_unop_latex(operator, name): r""" Helper function for LaTeX names of results of unary operator. + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import format_unop_latex + sage: format_unop_latex('-', 'a') + '-a' + sage: format_unop_latex('-', 'a+b') + '-\\left(a+b\\right)' + sage: format_unop_latex('-', '(a+b)') + '-(a+b)' + sage: format_unop_latex('-', None) + """ if name is None: return None @@ -219,21 +269,56 @@ def format_unop_latex(operator, name): class FormattedExpansion(SageObject): r""" Helper class for displaying tensor expansions. + """ def __init__(self, tensor): + r""" + EXAMPLE:: + + sage: from sage.tensor.modules.format_utilities import FormattedExpansion + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: v = M.an_element() + sage: f = FormattedExpansion(v) + sage: f.txt = 'v' ; f.latex = r'\tilde v' + sage: f + v + + """ self.tensor = tensor self.txt = None self.latex = None def _repr_(self): r""" - Special Sage function for the string representation of the object. + String representation of the object. + + EXAMPLE:: + + sage: from sage.tensor.modules.format_utilities import FormattedExpansion + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: v = M.an_element() + sage: f = FormattedExpansion(v) + sage: f.txt = 'v' ; f.latex = r'\tilde v' + sage: f._repr_() + 'v' + """ return self.txt def _latex_(self): r""" - Special Sage function for the LaTeX representation of the object. + LaTeX representation of the object. + + EXAMPLE:: + + sage: from sage.tensor.modules.format_utilities import FormattedExpansion + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: v = M.an_element() + sage: f = FormattedExpansion(v) + sage: f.txt = 'v' ; f.latex = r'\tilde v' + sage: f._latex_() + '\\tilde v' + """ return self.latex diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 82e39961aea..5a21605e1f0 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -49,9 +49,45 @@ class FreeModuleAltForm(FreeModuleTensor): - ``name`` -- (default: None) name given to the alternating form - ``latex_name`` -- (default: None) LaTeX symbol to denote the alternating form; if none is provided, the LaTeX symbol is set to ``name`` + + EXAMPLES: + + Alternating form of degree 2 on a rank-3 free module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.alternating_form(2, name='a') ; a + alternating form a of degree 2 on the rank-3 free module M over the Integer Ring + sage: type(a) + + sage: a.parent() + free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring + sage: a[1,2], a[2,3] = 4, -3 + sage: a.view() + a = 4 e^1/\e^2 - 3 e^2/\e^3 + + The alternating form acting on the basis elements:: + + sage: a(e[1],e[2]) + 4 + sage: a(e[1],e[3]) + 0 + sage: a(e[2],e[3]) + -3 + sage: a(e[2],e[1]) + -4 """ def __init__(self, fmodule, degree, name=None, latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: FreeModuleAltForm(M, 2, name='a') + alternating form a of degree 2 on the rank-3 free module M over the Integer Ring + + """ FreeModuleTensor.__init__(self, fmodule, (0,degree), name=name, latex_name=latex_name, antisym=range(degree)) FreeModuleAltForm._init_derived(self) # initialization of derived quantities @@ -59,6 +95,17 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): def _repr_(self): r""" String representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.alternating_form(2) + sage: a._repr_() + 'alternating form of degree 2 on the rank-3 free module M over the Integer Ring' + sage: a = M.alternating_form(2, name='a') + sage: a._repr_() + 'alternating form a of degree 2 on the rank-3 free module M over the Integer Ring' + """ description = "alternating form " if self._name is not None: @@ -70,12 +117,26 @@ def _repr_(self): def _init_derived(self): r""" Initialize the derived quantities + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.alternating_form(2) + sage: a._init_derived() + """ FreeModuleTensor._init_derived(self) def _del_derived(self): r""" Delete the derived quantities + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.alternating_form(2) + sage: a._del_derived() + """ FreeModuleTensor._del_derived(self) @@ -83,7 +144,16 @@ def _new_instance(self): r""" Create an instance of the same class as ``self``, on the same module and of the same degree. - + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.alternating_form(2, name='a') + sage: a._new_instance() + alternating form of degree 2 on the rank-3 free module M over the Integer Ring + sage: a._new_instance().parent() is a.parent() + True + """ return self.__class__(self._fmodule, self._tensor_rank) @@ -93,6 +163,15 @@ def _new_comp(self, basis): This method, which is already implemented in :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.alternating_form(2, name='a') + sage: a._new_comp(e) + fully antisymmetric 2-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + """ fmodule = self._fmodule # the base free module if self._tensor_rank == 1: @@ -106,7 +185,15 @@ def _new_comp(self, basis): def degree(self): r""" - Return the degree of the alternating form. + Return the degree of the alternating form. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.alternating_form(2, name='a') + sage: a.degree() + 2 + """ return self._tensor_rank @@ -441,12 +528,29 @@ class FreeModuleLinForm(FreeModuleAltForm): """ def __init__(self, fmodule, name=None, latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_alt_form import FreeModuleLinForm + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: FreeModuleLinForm(M, name='a') + linear form a on the rank-3 free module M over the Integer Ring + + """ FreeModuleAltForm.__init__(self, fmodule, 1, name=name, latex_name=latex_name) def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.linear_form('A') + sage: a._repr_() + 'linear form A on the rank-3 free module M over the Integer Ring' + """ description = "linear form " if self._name is not None: @@ -459,6 +563,15 @@ def _new_instance(self): Create an instance of the same class as ``self`` and on the same module. + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.linear_form('A') + sage: a._new_instance() + linear form on the rank-3 free module M over the Integer Ring + sage: a._new_instance().parent() is a.parent() + True + """ return self.__class__(self._fmodule) @@ -468,6 +581,15 @@ def _new_comp(self, basis): This method, which is already implemented in :meth:`FreeModuleAltForm._new_comp`, is redefined here for efficiency + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form('A') + sage: a._new_comp(e) + 1-index components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + """ fmodule = self._fmodule # the base free module return Components(fmodule._ring, basis, 1, start_index=fmodule._sindex, @@ -486,6 +608,18 @@ def __call__(self, vector): - ring element `\langle \omega, v \rangle` + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.linear_form('A') + sage: a[:] = 2, -4, 1 + sage: v = M([1,5,-2]) + sage: a.__call__(v) + -20 + sage: a.__call__(v) == a(v) + True + """ if not isinstance(vector, FiniteRankFreeModuleElement): raise TypeError("The argument must be a free module element.") diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 169c2cb01cd..0bd69327264 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -84,6 +84,19 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): """ def __init__(self, fmodule, symbol, latex_symbol=None): + r""" + TESTS:: + + sage: FiniteRankFreeModule._clear_cache_() # for doctests only + sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: FreeModuleBasis(M, 'e', latex_symbol=r'\epsilon') + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: e = FreeModuleBasis(M, 'e', latex_symbol=r'\epsilon') + sage: e is FreeModuleBasis(M, 'e', latex_symbol=r'\epsilon') # unique representation + True + + """ self._fmodule = fmodule self._name = "(" + \ ",".join([symbol + "_" + str(i) for i in fmodule.irange()]) +")" @@ -129,6 +142,18 @@ def __init__(self, fmodule, symbol, latex_symbol=None): def _repr_(self): r""" String representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e._repr_() + 'basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring' + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e1 = M1.basis('e') + sage: e1._repr_() + 'basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring' + """ return "basis " + self._name + " on the " + str(self._fmodule) @@ -142,6 +167,13 @@ def _init_dual_basis(self): - instance of :class:`FreeModuleCoBasis` representing the dual of ``self`` + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e._init_dual_basis() + dual basis (e^0,e^1,e^2) on the rank-3 free module M over the Integer Ring + """ return FreeModuleCoBasis(self, self._symbol, latex_symbol=self._latex_symbol) @@ -161,6 +193,13 @@ def _new_instance(self, symbol, latex_symbol=None): OUTPUT: - instance of :class:`FreeModuleBasis` + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e._new_instance('f') + basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring """ return FreeModuleBasis(self._fmodule, symbol, latex_symbol=latex_symbol) @@ -209,6 +248,22 @@ def dual_basis(self): def _latex_(self): r""" LaTeX representation of the object. + + EXAMPLES:: + + sage: FiniteRankFreeModule._clear_cache_() # for doctests only + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e._latex_() + '\\left(e_0,e_1,e_2\\right)' + sage: latex(e) + \left(e_0,e_1,e_2\right) + sage: f = M.basis('eps', latex_symbol=r'\epsilon') + sage: f._latex_() + '\\left(\\epsilon_0,\\epsilon_1,\\epsilon_2\\right)' + sage: latex(f) + \left(\epsilon_0,\epsilon_1,\epsilon_2\right) + """ return self._latex_name @@ -216,18 +271,53 @@ def __hash__(self): r""" Hash function (since instances of :class:`FreeModuleBasis` are used as dictionary keys). + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e.__hash__() # random + 140529558663376 + """ return id(self) def __eq__(self, other): r""" Equality (comparison) operator + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: f = M.basis('f') + sage: e.__eq__(f) + False + sage: e.__eq__(e) + True + sage: e.__eq__(M.basis('e')) + True + sage: M2 = FiniteRankFreeModule(ZZ, 2, name='M2') + sage: e.__eq__(M2.basis('e')) + False + """ return other is self def __ne__(self, other): r""" Non-equality operator. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: f = M.basis('f') + sage: e.__ne__(f) + True + sage: e.__ne__(e) + False + """ return not self.__eq__(other) @@ -238,6 +328,29 @@ def __getitem__(self, index): INPUT: - ``index`` -- the index of the basis element + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e.__getitem__(0) + element e_0 of the rank-3 free module M over the Integer Ring + sage: e.__getitem__(1) + element e_1 of the rank-3 free module M over the Integer Ring + sage: e.__getitem__(2) + element e_2 of the rank-3 free module M over the Integer Ring + sage: e[1] is e.__getitem__(1) + True + sage: e[1].parent() is M + True + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e1 = M1.basis('e') + sage: e1.__getitem__(1) + element e_1 of the rank-3 free module M over the Integer Ring + sage: e1.__getitem__(2) + element e_2 of the rank-3 free module M over the Integer Ring + sage: e1.__getitem__(3) + element e_3 of the rank-3 free module M over the Integer Ring """ n = self._fmodule._rank @@ -252,6 +365,19 @@ def __getitem__(self, index): def __len__(self): r""" Return the basis length, i.e. the rank of the free module. + + NB: the method __len__() is required for the basis to act as a + "frame" in the class :class:`~sage.tensor.modules.comp.Components`. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: e.__len__() + 3 + sage: len(e) + 3 + """ return self._fmodule._rank @@ -388,6 +514,16 @@ class FreeModuleCoBasis(SageObject): """ def __init__(self, basis, symbol, latex_symbol=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_basis import FreeModuleCoBasis + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: FreeModuleCoBasis(e, 'f') + dual basis (f^0,f^1,f^2) on the rank-3 free module M over the Integer Ring + + """ self._basis = basis self._fmodule = basis._fmodule self._name = "(" + \ @@ -412,12 +548,30 @@ def __init__(self, basis, symbol, latex_symbol=None): def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: f = e.dual_basis() + sage: f._repr_() + 'dual basis (e^0,e^1,e^2) on the rank-3 free module M over the Integer Ring' + """ return "dual basis " + self._name + " on the " + str(self._fmodule) def _latex_(self): r""" LaTeX representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: f = e.dual_basis() + sage: f._latex_() + '\\left(e^0,e^1,e^2\\right)' + """ return self._latex_name @@ -428,6 +582,28 @@ def __getitem__(self, index): INPUT: - ``index`` -- the index of the linear form + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: f = e.dual_basis() + sage: f.__getitem__(0) + linear form e^0 on the rank-3 free module M over the Integer Ring + sage: f.__getitem__(1) + linear form e^1 on the rank-3 free module M over the Integer Ring + sage: f.__getitem__(2) + linear form e^2 on the rank-3 free module M over the Integer Ring + sage: f[1] is f.__getitem__(1) + True + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: f1 = M1.basis('e').dual_basis() + sage: f1.__getitem__(1) + linear form e^1 on the rank-3 free module M over the Integer Ring + sage: f1.__getitem__(2) + linear form e^2 on the rank-3 free module M over the Integer Ring + sage: f1.__getitem__(3) + linear form e^3 on the rank-3 free module M over the Integer Ring """ n = self._fmodule._rank diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index 9c14c8290b4..f21c7a1c807 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -211,6 +211,15 @@ class FreeModuleTensor(ModuleElement): """ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, sym=None, antisym=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_tensor import FreeModuleTensor + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: FreeModuleTensor(M, (2,1), name='t', latex_name=r'\tau', sym=(0,1)) + type-(2,1) tensor t on the rank-3 free module M over the Integer Ring + + """ ModuleElement.__init__(self, fmodule.tensor_module(*tensor_type)) self._fmodule = fmodule self._tensor_type = tuple(tensor_type) @@ -267,6 +276,33 @@ def __nonzero__(self): Return True if ``self`` is nonzero and False otherwise. This method is called by self.is_zero(). + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: t = M.tensor((2,1)) + sage: t.add_comp(e) + 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: t.__nonzero__() # unitialized components are zero + False + sage: t == 0 + True + sage: t[e,1,0,2] = 4 # setting a non-zero component in basis e + sage: t.view() + 4 e_1*e_0*e^2 + sage: t.__nonzero__() + True + sage: t == 0 + False + sage: t[e,1,0,2] = 0 + sage: t.view() + 0 + sage: t.__nonzero__() + False + sage: t == 0 + True + """ basis = self.pick_a_basis() return not self._components[basis].is_zero() @@ -276,6 +312,14 @@ def __nonzero__(self): def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: t._repr_() + 'type-(2,1) tensor t on the rank-3 free module M over the Integer Ring' + """ # Special cases if self._tensor_type == (0,2) and self._sym == [(0,1)]: @@ -292,6 +336,24 @@ def _repr_(self): def _latex_(self): r""" LaTeX representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: t._latex_() + 't' + sage: latex(t) + t + sage: t = M.tensor((2,1), name='t', latex_name=r'\tau') + sage: t._latex_() + '\\tau' + sage: latex(t) + \tau + sage: t = M.tensor((2,1)) # unnamed tensor + sage: t._latex_() + '\\mbox{type-(2,1) tensor on the rank-3 free module M over the Integer Ring}' + """ if self._latex_name is None: return r'\mbox{' + str(self) + r'}' @@ -301,12 +363,26 @@ def _latex_(self): def _init_derived(self): r""" Initialize the derived quantities + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: t._init_derived() + """ pass # no derived quantities def _del_derived(self): r""" Delete the derived quantities + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: t._del_derived() + """ pass # no derived quantities @@ -552,6 +628,20 @@ def set_name(self, name=None, latex_name=None): tensor; if None while ``name`` is provided, the LaTeX symbol is set to ``name``. + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1)) ; t + type-(2,1) tensor on the rank-3 free module M over the Integer Ring + sage: t.set_name('t') ; t + type-(2,1) tensor t on the rank-3 free module M over the Integer Ring + sage: latex(t) + t + sage: t.set_name(latex_name=r'\tau') ; t + type-(2,1) tensor t on the rank-3 free module M over the Integer Ring + sage: latex(t) + \tau + """ if name is not None: self._name = name @@ -564,6 +654,15 @@ def _new_instance(self): r""" Create a tensor of the same tensor type and with the same symmetries as ``self``. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: t._new_instance() + type-(2,1) tensor on the rank-3 free module M over the Integer Ring + sage: t._new_instance().parent() is t.parent() + True """ return self.__class__(self._fmodule, self._tensor_type, sym=self._sym, @@ -581,6 +680,17 @@ def _new_comp(self, basis): - an instance of :class:`~sage.tensor.modules.comp.Components` (or of one of its subclass) + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: e = M.basis('e') + sage: t._new_comp(e) + 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: a = M.tensor((2,1), name='a', sym=(0,1)) + sage: a._new_comp(e) + 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, with symmetry on the index positions (0, 1) + """ fmodule = self._fmodule # the base free module if self._sym == [] and self._antisym == []: @@ -917,7 +1027,29 @@ def __getitem__(self, args): provided, all the components are returned. The basis can be passed as the first item of ``args``; if not, the free module's default basis is assumed. - + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: e = M.basis('e') + sage: t.add_comp(e) + 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: t.__getitem__((1,2,0)) # uninitialized components are zero + 0 + sage: t.__getitem__((e,1,2,0)) # same as above since e in the default basis + 0 + sage: t[1,2,0] = -4 + sage: t.__getitem__((e,1,2,0)) + -4 + sage: v = M([3,-5,2]) + sage: v.__getitem__(slice(None)) + [3, -5, 2] + sage: v.__getitem__(slice(None)) == v[:] + True + sage: v.__getitem__((e, slice(None))) + [3, -5, 2] + """ if isinstance(args, str): # tensor with specified indices return TensorWithIndices(self, args).update() @@ -953,6 +1085,26 @@ def __setitem__(self, args, value): - ``value`` -- the value to be set or a list of values if ``args`` == ``[:]`` + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((0,2), name='t') + sage: e = M.basis('e') + sage: t.__setitem__((e,0,1), 5) + sage: t.view() + t = 5 e^0*e^1 + sage: t.__setitem__((0,1), 5) # equivalent to above since e is the default basis + sage: t.view() + t = 5 e^0*e^1 + sage: t[0,1] = 5 # end-user usage + sage: t.view() + t = 5 e^0*e^1 + sage: t.__setitem__(slice(None), [[1,-2,3], [-4,5,-6], [7,-8,9]]) + sage: t[:] + [ 1 -2 3] + [-4 5 -6] + [ 7 -8 9] + """ if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], (int, Integer, slice, tuple)): @@ -1145,6 +1297,22 @@ def pick_a_basis(self): :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` representing the basis + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,0), name='t') + sage: e = M.basis('e') + sage: t[0,1] = 4 # component set in the default basis (e) + sage: t.pick_a_basis() + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: f = M.basis('f') + sage: t.add_comp(f)[2,1] = -4 # the components in basis e are not erased + sage: t.pick_a_basis() + basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: t.set_comp(f)[2,1] = -4 # the components in basis e not erased + sage: t.pick_a_basis() + basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring + """ if self._fmodule._def_basis in self._components: return self._fmodule._def_basis # the default basis is privileged @@ -1164,6 +1332,33 @@ def __eq__(self, other): - True if ``self`` is equal to ``other`` and False otherwise + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,0), name='t') + sage: e = M.basis('e') + sage: t[0,1] = 7 + sage: t.__eq__(0) + False + sage: t[0,1] = 0 + sage: t.__eq__(0) + True + sage: a = M.tensor((0,2), name='a') + sage: a[0,1] = 7 + sage: t[0,1] = 7 + sage: a[:], t[:] + ( + [0 7 0] [0 7 0] + [0 0 0] [0 0 0] + [0 0 0], [0 0 0] + ) + sage: t.__eq__(a) # False since t and a do not have the same tensor type + False + sage: a = M.tensor((2,0), name='a') # same tensor type as t + sage: a[0,1] = 7 + sage: t.__eq__(a) + True + """ if self._tensor_rank == 0: raise NotImplementedError("Scalar comparison not implemented.") @@ -1196,6 +1391,25 @@ def __ne__(self, other): - True if ``self`` is different from ``other`` and False otherwise + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,0), name='t') + sage: e = M.basis('e') + sage: t[0,1] = 7 + sage: t.__ne__(0) + True + sage: t[0,1] = 0 + sage: t.__ne__(0) + False + sage: a = M.tensor((2,0), name='a') # same tensor type as t + sage: a[0,1] = 7 + sage: t.__ne__(a) + True + sage: t[0,1] = 7 + sage: t.__ne__(a) + False + """ return not self.__eq__(other) @@ -1207,6 +1421,21 @@ def __pos__(self): - an exact copy of ``self`` + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,0), name='t') + sage: e = M.basis('e') + sage: t[0,1] = 7 + sage: p = t.__pos__() ; p + type-(2,0) tensor +t on the rank-3 free module M over the Integer Ring + sage: p.view() + +t = 7 e_0*e_1 + sage: p == t + True + sage: p is t + False + """ result = self._new_instance() for basis in self._components: @@ -1224,7 +1453,23 @@ def __neg__(self): OUTPUT: - the tensor `-T`, where `T` is ``self`` + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((2,0), name='t') + sage: e = M.basis('e') + sage: t[0,1], t[1,2] = 7, -4 + sage: t.view() + t = 7 e_0*e_1 - 4 e_1*e_2 + sage: a = t.__neg__() ; a + type-(2,0) tensor -t on the rank-3 free module M over the Integer Ring + sage: a.view() + -t = -7 e_0*e_1 + 4 e_1*e_2 + sage: a == -t + True + """ result = self._new_instance() for basis in self._components: @@ -1249,6 +1494,24 @@ def _add_(self, other): - the tensor resulting from the addition of ``self`` and ``other`` + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: b = M.tensor((2,0), name='b') + sage: b[:] = [[0,1], [2,3]] + sage: s = a._add_(b) ; s + type-(2,0) tensor a+b on the rank-2 free module M over the Integer Ring + sage: s[:] + [4 1] + [0 8] + sage: a._add_(-a) == 0 + True + sage: a._add_(a) == 2*a + True + """ # No need for consistency check since self and other are guaranted # to belong to the same tensor module @@ -1277,6 +1540,26 @@ def _sub_(self, other): - the tensor resulting from the subtraction of ``other`` from ``self`` + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: b = M.tensor((2,0), name='b') + sage: b[:] = [[0,1], [2,3]] + sage: s = a._sub_(b) ; s + type-(2,0) tensor a-b on the rank-2 free module M over the Integer Ring + sage: s[:] + [ 4 -1] + [-4 2] + sage: b._sub_(a) == -s + True + sage: a._sub_(a) == 0 + True + sage: a._sub_(-a) == 2*a + True + """ # No need for consistency check since self and other are guaranted # to belong to the same tensor module @@ -1297,6 +1580,28 @@ def _rmul_(self, other): r""" Multiplication on the left by ``other``. + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: s = a._rmul_(2) ; s + type-(2,0) tensor on the rank-2 free module M over the Integer Ring + sage: s[:] + [ 8 0] + [-4 10] + sage: s == a + a + True + sage: a._rmul_(0) + type-(2,0) tensor on the rank-2 free module M over the Integer Ring + sage: a._rmul_(0) == 0 + True + sage: a._rmul_(1) == a + True + sage: a._rmul_(-1) == -a + True + """ #!# The following test is probably not necessary: if isinstance(other, FreeModuleTensor): @@ -1315,6 +1620,29 @@ def __radd__(self, other): This allows to write "0 + t", where "t" is a tensor + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: b = M.tensor((2,0), name='b') + sage: b[:] = [[0,1], [2,3]] + sage: s = a.__radd__(b) ; s + type-(2,0) tensor a+b on the rank-2 free module M over the Integer Ring + sage: s[:] + [4 1] + [0 8] + sage: s == a+b + True + sage: s = a.__radd__(0) ; s + type-(2,0) tensor +a on the rank-2 free module M over the Integer Ring + sage: s == a + True + sage: 0 + a == a + True + + """ return self.__add__(other) @@ -1324,12 +1652,51 @@ def __rsub__(self, other): This allows to write "0 - t", where "t" is a tensor + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: b = M.tensor((2,0), name='b') + sage: b[:] = [[0,1], [2,3]] + sage: s = a.__rsub__(b) ; s + type-(2,0) tensor -a+b on the rank-2 free module M over the Integer Ring + sage: s[:] + [-4 1] + [ 4 -2] + sage: s == b - a + True + sage: s = a.__rsub__(0) ; s + type-(2,0) tensor +-a on the rank-2 free module M over the Integer Ring + sage: s == -a + True + sage: 0 - a == -a + True + """ return (-self).__add__(other) def __mul__(self, other): r""" Tensor product. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: b = M.tensor((0,2), name='b', antisym=(0,1)) + sage: b[0,1] = 3 + sage: s = a.__mul__(b) ; s + type-(2,2) tensor a*b on the rank-2 free module M over the Integer Ring + sage: s.symmetries() + no symmetry; antisymmetry: (2, 3) + sage: s[:] + [[[[0, 12], [-12, 0]], [[0, 0], [0, 0]]], + [[[0, -6], [6, 0]], [[0, 15], [-15, 0]]]] + """ from format_utilities import format_mul_txt, format_mul_latex if isinstance(other, FreeModuleTensor): @@ -1361,7 +1728,24 @@ def __mul__(self, other): def __div__(self, other): r""" - Division (by a scalar) + Division (by a scalar). + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[4,0], [-2,5]] + sage: s = a.__div__(4) ; s + type-(2,0) tensor on the rank-2 free module M over the Rational Field + sage: s[:] + [ 1 0] + [-1/2 5/4] + sage: 4*s == a + True + sage: s == a/4 + True + """ result = self._new_instance() for basis in self._components: @@ -1378,7 +1762,35 @@ def __call__(self, *args): - ``*args`` -- list of k linear forms and l module elements, ``self`` being a tensor of type (k,l). - + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: t = M.tensor((2,1), name='t', antisym=(0,1)) + sage: t[0,1,0], t[0,1,1] = 3, 2 + sage: t.view() + t = 3 e_0*e_1*e^0 + 2 e_0*e_1*e^1 - 3 e_1*e_0*e^0 - 2 e_1*e_0*e^1 + sage: a = M.linear_form() + sage: a[:] = 1, 2 + sage: b = M.linear_form() + sage: b[:] = 3, -1 + sage: v = M([-2,1]) + sage: t.__call__(a,b,v) + 28 + sage: t(a,b,v) == t.__call__(a,b,v) + True + sage: t(a,b,v) == t.contract(v).contract(b).contract(a) + True + sage: a.__call__(v) + 0 + sage: v.__call__(a) + 0 + sage: b.__call__(v) + -7 + sage: v.__call__(b) + -7 + """ from free_module_alt_form import FreeModuleLinForm # Consistency checks: @@ -2517,12 +2929,30 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): """ def __init__(self, fmodule, name=None, latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: FiniteRankFreeModuleElement(M, name='v') + element v of the rank-3 free module M over the Integer Ring + + """ FreeModuleTensor.__init__(self, fmodule, (1,0), name=name, latex_name=latex_name) def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: v = M([1,-2,3], name='v') + sage: v._repr_() + 'element v of the rank-3 free module M over the Integer Ring' + """ description = "element " if self._name is not None: @@ -2536,6 +2966,17 @@ def _new_comp(self, basis): This method, which is already implemented in :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: v = M([1,-2,3], name='v') + sage: v._new_comp(e) + 1-index components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + sage: type(v._new_comp(e)) + + """ fmodule = self._fmodule # the base free module return Components(fmodule._ring, basis, 1, start_index=fmodule._sindex, @@ -2546,6 +2987,16 @@ def _new_instance(self): r""" Create an instance of the same class as ``self``. + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: v = M([1,-2,3], name='v') + sage: v._new_instance() + element of the rank-3 free module M over the Integer Ring + sage: v._new_instance().parent() is v.parent() + True + """ return self.__class__(self._fmodule) diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py index b3570881d73..fce3e864d40 100644 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -96,12 +96,32 @@ class FreeModuleEndomorphism(FreeModuleTensor): """ def __init__(self, fmodule, name=None, latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleEndomorphism + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: FreeModuleEndomorphism(M, name='a') + endomorphism a on the rank-3 free module M over the Integer Ring + + """ FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, latex_name=latex_name) def _repr_(self): r""" String representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.endomorphism() + sage: a._repr_() + 'endomorphism on the rank-3 free module M over the Integer Ring' + sage: a = M.endomorphism(name='a') + sage: a._repr_() + 'endomorphism a on the rank-3 free module M over the Integer Ring' + """ description = "endomorphism " if self._name is not None: @@ -112,7 +132,14 @@ def _repr_(self): def _new_instance(self): r""" Create an instance of the same class as ``self``. + + EXAMPLE:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.endomorphism(name='a') + sage: a._new_instance() + endomorphism on the rank-3 free module M over the Integer Ring + """ return self.__class__(self._fmodule) @@ -120,6 +147,37 @@ def __call__(self, *arg): r""" Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single argument (module element). + + EXAMPLES: + + Call with a single argument --> return a module element:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.endomorphism(name='a') + sage: e = M.basis('e') + sage: a[0,1], a[1,1], a[2,1] = 2, 4, -5 + sage: v = M([2,1,4], name='v') + sage: s = a.__call__(v) ; s + element a(v) of the rank-3 free module M over the Integer Ring + sage: s.view() + a(v) = 2 e_0 + 4 e_1 - 5 e_2 + sage: s == a(v) + True + sage: s == a.contract(v) + True + + Call with two arguments (FreeModuleTensor behaviour) --> return a + scalar:: + + sage: b = M.linear_form(name='b') + sage: b[:] = 7, 0, 2 + sage: a.__call__(b,v) + 4 + sage: a(b,v) == a.__call__(b,v) + True + sage: a(b,v) == s(b) + True + """ from free_module_tensor import FiniteRankFreeModuleElement if len(arg) > 1: @@ -209,6 +267,15 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): """ def __init__(self, fmodule, name=None, latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleAutomorphism + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: FreeModuleAutomorphism(M, name='a') + automorphism a on the rank-3 free module M over the Rational Field + + """ FreeModuleEndomorphism.__init__(self, fmodule, name=name, latex_name=latex_name) self._inverse = None # inverse automorphism not set yet @@ -216,6 +283,17 @@ def __init__(self, fmodule, name=None, latex_name=None): def _repr_(self): r""" String representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: a = M.automorphism() + sage: a._repr_() + 'automorphism on the rank-3 free module M over the Rational Field' + sage: a = M.automorphism(name='a') + sage: a._repr_() + 'automorphism a on the rank-3 free module M over the Rational Field' + """ description = "automorphism " if self._name is not None: @@ -226,6 +304,18 @@ def _repr_(self): def _del_derived(self): r""" Delete the derived quantities + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: a = M.automorphism(name='a') + sage: e = M.basis('e') + sage: a[e,:] = [[1,0,-1], [0,3,0], [0,0,2]] + sage: b = a.inverse() + sage: a._inverse + automorphism a^(-1) on the rank-3 free module M over the Rational Field + sage: a._del_derived() + sage: a._inverse # has been reset to None """ # First delete the derived quantities pertaining to the mother class: @@ -399,6 +489,16 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): """ def __init__(self, fmodule, name='Id', latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityMap + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: FreeModuleIdentityMap(M) + identity map on the rank-3 free module M over the Integer Ring + + """ if latex_name is None and name == 'Id': latex_name = r'\mathrm{Id}' FreeModuleAutomorphism.__init__(self, fmodule, name=name, @@ -409,6 +509,15 @@ def __init__(self, fmodule, name='Id', latex_name=None): def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: id = M.identity_map() + sage: id._repr_() + 'identity map on the rank-3 free module M over the Integer Ring' + """ description = "identity map " if self._name != 'Id': @@ -420,6 +529,13 @@ def _del_derived(self): r""" Delete the derived quantities. + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: id = M.identity_map() + sage: id._del_derived() + """ # FreeModuleAutomorphism._del_derived is bypassed: FreeModuleEndomorphism._del_derived(self) @@ -427,7 +543,17 @@ def _del_derived(self): def _new_comp(self, basis): r""" Create some components in the given basis. - + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: id = M.identity_map() + sage: id._new_comp(e) + Kronecker delta of size 3x3 + sage: type(id._new_comp(e)) + + """ from comp import KroneckerDelta fmodule = self._fmodule # the base free module @@ -482,6 +608,16 @@ def set_comp(self, basis=None): Redefinition of the generic tensor method :meth:`FreeModuleTensor.set_comp`: should not be called. + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.identity_map() + sage: a.set_comp(e) + Traceback (most recent call last): + ... + NotImplementedError: The components of the identity map cannot be changed. + """ raise NotImplementedError("The components of the identity map " + "cannot be changed.") @@ -491,6 +627,16 @@ def add_comp(self, basis=None): Redefinition of the generic tensor method :meth:`FreeModuleTensor.add_comp`: should not be called. + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.identity_map() + sage: a.add_comp(e) + Traceback (most recent call last): + ... + NotImplementedError: The components of the identity map cannot be changed. + """ raise NotImplementedError("The components of the identity map " + "cannot be changed.") @@ -498,6 +644,36 @@ def add_comp(self, basis=None): def __call__(self, *arg): r""" Redefinition of :meth:`FreeModuleEndomorphism.__call__`. + + EXAMPLES: + + Call with a single argument --> return a module element:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: id = M.identity_map() + sage: v = M([-1,4,3]) + sage: s = id.__call__(v) ; s + element of the rank-3 free module M over the Integer Ring + sage: s == v + True + sage: s == id(v) + True + sage: s == id.contract(v) + True + + Call with two arguments (FreeModuleTensor behaviour) --> return a + scalar:: + + sage: b = M.linear_form(name='b') + sage: b[:] = 7, 0, 2 + sage: id.__call__(b,v) + -1 + sage: id(b,v) == id.__call__(b,v) + True + sage: id(b,v) == b(v) + True + """ from free_module_tensor import FiniteRankFreeModuleElement from free_module_alt_form import FreeModuleLinForm diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index e466315ce9e..adf40c2ef13 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -86,6 +86,7 @@ class TensorFreeModule(FiniteRankFreeModule): Set of tensors of type (1,2) on a free module of rank 3 over `\ZZ`:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule sage: T = TensorFreeModule(M, (1,2)) ; T free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring @@ -139,7 +140,7 @@ class TensorFreeModule(FiniteRankFreeModule): while non-zero elements are constructed by providing their components in a given basis:: - sage: e = M.basis('e') ; e + sage: e basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring sage: comp = [[[i-j+k for k in range(3)] for j in range(3)] for i in range(3)] sage: t = T(comp, basis=e, name='t') ; t @@ -176,12 +177,46 @@ class TensorFreeModule(FiniteRankFreeModule): sage: T is M.tensor_module(1,2) True + + All tests from the suite for the category + :class:`~sage.categories.modules.Modules` are passed:: + + sage: TestSuite(T).run(verbose=True) + running ._test_additive_associativity() . . . pass + running ._test_an_element() . . . pass + running ._test_category() . . . pass + running ._test_elements() . . . + Running the test suite of self.an_element() + running ._test_category() . . . pass + running ._test_eq() . . . pass + running ._test_nonzero_equal() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + pass + running ._test_elements_eq_reflexive() . . . pass + running ._test_elements_eq_symmetric() . . . pass + running ._test_elements_eq_transitive() . . . pass + running ._test_elements_neq() . . . pass + running ._test_eq() . . . pass + running ._test_not_implemented_methods() . . . pass + running ._test_pickling() . . . pass + running ._test_some_elements() . . . pass + running ._test_zero() . . . pass """ Element = FreeModuleTensor def __init__(self, fmodule, tensor_type, name=None, latex_name=None): + r""" + TEST: + + sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: TensorFreeModule(M, (2,3), name='T23', latex_name=r'T^2_3') + free module T23 of type-(2,3) tensors on the rank-3 free module M over the Integer Ring + + """ self._fmodule = fmodule self._tensor_type = tuple(tensor_type) rank = pow(fmodule._rank, tensor_type[0] + tensor_type[1]) @@ -215,7 +250,24 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None, sym=None, antisym=None): r""" - Construct a tensor + Construct a tensor. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: T = M.tensor_module(1,1) + sage: T._element_constructor_(0) is T.zero() + True + sage: e = M.basis('e') + sage: t = T._element_constructor_(comp=[[2,0],[1/2,-3]], basis=e, name='t') ; t + type-(1,1) tensor t on the rank-2 free module M over the Rational Field + sage: t.view() + t = 2 e_0*e^0 + 1/2 e_1*e^0 - 3 e_1*e^1 + sage: t.parent() + free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field + sage: t.parent() is T + True + """ if comp == 0: return self._zero_element @@ -229,6 +281,21 @@ def _element_constructor_(self, comp=[], basis=None, name=None, def _an_element_(self): r""" Construct some (unamed) tensor + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: T = M.tensor_module(1,1) + sage: e = M.basis('e') + sage: t = T._an_element_() ; t + type-(1,1) tensor on the rank-2 free module M over the Rational Field + sage: t.view() + 1/2 e_0*e^0 + sage: t.parent() is T + True + sage: M.tensor_module(2,3)._an_element_().view() + 1/2 e_0*e_0*e^0*e^0*e^0 + """ resu = self.element_class(self._fmodule, self._tensor_type) if self._fmodule._def_basis is not None: @@ -242,6 +309,17 @@ def _an_element_(self): def _repr_(self): r""" String representation of the object. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(QQ, 2, name='M') + sage: T = M.tensor_module(1,1) + sage: T._repr_() + 'free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field' + sage: T = M.tensor_module(0,1) + sage: T._repr_() + 'dual of the rank-2 free module M over the Rational Field' + """ if self._tensor_type == (0,1): return "dual of the " + str(self._fmodule) diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py index bd78756fef4..862d0c9d68f 100644 --- a/src/sage/tensor/modules/tensor_with_indices.py +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -151,6 +151,16 @@ class TensorWithIndices(SageObject): """ def __init__(self, tensor, indices): + r""" + TEST:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: TensorWithIndices(t, 'ab_c') + t^ab_c + + """ self._tensor = tensor # may be changed below self._changed = False # indicates whether self contains an altered # version of the original tensor (True if @@ -257,13 +267,30 @@ def __init__(self, tensor, indices): def _repr_(self): r""" String representation of the object. + + EXAMPLES:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: ti = TensorWithIndices(t, 'ab_c') + sage: ti._repr_() + 't^ab_c' + sage: t = M.tensor((0,2), name='t') + sage: ti = TensorWithIndices(t, '_{ij}') + sage: ti._repr_() + 't_ij' + """ - if self._tensor._name is not None: - name = self._tensor._name - else: - name = 'X' + name = 'X' + if hasattr(self._tensor, '_name'): + if self._tensor._name is not None: + name = self._tensor._name if self._con == '': - return name + '_' + self._cov + if self._cov == '': + return 'scalar' + else: + return name + '_' + self._cov elif self._cov == '': return name + '^' + self._con else: @@ -273,6 +300,25 @@ def update(self): r""" Return the tensor contains in ``self`` if it differs from that used for creating ``self``, otherwise return ``self``. + + EXAMPLES:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((1,1), name='a') + sage: a[:] = [[1,-2,3], [-4,5,-6], [7,-8,9]] + sage: a_ind = TensorWithIndices(a, 'i_j') ; a_ind + a^i_j + sage: a_ind.update() + a^i_j + sage: a_ind.update() is a_ind + True + sage: a_ind = TensorWithIndices(a, 'k_k') ; a_ind + scalar + sage: a_ind.update() + 15 + """ if self._changed: return self._tensor @@ -281,7 +327,35 @@ def update(self): def __mul__(self, other): r""" - Tensor contraction on specified indices + Tensor product or contraction on specified indices. + + EXAMPLES:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,0), name='a') + sage: a[:] = [[1,-2,3], [-4,5,-6], [7,-8,9]] + sage: b = M.linear_form(name='b') + sage: b[:] = [4,2,1] + sage: ai = TensorWithIndices(a, '^ij') + sage: bi = TensorWithIndices(b, '_k') + sage: s = ai.__mul__(bi) ; s # no repeated indices ==> tensor product + type-(2,1) tensor a*b on the rank-3 free module M over the Rational Field + sage: s == a*b + True + sage: s[:] + [[[4, 2, 1], [-8, -4, -2], [12, 6, 3]], + [[-16, -8, -4], [20, 10, 5], [-24, -12, -6]], + [[28, 14, 7], [-32, -16, -8], [36, 18, 9]]] + sage: ai = TensorWithIndices(a, '^kj') + sage: s = ai.__mul__(bi) ; s # repeated index k ==> contraction + element of the rank-3 free module M over the Rational Field + sage: s == a.contract(0, b) + True + sage: s[:] + [3, -6, 9] + """ if not isinstance(other, TensorWithIndices): raise TypeError("The second item of * must be a tensor with " + @@ -318,6 +392,19 @@ def __rmul__(self, other): r""" Multiplication on the left by ``other``. + EXAMPLE:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,1), name='a') + sage: a[0,2,1], a[1,2,0] = 7, -4 + sage: ai = TensorWithIndices(a, 'ij_k') + sage: s = ai.__rmul__(3) ; s + X^ij_k + sage: s._tensor == 3*a + True + """ return TensorWithIndices(other*self._tensor, self._con + '_' + self._cov) @@ -329,7 +416,20 @@ def __pos__(self): OUTPUT: - an exact copy of ``self`` - + + EXAMPLE:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,1), name='a') + sage: a[0,2,1], a[1,2,0] = 7, -4 + sage: ai = TensorWithIndices(a, 'ij_k') + sage: s = ai.__pos__() ; s + +a^ij_k + sage: s._tensor == a + True + """ return TensorWithIndices(+self._tensor, self._con + '_' + self._cov) @@ -341,6 +441,19 @@ def __neg__(self): OUTPUT: - - ``self`` + + EXAMPLE:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.tensor((2,1), name='a') + sage: a[0,2,1], a[1,2,0] = 7, -4 + sage: ai = TensorWithIndices(a, 'ij_k') + sage: s = ai.__neg__() ; s + -a^ij_k + sage: s._tensor == -a + True """ return TensorWithIndices(-self._tensor, From d1a509441ff8d1c33324e180cffaca0301b3d073 Mon Sep 17 00:00:00 2001 From: Darij Grinberg Date: Thu, 9 Oct 2014 21:11:26 -0700 Subject: [PATCH 037/129] godawful fix for MatrixSpace matrix method; breaks some floating-point stuff but that's for you guys --- src/sage/matrix/matrix_space.py | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/sage/matrix/matrix_space.py b/src/sage/matrix/matrix_space.py index 9f20bf99d79..2d5af1f59a1 100644 --- a/src/sage/matrix/matrix_space.py +++ b/src/sage/matrix/matrix_space.py @@ -1305,6 +1305,16 @@ def matrix(self, x=0, coerce=True, copy=True): sage: MatrixSpace(Qp(3),1,1)([Qp(3)(4/3)]) [3^-1 + 1 + O(3^19)] + One-rowed matrices over combinatorial free modules used to break + the constructor (:trac:`17124`). Check that this is fixed:: + + sage: Sym = SymmetricFunctions(QQ) + sage: h = Sym.h() + sage: MatrixSpace(h,1,1)([h[1]]) + [h[1]] + sage: MatrixSpace(h,2,1)([h[1], h[2]]) + [h[1]] + [h[2]] """ if x is None or isinstance(x, (int, integer.Integer)) and x == 0: if self._copy_zero: # faster to copy than to create a new one. @@ -1346,7 +1356,15 @@ def matrix(self, x=0, coerce=True, copy=True): for v in x: l = len(new_x) try: - new_x.extend(v) + from sage.structure.element import is_Vector + if isinstance(v, (list, tuple)) or is_Vector(v): + # The isinstance check should prevent the "flattening" + # of v if v is an iterable but not meant to be + # iterated (e.g., an element of a combinatorial free + # module). + new_x.extend(v) + else: + raise TypeError if len(new_x) - l != n: raise TypeError except TypeError: From a07a21ae1170e224192f9fe6bae34825dae23e1d Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 9 Oct 2014 21:18:59 -0700 Subject: [PATCH 038/129] Fixing the documentation. --- .../reference/tensor_free_modules/index.rst | 12 ++++++ src/doc/en/tensors_free_module/comp.rst | 13 ------ src/doc/en/tensors_free_module/conf.py | 37 ---------------- .../finite_rank_free_module.rst | 13 ------ .../free_module_alt_form.rst | 13 ------ .../tensors_free_module/free_module_basis.rst | 13 ------ .../free_module_tensor.rst | 13 ------ .../free_module_tensor_spec.rst | 13 ------ src/doc/en/tensors_free_module/index.rst | 43 ------------------- .../tensor_free_module.rst | 13 ------ .../tensor_with_indices.rst | 13 ------ 11 files changed, 12 insertions(+), 184 deletions(-) delete mode 100644 src/doc/en/tensors_free_module/comp.rst delete mode 100644 src/doc/en/tensors_free_module/conf.py delete mode 100644 src/doc/en/tensors_free_module/finite_rank_free_module.rst delete mode 100644 src/doc/en/tensors_free_module/free_module_alt_form.rst delete mode 100644 src/doc/en/tensors_free_module/free_module_basis.rst delete mode 100644 src/doc/en/tensors_free_module/free_module_tensor.rst delete mode 100644 src/doc/en/tensors_free_module/free_module_tensor_spec.rst delete mode 100644 src/doc/en/tensors_free_module/index.rst delete mode 100644 src/doc/en/tensors_free_module/tensor_free_module.rst delete mode 100644 src/doc/en/tensors_free_module/tensor_with_indices.rst diff --git a/src/doc/en/reference/tensor_free_modules/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst index cd6b237e3ac..a4a818484a1 100644 --- a/src/doc/en/reference/tensor_free_modules/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -1,6 +1,17 @@ Tensors on free modules of finite rank ====================================== +This work is part of the +`SageManifolds project `_ but it does +not depend upon other SageManifolds classes. In other words, it constitutes +a self-consistent subset that can be used independently of SageManifolds. + + +This document is licensed under a `Creative Commons Attribution-Share Alike +3.0 License`__. + +__ http://creativecommons.org/licenses/by-sa/3.0/ + .. toctree:: :maxdepth: 2 @@ -21,3 +32,4 @@ Tensors on free modules of finite rank sage/tensor/modules/tensor_with_indices .. include:: ../footer.txt + diff --git a/src/doc/en/tensors_free_module/comp.rst b/src/doc/en/tensors_free_module/comp.rst deleted file mode 100644 index 25434b1677d..00000000000 --- a/src/doc/en/tensors_free_module/comp.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.comp: - -Components -========== - -.. automodule:: sage.tensor.modules.comp - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/conf.py b/src/doc/en/tensors_free_module/conf.py deleted file mode 100644 index cc2c42da15d..00000000000 --- a/src/doc/en/tensors_free_module/conf.py +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 -*- -# -# Sage documentation build configuration file, created by -# sphinx-quickstart on Thu Aug 21 20:15:55 2008. -# -# This file is execfile()d with the current directory set to its containing dir. -# -# The contents of this file are pickled, so don't put values in the namespace -# that aren't pickleable (module imports are okay, they're removed automatically). -# -# All configuration values have a default; values that are commented out -# serve to show the default. - -import sys, os -sys.path.append(os.environ['SAGE_DOC']) -from common.conf import * - -# General information about the project. -project = u"Tensors on free modules" -name = 'tensors_free_modules_ref' -release = "0.2" - -# The name for this set of Sphinx documents. If None, it defaults to -# " v documentation". -html_title = project + " v" + release -copyright = "2014, Eric Gourgoulhon and Michal Bejger" - -# Output file base name for HTML help builder. -htmlhelp_basename = name - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, author, document class [howto/manual]). -latex_documents = [ - ('index', name+'.tex', u'Tensors on free modules', - u'', 'manual'), -] - diff --git a/src/doc/en/tensors_free_module/finite_rank_free_module.rst b/src/doc/en/tensors_free_module/finite_rank_free_module.rst deleted file mode 100644 index 21fb348db1c..00000000000 --- a/src/doc/en/tensors_free_module/finite_rank_free_module.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.finite_rank_free_module: - -Free modules of finite rank -=========================== - -.. automodule:: sage.tensor.modules.finite_rank_free_module - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/free_module_alt_form.rst b/src/doc/en/tensors_free_module/free_module_alt_form.rst deleted file mode 100644 index 4ecc124777d..00000000000 --- a/src/doc/en/tensors_free_module/free_module_alt_form.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_alt_form: - -Alternating forms on free modules -================================= - -.. automodule:: sage.tensor.modules.free_module_alt_form - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/free_module_basis.rst b/src/doc/en/tensors_free_module/free_module_basis.rst deleted file mode 100644 index 9313780235f..00000000000 --- a/src/doc/en/tensors_free_module/free_module_basis.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_basis: - -Bases of free modules -===================== - -.. automodule:: sage.tensor.modules.free_module_basis - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/free_module_tensor.rst b/src/doc/en/tensors_free_module/free_module_tensor.rst deleted file mode 100644 index 2f221ee7671..00000000000 --- a/src/doc/en/tensors_free_module/free_module_tensor.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_tensor: - -Tensors on free modules -======================= - -.. automodule:: sage.tensor.modules.free_module_tensor - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst b/src/doc/en/tensors_free_module/free_module_tensor_spec.rst deleted file mode 100644 index 01dee654b85..00000000000 --- a/src/doc/en/tensors_free_module/free_module_tensor_spec.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.free_module_tensor_spec: - -Tensors of type (1,1) on free modules -===================================== - -.. automodule:: sage.tensor.modules.free_module_tensor_spec - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/index.rst b/src/doc/en/tensors_free_module/index.rst deleted file mode 100644 index 6c01e9e7081..00000000000 --- a/src/doc/en/tensors_free_module/index.rst +++ /dev/null @@ -1,43 +0,0 @@ -Tensors on free modules of finite rank -====================================== - -This is the reference manual for tensors on free modules of finite rank over -a commutative ring. - -This work is part of the `SageManifolds project `_ -but it does not depend upon other SageManifolds classes. In other words, it -constitutes a self-consistent subset that can be used independently of -SageManifolds. - - -This document is licensed under a `Creative Commons Attribution-Share Alike -3.0 License`__. - -__ http://creativecommons.org/licenses/by-sa/3.0/ - - -.. toctree:: - :maxdepth: 2 - - finite_rank_free_module - - free_module_basis - - tensor_free_module - - free_module_tensor - - free_module_tensor_spec - - free_module_alt_form - - comp - - tensor_with_indices - -Indices and Tables ------------------- - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` diff --git a/src/doc/en/tensors_free_module/tensor_free_module.rst b/src/doc/en/tensors_free_module/tensor_free_module.rst deleted file mode 100644 index 295f44458ce..00000000000 --- a/src/doc/en/tensors_free_module/tensor_free_module.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.tensor_free_module: - -Tensor products of free modules -=============================== - -.. automodule:: sage.tensor.modules.tensor_free_module - :members: - :undoc-members: - :show-inheritance: - - diff --git a/src/doc/en/tensors_free_module/tensor_with_indices.rst b/src/doc/en/tensors_free_module/tensor_with_indices.rst deleted file mode 100644 index 4efeca0fc72..00000000000 --- a/src/doc/en/tensors_free_module/tensor_with_indices.rst +++ /dev/null @@ -1,13 +0,0 @@ -.. nodoctest - -.. _sage.tensor.modules.tensor_with_indices: - -Index notation for tensors -========================== - -.. automodule:: sage.tensor.modules.tensor_with_indices - :members: - :undoc-members: - :show-inheritance: - - From c43321bf78adc4ac6ff324f910a879610742d383 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Fri, 10 Oct 2014 22:18:46 +0200 Subject: [PATCH 039/129] Minor modifications in the documentation of tensor/modules. - Add missing colon to have :: before doctest codes in src/sage/tensor/modules/comp.py and tensor_free_module.py - Remove spurious file src/doc/en/reference/modules/tensors_free_modules.rst --- .../modules/tensors_free_modules.rst | 23 ------------------- src/sage/tensor/modules/comp.py | 2 +- src/sage/tensor/modules/tensor_free_module.py | 2 +- 3 files changed, 2 insertions(+), 25 deletions(-) delete mode 100644 src/doc/en/reference/modules/tensors_free_modules.rst diff --git a/src/doc/en/reference/modules/tensors_free_modules.rst b/src/doc/en/reference/modules/tensors_free_modules.rst deleted file mode 100644 index b30138946dd..00000000000 --- a/src/doc/en/reference/modules/tensors_free_modules.rst +++ /dev/null @@ -1,23 +0,0 @@ -Tensors on free modules of finite rank -====================================== - -More documentation (including worksheets) can be found on the `SageManifolds `_ home page. - -.. toctree:: - :maxdepth: 2 - - sage/tensor/modules/finite_free_module - - sage/tensor/modules/free_module_basis - - sage/tensor/modules/tensor_free_module - - sage/tensor/modules/free_module_tensor - - sage/tensor/modules/free_module_tensor_spec - - sage/tensor/modules/free_module_alt_form - - sage/tensor/modules/comp - -.. include:: ../footer.txt diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 49750e61a29..8ebf011fdd6 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -1179,7 +1179,7 @@ def __ne__(self, other): - True if ``self`` is different from ``other``, or False otherwise - EXAMPLES: + EXAMPLES:: sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 1) diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index adf40c2ef13..ec8724fbbd0 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -209,7 +209,7 @@ class TensorFreeModule(FiniteRankFreeModule): def __init__(self, fmodule, tensor_type, name=None, latex_name=None): r""" - TEST: + TEST:: sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule sage: M = FiniteRankFreeModule(ZZ, 3, name='M') From 6d7c46ddcf8e6f495e8b0e8856b3a28098576db6 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Fri, 10 Oct 2014 17:25:54 -0400 Subject: [PATCH 040/129] Fixed associativity issue with PD orientation-preserving and orientation-reversing isometries. --- .../hyperbolic_space/hyperbolic_isometry.py | 40 ++++++++++++++----- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 49a94977e90..7abd87bfa25 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -641,7 +641,7 @@ class HyperbolicIsometryUHP(HyperbolicIsometry): [1 0] [0 1] """ - def _call_(self, p): + def _call_(self, p): #UHP r""" Return image of ``p`` under the action of ``self``. @@ -655,7 +655,7 @@ def _call_(self, p): """ return self.codomain().get_point(mobius_transform(self._matrix, p.coordinates())) - def preserves_orientation(self): + def preserves_orientation(self): #UHP r""" Return ``True`` if ``self`` is orientation preserving and ``False`` otherwise. @@ -672,7 +672,7 @@ def preserves_orientation(self): """ return bool(self._matrix.det() > 0) - def classification(self): + def classification(self): #UHP r""" Classify the hyperbolic isometry as elliptic, parabolic, or hyperbolic. @@ -724,7 +724,7 @@ def classification(self): return 'reflection' return 'orientation-reversing hyperbolic' - def translation_length(self): + def translation_length(self): #UHP r""" For hyperbolic elements, return the translation length; otherwise, raise a ``ValueError``. @@ -749,7 +749,7 @@ def translation_length(self): return 2 * arccosh(tau/2) raise TypeError("translation length is only defined for hyperbolic transformations") - def fixed_point_set(self): + def fixed_point_set(self): #UHP r""" Return the a list or geodesic containing the fixed point set of orientation-preserving isometries. @@ -835,7 +835,7 @@ def fixed_point_set(self): return self.domain().get_geodesic(*pts) return pts - def repelling_fixed_point(self): + def repelling_fixed_point(self): #UHP r""" Return the repelling fixed point; otherwise raise a ``ValueError``. @@ -859,7 +859,7 @@ def repelling_fixed_point(self): return self.domain().get_point(infinity) return self.domain().get_point(v[0] / v[1]) - def attracting_fixed_point(self): + def attracting_fixed_point(self): #UHP r""" Return the attracting fixed point; otherwise raise a ``ValueError``. @@ -898,7 +898,7 @@ class HyperbolicIsometryPD(HyperbolicIsometry): [1 0] [0 1] """ - def _call_(self, p): + def _call_(self, p): #PD r""" Return image of ``p`` under the action of ``self``. @@ -915,7 +915,25 @@ def _call_(self, p): # _image = mobius_transform(I*matrix([[0,1],[1,0]]), _image) return self.codomain().get_point(_image) - def preserves_orientation(self): + def __mul__(self, other): #PD + r""" + Return image of ``p`` under the action of ``self``. + + EXAMPLES:: + + """ + if isinstance(other, HyperbolicIsometry): + M = self._cached_isometry*other._cached_isometry + return M.to_model('PD') + return super(HyperbolicIsometryPD, self).__mul__(other) + + def __pow__(self, n): #PD + r""" + EXAMPLES:: + """ + return (self._cached_isometry**n).to_model('PD') + + def preserves_orientation(self): #PD """ Return ``True`` if ``self`` preserves orientation and ``False`` otherwise. @@ -931,7 +949,7 @@ def preserves_orientation(self): return bool(self._matrix.det() > 0) and HyperbolicIsometryPD._orientation_preserving(self._matrix) @staticmethod - def _orientation_preserving(A): + def _orientation_preserving(A): #PD r""" For a matrix ``A`` of a PD isometry, determine if it preserves orientation. @@ -967,7 +985,7 @@ class HyperbolicIsometryKM(HyperbolicIsometry): [0 1 0] [0 0 1] """ - def _call_(self, p): + def _call_(self, p): #KM r""" Return image of ``p`` under the action of ``self``. From 81ecbb069f88098e38e88d590f1708bab089cd1e Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Fri, 10 Oct 2014 17:29:28 -0400 Subject: [PATCH 041/129] Fixed test failures caused by the previous bug fixes. --- src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py | 2 +- src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 887510ebe03..85d10bb457d 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -293,8 +293,8 @@ def image_isometry_matrix(self, x): sage: UHP = HyperbolicPlane().UHP() sage: phi = UHP.coerce_map_from(PD) sage: phi.image_isometry_matrix(matrix([[0,I],[I,0]])) - [ 0 -1] [-1 0] + [ 0 -1] """ from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD if not HyperbolicIsometryPD._orientation_preserving(x): diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 184b6f5efe4..e9bf5ca6f59 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -517,8 +517,8 @@ def reflection_involution(self): sage: gP = H.PD().get_geodesic(0, I) sage: RP = gP.reflection_involution(); RP Isometry in PD - [ 0 I] - [-I 0] + [ 1 0] + [ 0 -1] sage: RP*gP == gP True @@ -546,7 +546,7 @@ def reflection_involution(self): sage: H = HyperbolicPlane() # Remove before submitting XXX. sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform sage: R = H.PD().get_geodesic(-1,1).reflection_involution() - sage: mobius_transform(R.matrix(), 0) == 0 + sage: bool(mobius_transform(R.matrix(), 0) == 0) True """ return self._cached_geodesic.reflection_involution().to_model(self._model) @@ -758,7 +758,7 @@ def reflection_involution(self): sage: UHP = HyperbolicPlane().UHP() sage: g1 = UHP.get_geodesic(0, 1) - sage: .reflection_involution() + sage: g1.reflection_involution() Isometry in UHP [ 1 0] [ 2 -1] From a1c4496109c119d3d44cacf31b6c87108dab1ba8 Mon Sep 17 00:00:00 2001 From: Greg Laun Date: Fri, 10 Oct 2014 17:34:35 -0400 Subject: [PATCH 042/129] Added tests to hyperbolic_isometry.py. --- .../hyperbolic_space/hyperbolic_isometry.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 7abd87bfa25..d8f607e8342 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -921,6 +921,13 @@ def __mul__(self, other): #PD EXAMPLES:: + sage: PD = HyperbolicPlane().PD() + sage: X = PD.get_isometry(matrix([[3/4, -I/4], [-I/4, -3/4]])) + sage: X*X + Isometry in PD + [ 5/8 3/8*I] + [-3/8*I 5/8] + """ if isinstance(other, HyperbolicIsometry): M = self._cached_isometry*other._cached_isometry @@ -930,6 +937,14 @@ def __mul__(self, other): #PD def __pow__(self, n): #PD r""" EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: X = PD.get_isometry(matrix([[3/4, -I/4], [-I/4, -3/4]])) + sage: X^2 + Isometry in PD + [ 5/8 3/8*I] + [-3/8*I 5/8] + """ return (self._cached_isometry**n).to_model('PD') From c5dfd89e923df732ffb868dcf2a22f48b5d32a44 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 3 Nov 2014 11:18:05 -0800 Subject: [PATCH 043/129] Final fixes and touchups. --- .../hyperbolic_space/hyperbolic_coercion.py | 107 +++++++++++++++++- .../hyperbolic_space/hyperbolic_geodesic.py | 11 +- .../hyperbolic_space/hyperbolic_isometry.py | 6 +- .../hyperbolic_space/hyperbolic_model.py | 2 +- .../hyperbolic_space/hyperbolic_point.py | 5 + 5 files changed, 119 insertions(+), 12 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 85d10bb457d..d20a6ef8e12 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -215,6 +215,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: KM = HyperbolicPlane().KM() + sage: phi = KM.coerce_map_from(UHP) + sage: phi.image_isometry_matrix(identity_matrix(2)) + [1 0 0] + [0 1 0] + [0 0 1] """ return SL2R_to_SO21(x) @@ -245,6 +253,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: UHP = HyperbolicPlane().UHP() + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(UHP) + sage: phi.image_isometry_matrix(identity_matrix(2)) + [1 0 0] + [0 1 0] + [0 0 1] """ return SL2R_to_SO21(x) @@ -298,7 +314,6 @@ def image_isometry_matrix(self, x): """ from sage.geometry.hyperbolic_space.hyperbolic_isometry import HyperbolicIsometryPD if not HyperbolicIsometryPD._orientation_preserving(x): -# x = I*x return matrix([[1,I],[I,1]]) * x * matrix([[1,-I],[-I,1]]).conjugate() / Integer(2) return matrix([[1,I],[I,1]]) * x * matrix([[1,-I],[-I,1]]) / Integer(2) @@ -312,6 +327,12 @@ def image_coordinates(self, x): under ``self``. EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: KM = HyperbolicPlane().KM() + sage: phi = KM.coerce_map_from(PD) + sage: phi.image_coordinates(0.5+0.5*I) + (0.666666666666667, 0.666666666666667) """ return (2*real(x)/(Integer(1) + real(x)**2 +imag(x)**2), 2*imag(x)/(Integer(1) + real(x)**2 + imag(x)**2)) @@ -322,6 +343,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: KM = HyperbolicPlane().KM() + sage: phi = KM.coerce_map_from(PD) + sage: phi.image_isometry_matrix(matrix([[0,I],[I,0]])) + [-1 0 0] + [ 0 1 0] + [ 0 0 -1] """ return SL2R_to_SO21( matrix(2,[1,I,I,1]) * x * matrix(2,[1,-I,-I,1])/Integer(2) ) @@ -336,6 +365,12 @@ def image_coordinates(self, x): under ``self``. EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(PD) + sage: phi.image_coordinates(0.5+0.5*I) + (2.00000000000000, 2.00000000000000, 3.00000000000000) """ return vector(( 2*real(x)/(1 - real(x)**2 - imag(x)**2), @@ -349,6 +384,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: PD = HyperbolicPlane().PD() + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(PD) + sage: phi.image_isometry_matrix(matrix([[0,I],[I,0]])) + [-1 0 0] + [ 0 1 0] + [ 0 0 -1] """ return SL2R_to_SO21( matrix(2,[1,I,I,1]) * x * matrix(2,[1,-I,-I,1])/Integer(2) ) @@ -388,6 +431,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: KM = HyperbolicPlane().KM() + sage: UHP = HyperbolicPlane().UHP() + sage: phi = UHP.coerce_map_from(KM) + sage: m = matrix([[5/3,0,4/3], [0,1,0], [4/3,0,5/3]]) + sage: phi.image_isometry_matrix(m) + [2*sqrt(1/3) sqrt(1/3)] + [ sqrt(1/3) 2*sqrt(1/3)] """ return SO21_to_SL2R(x) @@ -401,6 +452,12 @@ def image_coordinates(self, x): under ``self``. EXAMPLES:: + + sage: KM = HyperbolicPlane().KM() + sage: PD = HyperbolicPlane().PD() + sage: phi = PD.coerce_map_from(KM) + sage: phi.image_coordinates((0, 0)) + 0 """ return ( x[0]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) + I*x[1]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) ) @@ -411,6 +468,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: KM = HyperbolicPlane().KM() + sage: PD = HyperbolicPlane().PD() + sage: phi = PD.coerce_map_from(KM) + sage: m = matrix([[5/3,0,4/3], [0,1,0], [4/3,0,5/3]]) + sage: phi.image_isometry_matrix(m) + [2*sqrt(1/3) sqrt(1/3)] + [ sqrt(1/3) 2*sqrt(1/3)] """ return (matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(x) * matrix(2,[1,I,I,1])/Integer(2)) @@ -441,6 +506,15 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: KM = HyperbolicPlane().KM() + sage: HM = HyperbolicPlane().HM() + sage: phi = HM.coerce_map_from(KM) + sage: m = matrix([[5/3,0,4/3], [0,1,0], [4/3,0,5/3]]) + sage: phi.image_isometry_matrix(m) + [5/3 0 4/3] + [ 0 1 0] + [4/3 0 5/3] """ return x @@ -474,6 +548,13 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: HM = HyperbolicPlane().HM() + sage: UHP = HyperbolicPlane().UHP() + sage: phi = UHP.coerce_map_from(HM) + sage: phi.image_isometry_matrix(identity_matrix(3)) + [1 0] + [0 1] """ return SO21_to_SL2R(x) @@ -487,6 +568,12 @@ def image_coordinates(self, x): under ``self``. EXAMPLES:: + + sage: HM = HyperbolicPlane().HM() + sage: PD = HyperbolicPlane().PD() + sage: phi = PD.coerce_map_from(HM) + sage: phi.image_coordinates( vector((0,0,1)) ) + 0 """ return x[0]/(1 + x[2]) + I*(x[1]/(1 + x[2])) @@ -496,6 +583,13 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: HM = HyperbolicPlane().HM() + sage: PD = HyperbolicPlane().PD() + sage: phi = PD.coerce_map_from(HM) + sage: phi.image_isometry_matrix(identity_matrix(3)) + [1 0] + [0 1] """ return (matrix(2,[1,-I,-I,1]) * SO21_to_SL2R(x) * matrix(2,[1,I,I,1])/Integer(2)) @@ -525,6 +619,14 @@ def image_isometry_matrix(self, x): under ``self``. EXAMPLES:: + + sage: HM = HyperbolicPlane().HM() + sage: KM = HyperbolicPlane().KM() + sage: phi = KM.coerce_map_from(HM) + sage: phi.image_isometry_matrix(identity_matrix(3)) + [1 0 0] + [0 1 0] + [0 0 1] """ return x @@ -561,7 +663,8 @@ def SL2R_to_SO21(A): Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + Integer(1)/Integer(2)*d**2] )) # Kill ~0 imaginary parts - #B = B.apply_map(attrcall('real')) + + #B = B.apply_map(attrcall('real')) if A.det() > 0: return B else: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index e9bf5ca6f59..3a71ee23309 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -539,11 +539,8 @@ def reflection_involution(self): True The above tests go through the Upper Half Plane. It remains to - test that the matrices in the models do what we intend. + test that the matrices in the models do what we intend. :: - :: - - sage: H = HyperbolicPlane() # Remove before submitting XXX. sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform sage: R = H.PD().get_geodesic(-1,1).reflection_involution() sage: bool(mobius_transform(R.matrix(), 0) == 0) @@ -783,6 +780,7 @@ def show(self, boundary=True, **options): EXAMPLES:: sage: HyperbolicPlane().UHP().get_geodesic(0, 1).show() + Graphics object consisting of 2 graphics primitives """ opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) @@ -998,7 +996,7 @@ def perpendicular_bisector(self): #UHP T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]]) T2 = matrix([[cos(pi/4), -sin(pi/4)], [sin(pi/4), cos(pi/4)]]) isom_mtrx = S.inverse() * (T1 * T2) * S # We need to clean this matrix up. - if (isom_mtrx - isom_mtrx.conjugate()).norm() < 2**-9: # Imaginary part is small. + if (isom_mtrx - isom_mtrx.conjugate()).norm() < 5*EPSILON: # Imaginary part is small. isom_mtrx = (isom_mtrx + isom_mtrx.conjugate()) / 2 # Set it to its real part. H = self._model.get_isometry(isom_mtrx) return self._model.get_geodesic(H(self._start), H(self._end)) @@ -1222,6 +1220,7 @@ def show(self, boundary=True, **options): EXAMPLES:: sage: HyperbolicPlane().PD().get_geodesic(0, 1).show() + Graphics object consisting of 2 graphics primitives """ opts = dict([('axes', False), ('aspect_ratio',1)]) opts.update(self.graphics_options()) @@ -1293,6 +1292,7 @@ def show(self, boundary=True, **options): EXAMPLES:: sage: HyperbolicPlane().KM().get_geodesic((0,0), (1,0)).show() + Graphics object consisting of 2 graphics primitives """ from sage.plot.line import line opts = dict ([('axes', False), ('aspect_ratio', 1)]) @@ -1333,6 +1333,7 @@ def show(self, show_hyperboloid=True, **graphics_options): sage: from sage.geometry.hyperbolic_space.hyperbolic_geodesic import * sage: g = HyperbolicPlane().HM().random_geodesic() sage: g.show() + Graphics3d Object """ from sage.calculus.var import var (x,y,z) = var('x,y,z') diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index d8f607e8342..8de5bdda6c8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -190,8 +190,8 @@ def __eq__(self, other): if self.domain().is_isometry_group_projective(): A,B = self.matrix(), other.matrix() # Rename for simplicity m = self.matrix().ncols() - A = A/sqrt(A.det(), m) # Normalized to have determinant 1 - B = B/sqrt(B.det(), m) + A = A / sqrt(A.det(), m) # Normalized to have determinant 1 + B = B / sqrt(B.det(), m) test_matrix = bool( (A - B).norm() < EPSILON or (A + B).norm() < EPSILON ) return self.domain() is other.domain() and test_matrix @@ -911,8 +911,6 @@ def _call_(self, p): #PD True """ _image = mobius_transform(self._matrix, p.coordinates()) - # if not self.preserves_orientation(): - # _image = mobius_transform(I*matrix([[0,1],[1,0]]), _image) return self.codomain().get_point(_image) def __mul__(self, other): #PD diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 4198fc739de..8d17ce09510 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -754,7 +754,7 @@ def isometry_from_fixed_points(self, repel, attract): [ 3/4 1/4*I] [-1/4*I 3/4] - :: + :: sage: p, q = PD.get_point(1/2 + I/2), PD.get_point(6/13 + 9/13*I) sage: PD.isometry_from_fixed_points(p, q) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index ad395892073..b95250a743e 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -502,8 +502,11 @@ def show(self, boundary=True, **options): EXAMPLES:: sage: HyperbolicPlane().PD().get_point(0).show() + Graphics object consisting of 2 graphics primitives sage: HyperbolicPlane().KM().get_point((0,0)).show() + Graphics object consisting of 2 graphics primitives sage: HyperbolicPlane().HM().get_point((0,0,1)).show() + Graphics3d Object """ p = self.coordinates() if p == infinity: @@ -577,7 +580,9 @@ def show(self, boundary=True, **options): EXAMPLES:: sage: HyperbolicPlane().UHP().get_point(I).show() + Graphics object consisting of 2 graphics primitives sage: HyperbolicPlane().UHP().get_point(0).show() + Graphics object consisting of 2 graphics primitives sage: HyperbolicPlane().UHP().get_point(infinity).show() Traceback (most recent call last): ... From 8ec7d5c3a4fbf47b551df2b288b74816eca74b7a Mon Sep 17 00:00:00 2001 From: Thierry Monteil Date: Fri, 7 Nov 2014 17:30:18 +0100 Subject: [PATCH 044/129] #17288 make mathjax a dependency of sagenb --- build/deps | 3 ++- build/pkgs/sagenb/spkg-install | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/build/deps b/build/deps index 727d7176c78..97e0e8f14e3 100644 --- a/build/deps +++ b/build/deps @@ -447,7 +447,8 @@ $(INST)/$(ZNPOLY): $(INST)/$(MPIR) $(INST)/$(PYTHON) +$(PIPE) "$(SAGE_SPKG) $(ZNPOLY) 2>&1" "tee -a $(SAGE_LOGS)/$(ZNPOLY).log" $(INST)/$(SAGENB): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(PEXPECT) \ - $(INST)/$(JINJA2) $(INST)/$(SPHINX) $(INST)/$(DOCUTILS) + $(INST)/$(JINJA2) $(INST)/$(SPHINX) $(INST)/$(DOCUTILS) \ + $(INST)/$(MATHJAX) +$(PIPE) "$(SAGE_SPKG) $(SAGENB) 2>&1" "tee -a $(SAGE_LOGS)/$(SAGENB).log" $(INST)/$(SQLALCHEMY): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) diff --git a/build/pkgs/sagenb/spkg-install b/build/pkgs/sagenb/spkg-install index 65865a93f89..257f82857ea 100755 --- a/build/pkgs/sagenb/spkg-install +++ b/build/pkgs/sagenb/spkg-install @@ -46,3 +46,14 @@ PKG=$(ls -1 src | GREP_OPTIONS= grep sagenb-) # Install sagenb into site-packages easy_install -H None "src/$PKG" || die "Error installing sagenb !" + +# let sagenb use mathjax spkg +cd $SAGE_LOCAL/lib/python/site-packages/sagenb-*.egg/sagenb/data +if [ -d mathjax ] ; then + rm -rf mathjax +fi +if [ -h mathjax ] ; then + rm mathjax +fi +ln -s ../../../../../../share/mathjax/ + From 015086df733e2c0a5bab26962aa9d0ff2ca08161 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Nov 2014 20:38:02 +0100 Subject: [PATCH 045/129] trac #9439 whitespace removal plus other cosmetic cleanup --- src/sage/geometry/hyperbolic_space/all.py | 1 - .../hyperbolic_space/hyperbolic_coercion.py | 7 ++-- .../hyperbolic_space/hyperbolic_constants.py | 5 +-- .../hyperbolic_space/hyperbolic_geodesic.py | 37 ++++++++++--------- .../hyperbolic_space/hyperbolic_interface.py | 17 +++++---- .../hyperbolic_space/hyperbolic_isometry.py | 20 +++++----- .../hyperbolic_space/hyperbolic_model.py | 19 ++++------ .../hyperbolic_space/hyperbolic_point.py | 4 +- 8 files changed, 51 insertions(+), 59 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/all.py b/src/sage/geometry/hyperbolic_space/all.py index 88da542543d..d1b2a930e04 100644 --- a/src/sage/geometry/hyperbolic_space/all.py +++ b/src/sage/geometry/hyperbolic_space/all.py @@ -1,4 +1,3 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.geometry.hyperbolic_space.hyperbolic_interface', 'HyperbolicPlane') - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index d20a6ef8e12..1d068f99093 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -334,7 +334,7 @@ def image_coordinates(self, x): sage: phi.image_coordinates(0.5+0.5*I) (0.666666666666667, 0.666666666666667) """ - return (2*real(x)/(Integer(1) + real(x)**2 +imag(x)**2), + return (2*real(x)/(Integer(1) + real(x)**2 + imag(x)**2), 2*imag(x)/(Integer(1) + real(x)**2 + imag(x)**2)) def image_isometry_matrix(self, x): @@ -422,7 +422,7 @@ def image_coordinates(self, x): if tuple(x) == (0, 1): return infinity return ( -x[0]/(x[1] - 1) - + I*(-(sqrt(-x[0]**2 -x[1]**2 + 1) - x[0]**2 - x[1]**2 + 1) + + I*(-(sqrt(-x[0]**2 - x[1]**2 + 1) - x[0]**2 - x[1]**2 + 1) / ((x[1] - 1)*sqrt(-x[0]**2 - x[1]**2 + 1) + x[1] - 1)) ) def image_isometry_matrix(self, x): @@ -720,6 +720,5 @@ def SO21_to_SL2R(M): #d = 0, so ad - bc = -bc = pm 1. b = - (det_sign*1)/c a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b - A = matrix(2,[a,b,c,d]) + A = matrix(2, [a, b, c, d]) return A - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py b/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py index f41d0543ca6..1ea800e58b0 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_constants.py @@ -1,5 +1,4 @@ from sage.matrix.constructor import matrix -EPSILON = 10**-9 -LORENTZ_GRAM = matrix(3,[1,0,0,0,1,0,0,0,-1]) - +EPSILON = 10 ** -9 +LORENTZ_GRAM = matrix(3, [1, 0, 0, 0, 1, 0, 0, 0, -1]) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index 3a71ee23309..f79603cc324 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1,7 +1,7 @@ r""" Hyperbolic Geodesics -This module implements the abstract base class for geodesics in +This module implements the abstract base class for geodesics in hyperbolic space of arbitrary dimension. It also contains the implementations for specific models of hyperbolic geometry. @@ -60,6 +60,7 @@ from sage.misc.lazy_import import lazy_import lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'mobius_transform') + class HyperbolicGeodesic(SageObject): r""" Abstract base class for oriented geodesics that are not necessarily @@ -138,7 +139,7 @@ def _complete(self): False sage: g.complete()._complete True - """ + """ if self._model.is_bounded(): return (self._start.is_boundary() and self._end.is_boundary()) return False #All non-bounded geodesics start life incomplete. @@ -519,7 +520,7 @@ def reflection_involution(self): Isometry in PD [ 1 0] [ 0 -1] - + sage: RP*gP == gP True @@ -537,12 +538,12 @@ def reflection_involution(self): sage: B = diagonal_matrix([1, -1, 1]) sage: bool((B - A.matrix()).norm() < 10**-9) True - + The above tests go through the Upper Half Plane. It remains to test that the matrices in the models do what we intend. :: sage: from sage.geometry.hyperbolic_space.hyperbolic_isometry import mobius_transform - sage: R = H.PD().get_geodesic(-1,1).reflection_involution() + sage: R = H.PD().get_geodesic(-1,1).reflection_involution() sage: bool(mobius_transform(R.matrix(), 0) == 0) True """ @@ -792,15 +793,15 @@ def show(self, boundary=True, **options): # If one of the endpoints is infinity, we replace it with a # large finite point if end_1 == CC(infinity): - end_1 = (real(end_2) ,(imag(end_2) + 10)) + end_1 = (real(end_2), (imag(end_2) + 10)) end_2 = (real(end_2), imag(end_2)) elif end_2 == CC(infinity): end_2 = (real(end_1), (imag(end_1) + 10)) - end_1 = (real(end_1),imag(end_1)) + end_1 = (real(end_1), imag(end_1)) from sage.plot.line import line - pic = line((end_1,end_2), **opts) + pic = line((end_1, end_2), **opts) if boundary: - cent = min(bd_1,bd_2) + cent = min(bd_1, bd_2) bd_dict = {'bd_min': cent - 3, 'bd_max': cent + 3} bd_pic = self._model.get_background_graphic(**bd_dict) pic = bd_pic + pic @@ -816,8 +817,9 @@ def show(self, boundary=True, **options): from sage.calculus.var import var from sage.plot.plot import parametric_plot x = var('x') - pic= parametric_plot((radius*cos(x) + real(center),radius*sin(x) + - imag(center)), (x, theta1, theta2), **opts) + pic = parametric_plot((radius*cos(x) + real(center), + radius*sin(x) + imag(center)), + (x, theta1, theta2), **opts) if boundary: # We want to draw a segment of the real line. The # computations below compute the projection of the @@ -1141,10 +1143,10 @@ def _to_std_geod(self, p): True """ B = matrix([[1, 0], [0, -I]]) - [s, e]= [k.coordinates() for k in self.complete().endpoints()] + [s, e] = [k.coordinates() for k in self.complete().endpoints()] # outmat below will be returned after we normalize the determinant. - outmat = B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e) - outmat = outmat/outmat.det().sqrt() + outmat = B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e) + outmat = outmat / outmat.det().sqrt() if abs(outmat - outmat.conjugate()) < 10**-9: # Small imaginary part. outmat = (outmat + outmat.conjugate()) / 2 # Set it equal to its real part. return outmat @@ -1235,11 +1237,11 @@ def show(self, boundary=True, **options): else: # If we are here, we know it's not a line # So we compute the center and radius of the circle - center = (1/(real(bd_1)*imag(bd_2)-real(bd_2)*imag(bd_1))* + center = (1/(real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1)) * ((imag(bd_2)-imag(bd_1)) + (real(bd_1)-real(bd_2))*I)) radius = RR(abs(bd_1 - center)) # abs is Euclidean distance # Now we calculate the angles for the parametric plot - theta1 = CC(end_1- center).arg() + theta1 = CC(end_1 - center).arg() theta2 = CC(end_2 - center).arg() if theta2 < theta1: theta1, theta2 = theta2, theta1 @@ -1349,7 +1351,7 @@ def show(self, show_hyperboloid=True, **graphics_options): v2 = u2 + v1_ldot_u2*v1 v2_norm = sqrt(v2[0]**2 + v2[1]**2 - v2[2]**2) v2 = v2/v2_norm - v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2] + v2_ldot_u2 = u2[0]*v2[0] + u2[1]*v2[1] - u2[2]*v2[2] # Now v1 and v2 are Lorentz orthogonal, and |v1| = -1, |v2|=1 # That is, v1 is unit timelike and v2 is unit spacelike. # This means that cosh(x)*v1 + sinh(x)*v2 is unit timelike. @@ -1361,4 +1363,3 @@ def show(self, show_hyperboloid=True, **graphics_options): bd_pic = self._model.get_background_graphic() pic = bd_pic + pic return pic - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index 7278a95b512..fe44634b9d1 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -55,20 +55,20 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.misc.abstract_method import abstract_method -from sage.misc.lazy_attribute import lazy_attribute from sage.categories.sets_cat import Sets from sage.categories.realizations import Realizations, Category_realization_of_parent from sage.geometry.hyperbolic_space.hyperbolic_model import ( HyperbolicModelUHP, HyperbolicModelPD, HyperbolicModelHM, HyperbolicModelKM) + def HyperbolicSpace(n): """ Return ``n`` dimensional hyperbolic space. EXAMPLES:: - sage: from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicSpace + sage: from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicSpace sage: HyperbolicSpace(2) Hyperbolic plane """ @@ -76,6 +76,7 @@ def HyperbolicSpace(n): return HyperbolicPlane() raise NotImplementedError("currently only implemented in dimension 2") + class HyperbolicPlane(Parent, UniqueRepresentation): """ The hyperbolic plane `\mathbb{H}^2`. @@ -134,6 +135,7 @@ def a_realization(self): HM = HyperbolicModelHM Hyperboloid = HM + class HyperbolicModels(Category_realization_of_parent): r""" The category of hyperbolic models of hyperbolic space. @@ -223,11 +225,10 @@ def dist(self, other): EXAMPLES:: - sage: UHP = HyperbolicPlane().UHP() - sage: p1 = UHP.get_point(5 + 7*I) - sage: p2 = UHP.get_point(1 + I) - sage: p1.dist(p2) - arccosh(33/7) + sage: UHP = HyperbolicPlane().UHP() + sage: p1 = UHP.get_point(5 + 7*I) + sage: p2 = UHP.get_point(1 + I) + sage: p1.dist(p2) + arccosh(33/7) """ return self.parent().dist(self, other) - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 8de5bdda6c8..7ec8d13a0c0 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -47,12 +47,11 @@ from sage.misc.lazy_attribute import lazy_attribute from sage.matrix.constructor import matrix from sage.modules.free_module_element import vector -from sage.symbolic.pynac import I from sage.rings.infinity import infinity from sage.misc.latex import latex -from sage.rings.all import CC, RDF -from sage.functions.other import real, imag, sqrt -from sage.functions.all import cos, sin, arccosh, arccos, sign +from sage.rings.all import RDF +from sage.functions.other import imag, sqrt +from sage.functions.all import arccosh, sign from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.geometry.hyperbolic_space.hyperbolic_geodesic import HyperbolicGeodesic @@ -708,14 +707,14 @@ def classification(self): #UHP tau = abs(A.trace()) a = A.list() if A.det() > 0: - tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < EPSILON) + tf = bool((a[0] - 1)**2 + a[1]**2 + a[2]**2 + (a[3] - 1)**2 < EPSILON) if tf: return 'identity' - if tau - 2 < -EPSILON: + if tau - 2 < -EPSILON: return 'elliptic' - if tau -2 > -EPSILON and tau - 2 < EPSILON: + if tau - 2 > -EPSILON and tau - 2 < EPSILON: return 'parabolic' - if tau - 2 > EPSILON: + if tau - 2 > EPSILON: return 'hyperbolic' raise ValueError("something went wrong with classification:" + " trace is {}".format(A.trace())) @@ -928,7 +927,7 @@ def __mul__(self, other): #PD """ if isinstance(other, HyperbolicIsometry): - M = self._cached_isometry*other._cached_isometry + M = self._cached_isometry*other._cached_isometry return M.to_model('PD') return super(HyperbolicIsometryPD, self).__mul__(other) @@ -1066,7 +1065,7 @@ def mobius_transform(A, z): TypeError: A must be an invertible 2x2 matrix over the complex numbers or a symbolic ring """ if A.ncols() == 2 and A.nrows() == 2 and A.det() != 0: - (a,b,c,d) = A.list() + (a, b, c, d) = A.list() if z == infinity: if c == 0: return infinity @@ -1080,4 +1079,3 @@ def mobius_transform(A, z): return (a*w + b) / (c*w + d) raise TypeError("A must be an invertible 2x2 matrix over the" " complex numbers or a symbolic ring") - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 8d17ce09510..2aee37d21af 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -16,7 +16,7 @@ `-1`. This module records information sufficient to enable computations in hyperbolic space without explicitly specifying the underlying set or its Riemannian metric. Although, see the -`SageManifolds `_ project if +`SageManifolds `_ project if you would like to take this approach. This module implements the abstract base class for a model of hyperbolic @@ -47,7 +47,7 @@ The isometry groups of UHP and PD are projective, while that of HM is linear: - + sage: U.is_isometry_group_projective() True sage: H.is_isometry_group_projective() @@ -56,7 +56,7 @@ The models are responsible for determining if the coordinates of points and the matrix of linear maps are appropriate for constructing points and isometries in hyperbolic space: - + sage: U.point_in_model(2 + I) True sage: U.point_in_model(2 - I) @@ -88,9 +88,7 @@ from sage.functions.other import imag, real, sqrt from sage.functions.all import arccosh from sage.rings.all import CC, RR, RDF -from sage.rings.integer import Integer from sage.rings.infinity import infinity -from sage.symbolic.constants import pi from sage.symbolic.pynac import I from sage.matrix.constructor import matrix from sage.categories.homset import Hom @@ -510,7 +508,7 @@ def get_isometry(self, A): def random_element(self, **kwargs): r""" - Return a random point in ``self``. + Return a random point in ``self``. The points are uniformly distributed over the rectangle `[-10, 10] \times [0, 10 i]` in the upper half plane model. @@ -1020,8 +1018,8 @@ def random_point(self, **kwargs): real_max = 10 imag_min = 0 imag_max = 10 - p = RR.random_element(min=real_min ,max=real_max) \ - + I*RR.random_element(min=imag_min, max=imag_max) + p = RR.random_element(min=real_min, max=real_max) \ + + I * RR.random_element(min=imag_min, max=imag_max) return self.get_point(p) #################### @@ -1222,7 +1220,7 @@ def boundary_point_in_model(self, p): """ if isinstance(p, HyperbolicPoint): return p.is_boundary() - return bool(abs(abs(CC(p))- 1) < EPSILON) + return bool(abs(abs(CC(p)) - 1) < EPSILON) def isometry_in_model(self, A): r""" @@ -1253,7 +1251,7 @@ def get_background_graphic(self, **bdry_options): sage: circ = HyperbolicPlane().PD().get_background_graphic() """ from sage.plot.circle import circle - return circle((0,0), 1, axes=False, color='black') + return circle((0, 0), 1, axes=False, color='black') ##################################################################### @@ -1481,4 +1479,3 @@ def get_background_graphic(self, **bdry_options): (x,y) = var('x,y') return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max), (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) - diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index b95250a743e..ac331d380d8 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -61,7 +61,6 @@ from sage.structure.element import Element from sage.symbolic.pynac import I -from sage.misc.lazy_attribute import lazy_attribute from sage.misc.latex import latex from sage.matrix.matrix import is_Matrix from sage.matrix.constructor import matrix @@ -591,7 +590,7 @@ def show(self, boundary=True, **options): p = self.coordinates() if p == infinity: raise NotImplementedError("can't draw the point infinity") - opts = dict([('axes', False), ('aspect_ratio',1)]) + opts = dict([('axes', False), ('aspect_ratio', 1)]) opts.update(self.graphics_options()) opts.update(options) from sage.misc.functional import numerical_approx @@ -611,4 +610,3 @@ def show(self, boundary=True, **options): bd_max = cent + 1) pic = bd_pic + pic return pic - From ef79bb7533500e8fa84faeca2197350e46e999df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Fri, 7 Nov 2014 21:15:48 +0100 Subject: [PATCH 046/129] trac #9439 correct doc index --- src/doc/en/reference/geometry/index.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/doc/en/reference/geometry/index.rst b/src/doc/en/reference/geometry/index.rst index 45eab53ff36..4e80e6b7e4f 100644 --- a/src/doc/en/reference/geometry/index.rst +++ b/src/doc/en/reference/geometry/index.rst @@ -48,12 +48,10 @@ Hyperbolic Geometry :maxdepth: 1 sage/geometry/hyperbolic_space/hyperbolic_point - sage/geometry/hyperbolic_space/hyperbolic_bdry_point sage/geometry/hyperbolic_space/hyperbolic_isometry sage/geometry/hyperbolic_space/hyperbolic_geodesic sage/geometry/hyperbolic_space/hyperbolic_model sage/geometry/hyperbolic_space/hyperbolic_interface - sage/geometry/hyperbolic_space/hyperbolic_methods Backends for Polyhedral Computations ------------------------------------ From 40d67292dd64e9403744c2c1f587ef4d2e5668cf Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Fri, 7 Nov 2014 21:03:14 +0000 Subject: [PATCH 047/129] In/Covariant of two ternary quadratics --- src/sage/rings/invariant_theory.py | 267 +++++++++++++++++++++++++++++ 1 file changed, 267 insertions(+) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 78e52726849..a6456fe4b19 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2299,6 +2299,220 @@ def second(self): return self._forms[1] +###################################################################### + +class TwoTernaryQuadratics(TwoAlgebraicForms): + """ + Invariant theory of two ternany quadratics. + + You should use the :class:`invariant_theory + ` factory object to construct instances + of this class. See + :meth:`~InvariantTheoryFactory.ternary_biquadratics` for + details. + + REFERENCES: + + .. [Salmon] + G. Salmon: A Treatise on Conic Sections, + Section on "Invariants and Covariants of Systems of Conics", + Art. 388 (a). + + TESTS:: + + sage: R. = QQ[] + sage: inv = invariant_theory.ternary_biquadratic(x^2+y^2+z^2, x*y+y*z+x*z, [x, y, z]) + sage: inv + Joint ternary quadratic with coefficients (1, 1, 1, 0, 0, 0) and ternary + quadratic with coefficients (0, 0, 0, 1, 1, 1) + sage: TestSuite(inv).run() + + sage: q1 = 73*x^2 + 96*x*y - 11*y^2 + 4*x + 63*y + 57 + sage: q2 = 61*x^2 - 100*x*y - 72*y^2 - 81*x + 39*y - 7 + sage: biquadratic = invariant_theory.ternary_biquadratic(q1, q2, [x,y]).homogenized() + sage: biquadratic._check_covariant('Delta_invariant', invariant=True) + sage: biquadratic._check_covariant('Delta_prime_invariant', invariant=True) + sage: biquadratic._check_covariant('Theta_invariant', invariant=True) + sage: biquadratic._check_covariant('Theta_prime_invariant', invariant=True) + sage: biquadratic._check_covariant('F_covariant') + sage: biquadratic._check_covariant('J_covariant') + """ + + def Delta_invariant(self): + """ + Return the `\Delta` invariant. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 + sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 + sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: q.Delta_invariant() == coeffs[3] + True + """ + return self.get_form(0).matrix().det() + + def Delta_prime_invariant(self): + r""" + Return the `\Delta'` invariant. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 + sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 + sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: q.Delta_prime_invariant() == coeffs[0] + True + """ + return self.get_form(1).matrix().det() + + def _Theta_helper(self, scaled_coeffs_1, scaled_coeffs_2): + """ + Internal helper method for :meth:`Theta_invariant` and + :meth:`Theta_prime_invariant`. + + TESTS:: + + sage: R. = QQ[] + sage: inv = invariant_theory.ternary_biquadratic(x^2 + y*z, x*y+z^2, x, y, z) + sage: inv._Theta_helper([1]*6, [2]*6) + 0 + """ + a00, a11, a22, a01, a02, a12 = scaled_coeffs_1 + b00, b11, b22, b01, b02, b12 = scaled_coeffs_2 + return -a12**2*b00 + a11*a22*b00 + 2*a02*a12*b01 - 2*a01*a22*b01 - \ + a02**2*b11 + a00*a22*b11 - 2*a11*a02*b02 + 2*a01*a12*b02 + \ + 2*a01*a02*b12 - 2*a00*a12*b12 - a01**2*b22 + a00*a11*b22 + + def Theta_invariant(self): + r""" + Return the `\Theta` invariant. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 + sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 + sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: q.Theta_invariant() == coeffs[2] + True + """ + return self._Theta_helper(self.get_form(0).scaled_coeffs(), self.get_form(1).scaled_coeffs()) + + def Theta_prime_invariant(self): + r""" + Return the `\Theta'` invariant. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 + sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 + sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: q.Theta_prime_invariant() == coeffs[1] + True + """ + return self._Theta_helper(self.get_form(1).scaled_coeffs(), self.get_form(0).scaled_coeffs()) + + def F_covariant(self): + r""" + Return the `F` covariant. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p1 = 73*x^2 + 96*x*y - 11*y^2 + 4*x + 63*y + 57 + sage: p2 = 61*x^2 - 100*x*y - 72*y^2 - 81*x + 39*y - 7 + sage: q = invariant_theory.ternary_biquadratic(p1, p2, [x, y]) + sage: q.F_covariant() + -32566577*x^2 + 29060637/2*x*y + 20153633/4*y^2 - + 30250497/2*x - 241241273/4*y - 323820473/16 + """ + C = self.first().covariant_conic(self.second()) + CI = TernaryQuadratic(3, 2, C, *self.variables()) + return CI.dual().polynomial() + + def J_covariant(self): + r""" + Return the `J` covariant. + + EXAMPLES:: + + sage: R. = QQ[] + sage: p1 = 73*x^2 + 96*x*y - 11*y^2 + 4*x + 63*y + 57 + sage: p2 = 61*x^2 - 100*x*y - 72*y^2 - 81*x + 39*y - 7 + sage: q = invariant_theory.ternary_biquadratic(p1, p2, [x, y]) + sage: q.J_covariant() + 1057324024445*x^3 + 1209531088209*x^2*y + 942116599708*x*y^2 + + 984553030871*y^3 + 543715345505/2*x^2 - 3065093506021/2*x*y + + 755263948570*y^2 - 1118430692650*x - 509948695327/4*y + 3369951531745/8 + """ + return self._jacobian_determinant( + (self.first().polynomial(), 2), + (self.second().polynomial(), 2), + (self.F_covariant(), 2)) + + def syzygy(self, Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J): + """ + Return the syzygy evaluated on the invariants and covariants. + + INPUT: + + - ``Delta``, ``Theta``, ``Theta_prime``, ``Delta_prime``, + ``S``, ``S_prime``, ``F``, ``J`` -- polynomials from the + same polynomial ring. + + OUTPUT: + + Zero if the ``S`` is the first polynomial, ``S_prime`` the + second polynomial, and the remaining input are the invariants + and covariants of a ternary biquadratic. + + EXAMPLES:: + + sage: R. = QQ[] + sage: monomials = [x^2, x*y, y^2, x*z, y*z, z^2] + sage: def q_rnd(): return sum(randint(-1000,1000)*m for m in monomials) + sage: biquadratic = invariant_theory.ternary_biquadratic(q_rnd(), q_rnd(), [x,y,z]) + sage: Delta = biquadratic.Delta_invariant() + sage: Theta = biquadratic.Theta_invariant() + sage: Theta_prime = biquadratic.Theta_prime_invariant() + sage: Delta_prime = biquadratic.Delta_prime_invariant() + sage: S = biquadratic.first().polynomial() + sage: S_prime = biquadratic.second().polynomial() + sage: F = biquadratic.F_covariant() + sage: J = biquadratic.J_covariant() + sage: biquadratic.syzygy(Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J) + 0 + + If the arguments are not the invariants and covariants then + the output is some (generically non-zero) polynomial:: + + sage: biquadratic.syzygy(1, 1, 1, 1, 1, 1, 1, x) + 1/32*x^2 + 2 + """ + return (J**2 / 32 + + 2 * F**3 + -4 * F**2 * Theta*S_prime + -4 * F**2 * Theta_prime*S + + 2 * F * S**2 * (Delta_prime * Theta + Theta_prime**2) + + 2 * F * S_prime**2 * (Delta * Theta_prime + Theta**2) + + 6 * F * S * S_prime * (Theta*Theta_prime - Delta*Delta_prime) + + 2 * S**3 * (Delta_prime**2 * Delta - Theta * Theta_prime * Delta_prime) + + 2 * S_prime**3 * (Delta**2 * Delta_prime - Theta_prime * Theta * Delta) + + 2 * S**2 * S_prime * ( + Delta_prime * Delta * Theta_prime - Theta * Theta_prime**2) + + 2 * S * S_prime**2 * ( + Delta * Delta_prime * Theta - Theta_prime * Theta**2) + ) + + ###################################################################### class TwoQuaternaryQuadratics(TwoAlgebraicForms): @@ -3053,6 +3267,59 @@ def ternary_cubic(self, cubic, *args, **kwds): """ return TernaryCubic(3, 3, cubic, *args, **kwds) + def ternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): + """ + Invariants of two quadratics in three variables. + + INPUT: + + - ``quadratic1``, ``quadratic2`` -- two polynomias. Either + homogeneous quadratic in 3 homogeneous variables, or + inhomogeneous quadratic in 2 variables. + + - ``x``, ``y``, ``z`` -- the variables. If ``z`` is ``None``, + the quadratics are assumed to be inhomogeneous. + + EXAMPLES:: + + sage: R. = QQ[] + sage: q1 = x^2+y^2+z^2 + sage: q2 = x*y + y*z + x*z + sage: inv = invariant_theory.ternary_biquadratic(q1, q2) + sage: type(inv) + + + Distance between two circles:: + + sage: R. = QQ[] + sage: S1 = -r1^2 + x^2 + y^2 + sage: S2 = -r2^2 + (x-a)^2 + (y-b)^2 + sage: inv = invariant_theory.ternary_biquadratic(S1, S2, [x, y]) + sage: inv.Delta_invariant() + -r1^2 + sage: inv.Delta_prime_invariant() + -r2^2 + sage: inv.Theta_invariant() + a^2 + b^2 - 2*r1^2 - r2^2 + sage: inv.Theta_prime_invariant() + a^2 + b^2 - r1^2 - 2*r2^2 + sage: inv.F_covariant() + 2*x^2*a^2 + y^2*a^2 - 2*x*a^3 + a^4 + 2*x*y*a*b - 2*y*a^2*b + x^2*b^2 + + 2*y^2*b^2 - 2*x*a*b^2 + 2*a^2*b^2 - 2*y*b^3 + b^4 - 2*x^2*r1^2 - 2*y^2*r1^2 + + 2*x*a*r1^2 - 2*a^2*r1^2 + 2*y*b*r1^2 - 2*b^2*r1^2 + r1^4 - 2*x^2*r2^2 - + 2*y^2*r2^2 + 2*x*a*r2^2 - 2*a^2*r2^2 + 2*y*b*r2^2 - 2*b^2*r2^2 + 2*r1^2*r2^2 + + r2^4 + sage: inv.J_covariant() + -8*x^2*y*a^3 + 8*x*y*a^4 + 8*x^3*a^2*b - 16*x*y^2*a^2*b - 8*x^2*a^3*b + + 8*y^2*a^3*b + 16*x^2*y*a*b^2 - 8*y^3*a*b^2 + 8*x*y^2*b^3 - 8*x^2*a*b^3 + + 8*y^2*a*b^3 - 8*x*y*b^4 + 8*x*y*a^2*r1^2 - 8*y*a^3*r1^2 - 8*x^2*a*b*r1^2 + + 8*y^2*a*b*r1^2 + 8*x*a^2*b*r1^2 - 8*x*y*b^2*r1^2 - 8*y*a*b^2*r1^2 + 8*x*b^3*r1^2 - + 8*x*y*a^2*r2^2 + 8*x^2*a*b*r2^2 - 8*y^2*a*b*r2^2 + 8*x*y*b^2*r2^2 + """ + q1 = TernaryQuadratic(3, 2, quadratic1, *args, **kwds) + q2 = TernaryQuadratic(3, 2, quadratic2, *args, **kwds) + return TwoTernaryQuadratics([q1, q2]) + def quaternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): """ Invariants of two quadratics in four variables. From 947d3f406406d9fe0935de3113330c8ae6d3685f Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Fri, 7 Nov 2014 21:10:10 +0000 Subject: [PATCH 048/129] Beautify the syzygy equation --- src/sage/rings/invariant_theory.py | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index a6456fe4b19..fcd67cef433 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2495,20 +2495,20 @@ def syzygy(self, Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J): the output is some (generically non-zero) polynomial:: sage: biquadratic.syzygy(1, 1, 1, 1, 1, 1, 1, x) - 1/32*x^2 + 2 - """ - return (J**2 / 32 + - 2 * F**3 - -4 * F**2 * Theta*S_prime - -4 * F**2 * Theta_prime*S - + 2 * F * S**2 * (Delta_prime * Theta + Theta_prime**2) - + 2 * F * S_prime**2 * (Delta * Theta_prime + Theta**2) - + 6 * F * S * S_prime * (Theta*Theta_prime - Delta*Delta_prime) - + 2 * S**3 * (Delta_prime**2 * Delta - Theta * Theta_prime * Delta_prime) - + 2 * S_prime**3 * (Delta**2 * Delta_prime - Theta_prime * Theta * Delta) - + 2 * S**2 * S_prime * ( + 1/64*x^2 + 1 + """ + return (J**2 / 64 + + F**3 + - 2 * F**2 * Theta*S_prime + - 2 * F**2 * Theta_prime*S + + F * S**2 * (Delta_prime * Theta + Theta_prime**2) + + F * S_prime**2 * (Delta * Theta_prime + Theta**2) + + 3 * F * S * S_prime * (Theta*Theta_prime - Delta*Delta_prime) + + S**3 * (Delta_prime**2 * Delta - Theta * Theta_prime * Delta_prime) + + S_prime**3 * (Delta**2 * Delta_prime - Theta_prime * Theta * Delta) + + S**2 * S_prime * ( Delta_prime * Delta * Theta_prime - Theta * Theta_prime**2) - + 2 * S * S_prime**2 * ( + + S * S_prime**2 * ( Delta * Delta_prime * Theta - Theta_prime * Theta**2) ) From e6e0c1f37c5cea77d10ef7437f7f829ddcd32c52 Mon Sep 17 00:00:00 2001 From: Peter Bruin Date: Tue, 4 Nov 2014 22:54:20 +0100 Subject: [PATCH 049/129] Trac 17327: simplify computation of values of Dirichlet characters --- src/sage/modular/dirichlet.py | 85 +++++++++++++---------------------- 1 file changed, 31 insertions(+), 54 deletions(-) diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index 8c79553c5a5..e786c0981c0 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -1476,7 +1476,7 @@ def restrict(self, M): def values(self): """ - Returns a list of the values of this character on each integer + Return a list of the values of this character on each integer between 0 and the modulus. EXAMPLES:: @@ -1526,84 +1526,61 @@ def values(self): return self.__values except AttributeError: pass + # Build cache of all values of the Dirichlet character. # I'm going to do it this way, since in my app the modulus # is *always* small and we want to evaluate the character # a *lot*. G = self.parent() R = G.base_ring() - zero = R(0) - one = R(1) - mod = self.__modulus - - if self.is_trivial(): # easy special case - x = [ one ] * int(mod) - - for p in mod.prime_divisors(): - p_mult = p - while p_mult < mod: - x[p_mult] = zero - p_mult += p - if not (mod == 1): - x[0] = zero - self.__values = x - return x - - result_list = [zero] * mod - zeta_order = G.zeta_order() - zeta = R.zeta(zeta_order) - A = rings.Integers(zeta_order) - A_zero = A.zero_element() - A_one = A.one_element() - ZZ = rings.ZZ - - S = G._integers - - R_values = G._zeta_powers + mod = self.__modulus + if mod == 1: + self.__values = [R.one_element()] + return self.__values + elif mod == 2: + self.__values = [R.zero_element(), R.one_element()] + return self.__values + result_list = [R.zero_element()] * mod gens = G.unit_gens() - last = [o - 1 for o in G.integers_mod().unit_group().gens_orders()] - ngens = len(gens) - exponents = [0] * ngens - n = S(1) + orders = G.integers_mod().unit_group().gens_orders() - value = A_zero - val_on_gen = [ A(R_values.index(x)) for x in self.values_on_gens() ] + A = rings.IntegerModRing(G.zeta_order()) + R_values = G._zeta_powers + val_on_gen = [A(R_values.index(x)) for x in self.values_on_gens()] - final_index = ngens-1 - stop = last[-1] - while exponents[-1] <= stop: + exponents = [0] * ngens + n = G.integers_mod().one_element() + value = A.zero_element() - ######################## + final_index = ngens - 1 + stop = orders[-1] + while exponents[-1] < stop: # record character value on n result_list[n] = R_values[value] # iterate: # increase the exponent vector by 1, # increase n accordingly, and increase value - exponents[0] += 1 # inc exponent - value += val_on_gen[0] # inc value - n *= gens[0] - ## n %= mod i = 0 - while i < final_index and exponents[i] > last[i]: + exponents[0] += 1 + value += val_on_gen[0] + n *= gens[0] + while i < final_index and exponents[i] >= orders[i]: exponents[i] = 0 - # now increment position i+1: - exponents[i+1] += 1 - value += val_on_gen[i+1] - n *= gens[i+1] - ## n %= mod i += 1 + exponents[i] += 1 + value += val_on_gen[i] + n *= gens[i] self.__values = result_list return self.__values def values_on_gens(self): - """ - Returns a tuple of the values of this character on each of the - minimal generators of `(\ZZ/N\ZZ)^*`, where - `N` is the modulus. + r""" + Return a tuple of the values of ``self`` on the standard + generators of `(\ZZ/N\ZZ)^*`, where `N` is the modulus. EXAMPLES:: @@ -2037,7 +2014,7 @@ def _coerce_in_dirichlet_character(self, x): for u in self.unit_gens(): v = u.lift() # have to do this, since e.g., unit gens mod 11 are not units mod 22. - while arith.GCD(x.modulus(),int(v)) != 1: + while x.modulus().gcd(v) != 1: v += self.modulus() a.append(R(x(v))) return self(a) From 5fba412cef169dc7bbc1c32b7d254ab0bf185d4a Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 23 Nov 2014 13:13:41 -0800 Subject: [PATCH 050/129] First pass of reviewer changes. --- src/sage/tensor/modules/comp.py | 2272 ++++++++--------- .../tensor/modules/finite_rank_free_module.py | 1276 ++++----- src/sage/tensor/modules/format_utilities.py | 159 +- .../tensor/modules/free_module_alt_form.py | 395 ++- src/sage/tensor/modules/free_module_basis.py | 434 ++-- src/sage/tensor/modules/free_module_tensor.py | 1919 +++++++------- .../tensor/modules/free_module_tensor_spec.py | 418 ++- src/sage/tensor/modules/tensor_free_module.py | 278 +- .../tensor/modules/tensor_with_indices.py | 275 +- 9 files changed, 3699 insertions(+), 3727 deletions(-) diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 8ebf011fdd6..6f0e1f14ac8 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -1,18 +1,18 @@ r""" -Components as indexed sets of ring elements. +Components as indexed sets of ring elements. -The class :class:`Components` is a technical class to take in charge the -storage and manipulation of **indexed elements of a commutative ring** that represent the -components of some "mathematical entity" with respect to some "frame". -Examples of *entity/frame* are *vector/vector-space basis* or +The class :class:`Components` is a technical class to take in charge the +storage and manipulation of **indexed elements of a commutative ring** that represent the +components of some "mathematical entity" with respect to some "frame". +Examples of *entity/frame* are *vector/vector-space basis* or *vector field/vector frame on some manifold*. More generally, the components -can be those of a tensor on a free module or those of a tensor field on a -manifold. They can also be non-tensorial quantities, like connection -coefficients or structure coefficients of a vector frame. +can be those of a tensor on a free module or those of a tensor field on a +manifold. They can also be non-tensorial quantities, like connection +coefficients or structure coefficients of a vector frame. -The individual components are assumed to belong to a given commutative ring +The individual components are assumed to belong to a given commutative ring and are labelled by *indices*, which are *tuples of integers*. -The following operations are implemented on components with respect +The following operations are implemented on components with respect to a given frame: * arithmetics (addition, subtraction, multiplication by a ring element) @@ -23,207 +23,207 @@ Various subclasses of class :class:`Components` are -* :class:`CompWithSym` for components with symmetries or antisymmetries w.r.t. +* :class:`CompWithSym` for components with symmetries or antisymmetries w.r.t. index permutations - - * :class:`CompFullySym` for fully symmetric components w.r.t. index + + * :class:`CompFullySym` for fully symmetric components w.r.t. index permutations - * :class:`KroneckerDelta` for the Kronecker delta symbol - - * :class:`CompFullyAntiSym` for fully antisymmetric components w.r.t. index + * :class:`KroneckerDelta` for the Kronecker delta symbol + + * :class:`CompFullyAntiSym` for fully antisymmetric components w.r.t. index permutations AUTHORS: - Eric Gourgoulhon, Michal Bejger (2014): initial version - Joris Vankerschaver (2010): for the idea of storing only the non-zero - components as dictionaries, whose keys are the component indices (see + components as dictionaries, whose keys are the component indices (see class :class:`~sage.tensor.differential_form_element.DifferentialForm`) EXAMPLES: - Set of components with 2 indices on a 3-dimensional vector space, the frame - being some basis of the vector space:: - - sage: from sage.tensor.modules.comp import Components - sage: V = VectorSpace(QQ,3) - sage: basis = V.basis() ; basis - [ - (1, 0, 0), - (0, 1, 0), - (0, 0, 1) - ] - sage: c = Components(QQ, basis, 2) ; c - 2-indices components w.r.t. [ - (1, 0, 0), - (0, 1, 0), - (0, 0, 1) - ] - - Actually, the frame can be any object that has some length, i.e. on which - the function :func:`len()` can be called:: - - sage: basis1 = V.gens() ; basis1 - ((1, 0, 0), (0, 1, 0), (0, 0, 1)) - sage: c1 = Components(QQ, basis1, 2) ; c1 - 2-indices components w.r.t. ((1, 0, 0), (0, 1, 0), (0, 0, 1)) - sage: basis2 = ['a', 'b' , 'c'] - sage: c2 = Components(QQ, basis2, 2) ; c2 - 2-indices components w.r.t. ['a', 'b', 'c'] - - A just created set of components is initialized to zero:: - - sage: c.is_zero() - True - sage: c == 0 - True - - This can also be checked on the list of components, which is returned by - the operator ``[:]``:: - - sage: c[:] - [0 0 0] - [0 0 0] - [0 0 0] - - Individual components are accessed by providing their indices inside - square brackets:: - - sage: c[1,2] = -3 - sage: c[:] - [ 0 0 0] - [ 0 0 -3] - [ 0 0 0] - sage: v = Components(QQ, basis, 1) - sage: v[:] - [0, 0, 0] - sage: v[0] - 0 - sage: v[:] = (-1,3,2) - sage: v[:] - [-1, 3, 2] - sage: v[0] - -1 - - By default, the indices range from `0` to `n-1`, where `n` is the length - of the frame. This can be changed via the argument ``start_index`` in - the :class:`Components` constructor:: - - sage: v1 = Components(QQ, basis, 1, start_index=1) - sage: v1[:] - [0, 0, 0] - sage: v1[0] - Traceback (most recent call last): - ... - IndexError: Index out of range: 0 not in [1,3] - sage: v1[1] - 0 - sage: v1[:] = v[:] # list copy of all components - sage: v1[:] - [-1, 3, 2] - sage: v1[1], v1[2], v1[3] - (-1, 3, 2) - sage: v[0], v[1], v[2] - (-1, 3, 2) - - If some formatter function or unbound method is provided via the argument - ``output_formatter`` in the :class:`Components` constructor, it is used to - change the ouput of the access operator ``[...]``:: - - sage: a = Components(QQ, basis, 2, output_formatter=Rational.numerical_approx) - sage: a[1,2] = 1/3 - sage: a[1,2] - 0.333333333333333 - - The format can be passed to the formatter as the last argument of the - access operator ``[...]``:: - - sage: a[1,2,10] # here the format is 10, for 10 bits of precision - 0.33 - sage: a[1,2,100] - 0.33333333333333333333333333333 - - The raw (unformatted) components are then accessed by the double bracket - operator:: - - sage: a[[1,2]] - 1/3 - - For sets of components declared without any output formatter, there is no - difference between ``[...]`` and ``[[...]]``:: - - sage: c[1,2] = 1/3 - sage: c[1,2], c[[1,2]] - (1/3, 1/3) - - The formatter is also used for the complete list of components:: - - sage: a[:] - [0.000000000000000 0.000000000000000 0.000000000000000] - [0.000000000000000 0.000000000000000 0.333333333333333] - [0.000000000000000 0.000000000000000 0.000000000000000] - sage: a[:,10] # with a format different from the default one (53 bits) - [0.00 0.00 0.00] - [0.00 0.00 0.33] - [0.00 0.00 0.00] - - The complete list of components in raw form can be recovered by the double - bracket operator, replacing ``:`` by ``slice(None)`` (since ``a[[:]]`` - generates a Python syntax error):: - - sage: a[[slice(None)]] - [ 0 0 0] - [ 0 0 1/3] - [ 0 0 0] - - Another example of formatter: the Python built-in function :func:`str` - to generate string outputs:: - - sage: b = Components(QQ, V.basis(), 1, output_formatter=str) - sage: b[:] = (1, 0, -4) - sage: b[:] - ['1', '0', '-4'] - - For such a formatter, 2-indices components are no longer displayed as a - matrix:: - - sage: b = Components(QQ, basis, 2, output_formatter=str) - sage: b[0,1] = 1/3 - sage: b[:] - [['0', '1/3', '0'], ['0', '0', '0'], ['0', '0', '0']] - - But unformatted outputs still are:: - - sage: b[[slice(None)]] - [ 0 1/3 0] - [ 0 0 0] - [ 0 0 0] - - Internally, the components are stored as a dictionary (:attr:`_comp`) whose - keys are the indices; only the non-zero components are stored:: - - sage: a[:] - [0.000000000000000 0.000000000000000 0.000000000000000] - [0.000000000000000 0.000000000000000 0.333333333333333] - [0.000000000000000 0.000000000000000 0.000000000000000] - sage: a._comp - {(1, 2): 1/3} - sage: v[:] = (-1, 0, 3) - sage: v._comp # random output order of the component dictionary - {(0,): -1, (2,): 3} - - In case of symmetries, only non-redundant components are stored:: - - sage: from sage.tensor.modules.comp import CompFullyAntiSym - sage: c = CompFullyAntiSym(QQ, basis, 2) - sage: c[0,1] = 3 - sage: c[:] - [ 0 3 0] - [-3 0 0] - [ 0 0 0] - sage: c._comp - {(0, 1): 3} +Set of components with 2 indices on a 3-dimensional vector space, the frame +being some basis of the vector space:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ,3) + sage: basis = V.basis() ; basis + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: c = Components(QQ, basis, 2) ; c + 2-indices components w.r.t. [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + +Actually, the frame can be any object that has some length, i.e. on which +the function :func:`len()` can be called:: + + sage: basis1 = V.gens() ; basis1 + ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sage: c1 = Components(QQ, basis1, 2) ; c1 + 2-indices components w.r.t. ((1, 0, 0), (0, 1, 0), (0, 0, 1)) + sage: basis2 = ['a', 'b' , 'c'] + sage: c2 = Components(QQ, basis2, 2) ; c2 + 2-indices components w.r.t. ['a', 'b', 'c'] + +A just created set of components is initialized to zero:: + + sage: c.is_zero() + True + sage: c == 0 + True + +This can also be checked on the list of components, which is returned by +the operator ``[:]``:: + + sage: c[:] + [0 0 0] + [0 0 0] + [0 0 0] + +Individual components are accessed by providing their indices inside +square brackets:: + + sage: c[1,2] = -3 + sage: c[:] + [ 0 0 0] + [ 0 0 -3] + [ 0 0 0] + sage: v = Components(QQ, basis, 1) + sage: v[:] + [0, 0, 0] + sage: v[0] + 0 + sage: v[:] = (-1,3,2) + sage: v[:] + [-1, 3, 2] + sage: v[0] + -1 + +By default, the indices range from `0` to `n-1`, where `n` is the length +of the frame. This can be changed via the argument ``start_index`` in +the :class:`Components` constructor:: + + sage: v1 = Components(QQ, basis, 1, start_index=1) + sage: v1[:] + [0, 0, 0] + sage: v1[0] + Traceback (most recent call last): + ... + IndexError: index out of range: 0 not in [1, 3] + sage: v1[1] + 0 + sage: v1[:] = v[:] # list copy of all components + sage: v1[:] + [-1, 3, 2] + sage: v1[1], v1[2], v1[3] + (-1, 3, 2) + sage: v[0], v[1], v[2] + (-1, 3, 2) + +If some formatter function or unbound method is provided via the argument +``output_formatter`` in the :class:`Components` constructor, it is used to +change the ouput of the access operator ``[...]``:: + + sage: a = Components(QQ, basis, 2, output_formatter=Rational.numerical_approx) + sage: a[1,2] = 1/3 + sage: a[1,2] + 0.333333333333333 + +The format can be passed to the formatter as the last argument of the +access operator ``[...]``:: + + sage: a[1,2,10] # here the format is 10, for 10 bits of precision + 0.33 + sage: a[1,2,100] + 0.33333333333333333333333333333 + +The raw (unformatted) components are then accessed by the double bracket +operator:: + + sage: a[[1,2]] + 1/3 + +For sets of components declared without any output formatter, there is no +difference between ``[...]`` and ``[[...]]``:: + + sage: c[1,2] = 1/3 + sage: c[1,2], c[[1,2]] + (1/3, 1/3) + +The formatter is also used for the complete list of components:: + + sage: a[:] + [0.000000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 0.333333333333333] + [0.000000000000000 0.000000000000000 0.000000000000000] + sage: a[:,10] # with a format different from the default one (53 bits) + [0.00 0.00 0.00] + [0.00 0.00 0.33] + [0.00 0.00 0.00] + +The complete list of components in raw form can be recovered by the double +bracket operator, replacing ``:`` by ``slice(None)`` (since ``a[[:]]`` +generates a Python syntax error):: + + sage: a[[slice(None)]] + [ 0 0 0] + [ 0 0 1/3] + [ 0 0 0] + +Another example of formatter: the Python built-in function :func:`str` +to generate string outputs:: + + sage: b = Components(QQ, V.basis(), 1, output_formatter=str) + sage: b[:] = (1, 0, -4) + sage: b[:] + ['1', '0', '-4'] + +For such a formatter, 2-indices components are no longer displayed as a +matrix:: + + sage: b = Components(QQ, basis, 2, output_formatter=str) + sage: b[0,1] = 1/3 + sage: b[:] + [['0', '1/3', '0'], ['0', '0', '0'], ['0', '0', '0']] + +But unformatted outputs still are:: + + sage: b[[slice(None)]] + [ 0 1/3 0] + [ 0 0 0] + [ 0 0 0] + +Internally, the components are stored as a dictionary (:attr:`_comp`) whose +keys are the indices; only the non-zero components are stored:: + + sage: a[:] + [0.000000000000000 0.000000000000000 0.000000000000000] + [0.000000000000000 0.000000000000000 0.333333333333333] + [0.000000000000000 0.000000000000000 0.000000000000000] + sage: a._comp + {(1, 2): 1/3} + sage: v[:] = (-1, 0, 3) + sage: v._comp # random output order of the component dictionary + {(0,): -1, (2,): 3} + +In case of symmetries, only non-redundant components are stored:: + + sage: from sage.tensor.modules.comp import CompFullyAntiSym + sage: c = CompFullyAntiSym(QQ, basis, 2) + sage: c[0,1] = 3 + sage: c[:] + [ 0 3 0] + [-3 0 0] + [ 0 0 0] + sage: c._comp + {(0, 1): 3} """ @@ -242,32 +242,32 @@ class :class:`~sage.tensor.differential_form_element.DifferentialForm`) class Components(SageObject): r""" - Indexed set of ring elements forming some components with respect - to a given "frame". - - The "frame" can be a basis of some vector space or a vector frame on some - manifold (i.e. a field of bases). - The stored quantities can be tensor components or non-tensorial quantities, - such as connection coefficients or structure coefficents. The symmetries + Indexed set of ring elements forming some components with respect + to a given "frame". + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). + The stored quantities can be tensor components or non-tensorial quantities, + such as connection coefficients or structure coefficents. The symmetries over some indices are dealt by subclasses of the class :class:`Components`. - + INPUT: - + - ``ring`` -- commutative ring in which each component takes its value - - ``frame`` -- frame with respect to which the components are defined; + - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have a method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size of a single index range - ``nb_indices`` -- number of integer indices labeling the components - - ``start_index`` -- (default: 0) first value of a single index; + - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey - ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound - method called to format the output of the component access + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take - 1 or 2 arguments: the 1st argument must be an element of ``ring`` and + 1 or 2 arguments: the 1st argument must be an element of ``ring`` and the second one, if any, some format specification. - + EXAMPLES: Set of components with 2 indices on a 3-dimensional vector space, the frame @@ -288,9 +288,9 @@ class Components(SageObject): (0, 0, 1) ] - Actually, the frame can be any object that has some length, i.e. on which + Actually, the frame can be any object that has some length, i.e. on which the function :func:`len()` can be called:: - + sage: basis1 = V.gens() ; basis1 ((1, 0, 0), (0, 1, 0), (0, 0, 1)) sage: c1 = Components(QQ, basis1, 2) ; c1 @@ -301,12 +301,12 @@ class Components(SageObject): By default, the indices range from `0` to `n-1`, where `n` is the length of the frame. This can be changed via the argument ``start_index``:: - + sage: c1 = Components(QQ, basis, 2, start_index=1) sage: c1[0,1] Traceback (most recent call last): ... - IndexError: Index out of range: 0 not in [1,3] + IndexError: index out of range: 0 not in [1, 3] sage: c[0,1] # for c, the index 0 is OK 0 sage: c[0,1] = -3 @@ -315,37 +315,37 @@ class Components(SageObject): -3 If some formatter function or unbound method is provided via the argument - ``output_formatter``, it is used to change the ouput of the access + ``output_formatter``, it is used to change the ouput of the access operator ``[...]``:: - + sage: a = Components(QQ, basis, 2, output_formatter=Rational.numerical_approx) sage: a[1,2] = 1/3 sage: a[1,2] 0.333333333333333 - - The format can be passed to the formatter as the last argument of the + + The format can be passed to the formatter as the last argument of the access operator ``[...]``:: - + sage: a[1,2,10] # here the format is 10, for 10 bits of precision 0.33 - sage: a[1,2,100] + sage: a[1,2,100] 0.33333333333333333333333333333 - - The raw (unformatted) components are then accessed by the double bracket + + The raw (unformatted) components are then accessed by the double bracket operator:: - + sage: a[[1,2]] 1/3 - + For sets of components declared without any output formatter, there is no difference between ``[...]`` and ``[[...]]``:: - + sage: c[1,2] = 1/3 sage: c[1,2], c[[1,2]] (1/3, 1/3) The formatter is also used for the complete list of components:: - + sage: a[:] [0.000000000000000 0.000000000000000 0.000000000000000] [0.000000000000000 0.000000000000000 0.333333333333333] @@ -354,11 +354,11 @@ class Components(SageObject): [0.00 0.00 0.00] [0.00 0.00 0.33] [0.00 0.00 0.00] - + The complete list of components in raw form can be recovered by the double - bracket operator, replacing ``:`` by ``slice(None)`` (since ``a[[:]]`` + bracket operator, replacing ``:`` by ``slice(None)`` (since ``a[[:]]`` generates a Python syntax error):: - + sage: a[[slice(None)]] [ 0 0 0] [ 0 0 1/3] @@ -366,15 +366,15 @@ class Components(SageObject): Another example of formatter: the Python built-in function :func:`str` to generate string outputs:: - + sage: b = Components(QQ, V.basis(), 1, output_formatter=str) sage: b[:] = (1, 0, -4) sage: b[:] ['1', '0', '-4'] - + For such a formatter, 2-indices components are no longer displayed as a matrix:: - + sage: b = Components(QQ, basis, 2, output_formatter=str) sage: b[0,1] = 1/3 sage: b[:] @@ -386,7 +386,7 @@ class Components(SageObject): [ 0 1/3 0] [ 0 0 0] [ 0 0 0] - + Internally, the components are stored as a dictionary (:attr:`_comp`) whose keys are the indices; only the non-zero components are stored:: @@ -400,12 +400,12 @@ class Components(SageObject): sage: v[:] = (-1, 0, 3) sage: v._comp # random output order of the component dictionary {(0,): -1, (2,): 3} - - + + ARITHMETIC EXAMPLES: Unary plus operator:: - + sage: a = Components(QQ, basis, 1) sage: a[:] = (-1, 0, 3) sage: s = +a ; s[:] @@ -414,12 +414,12 @@ class Components(SageObject): True Unary minus operator:: - + sage: s = -a ; s[:] [1, 0, -3] - + Addition:: - + sage: b = Components(QQ, basis, 1) sage: b[:] = (2, 1, 4) sage: s = a + b ; s[:] @@ -430,28 +430,28 @@ class Components(SageObject): True Subtraction:: - + sage: s = a - b ; s[:] [-3, -1, -1] sage: s + b == a True sage: a - b == - (b - a) True - + Multiplication by a scalar:: - + sage: s = 2*a ; s[:] [-2, 0, 6] Division by a scalar:: - + sage: s = a/2 ; s[:] [-1/2, 0, 3/2] sage: 2*(a/2) == a True Tensor product (by means of the operator ``*``):: - + sage: c = a*b ; c 2-indices components w.r.t. [ (1, 0, 0), @@ -478,17 +478,17 @@ class Components(SageObject): True """ - def __init__(self, ring, frame, nb_indices, start_index=0, + def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): r""" TEST:: - + sage: from sage.tensor.modules.comp import Components sage: Components(ZZ, [1,2,3], 2) 2-indices components w.r.t. [1, 2, 3] """ - # For efficiency, no test is performed regarding the type and range of + # For efficiency, no test is performed regarding the type and range of # the arguments: self._ring = ring self._frame = frame @@ -496,15 +496,15 @@ def __init__(self, ring, frame, nb_indices, start_index=0, self._dim = len(frame) self._sindex = start_index self._output_formatter = output_formatter - self._comp = {} # the dictionary of components, with the index tuples + self._comp = {} # the dictionary of components, with the index tuples # as keys - + def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c._repr_() @@ -518,32 +518,32 @@ def _repr_(self): description += "-indices" description += " components w.r.t. " + str(self._frame) return description - + def _new_instance(self): r""" - Creates a :class:`Components` instance of the same number of indices - and w.r.t. the same frame. + Creates a :class:`Components` instance of the same number of indices + and w.r.t. the same frame. - This method must be redefined by derived classes of + This method must be redefined by derived classes of :class:`Components`. - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c._new_instance() 2-indices components w.r.t. [1, 2, 3] """ - return Components(self._ring, self._frame, self._nid, self._sindex, + return Components(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) def copy(self): r""" - Returns an exact copy of ``self``. - + Return an exact copy of ``self``. + EXAMPLES: - + Copy of a set of components with a single index:: sage: from sage.tensor.modules.comp import Components @@ -575,12 +575,12 @@ def copy(self): def _del_zeros(self): r""" Deletes all the zeros in the dictionary :attr:`_comp` - + NB: The use case of this method must be rare because zeros are not stored in :attr:`_comp`. EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c._comp = {(0,1): 3, (0,2): 0, (1,2): -5, (2,2): 0} # enforcing zero storage @@ -596,23 +596,23 @@ def _del_zeros(self): if value == 0: zeros.append(ind) for ind in zeros: - del self._comp[ind] + del self._comp[ind] def _check_indices(self, indices): r""" Check the validity of a list of indices and returns a tuple from it - + INPUT: - + - ``indices`` -- list of indices (possibly a single integer if self is a 1-index object) - + OUTPUT: - + - a tuple containing valid indices - + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c._check_indices((0,1)) @@ -624,33 +624,31 @@ def _check_indices(self, indices): sage: c._check_indices([2,3]) Traceback (most recent call last): ... - IndexError: Index out of range: 3 not in [0,2] + IndexError: index out of range: 3 not in [0, 2] sage: c._check_indices(1) Traceback (most recent call last): ... - TypeError: Wrong number of indices: 2 expected, while 1 are provided. + ValueError: wrong number of indices: 2 expected, while 1 are provided sage: c._check_indices([1,2,3]) Traceback (most recent call last): ... - TypeError: Wrong number of indices: 2 expected, while 3 are provided. - + ValueError: wrong number of indices: 2 expected, while 3 are provided + """ if isinstance(indices, (int, Integer)): ind = (indices,) else: ind = tuple(indices) if len(ind) != self._nid: - raise TypeError("Wrong number of indices: " + str(self._nid) + - " expected, while " + str(len(ind)) + - " are provided.") + raise ValueError(("wrong number of indices: {} expected," + " while {} are provided").format(self._nid, len(ind))) si = self._sindex imax = self._dim - 1 + si for k in range(self._nid): i = ind[k] - if i < si or i > imax: - raise IndexError("Index out of range: " - + str(i) + " not in [" + str(si) + "," - + str(imax) + "]") + if i < si or i > imax: + raise IndexError("index out of range: " + + "{} not in [{}, {}]".format(i, si, imax)) return ind def __getitem__(self, args): @@ -658,19 +656,19 @@ def __getitem__(self, args): Returns the component corresponding to the given indices. INPUT: - + - ``args`` -- list of indices (possibly a single integer if self is a 1-index object) or the character ``:`` for the full list - of components. + of components OUTPUT: - + - the component corresponding to ``args`` or, if ``args`` = ``:``, - the full list of components, in the form ``T[i][j]...`` for the components - `T_{ij...}` (for a 2-indices object, a matrix is returned). - + the full list of components, in the form ``T[i][j]...`` for the + components `T_{ij...}` (for a 2-indices object, a matrix is returned) + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c[1,2] # unset components are zero @@ -692,7 +690,7 @@ def __getitem__(self, args): [ 0 0 0] [ 0 0 -4] [ 0 0 0] - + """ no_format = self._output_formatter is None format_type = None # default value, possibly redefined below @@ -719,7 +717,7 @@ def __getitem__(self, args): indices = args[:-1] if isinstance(indices, slice): return self._get_list(indices, no_format, format_type) - else: + else: ind = self._check_indices(indices) if ind in self._comp: if no_format: @@ -727,33 +725,33 @@ def __getitem__(self, args): elif format_type is None: return self._output_formatter(self._comp[ind]) else: - return self._output_formatter(self._comp[ind], format_type) + return self._output_formatter(self._comp[ind], format_type) else: # if the value is not stored in self._comp, it is zero: if no_format: return self._ring.zero_element() elif format_type is None: - return self._output_formatter(self._ring.zero_element()) + return self._output_formatter(self._ring.zero_element()) else: - return self._output_formatter(self._ring.zero_element(), - format_type) + return self._output_formatter(self._ring.zero_element(), + format_type) def _get_list(self, ind_slice, no_format=True, format_type=None): r""" Return the list of components. - + INPUT: - + - ``ind_slice`` -- a slice object - + OUTPUT: - - - the full list of components if ``ind_slice`` == ``[:]``, or a slice - of it if ``ind_slice`` == ``[a:b]`` (1-D case), in the form - ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices + + - the full list of components if ``ind_slice = [:]``, or a slice + of it if ``ind_slice = [a:b]`` (1-D case), in the form + ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices object, a matrix is returned). EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c[0,1], c[1,2] = 5, -4 @@ -777,27 +775,25 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): si = self._sindex nsi = si + self._dim if self._nid == 1: - if ind_slice.start is None: + if ind_slice.start is None: start = si else: start = ind_slice.start - if ind_slice.stop is None: + if ind_slice.stop is None: stop = nsi else: stop = ind_slice.stop if ind_slice.step is not None: - raise NotImplementedError("Function [start:stop:step] " + - "not implemented.") + raise NotImplementedError("function [start:stop:step] not implemented") if no_format: return [self[[i]] for i in range(start, stop)] else: return [self[i, format_type] for i in range(start, stop)] if ind_slice.start is not None or ind_slice.stop is not None: - raise NotImplementedError("Function [start:stop] not " + - "implemented for components with " + str(self._nid) + - " indices.") + raise NotImplementedError("function [start:stop] not " + + "implemented for components with {} indices".format(self._nid)) resu = [self._gen_list([i], no_format, format_type) - for i in range(si, nsi)] + for i in range(si, nsi)] if self._nid == 2: try: for i in range(self._dim): @@ -812,10 +808,10 @@ def _get_list(self, ind_slice, no_format=True, format_type=None): def _gen_list(self, ind, no_format=True, format_type=None): r""" - Recursive function to generate the list of values - + Recursive function to generate the list of values. + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c[0,1], c[1,2] = 5, -4 @@ -840,23 +836,23 @@ def _gen_list(self, ind, no_format=True, format_type=None): else: si = self._sindex nsi = si + self._dim - return [self._gen_list(ind + [i], no_format, format_type) - for i in range(si, nsi)] + return [self._gen_list(ind + [i], no_format, format_type) + for i in range(si, nsi)] def __setitem__(self, args, value): r""" Sets the component corresponding to the given indices. INPUT: - + - ``args`` -- list of indices (possibly a single integer if - self is a 1-index object) ; if [:] is provided, all the components - are set. - - ``value`` -- the value to be set or a list of values if ``args`` - == ``[:]`` (slice(None)) - + self is a 1-index object); if ``[:]`` is provided, all the + components are set + - ``value`` -- the value to be set or a list of values if + ``args = [:]`` (``slice(None)``) + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c.__setitem__((0,1), -4) @@ -911,8 +907,8 @@ def __setitem__(self, args, value): self._comp[ind] = self._ring(value) else: self._comp[ind] = self._ring({format_type: value}) - # NB: the writing - # self._comp[ind] = self._ring(value, format_type) + # NB: the writing + # self._comp[ind] = self._ring(value, format_type) # is not allowed when ring is an algebra and value some # element of the algebra's base ring, cf. the discussion at # http://trac.sagemath.org/ticket/16054 @@ -920,18 +916,18 @@ def __setitem__(self, args, value): def _set_list(self, ind_slice, format_type, values): r""" Set the components from a list. - + INPUT: - + - ``ind_slice`` -- a slice object - ``format_type`` -- format possibly used to construct a ring element - - ``values`` -- list of values for the components : the full list if - ``ind_slice`` == ``[:]``, in the form ``T[i][j]...`` for the - component `T_{ij...}`. In the 1-D case, ``ind_slice`` can be + - ``values`` -- list of values for the components : the full list if + ``ind_slice = [:]``, in the form ``T[i][j]...`` for the + component `T_{ij...}`; in the 1-D case, ``ind_slice`` can be a slice of the full list, in the form ``[a:b]`` - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c._set_list(slice(None), None, [[0, 1, 2], [3, 4, 5], [6, 7, 8]]) @@ -944,30 +940,28 @@ def _set_list(self, ind_slice, format_type, values): si = self._sindex nsi = si + self._dim if self._nid == 1: - if ind_slice.start is None: + if ind_slice.start is None: start = si else: start = ind_slice.start - if ind_slice.stop is None: + if ind_slice.stop is None: stop = nsi else: stop = ind_slice.stop if ind_slice.step is not None: - raise NotImplementedError("Function [start:stop:step] " + - "not implemented.") + raise NotImplementedError("function [start:stop:step] not implemented") for i in range(start, stop): self[i, format_type] = values[i-start] else: if ind_slice.start is not None or ind_slice.stop is not None: - raise NotImplementedError("Function [start:stop] not " + - "implemented for components with " + str(self._nid) + - " indices.") + raise NotImplementedError("function [start:stop] not " + + "implemented for components with {} indices".format(self._nid)) for i in range(si, nsi): self._set_value_list([i], format_type, values[i-si]) def _set_value_list(self, ind, format_type, val): r""" - Recursive function to set a list of values to self + Recursive function to set a list of values to ``self``. EXAMPLE:: @@ -988,7 +982,7 @@ def _set_value_list(self, ind, format_type, val): [-1 -2 -3] [ 4 5 6] [ 7 -8 9] - + """ if len(ind) == self._nid: if format_type is not None: @@ -1002,27 +996,27 @@ def _set_value_list(self, ind, format_type, val): def swap_adjacent_indices(self, pos1, pos2, pos3): r""" - Swap two adjacent sets of indices. - - This method is essentially required to reorder the covariant and - contravariant indices in the computation of a tensor product. - + Swap two adjacent sets of indices. + + This method is essentially required to reorder the covariant and + contravariant indices in the computation of a tensor product. + INPUT: - - - ``pos1`` -- position of the first index of set 1 (with the convention - position=0 for the first slot) - - ``pos2`` -- position of the first index of set 2 = 1 + position of - the last index of set 1 (since the two sets are adjacent) - - ``pos3`` -- 1 + position of the last index of set 2 - + + - ``pos1`` -- position of the first index of set 1 (with the convention + ``position=0`` for the first slot) + - ``pos2`` -- position of the first index of set 2 equals 1 plus the + position of the last index of set 1 (since the two sets are adjacent) + - ``pos3`` -- 1 plus position of the last index of set 2 + OUTPUT: - - - Components with index set 1 permuted with index set 2. - + + - Components with index set 1 permuted with index set 2. + EXAMPLES: - + Swap of the two indices of a 2-indices set of components:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 2) @@ -1036,7 +1030,7 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): ) Swap of two pairs of indices on a 4-indices set of components:: - + sage: d = c*c1 ; d 4-indices components w.r.t. [ (1, 0, 0), @@ -1057,21 +1051,21 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): result = self._new_instance() for ind, val in self._comp.iteritems(): new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] - result._comp[new_ind] = val - # the above writing is more efficient than result[new_ind] = val - # it does not work for the derived class CompWithSym, but for the + result._comp[new_ind] = val + # the above writing is more efficient than result[new_ind] = val + # it does not work for the derived class CompWithSym, but for the # latter, the function CompWithSym.swap_adjacent_indices will be - # called and not the present function. + # called and not the present function. return result - + def is_zero(self): - r""" - Return True if all the components are zero and False otherwise. + r""" + Return ``True`` if all the components are zero and ``False`` otherwise. EXAMPLES: - + A just-created set of components is initialized to zero:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ,3) sage: c = Components(QQ, V.basis(), 1) @@ -1089,46 +1083,46 @@ def is_zero(self): True It is equivalent to use the operator == to compare to zero:: - + sage: c == 0 True sage: c != 0 False Comparing to a nonzero number is meaningless:: - + sage: c == 1 Traceback (most recent call last): ... - TypeError: Cannot compare a set of components to a number. + TypeError: cannot compare a set of components to a number """ - if self._comp == {}: - return True - else: - #!# What follows could be skipped since _comp should not contain - # any zero value - # In other words, the full method should be - # return self.comp == {} - for val in self._comp.itervalues(): - if val != 0: - return False + if not self._comp: return True + #!# What follows could be skipped since _comp should not contain + # any zero value + # In other words, the full method should be + # return self.comp == {} + for val in self._comp.itervalues(): + if val != 0: + return False + return True + def __eq__(self, other): r""" - Comparison (equality) operator. - + Comparison (equality) operator. + INPUT: - + - ``other`` -- a set of components or 0 - + OUTPUT: - - - True if ``self`` is equal to ``other``, or False otherwise - + + - ``True`` if ``self`` is equal to ``other``, or ``False`` otherwise + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 2) sage: c.__eq__(0) # uninitialized components are zero @@ -1152,11 +1146,10 @@ def __eq__(self, other): if other == 0: return self.is_zero() else: - raise TypeError("Cannot compare a set of components to a " + - "number.") + raise TypeError("cannot compare a set of components to a number") else: # other is another Components if not isinstance(other, Components): - raise TypeError("An instance of Components is expected.") + raise TypeError("an instance of Components is expected") if other._frame != self._frame: return False if other._nid != self._nid: @@ -1169,18 +1162,18 @@ def __eq__(self, other): def __ne__(self, other): r""" - Non-equality operator. - + Non-equality operator. + INPUT: - + - ``other`` -- a set of components or 0 - + OUTPUT: - + - True if ``self`` is different from ``other``, or False otherwise - + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 1) sage: c.__ne__(0) # uninitialized components are zero @@ -1196,17 +1189,17 @@ def __ne__(self, other): """ return not self.__eq__(other) - + def __pos__(self): r""" - Unary plus operator. - + Unary plus operator. + OUTPUT: - + - an exact copy of ``self`` - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 1) sage: c[:] = 5, 0, -4 @@ -1224,14 +1217,14 @@ def __pos__(self): def __neg__(self): r""" - Unary minus operator. - + Unary minus operator. + OUTPUT: - + - the opposite of the components represented by ``self`` - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: c = Components(ZZ, [1,2,3], 1) sage: c[:] = 5, 0, -4 @@ -1250,19 +1243,19 @@ def __neg__(self): def __add__(self, other): r""" - Component addition. - + Component addition. + INPUT: - + - ``other`` -- components of the same number of indices and defined on the same frame as ``self`` - + OUTPUT: - + - components resulting from the addition of ``self`` and ``other`` - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(ZZ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1279,19 +1272,19 @@ def __add__(self, other): if other == 0: return +self if not isinstance(other, Components): - raise TypeError("The second argument for the addition must be " + - "an instance of Components.") + raise TypeError("the second argument for the addition must be " + + "an instance of Components") if isinstance(other, CompWithSym): return other + self # to deal properly with symmetries if other._frame != self._frame: - raise TypeError("The two sets of components are not defined on " + - "the same frame.") + raise ValueError("the two sets of components are not defined on " + + "the same frame") if other._nid != self._nid: - raise TypeError("The two sets of components do not have the " + - "same number of indices.") + raise ValueError("the two sets of components do not have the " + + "same number of indices") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + - "same starting index.") + raise ValueError("the two sets of components do not have the " + + "same starting index") result = self.copy() for ind, val in other._comp.iteritems(): result[[ind]] += val @@ -1300,9 +1293,9 @@ def __add__(self, other): def __radd__(self, other): r""" Reflected addition (addition on the right to `other``) - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(ZZ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1318,25 +1311,25 @@ def __radd__(self, other): 1-index components w.r.t. [1, 2, 3] sage: s == a True - + """ return self.__add__(other) def __sub__(self, other): r""" - Component subtraction. - + Component subtraction. + INPUT: - + - ``other`` -- components, of the same type as ``self`` - + OUTPUT: - + - components resulting from the subtraction of ``other`` from ``self`` - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(ZZ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1352,15 +1345,15 @@ def __sub__(self, other): """ if other == 0: return +self - return self.__add__(-other) #!# correct, deals properly with + return self.__add__(-other) #!# correct, deals properly with # symmetries, but is probably not optimal def __rsub__(self, other): r""" - Reflected subtraction (subtraction from ``other``). - + Reflected subtraction (subtraction from ``other``). + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(ZZ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1370,7 +1363,7 @@ def __rsub__(self, other): 1-index components w.r.t. [1, 2, 3] sage: s[:] [3, 5, 9] - sage: s == b - a + sage: s == b - a True sage: s = 0 - a ; s 1-index components w.r.t. [1, 2, 3] @@ -1378,25 +1371,25 @@ def __rsub__(self, other): [-1, 0, 3] sage: s == -a True - + """ return (-self).__add__(other) def __mul__(self, other): r""" - Component tensor product. - + Component tensor product. + INPUT: - + - ``other`` -- components, on the same frame as ``self`` - - OUTPUT: - + + OUTPUT: + - the tensor product of ``self`` by ``other`` - + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(ZZ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1413,14 +1406,14 @@ def __mul__(self, other): """ if not isinstance(other, Components): - raise TypeError("The second argument for the tensor product " + - "must be an instance of Components.") + raise TypeError("the second argument for the tensor product " + + "must be an instance of Components") if other._frame != self._frame: - raise TypeError("The two sets of components are not defined on " + - "the same frame.") + raise ValueError("the two sets of components are not defined on " + + "the same frame") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + - "same starting index.") + raise ValueError("the two sets of components do not have the " + + "same starting index") if isinstance(other, CompWithSym): sym = [] if other._sym != []: @@ -1432,16 +1425,16 @@ def __mul__(self, other): for s in other._antisym: ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) - result = CompWithSym(self._ring, self._frame, self._nid + other._nid, - self._sindex, self._output_formatter, sym, + result = CompWithSym(self._ring, self._frame, self._nid + other._nid, + self._sindex, self._output_formatter, sym, antisym) elif self._nid == 1 and other._nid == 1: - if self is other: # == would be dangerous here + if self is other: # == would be dangerous here # the result is symmetric: - result = CompFullySym(self._ring, self._frame, 2, self._sindex, + result = CompFullySym(self._ring, self._frame, 2, self._sindex, self._output_formatter) else: - result = Components(self._ring, self._frame, 2, self._sindex, + result = Components(self._ring, self._frame, 2, self._sindex, self._output_formatter) else: result = Components(self._ring, self._frame, self._nid + other._nid, @@ -1450,14 +1443,14 @@ def __mul__(self, other): for ind_o, val_o in other._comp.iteritems(): result._comp[ind_s + ind_o] = val_s * val_o return result - + def __rmul__(self, other): r""" - Reflected multiplication (multiplication on the left by ``other``). - + Reflected multiplication (multiplication on the left by ``other``). + EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(ZZ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1472,8 +1465,8 @@ def __rmul__(self, other): """ if isinstance(other, Components): - raise NotImplementedError("Left tensor product not implemented.") - # Left multiplication by a "scalar": + raise NotImplementedError("left tensor product not implemented") + # Left multiplication by a "scalar": result = self._new_instance() if other == 0: return result # because a just created Components is zero @@ -1484,10 +1477,10 @@ def __rmul__(self, other): def __div__(self, other): r""" - Division (by a scalar). + Division (by a scalar). EXAMPLES:: - + sage: from sage.tensor.modules.comp import Components sage: a = Components(QQ, [1,2,3], 1) sage: a[:] = 1, 0, -3 @@ -1502,31 +1495,31 @@ def __div__(self, other): """ if isinstance(other, Components): - raise NotImplementedError("Division by an object of type " + - "Components not implemented.") + raise NotImplementedError("division by an object of type " + + "Components not implemented") result = self._new_instance() for ind, val in self._comp.iteritems(): result._comp[ind] = val / other return result def trace(self, pos1, pos2): - r""" + r""" Index contraction. - + INPUT: - - - ``pos1`` -- position of the first index for the contraction (with the + + - ``pos1`` -- position of the first index for the contraction (with the convention position=0 for the first slot) - ``pos2`` -- position of the second index for the contraction - + OUTPUT: - + - set of components resulting from the (pos1, pos2) contraction - + EXAMPLES: - + Self-contraction of a set of components with 2 indices:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 2) @@ -1537,7 +1530,7 @@ def trace(self, pos1, pos2): 15 Three self-contractions of a set of components with 3 indices:: - + sage: v = Components(QQ, V.basis(), 1) sage: v[:] = (-1,2,3) sage: a = c*v ; a @@ -1567,25 +1560,25 @@ def trace(self, pos1, pos2): """ if self._nid < 2: - raise TypeError("Contraction can be perfomed only on " + - "components with at least 2 indices.") + raise ValueError("contraction can be perfomed only on " + + "components with at least 2 indices") if pos1 < 0 or pos1 > self._nid - 1: - raise IndexError("pos1 out of range.") + raise IndexError("pos1 out of range") if pos2 < 0 or pos2 > self._nid - 1: - raise IndexError("pos2 out of range.") + raise IndexError("pos2 out of range") if pos1 == pos2: - raise IndexError("The two positions must differ for the " + - "contraction to be meaningful.") + raise IndexError("the two positions must differ for the " + + "contraction to be meaningful") si = self._sindex nsi = si + self._dim if self._nid == 2: - res = 0 + res = 0 for i in range(si, nsi): res += self[[i,i]] return res else: # More than 2 indices - result = Components(self._ring, self._frame, self._nid - 2, + result = Components(self._ring, self._frame, self._nid - 2, self._sindex, self._output_formatter) if pos1 > pos2: pos1, pos2 = (pos2, pos1) @@ -1597,15 +1590,15 @@ def trace(self, pos1, pos2): return result def contract(self, *args): - r""" + r""" Contraction on one or many indices with another instance of - :class:`Components`. - + :class:`Components`. + INPUT: - + - ``pos1`` -- positions of the indices in ``self`` involved in the contraction; ``pos1`` must be a sequence of integers, with 0 standing - for the first index position, 1 for the second one, etc. If ``pos1`` + for the first index position, 1 for the second one, etc. If ``pos1`` is not provided, a single contraction on the last index position of ``self`` is assumed - ``other`` -- the set of components to contract with @@ -1613,11 +1606,11 @@ def contract(self, *args): contraction, with the same conventions as for ``pos1``. If ``pos2`` is not provided, a single contraction on the first index position of ``other`` is assumed - + OUTPUT: - + - set of components resulting from the contraction - + EXAMPLES: Contraction of a 1-index set of components with a 2-index one:: @@ -1642,16 +1635,16 @@ def contract(self, *args): [12, 24, 36] sage: [sum(a[j]*b[i,j] for j in range(3)) for i in range(3)] # check [12, 24, 36] - + Contraction on 2 indices:: - - sage: c = a*b ; c + + sage: c = a*b ; c 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) ] - sage: s = c.contract(1,2, b, 0,1) ; s + sage: s = c.contract(1,2, b, 0,1) ; s 1-index components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -1659,13 +1652,14 @@ def contract(self, *args): ] sage: s[:] [-285, 570, 855] - sage: [sum(sum(c[i,j,k]*b[j,k] for k in range(3)) for j in range(3)) for i in range(3)] # check - [-285, 570, 855] + sage: [sum(sum(c[i,j,k]*b[j,k] for k in range(3)) # check + ....: for j in range(3)) for i in range(3)] + [-285, 570, 855] Consistency check with :meth:`trace`:: sage: b = a*a ; b # the tensor product of a with itself - fully symmetric 2-indices components w.r.t. [ + Fully symmetric 2-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -1690,8 +1684,8 @@ def contract(self, *args): it = i break else: - raise TypeError("A set of components must be provided in the " + - "argument list.") + raise ValueError("a set of components must be provided in the " + + "argument list") if it == 0: pos1 = (self._nid - 1,) else: @@ -1707,26 +1701,26 @@ def contract(self, *args): raise TypeError("The two sets of components are not defined on " + "the same frame.") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + + raise TypeError("The two sets of components do not have the " + "same starting index.") contractions = [(pos1[i], pos2[i]) for i in range(ncontr)] res_nid = self._nid + other._nid - 2*ncontr - # + # # Special case of a scalar result # if res_nid == 0: - # To generate the indices tuples (of size ncontr) involved in the + # To generate the indices tuples (of size ncontr) involved in the # the contraction, we create an empty instance of Components with # ncontr indices and call the method index_generator() on it: - comp_for_contr = Components(self._ring, self._frame, ncontr, - start_index=self._sindex) + comp_for_contr = Components(self._ring, self._frame, ncontr, + start_index=self._sindex) res = 0 for ind in comp_for_contr.index_generator(): res += self[[ind]] * other[[ind]] return res # # Positions of self and other indices in the result - # (None = the position is involved in a contraction and therefore + # (None = the position is involved in a contraction and therefore # does not appear in the final result) # pos_s = [None for i in range(self._nid)] # initialization @@ -1804,36 +1798,36 @@ def contract(self, *args): max_len_antisym = max(max_len_antisym, len(r_isym)) # print "res_sym: ", res_sym # print "res_antisym: ", res_antisym - # print "max_len_sym: ", max_len_sym - # print "max_len_antisym: ", max_len_antisym + # print "max_len_sym: ", max_len_sym + # print "max_len_antisym: ", max_len_antisym # # Construction of the result object in view of the remaining symmetries: # if max_len_sym == 0 and max_len_antisym == 0: - res = Components(self._ring, self._frame, res_nid, - start_index=self._sindex, + res = Components(self._ring, self._frame, res_nid, + start_index=self._sindex, output_formatter=self._output_formatter) elif max_len_sym == res_nid: - res = CompFullySym(self._ring, self._frame, res_nid, - start_index=self._sindex, + res = CompFullySym(self._ring, self._frame, res_nid, + start_index=self._sindex, output_formatter=self._output_formatter) elif max_len_antisym == res_nid: - res = CompFullyAntiSym(self._ring, self._frame, res_nid, - start_index=self._sindex, + res = CompFullyAntiSym(self._ring, self._frame, res_nid, + start_index=self._sindex, output_formatter=self._output_formatter) else: - res = CompWithSym(self._ring, self._frame, res_nid, - start_index=self._sindex, - output_formatter=self._output_formatter, + res = CompWithSym(self._ring, self._frame, res_nid, + start_index=self._sindex, + output_formatter=self._output_formatter, sym=res_sym, antisym=res_antisym) # # Performing the contraction # - # To generate the indices tuples (of size ncontr) involved in the + # To generate the indices tuples (of size ncontr) involved in the # the contraction, we create an empty instance of Components with # ncontr indices and call the method index_generator() on it: - comp_for_contr = Components(self._ring, self._frame, ncontr, - start_index=self._sindex) + comp_for_contr = Components(self._ring, self._frame, ncontr, + start_index=self._sindex) shift_o = self._nid - ncontr for ind in res.non_redundant_index_generator(): ind_s = [None for i in range(self._nid)] # initialization @@ -1853,20 +1847,20 @@ def contract(self, *args): sm += self[[ind_s]] * other[[ind_o]] res[[ind]] = sm return res - + def index_generator(self): r""" - Generator of indices. - + Generator of indices. + OUTPUT: - + - an iterable index EXAMPLES: - + Indices on a 3-dimensional vector space:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ,3) sage: c = Components(QQ, V.basis(), 1) @@ -1898,24 +1892,24 @@ def index_generator(self): else: ind[pos] = si ret = 1 - + def non_redundant_index_generator(self): r""" - Generator of non redundant indices. - - In the absence of declared symmetries, all possible indices are - generated. So this method is equivalent to :meth:`index_generator`. - Only versions for derived classes with symmetries or antisymmetries - are not trivial. - + Generator of non redundant indices. + + In the absence of declared symmetries, all possible indices are + generated. So this method is equivalent to :meth:`index_generator`. + Only versions for derived classes with symmetries or antisymmetries + are not trivial. + OUTPUT: - + - an iterable index EXAMPLES: - + Indices on a 3-dimensional vector space:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ,3) sage: c = Components(QQ, V.basis(), 2) @@ -1932,29 +1926,29 @@ def non_redundant_index_generator(self): def symmetrize(self, *pos): r""" - Symmetrization over the given index positions - + Symmetrization over the given index positions. + INPUT: - - - ``pos`` -- list of index positions involved in the - symmetrization (with the convention position=0 for the first slot); + + - ``pos`` -- list of index positions involved in the + symmetrization (with the convention position=0 for the first slot); if none, the symmetrization is performed over all the indices - + OUTPUT: - - - an instance of :class:`CompWithSym` describing the symmetrized - components. - + + - an instance of :class:`CompWithSym` describing the symmetrized + components + EXAMPLES: - + Symmetrization of 2-indices components:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 2) sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: s = c.symmetrize() ; s - fully symmetric 2-indices components w.r.t. [ + Fully symmetric 2-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -1969,11 +1963,11 @@ def symmetrize(self, *pos): True Full symmetrization of 3-indices components:: - + sage: c = Components(QQ, V.basis(), 3) sage: c[:] = [[[1,2,3], [4,5,6], [7,8,9]], [[10,11,12], [13,14,15], [16,17,18]], [[19,20,21], [22,23,24], [25,26,27]]] sage: s = c.symmetrize() ; s - fully symmetric 3-indices components w.r.t. [ + Fully symmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -1985,18 +1979,14 @@ def symmetrize(self, *pos): [[[1, 16/3, 29/3], [16/3, 29/3, 14], [29/3, 14, 55/3]], [[16/3, 29/3, 14], [29/3, 14, 55/3], [14, 55/3, 68/3]], [[29/3, 14, 55/3], [14, 55/3, 68/3], [55/3, 68/3, 27]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]+c[i,k,j]+c[j,k,i]+c[j,i,k]+c[k,i,j]+c[k,j,i])/6, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]+c[i,k,j]+c[j,k,i]+c[j,i,k]+c[k,i,j]+c[k,j,i])/6 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: c.symmetrize() == c.symmetrize(0,1,2) True Partial symmetrization of 3-indices components:: - + sage: s = c.symmetrize(0,1) ; s # symmetrization on the first two indices 3-indices components w.r.t. [ (1, 0, 0), @@ -2010,13 +2000,9 @@ def symmetrize(self, *pos): [[[1, 2, 3], [7, 8, 9], [13, 14, 15]], [[7, 8, 9], [13, 14, 15], [19, 20, 21]], [[13, 14, 15], [19, 20, 21], [25, 26, 27]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]+c[j,i,k])/2, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]+c[j,i,k])/2 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s = c.symmetrize(1,2) ; s # symmetrization on the last two indices 3-indices components w.r.t. [ (1, 0, 0), @@ -2030,13 +2016,9 @@ def symmetrize(self, *pos): [[[1, 3, 5], [3, 5, 7], [5, 7, 9]], [[10, 12, 14], [12, 14, 16], [14, 16, 18]], [[19, 21, 23], [21, 23, 25], [23, 25, 27]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]+c[i,k,j])/2, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]+c[i,k,j])/2 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s = c.symmetrize(0,2) ; s # symmetrization on the first and last indices 3-indices components w.r.t. [ (1, 0, 0), @@ -2050,13 +2032,9 @@ def symmetrize(self, *pos): [[[1, 6, 11], [4, 9, 14], [7, 12, 17]], [[6, 11, 16], [9, 14, 19], [12, 17, 22]], [[11, 16, 21], [14, 19, 24], [17, 22, 27]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]+c[k,j,i])/2, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]+c[k,j,i])/2 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup @@ -2064,16 +2042,16 @@ def symmetrize(self, *pos): pos = range(self._nid) else: if len(pos) < 2: - raise TypeError("At least two index positions must be given.") + raise ValueError("at least two index positions must be given") if len(pos) > self._nid: - raise TypeError("Number of index positions larger than the " \ - "total number of indices.") + raise ValueError("number of index positions larger than the " + "total number of indices") n_sym = len(pos) # number of indices involved in the symmetry if n_sym == self._nid: result = CompFullySym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) else: - result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, + result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter, sym=pos) sym_group = SymmetricGroup(n_sym) for ind in result.non_redundant_index_generator(): @@ -2088,32 +2066,31 @@ def symmetrize(self, *pos): result[[ind]] = sum / sym_group.order() return result - def antisymmetrize(self, *pos): r""" Antisymmetrization over the given index positions - + INPUT: - - - ``pos`` -- list of index positions involved in the antisymmetrization - (with the convention position=0 for the first slot); if none, the + + - ``pos`` -- list of index positions involved in the antisymmetrization + (with the convention position=0 for the first slot); if none, the antisymmetrization is performed over all the indices - + OUTPUT: - - - an instance of :class:`CompWithSym` describing the antisymmetrized - components. - + + - an instance of :class:`CompWithSym` describing the antisymmetrized + components. + EXAMPLES: - + Antisymmetrization of 2-indices components:: - + sage: from sage.tensor.modules.comp import Components sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 2) sage: c[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: s = c.antisymmetrize() ; s - fully antisymmetric 2-indices components w.r.t. [ + Fully antisymmetric 2-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -2126,13 +2103,13 @@ def antisymmetrize(self, *pos): ) sage: c.antisymmetrize() == c.antisymmetrize(0,1) True - + Full antisymmetrization of 3-indices components:: - + sage: c = Components(QQ, V.basis(), 3) sage: c[:] = [[[-1,-2,3], [4,-5,4], [-7,8,9]], [[10,10,12], [13,-14,15], [-16,17,19]], [[-19,20,21], [1,2,3], [-25,26,27]]] sage: s = c.antisymmetrize() ; s - fully antisymmetric 3-indices components w.r.t. [ + Fully antisymmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -2144,17 +2121,14 @@ def antisymmetrize(self, *pos): [[[0, 0, 0], [0, 0, -13/6], [0, 13/6, 0]], [[0, 0, 13/6], [0, 0, 0], [-13/6, 0, 0]], [[0, -13/6, 0], [13/6, 0, 0], [0, 0, 0]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6, - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: c.symmetrize() == c.symmetrize(0,1,2) True Partial antisymmetrization of 3-indices components:: - + sage: s = c.antisymmetrize(0,1) ; s # antisymmetrization on the first two indices 3-indices components w.r.t. [ (1, 0, 0), @@ -2168,13 +2142,9 @@ def antisymmetrize(self, *pos): [[[0, 0, 0], [-3, -15/2, -4], [6, -6, -6]], [[3, 15/2, 4], [0, 0, 0], [-17/2, 15/2, 8]], [[-6, 6, 6], [17/2, -15/2, -8], [0, 0, 0]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]-c[j,i,k])/2, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]-c[j,i,k])/2 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s = c.antisymmetrize(1,2) ; s # antisymmetrization on the last two indices 3-indices components w.r.t. [ (1, 0, 0), @@ -2188,13 +2158,9 @@ def antisymmetrize(self, *pos): [[[0, -3, 5], [3, 0, -2], [-5, 2, 0]], [[0, -3/2, 14], [3/2, 0, -1], [-14, 1, 0]], [[0, 19/2, 23], [-19/2, 0, -23/2], [-23, 23/2, 0]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j])/2, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == (c[i,j,k]-c[i,k,j])/2 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s = c.antisymmetrize(0,2) ; s # antisymmetrization on the first and last indices 3-indices components w.r.t. [ (1, 0, 0), @@ -2208,43 +2174,39 @@ def antisymmetrize(self, *pos): [[[0, -6, 11], [0, -9, 3/2], [0, 12, 17]], [[6, 0, -4], [9, 0, 13/2], [-12, 0, -7/2]], [[-11, 4, 0], [-3/2, -13/2, 0], [-17, 7/2, 0]]]) - sage: # Check of the result: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]-c[k,j,i])/2, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True - + sage: all(s[i,j,k] == (c[i,j,k]-c[k,j,i])/2 # Check of the result: + ....: for i in range(3) for j in range(3) for k in range(3)) + True + The order of index positions in the argument does not matter:: - + sage: c.antisymmetrize(1,0) == c.antisymmetrize(0,1) True sage: c.antisymmetrize(2,1) == c.antisymmetrize(1,2) True sage: c.antisymmetrize(2,0) == c.antisymmetrize(0,2) True - + """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup if not pos: pos = range(self._nid) else: if len(pos) < 2: - raise TypeError("At least two index positions must be given.") + raise ValueError("at least two index positions must be given") if len(pos) > self._nid: - raise TypeError("Number of index positions larger than the " \ - "total number of indices.") + raise ValueError("number of index positions larger than the " + "total number of indices") n_sym = len(pos) # number of indices involved in the antisymmetry if n_sym == self._nid: - result = CompFullyAntiSym(self._ring, self._frame, self._nid, + result = CompFullyAntiSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) else: - result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, + result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter, antisym=pos) sym_group = SymmetricGroup(n_sym) for ind in result.non_redundant_index_generator(): - sum = 0 + sum = 0 for perm in sym_group.list(): # action of the permutation on [0,1,...,n_sym-1]: perm_action = map(lambda x: x-1, perm.domain()) @@ -2258,83 +2220,83 @@ def antisymmetrize(self, *pos): result[[ind]] = sum / sym_group.order() return result - + #****************************************************************************** class CompWithSym(Components): r""" - Indexed set of ring elements forming some components with respect to a + Indexed set of ring elements forming some components with respect to a given "frame", with symmetries or antisymmetries regarding permutations - of the indices. - - The "frame" can be a basis of some vector space or a vector frame on some - manifold (i.e. a field of bases). - The stored quantities can be tensor components or non-tensorial quantities, - such as connection coefficients or structure coefficents. - + of the indices. + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). + The stored quantities can be tensor components or non-tensorial quantities, + such as connection coefficients or structure coefficents. + Subclasses of :class:`CompWithSym` are - + * :class:`CompFullySym` for fully symmetric components. * :class:`CompFullyAntiSym` for fully antisymmetric components. INPUT: - + - ``ring`` -- commutative ring in which each component takes its value - - ``frame`` -- frame with respect to which the components are defined; + - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size of a single index range - ``nb_indices`` -- number of indices labeling the components - - ``start_index`` -- (default: 0) first value of a single index; + - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey - ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound - method called to format the output of the component access + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take - 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and the second one, if any, some format specification. - - ``sym`` -- (default: None) a symmetry or a list of symmetries among the - indices: each symmetry is described by a tuple containing the positions - of the involved indices, with the convention position=0 for the first - slot. For instance: - - * sym=(0,1) for a symmetry between the 1st and 2nd indices - * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd + - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries among + the indices: each symmetry is described by a tuple containing the + positions of the involved indices, with the convention ``position=0`` + for the first slot; for instance: + + * ``sym = (0, 1)`` for a symmetry between the 1st and 2nd indices + * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd indices and a symmetry between the 2nd, 4th and 5th indices. - - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries - among the indices, with the same convention as for ``sym``. - + - ``antisym`` -- (default: ``None``) antisymmetry or list of antisymmetries + among the indices, with the same convention as for ``sym`` + EXAMPLES: Symmetric components with 2 indices:: - + sage: from sage.tensor.modules.comp import Components, CompWithSym sage: V = VectorSpace(QQ,3) - sage: c = CompWithSym(QQ, V.basis(), 2, sym=(0,1)) # for demonstration only: it is preferable to use CompFullySym in this case - sage: c[0,1] = 3 + sage: c = CompWithSym(QQ, V.basis(), 2, sym=(0,1)) # for demonstration only: it is preferable to use CompFullySym in this case + sage: c[0,1] = 3 sage: c[:] # note that c[1,0] has been set automatically [0 3 0] [3 0 0] [0 0 0] Antisymmetric components with 2 indices:: - - sage: c = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to use CompFullyAntiSym in this case + + sage: c = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to use CompFullyAntiSym in this case sage: c[0,1] = 3 sage: c[:] # note that c[1,0] has been set automatically [ 0 3 0] [-3 0 0] [ 0 0 0] - + Internally, only non-redundant components are stored:: - + sage: c._comp {(0, 1): 3} - - Components with 6 indices, symmetric among 3 indices (at position ((0,1,5)) - and antisymmetric among 2 indices (at position (2,4)):: - + + Components with 6 indices, symmetric among 3 indices (at position + `(0, 1, 5)`) and antisymmetric among 2 indices (at position `(2, 4)`):: + sage: c = CompWithSym(QQ, V.basis(), 6, sym=(0,1,5), antisym=(2,4)) sage: c[0,1,2,0,1,2] = 3 sage: c[1,0,2,0,1,2] # symmetry between indices in position 0 and 1 @@ -2348,7 +2310,7 @@ class CompWithSym(Components): Components with 4 indices, antisymmetric with respect to the first pair of indices as well as with the second pair of indices:: - + sage: c = CompWithSym(QQ, V.basis(), 4, antisym=[(0,1),(2,3)]) sage: c[0,1,0,1] = 3 sage: c[1,0,0,1] # antisymmetry on the first pair of indices @@ -2357,12 +2319,12 @@ class CompWithSym(Components): -3 sage: c[1,0,1,0] # consequence of the above 3 - - ARITHMETIC EXAMPLES: - Addition of a symmetric set of components with a non-symmetric one: the + .. RUBRIC:: ARITHMETIC EXAMPLES + + Addition of a symmetric set of components with a non-symmetric one: the symmetry is lost:: - + sage: V = VectorSpace(QQ, 3) sage: a = Components(QQ, V.basis(), 2) sage: a[:] = [[1,-2,3], [4,5,-6], [-7,8,9]] @@ -2386,7 +2348,7 @@ class CompWithSym(Components): True Addition of two symmetric set of components: the symmetry is preserved:: - + sage: c = CompWithSym(QQ, V.basis(), 2, sym=(0,1)) # for demonstration only: it is preferable to declare c = CompFullySym(QQ, V.basis(), 2) sage: c[0,0], c[0,1], c[0,2] = -4, 7, -8 sage: c[1,1], c[1,2] = 2, -4 @@ -2418,10 +2380,10 @@ class CompWithSym(Components): True sage: bn + cn == b + c True - - Addition of an antisymmetric set of components with a non-symmetric one: + + Addition of an antisymmetric set of components with a non-symmetric one: the antisymmetry is lost:: - + sage: d = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to declare d = CompFullyAntiSym(QQ, V.basis(), 2) sage: d[0,1], d[0,2], d[1,2] = 4, -1, 3 sage: s = a + d ; s @@ -2440,7 +2402,7 @@ class CompWithSym(Components): True Addition of two antisymmetric set of components: the antisymmetry is preserved:: - + sage: e = CompWithSym(QQ, V.basis(), 2, antisym=(0,1)) # for demonstration only: it is preferable to declare e = CompFullyAntiSym(QQ, V.basis(), 2) sage: e[0,1], e[0,2], e[1,2] = 2, 3, -1 sage: s = d + e ; s @@ -2459,44 +2421,44 @@ class CompWithSym(Components): True """ - def __init__(self, ring, frame, nb_indices, start_index=0, + def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None, sym=None, antisym=None): r""" TEST:: - + sage: from sage.tensor.modules.comp import CompWithSym - sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) - 4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) + sage: C = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + sage: TestSuite(C).run() """ - Components.__init__(self, ring, frame, nb_indices, start_index, + Components.__init__(self, ring, frame, nb_indices, start_index, output_formatter) self._sym = [] if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): + if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple -> 1-item list: sym = [tuple(sym)] for isym in sym: if len(isym) < 2: - raise IndexError("At least two index positions must be " + - "provided to define a symmetry.") + raise IndexError("at least two index positions must be " + + "provided to define a symmetry") for i in isym: if i<0 or i>self._nid-1: - raise IndexError("Invalid index position: " + str(i) + + raise IndexError("invalid index position: " + str(i) + " not in [0," + str(self._nid-1) + "]") - self._sym.append(tuple(isym)) + self._sym.append(tuple(isym)) self._antisym = [] if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): + if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple -> 1-item list: antisym = [tuple(antisym)] for isym in antisym: if len(isym) < 2: - raise IndexError("At least two index positions must be " + - "provided to define an antisymmetry.") + raise IndexError("at least two index positions must be " + + "provided to define an antisymmetry") for i in isym: if i<0 or i>self._nid-1: - raise IndexError("Invalid index position: " + str(i) + + raise IndexError("invalid index position: " + str(i) + " not in [0," + str(self._nid-1) + "]") self._antisym.append(tuple(isym)) # Final consistency check: @@ -2507,22 +2469,23 @@ def __init__(self, ring, frame, nb_indices, start_index=0, index_list += isym if len(index_list) != len(set(index_list)): # There is a repeated index position: - raise IndexError("Incompatible lists of symmetries: the same " + - "index position appears more then once.") + raise IndexError("incompatible lists of symmetries: the same " + + "index position appears more then once") def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym - sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) - sage: c._repr_() - '4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1)' - sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) - sage: c._repr_() - '4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3)' + sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) + 4-indices components w.r.t. [1, 2, 3], + with symmetry on the index positions (0, 1) + sage: CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) + 4-indices components w.r.t. [1, 2, 3], + with symmetry on the index positions (0, 1), + with antisymmetry on the index positions (2, 3) """ description = str(self._nid) @@ -2538,48 +2501,49 @@ def _repr_(self): description += ", with antisymmetry on the index positions " + \ str(tuple(isym)) return description - + def _new_instance(self): r""" - Creates a :class:`CompWithSym` instance w.r.t. the same frame, - and with the same number of indices and the same symmetries - + Create a :class:`CompWithSym` instance w.r.t. the same frame, + and with the same number of indices and the same symmetries. + EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1)) sage: a = c._new_instance() ; a 4-indices components w.r.t. [1, 2, 3], with symmetry on the index positions (0, 1) - + """ - return CompWithSym(self._ring, self._frame, self._nid, self._sindex, + return CompWithSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter, self._sym, self._antisym) def _ordered_indices(self, indices): r""" - Given a set of indices, returns a set of indices with the indices - at the positions of symmetries or antisymmetries being ordered, + Given a set of indices, return a set of indices with the indices + at the positions of symmetries or antisymmetries being ordered, as well as some antisymmetry indicator. - + INPUT: - + - ``indices`` -- list of indices (possibly a single integer if self is a 1-index object) - + OUTPUT: - - - a pair `(s,ind)` where ind is a tuple that differs from the original - list of indices by a reordering at the positions of symmetries and - antisymmetries and - * `s=0` if the value corresponding to ``indices`` vanishes by - antisymmetry (repeated indices); `ind` is then set to None - * `s=1` if the value corresponding to ``indices`` is the same as - that corresponding to `ind` - * `s=-1` if the value corresponding to ``indices`` is the opposite - of that corresponding to `ind` + + - a pair ``(s,ind)`` where ``ind`` is a tuple that differs from the + original list of indices by a reordering at the positions of + symmetries and antisymmetries and + + * ``s = 0`` if the value corresponding to ``indices`` vanishes by + antisymmetry (repeated indices); `ind` is then set to ``None`` + * ``s = 1`` if the value corresponding to ``indices`` is the same as + that corresponding to ``ind`` + * ``s = -1`` if the value corresponding to ``indices`` is the + opposite of that corresponding to ``ind`` EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) sage: c._ordered_indices([0,1,1,2]) @@ -2610,16 +2574,16 @@ def _ordered_indices(self, indices): if len(indsym) != len(set(indsym)): return (0, None) # From here, all the indices in indsym are distinct and we need - # to determine whether they form an even permutation of their + # to determine whether they form an even permutation of their # ordered series indsym_ordered = sorted(indsym) for k, pos in enumerate(isym): ind[pos] = indsym_ordered[k] if indsym_ordered != indsym: # Permutation linking indsym_ordered to indsym: - # (the +1 is required to fulfill the convention of Permutation) + # (the +1 is required to fulfill the convention of Permutation) perm = [indsym.index(i) +1 for i in indsym_ordered] - #c# print "indsym_ordered, indsym: ", indsym_ordered, indsym + #c# print "indsym_ordered, indsym: ", indsym_ordered, indsym #c# print "Permutation: ", Permutation(perm), " signature = ", \ #c# Permutation(perm).signature() sign *= Permutation(perm).signature() @@ -2628,22 +2592,22 @@ def _ordered_indices(self, indices): def __getitem__(self, args): r""" - Returns the component corresponding to the given indices. + Return the component corresponding to the given indices. INPUT: - + - ``args`` -- list of indices (possibly a single integer if self is a 1-index object) or the character ``:`` for the full list - of components. - + of components + OUTPUT: - + - the component corresponding to ``args`` or, if ``args`` = ``:``, the full list of components, in the form ``T[i][j]...`` for the components `T_{ij...}` (for a 2-indices object, a matrix is returned). EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym sage: c = CompWithSym(ZZ, [1,2,3], 4, sym=(0,1), antisym=(2,3)) sage: c.__getitem__((0,1,1,2)) # uninitialized components are zero @@ -2684,7 +2648,7 @@ def __getitem__(self, args): indices = args[:-1] if isinstance(indices, slice): return self._get_list(indices, no_format, format_type) - else: + else: sign, ind = self._ordered_indices(indices) if (sign == 0) or (ind not in self._comp): # the value is zero: if no_format: @@ -2692,8 +2656,8 @@ def __getitem__(self, args): elif format_type is None: return self._output_formatter(self._ring.zero_element()) else: - return self._output_formatter(self._ring.zero_element(), - format_type) + return self._output_formatter(self._ring.zero_element(), + format_type) else: # non zero value if no_format: if sign == 1: @@ -2718,15 +2682,15 @@ def __setitem__(self, args, value): Sets the component corresponding to the given indices. INPUT: - + - ``args`` -- list of indices (possibly a single integer if - self is a 1-index object) ; if [:] is provided, all the components - are set. - - ``value`` -- the value to be set or a list of values if ``args`` - == ``[:]`` + self is a 1-index object) ; if ``[:]`` is provided, all the + components are set + - ``value`` -- the value to be set or a list of values if + ``args = [:]`` EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym sage: c = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) sage: c.__setitem__((1,2), 5) @@ -2743,7 +2707,7 @@ def __setitem__(self, args, value): sage: c.__setitem__((2,2), 5) Traceback (most recent call last): ... - ValueError: By antisymmetry, the component cannot have a nonzero value for the indices (2, 2) + ValueError: by antisymmetry, the component cannot have a nonzero value for the indices (2, 2) """ format_type = None # default value, possibly redefined below @@ -2773,9 +2737,9 @@ def __setitem__(self, args, value): sign, ind = self._ordered_indices(indices) if sign == 0: if value != 0: - raise ValueError( - "By antisymmetry, the component cannot have a " + - "nonzero value for the indices " + str(indices)) + raise ValueError("by antisymmetry, the component cannot " + + "have a nonzero value for the indices " + + str(indices)) if ind in self._comp: del self._comp[ind] # zero values are not stored elif value == 0: @@ -2795,32 +2759,32 @@ def __setitem__(self, args, value): def swap_adjacent_indices(self, pos1, pos2, pos3): r""" - Swap two adjacent sets of indices. - - This method is essentially required to reorder the covariant and - contravariant indices in the computation of a tensor product. - + Swap two adjacent sets of indices. + + This method is essentially required to reorder the covariant and + contravariant indices in the computation of a tensor product. + The symmetries are preserved and the corresponding indices are adjusted - consequently. - + consequently. + INPUT: - - - ``pos1`` -- position of the first index of set 1 (with the convention + + - ``pos1`` -- position of the first index of set 1 (with the convention position=0 for the first slot) - - ``pos2`` -- position of the first index of set 2 = 1 + position of + - ``pos2`` -- position of the first index of set 2 = 1 + position of the last index of set 1 (since the two sets are adjacent) - ``pos3`` -- 1 + position of the last index of set 2 - + OUTPUT: - - - Components with index set 1 permuted with index set 2. - + + - Components with index set 1 permuted with index set 2. + EXAMPLES: - - Swap of the index in position 0 with the pair of indices in position + + Swap of the index in position 0 with the pair of indices in position (1,2) in a set of components antisymmetric with respect to the indices in position (1,2):: - + sage: from sage.tensor.modules.comp import CompWithSym sage: V = VectorSpace(QQ, 3) sage: c = CompWithSym(QQ, V.basis(), 3, antisym=(1,2)) @@ -2843,7 +2807,6 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): sage: c1[2,1,0] -3 - """ result = self._new_instance() # The symmetries: @@ -2860,24 +2823,24 @@ def swap_adjacent_indices(self, pos1, pos2, pos3): # The values: for ind, val in self._comp.iteritems(): new_ind = ind[:pos1] + ind[pos2:pos3] + ind[pos1:pos2] + ind[pos3:] - result[new_ind] = val + result[new_ind] = val return result def __add__(self, other): r""" - Component addition. - + Component addition. + INPUT: - + - ``other`` -- components of the same number of indices and defined on the same frame as ``self`` - + OUTPUT: - + - components resulting from the addition of ``self`` and ``other`` - + EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym sage: a = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) sage: a[0,1], a[1,2] = 4, 5 @@ -2899,22 +2862,22 @@ def __add__(self, other): [ 0 7 7] [ 1 0 5] [-7 5 0] - + """ if other == 0: return +self if not isinstance(other, Components): - raise TypeError("The second argument for the addition must be a " + - "an instance of Components.") + raise TypeError("the second argument for the addition must be a " + + "an instance of Components") if other._frame != self._frame: - raise TypeError("The two sets of components are not defined on " + - "the same frame.") + raise ValueError("the two sets of components are not defined on " + + "the same frame") if other._nid != self._nid: - raise TypeError("The two sets of components do not have the " + - "same number of indices.") + raise ValueError("the two sets of components do not have the " + + "same number of indices") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + - "same starting index.") + raise ValueError("the two sets of components do not have the " + + "same starting index") if isinstance(other, CompWithSym): # Are the symmetries of the same type ? diff_sym = set(self._sym).symmetric_difference(set(other._sym)) @@ -2927,7 +2890,7 @@ def __add__(self, other): result[[ind]] += val return result else: - # The symmetries/antisymmetries are different: only the + # The symmetries/antisymmetries are different: only the # common ones are kept common_sym = [] for isym in self._sym: @@ -2940,18 +2903,18 @@ def __add__(self, other): for osym in other._antisym: com = tuple(set(isym).intersection(set(osym))) if len(com) > 1: - common_antisym.append(com) + common_antisym.append(com) if common_sym != [] or common_antisym != []: - result = CompWithSym(self._ring, self._frame, self._nid, - self._sindex, self._output_formatter, + result = CompWithSym(self._ring, self._frame, self._nid, + self._sindex, self._output_formatter, common_sym, common_antisym) else: # no common symmetry -> the result is a generic Components: - result = Components(self._ring, self._frame, self._nid, + result = Components(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) else: # other has no symmetry at all: - result = Components(self._ring, self._frame, self._nid, + result = Components(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) for ind in result.non_redundant_index_generator(): result[[ind]] = self[[ind]] + other[[ind]] @@ -2960,19 +2923,18 @@ def __add__(self, other): def __mul__(self, other): r""" - Component tensor product. - + Component tensor product. + INPUT: - + - ``other`` -- components, on the same frame as ``self`` - - OUTPUT: - + + OUTPUT: + - the tensor product of ``self`` by ``other`` - EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompWithSym sage: a = CompWithSym(ZZ, [1,2,3], 2, sym=(0,1)) sage: a[0,1], a[1,2] = 4, 5 @@ -2999,14 +2961,14 @@ def __mul__(self, other): """ if not isinstance(other, Components): - raise TypeError("The second argument for the tensor product " + - "be an instance of Components.") + raise TypeError("the second argument for the tensor product " + + "be an instance of Components") if other._frame != self._frame: - raise TypeError("The two sets of components are not defined on " + - "the same frame.") + raise ValueError("the two sets of components are not defined on " + + "the same frame") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + - "same starting index.") + raise ValueError("the two sets of components do not have the " + + "same starting index") sym = list(self._sym) antisym = list(self._antisym) if isinstance(other, CompWithSym): @@ -3018,7 +2980,7 @@ def __mul__(self, other): for s in other._antisym: ns = tuple(s[i]+self._nid for i in range(len(s))) antisym.append(ns) - result = CompWithSym(self._ring, self._frame, self._nid + other._nid, + result = CompWithSym(self._ring, self._frame, self._nid + other._nid, self._sindex, self._output_formatter, sym, antisym) for ind_s, val_s in self._comp.iteritems(): for ind_o, val_o in other._comp.iteritems(): @@ -3027,25 +2989,25 @@ def __mul__(self, other): def trace(self, pos1, pos2): - r""" + r""" Index contraction, taking care of the symmetries. - + INPUT: - - - ``pos1`` -- position of the first index for the contraction (with + + - ``pos1`` -- position of the first index for the contraction (with the convention position=0 for the first slot) - ``pos2`` -- position of the second index for the contraction - + OUTPUT: - + - set of components resulting from the (pos1, pos2) contraction EXAMPLES: Self-contraction of symmetric 2-indices components:: - + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ - ... CompFullySym, CompFullyAntiSym + ....: CompFullySym, CompFullyAntiSym sage: V = VectorSpace(QQ, 3) sage: a = CompFullySym(QQ, V.basis(), 2) sage: a[:] = [[1,2,3],[2,4,5],[3,5,6]] @@ -3055,13 +3017,12 @@ def trace(self, pos1, pos2): 11 Self-contraction of antisymmetric 2-indices components:: - + sage: b = CompFullyAntiSym(QQ, V.basis(), 2) sage: b[0,1], b[0,2], b[1,2] = (3, -2, 1) sage: b.trace(0,1) # must be zero by antisymmetry 0 - Self-contraction of 3-indices components with one symmetry:: sage: v = Components(QQ, V.basis(), 1) @@ -3088,7 +3049,7 @@ def trace(self, pos1, pos2): (0, 1, 0), (0, 0, 1) ] - sage: s[:] # is zero by antisymmetry + sage: s[:] # is zero by antisymmetry [0, 0, 0] sage: c = b*v ; c 3-indices components w.r.t. [ @@ -3101,11 +3062,11 @@ def trace(self, pos1, pos2): [0, 0, 0] sage: s = c.trace(1,2) ; s[:] [28, -2, -8] - sage: [sum(b[i,k]*v[k] for k in range(3)) for i in range(3)] # check + sage: [sum(b[i,k]*v[k] for k in range(3)) for i in range(3)] # check [28, -2, -8] Self-contraction of 4-indices components with two symmetries:: - + sage: c = a*b ; c 4-indices components w.r.t. [ (1, 0, 0), @@ -3113,7 +3074,7 @@ def trace(self, pos1, pos2): (0, 0, 1) ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) sage: s = c.trace(0,1) ; s # the symmetry on (0,1) is lost: - fully antisymmetric 2-indices components w.r.t. [ + Fully antisymmetric 2-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -3137,21 +3098,21 @@ def trace(self, pos1, pos2): sage: [[sum(c[i,k,k,j] for k in range(3)) for j in range(3)] for i in range(3)] # check [[0, 0, 0], [-2, 1, 0], [-3, 3, -1]] - """ + """ if self._nid < 2: - raise TypeError("Contraction can be perfomed only on " + - "components with at least 2 indices.") + raise TypeError("contraction can be perfomed only on " + + "components with at least 2 indices") if pos1 < 0 or pos1 > self._nid - 1: - raise IndexError("pos1 out of range.") + raise IndexError("pos1 out of range") if pos2 < 0 or pos2 > self._nid - 1: - raise IndexError("pos2 out of range.") + raise IndexError("pos2 out of range") if pos1 == pos2: - raise IndexError("The two positions must differ for the " + - "contraction to take place.") + raise IndexError("the two positions must differ for the " + + "contraction to take place") si = self._sindex nsi = si + self._dim if self._nid == 2: - res = 0 + res = 0 for i in range(si, nsi): res += self[[i,i]] return res @@ -3165,7 +3126,7 @@ def trace(self, pos1, pos2): isym_res = list(isym) if pos1 in isym: isym_res.remove(pos1) - if pos2 in isym: + if pos2 in isym: isym_res.remove(pos2) if len(isym_res) < 2: # the symmetry is lost sym_res.remove(isym) @@ -3176,7 +3137,7 @@ def trace(self, pos1, pos2): isym_res = list(isym) if pos1 in isym: isym_res.remove(pos1) - if pos2 in isym: + if pos2 in isym: isym_res.remove(pos2) if len(isym_res) < 2: # the symmetry is lost antisym_res.remove(isym) @@ -3195,7 +3156,7 @@ def trace(self, pos1, pos2): else: isym_res.append(pos-2) max_sym = max(max_sym, len(isym_res)) - sym_res[k] = tuple(isym_res) + sym_res[k] = tuple(isym_res) max_antisym = 0 for k in range(len(antisym_res)): isym_res = [] @@ -3221,8 +3182,8 @@ def trace(self, pos1, pos2): result = CompFullyAntiSym(self._ring, self._frame, nid_res, self._sindex, self._output_formatter) else: - result = CompWithSym(self._ring, self._frame, nid_res, - self._sindex, self._output_formatter, + result = CompWithSym(self._ring, self._frame, nid_res, + self._sindex, self._output_formatter, sym=sym_res, antisym=antisym_res) # The contraction itself: for ind_res in result.non_redundant_index_generator(): @@ -3232,7 +3193,7 @@ def trace(self, pos1, pos2): res = 0 for i in range(si, nsi): ind[pos1] = i - ind[pos2] = i + ind[pos2] = i res += self[[ind]] result[[ind_res]] = res return result @@ -3240,17 +3201,17 @@ def trace(self, pos1, pos2): def non_redundant_index_generator(self): r""" - Generator of indices, with only ordered indices in case of symmetries, - so that only non-redundant indices are generated. - + Generator of indices, with only ordered indices in case of symmetries, + so that only non-redundant indices are generated. + OUTPUT: - + - an iterable index EXAMPLES: - + Indices on a 2-dimensional space:: - + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ ... CompFullySym, CompFullyAntiSym sage: V = VectorSpace(QQ, 2) @@ -3265,7 +3226,7 @@ def non_redundant_index_generator(self): (0, 1) Indices on a 3-dimensional space:: - + sage: V = VectorSpace(QQ, 3) sage: c = CompFullySym(QQ, V.basis(), 2) sage: for ind in c.non_redundant_index_generator(): print ind, @@ -3278,19 +3239,23 @@ def non_redundant_index_generator(self): (0, 1) (0, 2) (1, 2) sage: c = CompWithSym(QQ, V.basis(), 3, sym=(1,2)) # symmetry on the last two indices sage: for ind in c.non_redundant_index_generator(): print ind, - (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 1) (0, 1, 2) (0, 2, 2) (1, 0, 0) (1, 0, 1) (1, 0, 2) (1, 1, 1) (1, 1, 2) (1, 2, 2) (2, 0, 0) (2, 0, 1) (2, 0, 2) (2, 1, 1) (2, 1, 2) (2, 2, 2) + (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 1) (0, 1, 2) (0, 2, 2) + (1, 0, 0) (1, 0, 1) (1, 0, 2) (1, 1, 1) (1, 1, 2) (1, 2, 2) + (2, 0, 0) (2, 0, 1) (2, 0, 2) (2, 1, 1) (2, 1, 2) (2, 2, 2) sage: c = CompWithSym(QQ, V.basis(), 3, antisym=(1,2)) # antisymmetry on the last two indices sage: for ind in c.non_redundant_index_generator(): print ind, - (0, 0, 1) (0, 0, 2) (0, 1, 2) (1, 0, 1) (1, 0, 2) (1, 1, 2) (2, 0, 1) (2, 0, 2) (2, 1, 2) + (0, 0, 1) (0, 0, 2) (0, 1, 2) (1, 0, 1) (1, 0, 2) (1, 1, 2) + (2, 0, 1) (2, 0, 2) (2, 1, 2) sage: c = CompFullySym(QQ, V.basis(), 3) sage: for ind in c.non_redundant_index_generator(): print ind, - (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 1) (0, 1, 2) (0, 2, 2) (1, 1, 1) (1, 1, 2) (1, 2, 2) (2, 2, 2) + (0, 0, 0) (0, 0, 1) (0, 0, 2) (0, 1, 1) (0, 1, 2) (0, 2, 2) + (1, 1, 1) (1, 1, 2) (1, 2, 2) (2, 2, 2) sage: c = CompFullyAntiSym(QQ, V.basis(), 3) sage: for ind in c.non_redundant_index_generator(): print ind, (0, 1, 2) Indices on a 4-dimensional space:: - + sage: V = VectorSpace(QQ, 4) sage: c = Components(QQ, V.basis(), 1) sage: for ind in c.non_redundant_index_generator(): print ind, @@ -3319,7 +3284,7 @@ def non_redundant_index_generator(self): for k in range(len(isym)-1): if ind[isym[k+1]] < ind[isym[k]]: ordered = False - break + break for isym in self._antisym: for k in range(len(isym)-1): if ind[isym[k+1]] <= ind[isym[k]]: @@ -3341,25 +3306,25 @@ def non_redundant_index_generator(self): def symmetrize(self, *pos): r""" - Symmetrization over the given index positions - + Symmetrization over the given index positions. + INPUT: - - - ``pos`` -- list of index positions involved in the - symmetrization (with the convention position=0 for the first slot); - if none, the symmetrization is performed over all the indices - + + - ``pos`` -- list of index positions involved in the + symmetrization (with the convention ``position=0`` for the first + slot); if none, the symmetrization is performed over all the indices + OUTPUT: - - - an instance of :class:`CompWithSym` describing the symmetrized - components. - + + - an instance of :class:`CompWithSym` describing the symmetrized + components + EXAMPLES: - + Symmetrization of 3-indices components on a 3-dimensional space:: - + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ - ... CompFullySym, CompFullyAntiSym + ....: CompFullySym, CompFullyAntiSym sage: V = VectorSpace(QQ, 3) sage: c = Components(QQ, V.basis(), 3) sage: c[:] = [[[1,2,3], [4,5,6], [7,8,9]], [[10,11,12], [13,14,15], [16,17,18]], [[19,20,21], [22,23,24], [25,26,27]]] @@ -3370,7 +3335,7 @@ def symmetrize(self, *pos): (0, 0, 1) ], with symmetry on the index positions (0, 1) sage: s = cs.symmetrize() ; s - fully symmetric 3-indices components w.r.t. [ + Fully symmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -3402,26 +3367,26 @@ def symmetrize(self, *pos): (0, 0, 1) ], with symmetry on the index positions (1, 2) sage: s2 = cs1.symmetrize() ; s2 - fully symmetric 3-indices components w.r.t. [ + Fully symmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) ] sage: s2 == c.symmetrize() True - + Symmetrization alters pre-existing symmetries: let us symmetrize w.r.t. - the index positions (1,2) a set of components that is symmetric w.r.t. - the index positions (0,1):: + the index positions `(1, 2)` a set of components that is symmetric + w.r.t. the index positions `(0, 1)`:: - sage: cs = c.symmetrize(0,1) ; cs + sage: cs = c.symmetrize(0,1) ; cs 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1) - sage: css = cs.symmetrize(1,2) - sage: css # the symmetry (0,1) has been lost: + sage: css = cs.symmetrize(1,2) + sage: css # the symmetry (0,1) has been lost: 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3440,9 +3405,9 @@ def symmetrize(self, *pos): sage: css.symmetrize() == c.symmetrize() # one has to symmetrize css over all indices to recover it True - Another example of symmetry alteration: symmetrization over (0,1) of - a 4-indices set of components that is symmetric w.r.t. (1,2,3):: - + Another example of symmetry alteration: symmetrization over `(0, 1)` of + a 4-indices set of components that is symmetric w.r.t. `(1, 2, 3)`:: + sage: v = Components(QQ, V.basis(), 1) sage: v[:] = (-2,1,4) sage: a = v*s ; a @@ -3470,7 +3435,7 @@ def symmetrize(self, *pos): Partial symmetrization of 4-indices components with an antisymmetry on the last two indices:: - + sage: a = Components(QQ, V.basis(), 2) sage: a[:] = [[-1,2,3], [4,5,-6], [7,8,9]] sage: b = CompFullyAntiSym(QQ, V.basis(), 2) @@ -3490,7 +3455,7 @@ def symmetrize(self, *pos): sage: s[0,1,2,1] == (c[0,1,2,1] + c[1,0,2,1]) / 2 # check of the symmetrization True sage: s = c.symmetrize() ; s # symmetrization over all the indices - fully symmetric 4-indices components w.r.t. [ + Fully symmetric 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -3532,8 +3497,12 @@ def symmetrize(self, *pos): (1, 0, 0), (0, 1, 0), (0, 0, 1) - ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) - sage: # Note that the antisymmetry on (1, 2, 3) has been reduced to (2, 3) only + ], with symmetry on the index positions (0, 1), + with antisymmetry on the index positions (2, 3) + + Note that the antisymmetry on `(1, 2, 3)` has been reduced to + `(2, 3)` only:: + sage: s = c.symmetrize(1,2) ; s 4-indices components w.r.t. [ (1, 0, 0), @@ -3542,17 +3511,17 @@ def symmetrize(self, *pos): ], with symmetry on the index positions (1, 2) sage: s == 0 # because (1,2) are involved in the original antisymmetry True - + """ from sage.groups.perm_gps.permgroup_named import SymmetricGroup if not pos: pos = range(self._nid) else: if len(pos) < 2: - raise TypeError("At least two index positions must be given.") + raise ValueError("at least two index positions must be given") if len(pos) > self._nid: - raise TypeError("Number of index positions larger than the " \ - "total number of indices.") + raise ValueError("number of index positions larger than the " \ + "total number of indices") pos = tuple(pos) pos_set = set(pos) # If the symmetry is already present, there is nothing to do: @@ -3561,14 +3530,14 @@ def symmetrize(self, *pos): return self.copy() # # Interference of the new symmetry with existing ones: - # + # sym_res = [pos] # starting the list of symmetries of the result for isym in self._sym: inter = pos_set.intersection(set(isym)) # if len(inter) == len(isym), isym is included in the new symmetry # and therefore has not to be included in sym_res if len(inter) != len(isym): - if len(inter) >= 1: + if len(inter) >= 1: # some part of isym is lost isym_set = set(isym) for k in inter: @@ -3583,25 +3552,25 @@ def symmetrize(self, *pos): sym_res.append(isym) # # Interference of the new symmetry with existing antisymmetries: - # + # antisym_res = [] # starting the list of antisymmetries of the result zero_result = False for iasym in self._antisym: inter = pos_set.intersection(set(iasym)) - if len(inter) > 1: - # If at least two of the symmetry indices are already involved + if len(inter) > 1: + # If at least two of the symmetry indices are already involved # in the antisymmetry, the outcome is zero: zero_result = True elif len(inter) == 1: # some piece of antisymmetry is lost - k = inter.pop() # the symmetry index position involved in the + k = inter.pop() # the symmetry index position involved in the # antisymmetry iasym_set = set(iasym) iasym_set.remove(k) if len(iasym_set) > 1: iasym_res = tuple(iasym_set) antisym_res.append(iasym_res) - # if len(iasym_set) == 1, the antisymmetry is fully lost, it is + # if len(iasym_set) == 1, the antisymmetry is fully lost, it is # therefore not appended to antisym_res else: # case len(inter)=0: no interference: the antisymmetry is @@ -3618,7 +3587,7 @@ def symmetrize(self, *pos): self._output_formatter) else: result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, - self._output_formatter, sym=sym_res, + self._output_formatter, sym=sym_res, antisym=antisym_res) if zero_result: return result # since a just created instance is zero @@ -3628,7 +3597,7 @@ def symmetrize(self, *pos): n_sym = len(pos) # number of indices involved in the symmetry sym_group = SymmetricGroup(n_sym) for ind in result.non_redundant_index_generator(): - sum = 0 + sum = 0 for perm in sym_group.list(): # action of the permutation on [0,1,...,n_sym-1]: perm_action = map(lambda x: x-1, perm.domain()) @@ -3642,23 +3611,23 @@ def symmetrize(self, *pos): def antisymmetrize(self, *pos): r""" - Antisymmetrization over the given index positions - + Antisymmetrization over the given index positions. + INPUT: - + - ``pos`` -- list of index positions involved in the antisymmetrization - (with the convention position=0 for the first slot); if none, the + (with the convention ``position=0`` for the first slot); if none, the antisymmetrization is performed over all the indices - + OUTPUT: - - - an instance of :class:`CompWithSym` describing the antisymmetrized - components. - + + - an instance of :class:`CompWithSym` describing the antisymmetrized + components + EXAMPLES: - + Antisymmetrization of 3-indices components on a 3-dimensional space:: - + sage: from sage.tensor.modules.comp import Components, CompWithSym, \ ... CompFullySym, CompFullyAntiSym sage: V = VectorSpace(QQ, 3) @@ -3673,7 +3642,7 @@ def antisymmetrize(self, *pos): (0, 0, 1) ], with antisymmetry on the index positions (1, 2) sage: s = c.antisymmetrize() ; s - fully antisymmetric 3-indices components w.r.t. [ + Fully antisymmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -3685,18 +3654,17 @@ def antisymmetrize(self, *pos): [[[0, 0, 0], [0, 0, 7/3], [0, -7/3, 0]], [[0, 0, -7/3], [0, 0, 0], [7/3, 0, 0]], [[0, 7/3, 0], [-7/3, 0, 0], [0, 0, 0]]]) - sage: # Check of the antisymmetrization: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6, - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True - + + Check of the antisymmetrization:: + + sage: all(s[i,j,k] == (c[i,j,k]-c[i,k,j]+c[j,k,i]-c[j,i,k]+c[k,i,j]-c[k,j,i])/6 + ....: for i in range(3) for j in range(3) for k in range(3)) + True + Antisymmetrization over already antisymmetric indices does not change anything:: - + sage: s1 = s.antisymmetrize(1,2) ; s1 - fully antisymmetric 3-indices components w.r.t. [ + Fully antisymmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -3713,16 +3681,16 @@ def antisymmetrize(self, *pos): True But in general, antisymmetrization may alter previous antisymmetries:: - - sage: c2 = c.antisymmetrize(0,1) ; c2 # the antisymmetry (2,3) is lost: + + sage: c2 = c.antisymmetrize(0,1) ; c2 # the antisymmetry (2,3) is lost: 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) ], with antisymmetry on the index positions (0, 1) - sage: c2 == c + sage: c2 == c False - sage: c = s*a ; c + sage: c = s*a ; c 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), @@ -3733,17 +3701,18 @@ def antisymmetrize(self, *pos): (1, 0, 0), (0, 1, 0), (0, 0, 1) - ], with antisymmetry on the index positions (1, 3), with antisymmetry on the index positions (0, 2) + ], with antisymmetry on the index positions (1, 3), + with antisymmetry on the index positions (0, 2) sage: s._antisym # the antisymmetry (0,1,2) has been reduced to (0,2), since 1 is involved in the new antisymmetry (1,3): [(1, 3), (0, 2)] - Partial antisymmetrization of 4-indices components with a symmetry on + Partial antisymmetrization of 4-indices components with a symmetry on the first two indices:: - + sage: a = CompFullySym(QQ, V.basis(), 2) sage: a[:] = [[-2,1,3], [1,0,-5], [3,-5,4]] sage: b = Components(QQ, V.basis(), 2) - sage: b[:] = [[1,2,3], [5,7,11], [13,17,19]] + sage: b[:] = [[1,2,3], [5,7,11], [13,17,19]] sage: c = a*b ; c 4-indices components w.r.t. [ (1, 0, 0), @@ -3755,19 +3724,21 @@ def antisymmetrize(self, *pos): (1, 0, 0), (0, 1, 0), (0, 0, 1) - ], with symmetry on the index positions (0, 1), with antisymmetry on the index positions (2, 3) - sage: # Some check of the antisymmetrization: + ], with symmetry on the index positions (0, 1), + with antisymmetry on the index positions (2, 3) + + Some check of the antisymmetrization:: + sage: for i in range(3): ....: for j in range(i,3): ....: print (s[2,2,i,j], s[2,2,i,j] == (c[2,2,i,j] - c[2,2,j,i])/2), - ....: (0, True) (-6, True) (-20, True) (0, True) (-12, True) (0, True) - The full antisymmetrization results in zero because of the symmetry on the + The full antisymmetrization results in zero because of the symmetry on the first two indices:: - + sage: s = c.antisymmetrize() ; s - fully antisymmetric 4-indices components w.r.t. [ + Fully antisymmetric 4-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -3776,7 +3747,7 @@ def antisymmetrize(self, *pos): True Similarly, the partial antisymmetrization on the first two indices results in zero:: - + sage: s = c.antisymmetrize(0,1) ; s 4-indices components w.r.t. [ (1, 0, 0), @@ -3786,8 +3757,9 @@ def antisymmetrize(self, *pos): sage: s == 0 True - The partial antisymmetrization on the positions (0,2) destroys the symmetry on (0,1):: - + The partial antisymmetrization on the positions `(0, 2)` destroys + the symmetry on `(0, 1)`:: + sage: s = c.antisymmetrize(0,2) ; s 4-indices components w.r.t. [ (1, 0, 0), @@ -3809,10 +3781,10 @@ def antisymmetrize(self, *pos): pos = range(self._nid) else: if len(pos) < 2: - raise TypeError("At least two index positions must be given.") + raise ValueError("at least two index positions must be given") if len(pos) > self._nid: - raise TypeError("Number of index positions larger than the " \ - "total number of indices.") + raise ValueError("number of index positions larger than the " \ + "total number of indices") pos = tuple(pos) pos_set = set(pos) # If the antisymmetry is already present, there is nothing to do: @@ -3821,49 +3793,49 @@ def antisymmetrize(self, *pos): return self.copy() # # Interference of the new antisymmetry with existing ones - # + # antisym_res = [pos] # starting the list of symmetries of the result for iasym in self._antisym: inter = pos_set.intersection(set(iasym)) - # if len(inter) == len(iasym), iasym is included in the new + # if len(inter) == len(iasym), iasym is included in the new # antisymmetry and therefore has not to be included in antisym_res if len(inter) != len(iasym): - if len(inter) >= 1: + if len(inter) >= 1: # some part of iasym is lost iasym_set = set(iasym) for k in inter: iasym_set.remove(k) if len(iasym_set) > 1: - # some part of iasym remains and must be included in + # some part of iasym remains and must be included in # antisym_res: iasym_res = tuple(iasym_set) antisym_res.append(iasym_res) else: - # case len(inter)=0: no interference: the existing - # antisymmetry is added to the list of antisymmetries for + # case len(inter)=0: no interference: the existing + # antisymmetry is added to the list of antisymmetries for # the result: antisym_res.append(iasym) # # Interference of the new antisymmetry with existing symmetries - # + # sym_res = [] # starting the list of symmetries of the result zero_result = False for isym in self._sym: inter = pos_set.intersection(set(isym)) - if len(inter) > 1: - # If at least two of the antisymmetry indices are already - # involved in the symmetry, the outcome is zero: + if len(inter) > 1: + # If at least two of the antisymmetry indices are already + # involved in the symmetry, the outcome is zero: zero_result = True elif len(inter) == 1: # some piece of the symmetry is lost - k = inter.pop() # the antisymmetry index position involved in + k = inter.pop() # the antisymmetry index position involved in # the symmetry isym_set = set(isym) isym_set.remove(k) if len(isym_set) > 1: isym_res = tuple(isym_set) sym_res.append(isym_res) - # if len(isym_set) == 1, the symmetry is fully lost, it is + # if len(isym_set) == 1, the symmetry is fully lost, it is # therefore not appended to sym_res else: # case len(inter)=0: no interference: the symmetry is @@ -3876,11 +3848,11 @@ def antisymmetrize(self, *pos): for isym in antisym_res: max_sym = max(max_sym, len(isym)) if max_sym == self._nid: - result = CompFullyAntiSym(self._ring, self._frame, self._nid, + result = CompFullyAntiSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) else: result = CompWithSym(self._ring, self._frame, self._nid, self._sindex, - self._output_formatter, sym=sym_res, + self._output_formatter, sym=sym_res, antisym=antisym_res) if zero_result: return result # since a just created instance is zero @@ -3890,7 +3862,7 @@ def antisymmetrize(self, *pos): n_sym = len(pos) # number of indices involved in the antisymmetry sym_group = SymmetricGroup(n_sym) for ind in result.non_redundant_index_generator(): - sum = 0 + sum = 0 for perm in sym_group.list(): # action of the permutation on [0,1,...,n_sym-1]: perm_action = map(lambda x: x-1, perm.domain()) @@ -3909,35 +3881,35 @@ def antisymmetrize(self, *pos): class CompFullySym(CompWithSym): r""" - Indexed set of ring elements forming some components with respect to a + Indexed set of ring elements forming some components with respect to a given "frame" that are fully symmetric with respect to any permutation - of the indices. - - The "frame" can be a basis of some vector space or a vector frame on some - manifold (i.e. a field of bases). + of the indices. + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). The stored quantities can be tensor components or non-tensorial quantities. - + INPUT: - ``ring`` -- commutative ring in which each component takes its value - - ``frame`` -- frame with respect to which the components are defined; + - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size of a single index range - ``nb_indices`` -- number of indices labeling the components - - ``start_index`` -- (default: 0) first value of a single index; + - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey - ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound - method called to format the output of the component access + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take - 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and the second one, if any, some format specification. - + EXAMPLES: - + Symmetric components with 2 indices on a 3-dimensional space:: - + sage: from sage.tensor.modules.comp import CompFullySym, CompWithSym sage: V = VectorSpace(QQ, 3) sage: c = CompFullySym(QQ, V.basis(), 2) @@ -3946,33 +3918,33 @@ class CompFullySym(CompWithSym): [ 1 -2 0] [-2 0 3] [ 0 3 0] - + Internally, only non-redundant and non-zero components are stored:: sage: c._comp # random output order of the component dictionary {(0, 0): 1, (0, 1): -2, (1, 2): 3} Same thing, but with the starting index set to 1:: - + sage: c1 = CompFullySym(QQ, V.basis(), 2, start_index=1) sage: c1[1,1], c1[1,2], c1[2,3] = 1, -2, 3 sage: c1[:] [ 1 -2 0] [-2 0 3] [ 0 3 0] - + The values stored in ``c`` and ``c1`` are equal:: - + sage: c1[:] == c[:] True - + but not ``c`` and ``c1``, since their starting indices differ:: - + sage: c1 == c False Fully symmetric components with 3 indices on a 3-dimensional space:: - + sage: a = CompFullySym(QQ, V.basis(), 3) sage: a[0,1,2] = 3 sage: a[:] @@ -3984,13 +3956,13 @@ class CompFullySym(CompWithSym): [[[0, 4, 0], [4, 0, 3], [0, 3, 0]], [[4, 0, 3], [0, 0, 0], [3, 0, 0]], [[0, 3, 0], [3, 0, 0], [0, 0, 0]]] - + The full symmetry is preserved by the arithmetics:: - + sage: b = CompFullySym(QQ, V.basis(), 3) sage: b[0,0,0], b[0,1,0], b[1,0,2], b[1,2,2] = -2, 3, 1, -5 sage: s = a + 2*b ; s - fully symmetric 3-indices components w.r.t. [ + Fully symmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -4007,7 +3979,7 @@ class CompFullySym(CompWithSym): [[0, 5, 0], [5, 0, -10], [0, -10, 0]]]) It is lost if the added object is not fully symmetric:: - + sage: b1 = CompWithSym(QQ, V.basis(), 3, sym=(0,1)) # b1 has only symmetry on index positions (0,1) sage: b1[0,0,0], b1[0,1,0], b1[1,0,2], b1[1,2,2] = -2, 3, 1, -5 sage: s = a + 2*b1 ; s # the result has the same symmetry as b1: @@ -4032,18 +4004,18 @@ class CompFullySym(CompWithSym): (0, 1, 0), (0, 0, 1) ], with symmetry on the index positions (0, 1) - sage: 2*b1 + a == a + 2*b1 + sage: 2*b1 + a == a + 2*b1 True - + """ - def __init__(self, ring, frame, nb_indices, start_index=0, + def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): r""" TEST:: - + sage: from sage.tensor.modules.comp import CompFullySym - sage: CompFullySym(ZZ, (1,2,3), 2) - fully symmetric 2-indices components w.r.t. (1, 2, 3) + sage: C = CompFullySym(ZZ, (1,2,3), 2) + sage: TestSuite(C).run() """ CompWithSym.__init__(self, ring, frame, nb_indices, start_index, @@ -4051,53 +4023,52 @@ def __init__(self, ring, frame, nb_indices, start_index=0, def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: from sage.tensor.modules.comp import CompFullySym - sage: c = CompFullySym(ZZ, (1,2,3), 4) - sage: c._repr_() - 'fully symmetric 4-indices components w.r.t. (1, 2, 3)' - + sage: CompFullySym(ZZ, (1,2,3), 4) + Fully symmetric 4-indices components w.r.t. (1, 2, 3) + """ - return "fully symmetric " + str(self._nid) + "-indices" + \ + return "Fully symmetric " + str(self._nid) + "-indices" + \ " components w.r.t. " + str(self._frame) - + def _new_instance(self): r""" Creates a :class:`CompFullySym` instance w.r.t. the same frame, and with the same number of indices. - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import CompFullySym sage: c = CompFullySym(ZZ, (1,2,3), 4) sage: c._new_instance() - fully symmetric 4-indices components w.r.t. (1, 2, 3) + Fully symmetric 4-indices components w.r.t. (1, 2, 3) """ - return CompFullySym(self._ring, self._frame, self._nid, self._sindex, + return CompFullySym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) def __getitem__(self, args): r""" - Returns the component corresponding to the given indices. + Return the component corresponding to the given indices of ``self``. INPUT: - + - ``args`` -- list of indices (possibly a single integer if self is a 1-index object) or the character ``:`` for the full list - of components. - + of components + OUTPUT: - + - the component corresponding to ``args`` or, if ``args`` = ``:``, - the full list of components, in the form ``T[i][j]...`` for the components - `T_{ij...}` (for a 2-indices object, a matrix is returned). - + the full list of components, in the form ``T[i][j]...`` for the + components `T_{ij...}` (for a 2-indices object, a matrix is returned) + EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompFullySym sage: c = CompFullySym(ZZ, (1,2,3), 2) sage: c[0,1] = 4 @@ -4109,7 +4080,7 @@ def __getitem__(self, args): [0 4 0] [4 0 0] [0 0 0] - + """ no_format = self._output_formatter is None format_type = None # default value, possibly redefined below @@ -4134,40 +4105,42 @@ def __getitem__(self, args): else: format_type = args[-1] indices = args[:-1] + if isinstance(indices, slice): return self._get_list(indices, no_format, format_type) + + ind = self._ordered_indices(indices)[1] # [0]=sign is not used + if ind in self._comp: # non zero value + if no_format: + return self._comp[ind] + elif format_type is None: + return self._output_formatter(self._comp[ind]) + else: + return self._output_formatter(self._comp[ind], format_type) + + # the value is zero + if no_format: + return self._ring.zero_element() + elif format_type is None: + return self._output_formatter(self._ring.zero_element()) else: - ind = self._ordered_indices(indices)[1] # [0]=sign is not used - if ind in self._comp: # non zero value - if no_format: - return self._comp[ind] - elif format_type is None: - return self._output_formatter(self._comp[ind]) - else: - return self._output_formatter(self._comp[ind], format_type) - else: # the value is zero - if no_format: - return self._ring.zero_element() - elif format_type is None: - return self._output_formatter(self._ring.zero_element()) - else: - return self._output_formatter(self._ring.zero_element(), - format_type) + return self._output_formatter(self._ring.zero_element(), + format_type) def __setitem__(self, args, value): r""" Sets the component corresponding to the given indices. INPUT: - + - ``indices`` -- list of indices (possibly a single integer if - self is a 1-index object) ; if [:] is provided, all the components - are set. - - ``value`` -- the value to be set or a list of values if ``args`` - == ``[:]`` - + self is a 1-index object) ; if [:] is provided, all the components + are set. + - ``value`` -- the value to be set or a list of values if + ``args = [:]`` + EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompFullySym sage: c = CompFullySym(ZZ, (1,2,3), 2) sage: c.__setitem__((0,1), 4) @@ -4185,7 +4158,7 @@ def __setitem__(self, args, value): [1 2 3] [2 4 5] [3 5 6] - + """ format_type = None # default value, possibly redefined below if isinstance(args, list): # case of [[...]] syntax @@ -4223,17 +4196,17 @@ def __setitem__(self, args, value): def __add__(self, other): r""" - Component addition. - + Component addition. + INPUT: - + - ``other`` -- components of the same number of indices and defined on the same frame as ``self`` - + OUTPUT: - + - components resulting from the addition of ``self`` and ``other`` - + EXAMPLES:: sage: from sage.tensor.modules.comp import CompFullySym @@ -4242,7 +4215,7 @@ def __add__(self, other): sage: b = CompFullySym(ZZ, (1,2,3), 2) sage: b[0,1], b[2,2] = 2, -3 sage: s = a.__add__(b) ; s # the symmetry is kept - fully symmetric 2-indices components w.r.t. (1, 2, 3) + Fully symmetric 2-indices components w.r.t. (1, 2, 3) sage: s[:] [ 0 6 0] [ 6 0 5] @@ -4265,18 +4238,18 @@ def __add__(self, other): if other == 0: return +self if not isinstance(other, Components): - raise TypeError("The second argument for the addition must be a " + - "an instance of Components.") + raise TypeError("the second argument for the addition must be a " + + "an instance of Components") if isinstance(other, CompFullySym): if other._frame != self._frame: - raise TypeError("The two sets of components are not defined " + - "on the same frame.") + raise ValueError("the two sets of components are not defined " + + "on the same frame") if other._nid != self._nid: - raise TypeError("The two sets of components do not have the " + - "same number of indices.") + raise ValueError("the two sets of components do not have the " + + "same number of indices") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + - "same starting index.") + raise ValueError("the two sets of components do not have the " + + "same starting index") result = self.copy() for ind, val in other._comp.iteritems(): result[[ind]] += val @@ -4289,35 +4262,35 @@ def __add__(self, other): class CompFullyAntiSym(CompWithSym): r""" - Indexed set of ring elements forming some components with respect to a + Indexed set of ring elements forming some components with respect to a given "frame" that are fully antisymmetric with respect to any permutation - of the indices. - - The "frame" can be a basis of some vector space or a vector frame on some - manifold (i.e. a field of bases). + of the indices. + + The "frame" can be a basis of some vector space or a vector frame on some + manifold (i.e. a field of bases). The stored quantities can be tensor components or non-tensorial quantities. - + INPUT: - ``ring`` -- commutative ring in which each component takes its value - - ``frame`` -- frame with respect to which the components are defined; + - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size of a single index range - ``nb_indices`` -- number of indices labeling the components - - ``start_index`` -- (default: 0) first value of a single index; + - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey - ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound - method called to format the output of the component access + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take - 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and + 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and the second one, if any, some format specification. - + EXAMPLES: - + Antisymmetric components with 2 indices on a 3-dimensional space:: - + sage: from sage.tensor.modules.comp import CompWithSym, CompFullyAntiSym sage: V = VectorSpace(QQ, 3) sage: c = CompFullyAntiSym(QQ, V.basis(), 2) @@ -4326,14 +4299,14 @@ class CompFullyAntiSym(CompWithSym): [ 0 3 1/2] [ -3 0 -1] [-1/2 1 0] - + Internally, only non-redundant and non-zero components are stored:: - + sage: c._comp # random output order of the component dictionary {(0, 1): 3, (0, 2): 1/2, (1, 2): -1} - + Same thing, but with the starting index set to 1:: - + sage: c1 = CompFullyAntiSym(QQ, V.basis(), 2, start_index=1) sage: c1[1,2], c1[1,3], c1[2,3] = 3, 1/2, -1 sage: c1[:] @@ -4342,40 +4315,40 @@ class CompFullyAntiSym(CompWithSym): [-1/2 1 0] The values stored in ``c`` and ``c1`` are equal:: - + sage: c1[:] == c[:] True - + but not ``c`` and ``c1``, since their starting indices differ:: sage: c1 == c False - + Fully antisymmetric components with 3 indices on a 3-dimensional space:: - + sage: a = CompFullyAntiSym(QQ, V.basis(), 3) sage: a[0,1,2] = 3 # the only independent component in dimension 3 sage: a[:] [[[0, 0, 0], [0, 0, 3], [0, -3, 0]], [[0, 0, -3], [0, 0, 0], [3, 0, 0]], [[0, 3, 0], [-3, 0, 0], [0, 0, 0]]] - + Setting a nonzero value incompatible with the antisymmetry results in an error:: - + sage: a[0,1,0] = 4 Traceback (most recent call last): ... - ValueError: By antisymmetry, the component cannot have a nonzero value for the indices (0, 1, 0) + ValueError: by antisymmetry, the component cannot have a nonzero value for the indices (0, 1, 0) sage: a[0,1,0] = 0 # OK sage: a[2,0,1] = 3 # OK The full antisymmetry is preserved by the arithmetics:: - + sage: b = CompFullyAntiSym(QQ, V.basis(), 3) sage: b[0,1,2] = -4 sage: s = a + 2*b ; s - fully antisymmetric 3-indices components w.r.t. [ + Fully antisymmetric 3-indices components w.r.t. [ (1, 0, 0), (0, 1, 0), (0, 0, 1) @@ -4392,7 +4365,7 @@ class CompFullyAntiSym(CompWithSym): [[0, -5, 0], [5, 0, 0], [0, 0, 0]]]) It is lost if the added object is not fully antisymmetric:: - + sage: b1 = CompWithSym(QQ, V.basis(), 3, antisym=(0,1)) # b1 has only antisymmetry on index positions (0,1) sage: b1[0,1,2] = -4 sage: s = a + 2*b1 ; s # the result has the same symmetry as b1: @@ -4421,14 +4394,14 @@ class CompFullyAntiSym(CompWithSym): True """ - def __init__(self, ring, frame, nb_indices, start_index=0, + def __init__(self, ring, frame, nb_indices, start_index=0, output_formatter=None): r""" - TEST:: - + TESTS:: + sage: from sage.tensor.modules.comp import CompFullyAntiSym - sage: CompFullyAntiSym(ZZ, (1,2,3), 2) - fully antisymmetric 2-indices components w.r.t. (1, 2, 3) + sage: C = CompFullyAntiSym(ZZ, (1,2,3), 2) + sage: TestSuite(C).run() """ CompWithSym.__init__(self, ring, frame, nb_indices, start_index, @@ -4436,58 +4409,57 @@ def __init__(self, ring, frame, nb_indices, start_index=0, def _repr_(self): r""" - String representation of the object. - - EXAMPLE:: - + Return a string representation of ``self``. + + EXAMPLES:: + sage: from sage.tensor.modules.comp import CompFullyAntiSym - sage: c = CompFullyAntiSym(ZZ, (1,2,3), 4) - sage: c._repr_() - 'fully antisymmetric 4-indices components w.r.t. (1, 2, 3)' + sage: CompFullyAntiSym(ZZ, (1,2,3), 4) + Fully antisymmetric 4-indices components w.r.t. (1, 2, 3) """ - return "fully antisymmetric " + str(self._nid) + "-indices" + \ + return "Fully antisymmetric " + str(self._nid) + "-indices" + \ " components w.r.t. " + str(self._frame) - + def _new_instance(self): r""" Creates a :class:`CompFullyAntiSym` instance w.r.t. the same frame, and with the same number of indices. EXAMPLE:: - + sage: from sage.tensor.modules.comp import CompFullyAntiSym sage: c = CompFullyAntiSym(ZZ, (1,2,3), 4) sage: c._new_instance() - fully antisymmetric 4-indices components w.r.t. (1, 2, 3) - + Fully antisymmetric 4-indices components w.r.t. (1, 2, 3) + """ - return CompFullyAntiSym(self._ring, self._frame, self._nid, self._sindex, + return CompFullyAntiSym(self._ring, self._frame, self._nid, self._sindex, self._output_formatter) def __add__(self, other): r""" - Component addition. - + Component addition. + INPUT: - + - ``other`` -- components of the same number of indices and defined on the same frame as ``self`` - + OUTPUT: - + - components resulting from the addition of ``self`` and ``other`` - + EXAMPLES:: - + sage: from sage.tensor.modules.comp import CompFullyAntiSym sage: a = CompFullyAntiSym(ZZ, (1,2,3), 2) sage: a[0,1], a[1,2] = 4, 5 sage: b = CompFullyAntiSym(ZZ, (1,2,3), 2) sage: b[0,1], b[0,2] = 2, -3 sage: s = a.__add__(b) ; s # the antisymmetry is kept - fully antisymmetric 2-indices components w.r.t. (1, 2, 3) + Fully antisymmetric 2-indices components w.r.t. (1, 2, 3) sage: s[:] [ 0 6 -3] [-6 0 5] @@ -4505,23 +4477,23 @@ def __add__(self, other): [ 7 -5 0] sage: s == a + c True - + """ if other == 0: return +self if not isinstance(other, Components): - raise TypeError("The second argument for the addition must be a " + - "an instance of Components.") + raise TypeError("the second argument for the addition must be a " + + "an instance of Components") if isinstance(other, CompFullyAntiSym): if other._frame != self._frame: - raise TypeError("The two sets of components are not defined " + - "on the same frame.") + raise ValueError("the two sets of components are not defined " + + "on the same frame") if other._nid != self._nid: - raise TypeError("The two sets of components do not have the " + - "same number of indices.") + raise ValueError("the two sets of components do not have the " + + "same number of indices") if other._sindex != self._sindex: - raise TypeError("The two sets of components do not have the " + - "same starting index.") + raise ValueError("the two sets of components do not have the " + + "same starting index") result = self.copy() for ind, val in other._comp.iteritems(): result[[ind]] += val @@ -4529,33 +4501,33 @@ def __add__(self, other): else: return CompWithSym.__add__(self, other) - + #****************************************************************************** class KroneckerDelta(CompFullySym): r""" Kronecker delta `\delta_{ij}`. - + INPUT: - ``ring`` -- commutative ring in which each component takes its value - - ``frame`` -- frame with respect to which the components are defined; + - ``frame`` -- frame with respect to which the components are defined; whatever type ``frame`` is, it should have some method ``__len__()`` implemented, so that ``len(frame)`` returns the dimension, i.e. the size of a single index range - - ``start_index`` -- (default: 0) first value of a single index; + - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey - ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound - method called to format the output of the component access - operator ``[...]`` (method __getitem__); ``output_formatter`` must take - 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and - the second one, if any, some format specification. + ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. + - ``output_formatter`` -- (default: None) function or unbound + method called to format the output of the component access + operator ``[...]`` (method ``__getitem__``); ``output_formatter`` must + take 1 or 2 arguments: the first argument must be an instance of + ``ring`` and the second one, if any, some format specification EXAMPLES: The Kronecker delta on a 3-dimensional space:: - + sage: from sage.tensor.modules.comp import KroneckerDelta sage: V = VectorSpace(QQ,3) sage: d = KroneckerDelta(QQ, V.basis()) ; d @@ -4566,16 +4538,16 @@ class KroneckerDelta(CompFullySym): [0 0 1] One can read, but not set, the components of a Kronecker delta:: - + sage: d[1,1] 1 sage: d[1,1] = 2 Traceback (most recent call last): ... - NotImplementedError: The components of a Kronecker delta cannot be changed. + TypeError: the components of a Kronecker delta cannot be changed Examples of use with output formatters:: - + sage: d = KroneckerDelta(QQ, V.basis(), output_formatter=Rational.numerical_approx) sage: d[:] # default format (53 bits of precision) [ 1.00000000000000 0.000000000000000 0.000000000000000] @@ -4588,51 +4560,49 @@ class KroneckerDelta(CompFullySym): sage: d = KroneckerDelta(QQ, V.basis(), output_formatter=str) sage: d[:] [['1', '0', '0'], ['0', '1', '0'], ['0', '0', '1']] - - + """ def __init__(self, ring, frame, start_index=0, output_formatter=None): r""" TEST:: sage: from sage.tensor.modules.comp import KroneckerDelta - sage: KroneckerDelta(ZZ, (1,2,3)) - Kronecker delta of size 3x3 + sage: d = KroneckerDelta(ZZ, (1,2,3)) + sage: TestSuite(d).run() """ - CompFullySym.__init__(self, ring, frame, 2, start_index, + CompFullySym.__init__(self, ring, frame, 2, start_index, output_formatter) for i in range(self._sindex, self._dim + self._sindex): self._comp[(i,i)] = self._ring(1) def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: from sage.tensor.modules.comp import KroneckerDelta - sage: d = KroneckerDelta(ZZ, (1,2,3)) - sage: d._repr_() - 'Kronecker delta of size 3x3' + sage: KroneckerDelta(ZZ, (1,2,3)) + Kronecker delta of size 3x3 """ n = str(self._dim) - return "Kronecker delta of size " + n + "x" + n - + return "Kronecker delta of size " + n + "x" + n + def __setitem__(self, args, value): r""" Should not be used (the components of a Kronecker delta are constant) - + EXAMPLE:: - + sage: from sage.tensor.modules.comp import KroneckerDelta sage: d = KroneckerDelta(ZZ, (1,2,3)) - sage: d.__setitem__((0,0), 1) + sage: d.__setitem__((0,0), 1) Traceback (most recent call last): ... - NotImplementedError: The components of a Kronecker delta cannot be changed. + TypeError: the components of a Kronecker delta cannot be changed """ - raise NotImplementedError("The components of a Kronecker delta " + - "cannot be changed.") + raise TypeError("the components of a Kronecker delta cannot be changed") + diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index bba6e2899cd..d319c5ea18c 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -2,51 +2,51 @@ Free modules of finite rank The class :class:`FiniteRankFreeModule` implements free modules of finite rank -over a commutative ring. +over a commutative ring. A *free module of finite rank* over a commutative ring `R` is a module `M` over `R` that admits a *finite basis*, i.e. a finite familly of linearly independent -generators. Since `R` is commutative, it has the invariant basis number -property, so that the rank of the free module `M` is defined uniquely, as the -cardinality of any basis of `M`. +generators. Since `R` is commutative, it has the invariant basis number +property, so that the rank of the free module `M` is defined uniquely, as the +cardinality of any basis of `M`. -No distinguished basis of `M` is assumed. On the contrary, many bases can be -introduced on the free module along with change-of-basis rules (as module +No distinguished basis of `M` is assumed. On the contrary, many bases can be +introduced on the free module along with change-of-basis rules (as module automorphisms). Each -module element has then various representations over the various bases. +module element has then various representations over the various bases. .. NOTE:: - The class :class:`FiniteRankFreeModule` does not inherit from - :class:`~sage.modules.free_module.FreeModule_generic` since the latter - is a derived class of :class:`~sage.modules.module.Module_old`, + The class :class:`FiniteRankFreeModule` does not inherit from + :class:`~sage.modules.free_module.FreeModule_generic` since the latter + is a derived class of :class:`~sage.modules.module.Module_old`, which does not conform to the new coercion model. Moreover, the class :class:`~sage.modules.free_module.FreeModule_generic` seems to assume a distinguished basis (cf. its method :meth:`~sage.modules.free_module.FreeModule_generic.basis`). - Besides, the class :class:`FiniteRankFreeModule` does not inherit - from the class + Besides, the class :class:`FiniteRankFreeModule` does not inherit + from the class :class:`~sage.combinat.free_module.CombinatorialFreeModule` (which conforms - to the new coercion model) since this class is devoted to modules with a - distinguished basis. + to the new coercion model) since this class is devoted to modules with a + distinguished basis. -For the above reasons, the class :class:`FiniteRankFreeModule` inherits +For the above reasons, the class :class:`FiniteRankFreeModule` inherits directly from the generic class :class:`~sage.structure.parent.Parent` with the category set to :class:`~sage.categories.modules.Modules` -and not to +and not to :class:`~sage.categories.modules_with_basis.ModulesWithBasis`. -TODO: - -- implement submodules -- implement free module homomorphisms (at the moment, only two specific kinds - of homomorphisms are implemented: endomorphisms, cf. :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism`, - and linear forms, cf. - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm`) -- create a FreeModules category (cf. the *TODO* statement in the documentation - of :class:`~sage.categories.modules.Modules`: *Implement a FreeModules(R) - category, when so prompted by a concrete use case*) +.. TODO:: + - implement submodules + - implement free module homomorphisms (at the moment, only two specific + kinds of homomorphisms are implemented: endomorphisms, cf. + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism`, + and linear forms, cf. + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm`) + - create a FreeModules category (cf. the *TODO* statement in the + documentation of :class:`~sage.categories.modules.Modules`: *Implement + a ``FreeModules(R)`` category, when so prompted by a concrete use case*) AUTHORS: @@ -57,23 +57,23 @@ Let us define a free module of rank 2 over `\ZZ`:: sage: M = FiniteRankFreeModule(ZZ, 2, name='M') ; M - rank-2 free module M over the Integer Ring - -We introduce a first basis on M:: + Rank-2 free module M over the Integer Ring + +We introduce a first basis on ``M``:: sage: e = M.basis('e') ; e - basis (e_0,e_1) on the rank-2 free module M over the Integer Ring + Basis (e_0,e_1) on the Rank-2 free module M over the Integer Ring The elements of the basis are of course module elements:: sage: e[0] - element e_0 of the rank-2 free module M over the Integer Ring + Element e_0 of the Rank-2 free module M over the Integer Ring sage: e[1] - element e_1 of the rank-2 free module M over the Integer Ring + Element e_1 of the Rank-2 free module M over the Integer Ring sage: e[0].parent() - rank-2 free module M over the Integer Ring + Rank-2 free module M over the Integer Ring -We define a module element by its components w.r.t. basis e:: +We define a module element by its components w.r.t. basis ``e``:: sage: u = M([2,-3], basis=e, name='u') sage: u.view(basis=e) @@ -91,7 +91,8 @@ sage: u == 2*e[0] - 3*e[1] True -We define a second basis on M by linking it to e via a module automorphism:: +We define a second basis on M by linking it to ``e`` via a module +automorphism:: sage: a = M.automorphism() sage: a.set_comp(basis=e)[0,1] = -1 ; a.set_comp(basis=e)[1,0] = 1 # only the non-zero components have to be set @@ -99,32 +100,33 @@ [ 0 -1] [ 1 0] sage: f = e.new_basis(a, 'f') ; f - basis (f_0,f_1) on the rank-2 free module M over the Integer Ring + Basis (f_0,f_1) on the Rank-2 free module M over the Integer Ring sage: f[0].view() f_0 = e_1 sage: f[1].view() f_1 = -e_0 - -We may check that the basis f is the image of e by the automorphism a:: + +We may check that the basis ``f`` is the image of ``e`` by the +automorphism ``a``:: sage: f[0] == a(e[0]) True sage: f[1] == a(e[1]) True -We introduce a new module element via its components w.r.t. basis f:: +We introduce a new module element via its components w.r.t. basis ``f``:: sage: v = M([2,4], basis=f, name='v') sage: v.view(basis=f) v = 2 f_0 + 4 f_1 - -The sum of the two module elements u and v can be performed even if they have -been defined on different bases, thanks to the known relation between the -two bases:: + +The sum of the two module elements ``u`` and ``v`` can be performed even if +they have been defined on different bases, thanks to the known relation +between the two bases:: sage: s = u + v ; s - element u+v of the rank-2 free module M over the Integer Ring - + Element u+v of the Rank-2 free module M over the Integer Ring + We can view the result in either basis:: sage: s.view(basis=e) # a shortcut is s.view(), e being the default basis @@ -142,25 +144,31 @@ Tensor products are implemented:: sage: t = u*v ; t - type-(2,0) tensor u*v on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor u*v on the Rank-2 free module M over the Integer Ring sage: t.parent() - free module of type-(2,0) tensors on the rank-2 free module M over the Integer Ring + Free module of type-(2,0) tensors on the + Rank-2 free module M over the Integer Ring sage: t.view() - u*v = -8 e_0*e_0 + 4 e_0*e_1 + 12 e_1*e_0 - 6 e_1*e_1 + u*v = -8 e_0*e_0 + 4 e_0*e_1 + 12 e_1*e_0 - 6 e_1*e_1 -The automorphism a is considered as a tensor of type (1,1) on M:: +The automorphism ``a`` is considered as a tensor of type `(1,1)` on ``M``:: sage: a.parent() - free module of type-(1,1) tensors on the rank-2 free module M over the Integer Ring + Free module of type-(1,1) tensors on the + Rank-2 free module M over the Integer Ring sage: a.view() -e_0*e^1 + e_1*e^0 -As such, we can form its tensor product with t, yielding a tensor of type (3,1):: +As such, we can form its tensor product with ``t``, yielding a tensor of +type `(3,1)`:: sage: (t*a).parent() - free module of type-(3,1) tensors on the rank-2 free module M over the Integer Ring + Free module of type-(3,1) tensors on the + Rank-2 free module M over the Integer Ring sage: (t*a).view() - 8 e_0*e_0*e_0*e^1 - 8 e_0*e_0*e_1*e^0 - 4 e_0*e_1*e_0*e^1 + 4 e_0*e_1*e_1*e^0 - 12 e_1*e_0*e_0*e^1 + 12 e_1*e_0*e_1*e^0 + 6 e_1*e_1*e_0*e^1 - 6 e_1*e_1*e_1*e^0 + 8 e_0*e_0*e_0*e^1 - 8 e_0*e_0*e_1*e^0 - 4 e_0*e_1*e_0*e^1 + + 4 e_0*e_1*e_1*e^0 - 12 e_1*e_0*e_0*e^1 + 12 e_1*e_0*e_1*e^0 + + 6 e_1*e_1*e_0*e^1 - 6 e_1*e_1*e_1*e^0 """ #****************************************************************************** @@ -176,96 +184,97 @@ from sage.structure.unique_representation import UniqueRepresentation from sage.structure.parent import Parent from sage.categories.modules import Modules -from free_module_tensor import FiniteRankFreeModuleElement +from sage.categories.rings import Rings +from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement class FiniteRankFreeModule(UniqueRepresentation, Parent): r""" Free module of finite rank over a commutative ring `R`. - + .. NOTE:: - - The class :class:`FiniteRankFreeModule` does not inherit from - :class:`~sage.modules.free_module.FreeModule_generic` since the latter - is a derived class of :class:`~sage.modules.module.Module_old`, + + The class :class:`FiniteRankFreeModule` does not inherit from + :class:`~sage.modules.free_module.FreeModule_generic` since the latter + is a derived class of :class:`~sage.modules.module.Module_old`, which does not conform to the new coercion model. - Besides, the class :class:`FiniteRankFreeModule` does not inherit + Besides, the class :class:`FiniteRankFreeModule` does not inherit from the class :class:`CombinatorialFreeModule` since the latter is - devoted to modules *with a basis*. + devoted to modules *with a basis*. .. NOTE:: - Following the recommendation exposed in + Following the recommendation exposed in `trac ticket 16427 `_ - the class :class:`FiniteRankFreeModule` inherits directly from + the class :class:`FiniteRankFreeModule` inherits directly from :class:`~sage.structure.parent.Parent` and not from the Cython class - :class:`~sage.modules.module.Module`. - - The class :class:`FiniteRankFreeModule` is a Sage *Parent* class whose - elements belong to the class - :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`. + :class:`~sage.modules.module.Module`. + + The class :class:`FiniteRankFreeModule` is a Sage *Parent* class whose + elements belong to the class + :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`. INPUT: - - - ``ring`` -- commutative ring `R` over which the free module is - constructed. - - ``rank`` -- (positive integer) rank of the free module - - ``name`` -- (string; default: None) name given to the free module - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the free - module; if none is provided, it is set to ``name`` - - ``start_index`` -- (integer; default: 0) lower bound of the range of + + - ``ring`` -- commutative ring `R` over which the free module is + constructed + - ``rank`` -- positive integer; rank of the free module + - ``name`` -- (default: ``None``) string; name given to the free module + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the freemodule; if none is provided, it is set to ``name`` + - ``start_index`` -- (default: 0) integer; lower bound of the range of indices in bases defined on the free module - - ``output_formatter`` -- (default: None) function or unbound - method called to format the output of the tensor components; - ``output_formatter`` must take 1 or 2 arguments: the 1st argument must be - an element of the ring `R` and the second one, if any, some format - specification. + - ``output_formatter`` -- (default: ``None``) function or unbound + method called to format the output of the tensor components; + ``output_formatter`` must take 1 or 2 arguments: the first argument + must be an element of the ring `R` and the second one, if any, some + format specification EXAMPLES: - + Free module of rank 3 over `\ZZ`:: - + sage: M = FiniteRankFreeModule(ZZ, 3) ; M - rank-3 free module over the Integer Ring + Rank-3 free module over the Integer Ring sage: M = FiniteRankFreeModule(ZZ, 3, name='M') ; M # declaration with a name - rank-3 free module M over the Integer Ring + Rank-3 free module M over the Integer Ring sage: M.category() Category of modules over Integer Ring sage: M.base_ring() Integer Ring sage: M.rank() 3 - - If the base ring is a field, the free module is in the category of vector + + If the base ring is a field, the free module is in the category of vector spaces:: - + sage: V = FiniteRankFreeModule(QQ, 3, name='V') ; V - rank-3 free module V over the Rational Field + Rank-3 free module V over the Rational Field sage: V.category() Category of vector spaces over Rational Field The LaTeX output is adjusted via the parameter ``latex_name``:: - + sage: latex(M) # the default is the symbol provided in the string ``name`` M sage: M = FiniteRankFreeModule(ZZ, 3, name='M', latex_name=r'\mathcal{M}') sage: latex(M) \mathcal{M} - M is a *parent* object, whose elements are instances of + M is a *parent* object, whose elements are instances of :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`:: - + sage: v = M.an_element() ; v - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement sage: isinstance(v, FiniteRankFreeModuleElement) - True + True sage: v in M True sage: M.is_parent_of(v) True The free module M has no distinguished basis:: - + sage: M in ModulesWithBasis(ZZ) False sage: M in Modules(ZZ) @@ -275,41 +284,40 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): the first defined basis being considered as the *default basis*, meaning it can be skipped in function arguments required a basis (this can be changed by means of the method :meth:`set_default_basis`):: - + sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: M.default_basis() - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + Constructing an element of ``M`` from (the integer) 0 yields + the zero element of ``M``:: - Elements can be constructed by means of the __call__ operator acting - on the parent; 0 yields the zero element:: - sage: M(0) - element zero of the rank-3 free module M over the Integer Ring + Element zero of the Rank-3 free module M over the Integer Ring sage: M(0) is M.zero() True - - Non-zero elements are constructed by providing their components in + + Non-zero elements are constructed by providing their components in a given basis:: - + sage: v = M([-1,0,3]) ; v # components in the default basis (e) - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: v.view() -e_0 + 3 e_2 sage: f = M.basis('f') sage: v = M([-1,0,3], basis=f) ; v # components in a specific basis - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: v.view(f) -f_0 + 3 f_2 sage: v = M([-1,0,3], basis=f, name='v') ; v - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: v.view(f) v = -f_0 + 3 f_2 - An alternative is to construct the element from an empty list of components - and to set the nonzero components afterwards:: - + An alternative is to construct the element from an empty list of + componentsand to set the nonzero components afterwards:: + sage: v = M([], name='v') sage: v.set_comp(e)[0] = -1 sage: v.set_comp(e)[2] = 3 @@ -317,22 +325,22 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): v = -e_0 + 3 e_2 Indices on the free module, such as indices labelling the element of a - basis, are provided by the generator method :meth:`irange`. By default, + basis, are provided by the generator method :meth:`irange`. By default, they range from 0 to the module's rank minus one:: - + sage: for i in M.irange(): print i, 0 1 2 - This can be changed via the parameter ``start_index`` in the module + This can be changed via the parameter ``start_index`` in the module construction:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: for i in M.irange(): print i, 1 2 3 The parameter ``output_formatter`` in the constructor of the free module is used to set the output format of tensor components:: - + sage: M = FiniteRankFreeModule(QQ, 3, output_formatter=Rational.numerical_approx) sage: e = M.basis('e') sage: v = M([1/3, 0, -2], basis=e) @@ -340,56 +348,33 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): [0.333333333333333, 0.000000000000000, -2.00000000000000] sage: v.view(e) # default format (53 bits of precision) 0.333333333333333 e_0 - 2.00000000000000 e_2 - sage: v.view(e, format_spec=10) # 10 bits of precision + sage: v.view(e, format_spec=10) # 10 bits of precision 0.33 e_0 - 2.0 e_2 - - All tests from the suite for the category - :class:`~sage.categories.modules.Modules` are passed:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: TestSuite(M).run(verbose=True) - running ._test_additive_associativity() . . . pass - running ._test_an_element() . . . pass - running ._test_category() . . . pass - running ._test_elements() . . . - Running the test suite of self.an_element() - running ._test_category() . . . pass - running ._test_eq() . . . pass - running ._test_nonzero_equal() . . . pass - running ._test_not_implemented_methods() . . . pass - running ._test_pickling() . . . pass - pass - running ._test_elements_eq_reflexive() . . . pass - running ._test_elements_eq_symmetric() . . . pass - running ._test_elements_eq_transitive() . . . pass - running ._test_elements_neq() . . . pass - running ._test_eq() . . . pass - running ._test_not_implemented_methods() . . . pass - running ._test_pickling() . . . pass - running ._test_some_elements() . . . pass - running ._test_zero() . . . pass """ - + Element = FiniteRankFreeModuleElement - + def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, output_formatter=None): r""" - TEST:: - - sage: FiniteRankFreeModule(ZZ, 3, name='M') - rank-3 free module M over the Integer Ring + See :class:`FiniteRankFreeModule` for documentation and examples. + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: TestSuite(M).run() + sage: e = M.basis('e') + sage: TestSuite(M).run() + sage: f = M.basis('f') + sage: TestSuite(M).run() - See :class:`FiniteRankFreeModule` for documentation / doctests. - """ - if not ring.is_commutative(): - raise TypeError("The module base ring must be commutative.") + if ring not in Rings().Commutative(): + raise TypeError("the module base ring must be commutative") Parent.__init__(self, base=ring, category=Modules(ring)) self._ring = ring # same as self._base - self._rank = rank + self._rank = rank self._name = name if latex_name is None: self._latex_name = self._name @@ -397,7 +382,7 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, self._latex_name = latex_name self._sindex = start_index self._output_formatter = output_formatter - # Dictionary of the tensor modules built on self + # Dictionary of the tensor modules built on self # (dict. keys = (k,l) --the tensor type) self._tensor_modules = {(1,0): self} # self is considered as the set of # tensors of type (1,0) @@ -406,347 +391,351 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, self._basis_changes = {} # Dictionary of the changes of bases # Zero element: if not hasattr(self, '_zero_element'): - self._zero_element = self._element_constructor_(name='zero', + self._zero_element = self._element_constructor_(name='zero', latex_name='0') - - #### Methods required for any Parent - def _element_constructor_(self, comp=[], basis=None, name=None, + #### Methods required for any Parent + + def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None): r""" - Construct an element of the module - + Construct an element of ``self``. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: v = M._element_constructor_(comp=[1,0,-2], basis=e, name='v') ; v - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: v.view() v = e_0 - 2 e_2 sage: v == M([1,0,-2]) True sage: v = M._element_constructor_(0) ; v - element zero of the rank-3 free module M over the Integer Ring + Element zero of the Rank-3 free module M over the Integer Ring sage: v = M._element_constructor_() ; v - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring """ if comp == 0: return self._zero_element resu = self.element_class(self, name=name, latex_name=latex_name) - if comp != []: + if comp: resu.set_comp(basis)[:] = comp return resu def _an_element_(self): r""" Construct some (unamed) element of the module. - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: v = M._an_element_() ; v - element of the rank-3 free module M over the Integer Ring + sage: v = M._an_element_(); v + Element of the Rank-3 free module M over the Integer Ring sage: v.view() e_0 + e_1 + e_2 sage: v == M.an_element() True sage: v.parent() - rank-3 free module M over the Integer Ring + Rank-3 free module M over the Integer Ring """ + if self._def_basis is None: + self.basis('e') resu = self.element_class(self) - if self._def_basis is not None: - resu.set_comp()[:] = [self._ring.an_element() for i in - range(self._rank)] + resu.set_comp()[:] = [self._ring.an_element() for i in range(self._rank)] return resu - - #### End of methods required for any Parent + + #### End of methods required for any Parent #### Methods to be redefined by derived classes #### def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M._repr_() - 'rank-3 free module M over the Integer Ring' + sage: FiniteRankFreeModule(ZZ, 3, name='M') + Rank-3 free module M over the Integer Ring """ - description = "rank-" + str(self._rank) + " free module " + description = "Rank-{} free module ".format(self._rank) if self._name is not None: description += self._name + " " description += "over the " + str(self._ring) return description - + def tensor_module(self, k, l): r""" - Return the free module of all tensors of type (k,l) defined on - ``self``. - - INPUT: - - - ``k`` -- (non-negative integer) the contravariant rank, the tensor type - being (k,l) - - ``l`` -- (non-negative integer) the covariant rank, the tensor type - being (k,l) - + Return the free module of all tensors of type `(k, l)` defined on + ``self``. + + INPUT: + + - ``k`` -- non-negative integer; the contravariant rank, the tensor + type being `(k, l)` + - ``l`` -- non-negative integer; the covariant rank, the tensor type + being `(k, l)` + OUTPUT: - - instance of - :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` - representing the free module - `T^{(k,l)}(M)` of type-`(k,l)` tensors on the free module ``self``. - + - instance of + :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` + representing the free module + `T^{(k,l)}(M)` of type-`(k,l)` tensors on the free module ``self`` + EXAMPLES: - + Tensor modules over a free module over `\ZZ`:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: T = M.tensor_module(1,2) ; T - free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,2) tensors on the Rank-3 free module M + over the Integer Ring sage: T.an_element() - type-(1,2) tensor on the rank-3 free module M over the Integer Ring - + Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring + Tensor modules are unique:: - + sage: M.tensor_module(1,2) is T True - The base module is itself the module of all type-(1,0) tensors:: - + The base module is itself the module of all type-`(1,0)` tensors:: + sage: M.tensor_module(1,0) is M True - + """ - from tensor_free_module import TensorFreeModule + from sage.tensor.modules.tensor_free_module import TensorFreeModule if (k,l) not in self._tensor_modules: self._tensor_modules[(k,l)] = TensorFreeModule(self, (k,l)) return self._tensor_modules[(k,l)] def basis(self, symbol=None, latex_symbol=None): - r""" + r""" Define a basis of the free module. - + If the basis specified by the given symbol already exists, it is simply returned. - + INPUT: - - - ``symbol`` -- (string; default: None) a letter (of a few letters) to - denote a generic element of the basis; if None, the module's default - basis is returned. - - ``latex_symbol`` -- (string; default: None) symbol to denote a - generic element of the basis; if None, the value of ``symbol`` is - used. + + - ``symbol`` -- (default: ``None``) string; a letter (of a few letters) + to denote a generic element of the basis; if ``None``, the module's + default basis is returned + - ``latex_symbol`` -- (default: ``None``) string; symbol to denote a + generic element of the basis; if ``None``, the value of ``symbol`` + is used OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` - representing a basis on ``self``. - + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing a basis on ``self`` + EXAMPLES: - + Bases on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: e[0] - element e_0 of the rank-3 free module M over the Integer Ring + Element e_0 of the Rank-3 free module M over the Integer Ring sage: latex(e) \left(e_0,e_1,e_2\right) The LaTeX symbol can be set explicitely, as the second argument of :meth:`basis`:: - + sage: eps = M.basis('eps', r'\epsilon') ; eps - basis (eps_0,eps_1,eps_2) on the rank-3 free module M over the Integer Ring + Basis (eps_0,eps_1,eps_2) on the Rank-3 free module M + over the Integer Ring sage: latex(eps) \left(\epsilon_0,\epsilon_1,\epsilon_2\right) - + If the provided symbol is that of an already defined basis, the latter is returned (no new basis is created):: - + sage: M.basis('e') is e True sage: M.basis('eps') is eps True If no symbol is provided, the module's default basis is returned:: - + sage: M.basis() - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: M.basis() is e True sage: M.basis() is M.default_basis() True - - The individual elements of the basis are labelled according the + + The individual elements of the basis are labelled according the parameter ``start_index`` provided at the free module construction:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: e[1] - element e_1 of the rank-3 free module M over the Integer Ring - + Element e_1 of the Rank-3 free module M over the Integer Ring + """ from free_module_basis import FreeModuleBasis if symbol is None: return self.default_basis() - else: - for other in self._known_bases: - if symbol == other._symbol: - return other - return FreeModuleBasis(self, symbol, latex_symbol) + + for other in self._known_bases: + if symbol == other._symbol: + return other + return FreeModuleBasis(self, symbol, latex_symbol) - def tensor(self, tensor_type, name=None, latex_name=None, sym=None, + def tensor(self, tensor_type, name=None, latex_name=None, sym=None, antisym=None): r""" - Construct a tensor on the free module. - + Construct a tensor on the free module. + INPUT: - - - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and - l the covariant rank - - ``name`` -- (string; default: None) name given to the tensor - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - tensor; if none is provided, the LaTeX symbol is set to ``name`` - - ``sym`` -- (default: None) a symmetry or a list of symmetries among - the tensor arguments: each symmetry is described by a tuple - containing the positions of the involved arguments, with the - convention position=0 for the first argument. For instance: - - * sym=(0,1) for a symmetry between the 1st and 2nd arguments - * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd + + - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the + contravariant rank and ``l`` the covariant rank + - ``name`` -- (default: ``None``) string; name given to the tensor + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the tensor; if none is provided, the LaTeX symbol is set + to ``name`` + - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries + among the tensor arguments: each symmetry is described by a tuple + containing the positions of the involved arguments, with the + convention ``position = 0`` for the first argument. For instance: + + * ``sym = (0,1)`` for a symmetry between the 1st and 2nd arguments + * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd arguments and a symmetry between the 2nd, 4th and 5th arguments. - - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries - among the arguments, with the same convention as for ``sym``. - + - ``antisym`` -- (default: ``None``) antisymmetry or list of + antisymmetries among the arguments, with the same convention + as for ``sym`` + OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` - representing the tensor defined on ``self`` with the provided - characteristics. - + + - instance of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + representing the tensor defined on ``self`` with the provided + characteristics + EXAMPLES: - + Tensors on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,0), name='t') ; t - element t of the rank-3 free module M over the Integer Ring + Element t of the Rank-3 free module M over the Integer Ring sage: t = M.tensor((0,1), name='t') ; t - linear form t on the rank-3 free module M over the Integer Ring + Linear form t on the Rank-3 free module M over the Integer Ring sage: t = M.tensor((1,1), name='t') ; t - endomorphism t on the rank-3 free module M over the Integer Ring + Endomorphism t on the Rank-3 free module M over the Integer Ring sage: t = M.tensor((0,2), name='t', sym=(0,1)) ; t - symmetric bilinear form t on the rank-3 free module M over the Integer Ring + Symmetric bilinear form t on the + Rank-3 free module M over the Integer Ring sage: t = M.tensor((0,2), name='t', antisym=(0,1)) ; t - alternating form t of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form t of degree 2 on the + Rank-3 free module M over the Integer Ring sage: t = M.tensor((1,2), name='t') ; t - type-(1,2) tensor t on the rank-3 free module M over the Integer Ring - - See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + Type-(1,2) tensor t on the Rank-3 free module M over the Integer Ring + + See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` for more examples and documentation. - + """ from free_module_tensor_spec import FreeModuleEndomorphism from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm - if tensor_type==(1,0): + if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) - #!# the above is preferable to + #!# the above is preferable to # return FiniteRankFreeModuleElement(self, name=name, latex_name=latex_name) # because self.element_class is a (dynamically created) derived - # class of FiniteRankFreeModuleElement - elif tensor_type==(0,1): + # class of FiniteRankFreeModuleElement + elif tensor_type == (0,1): return FreeModuleLinForm(self, name=name, latex_name=latex_name) - elif tensor_type==(1,1): - return FreeModuleEndomorphism(self, name=name, - latex_name=latex_name) - elif tensor_type[0]==0 and tensor_type[1]>1 and antisym is not None\ - and antisym !=[]: + elif tensor_type == (1,1): + return FreeModuleEndomorphism(self, name=name, + latex_name=latex_name) + elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: if isinstance(antisym, list): antisym0 = antisym[0] else: antisym0 = antisym - if len(antisym0)==tensor_type[1]: - return FreeModuleAltForm(self, tensor_type[1], name=name, + if len(antisym0) == tensor_type[1]: + return FreeModuleAltForm(self, tensor_type[1], name=name, latex_name=latex_name) else: - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, - latex_name=latex_name, sym=sym, + return self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, + latex_name=latex_name, sym=sym, antisym=antisym) else: return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, - latex_name=latex_name, sym=sym, - antisym=antisym) + tensor_type, name=name, + latex_name=latex_name, sym=sym, + antisym=antisym) + - def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): r""" Construct a tensor on the free module from a set of components. - + The tensor symmetries are deduced from those of the components. - + INPUT: - - - ``tensor_type`` -- pair (k,l) with k being the contravariant rank - and l the covariant rank - - ``comp`` -- instance of :class:`~sage.tensor.modules.comp.Components` + + - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the + contravariant rank and ``l`` the covariant rank + - ``comp`` -- instance of :class:`~sage.tensor.modules.comp.Components` representing the tensor components in a given basis - - ``name`` -- (string; default: None) name given to the tensor - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - tensor; if none is provided, the LaTeX symbol is set to ``name`` - + - ``name`` -- (default: ``None``) string; name given to the tensor + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the tensor; if none is provided, the LaTeX symbol is set to ``name`` + OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` - representing the tensor defined on ``self`` with the provided + + - instance of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + representing the tensor defined on ``self`` with the provided characteristics. - + EXAMPLES: - + Construction of a tensor of rank 1:: - + sage: from sage.tensor.modules.comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: c = Components(ZZ, e, 1) sage: c[:] [0, 0, 0] sage: c[:] = [-1,4,2] sage: t = M.tensor_from_comp((1,0), c) - sage: t - element of the rank-3 free module M over the Integer Ring + sage: t + Element of the Rank-3 free module M over the Integer Ring sage: t.view(e) -e_0 + 4 e_1 + 2 e_2 sage: t = M.tensor_from_comp((0,1), c) ; t - linear form on the rank-3 free module M over the Integer Ring + Linear form on the Rank-3 free module M over the Integer Ring sage: t.view(e) -e^0 + 4 e^1 + 2 e^2 Construction of a tensor of rank 2:: - + sage: c = CompFullySym(ZZ, e, 2) sage: c[0,0], c[1,2] = 4, 5 sage: t = M.tensor_from_comp((0,2), c) ; t - symmetric bilinear form on the rank-3 free module M over the Integer Ring + Symmetric bilinear form on the + Rank-3 free module M over the Integer Ring sage: t.symmetries() symmetry: (0, 1); no antisymmetry sage: t.view(e) @@ -754,10 +743,11 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): sage: c = CompFullyAntiSym(ZZ, e, 2) sage: c[0,1], c[1,2] = 4, 5 sage: t = M.tensor_from_comp((0,2), c) ; t - alternating form of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form of degree 2 on the + Rank-3 free module M over the Integer Ring sage: t.view(e) 4 e^0/\e^1 + 5 e^1/\e^2 - + """ from free_module_tensor_spec import FreeModuleEndomorphism from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm @@ -765,34 +755,34 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): # # 0/ Compatibility checks: if comp._ring is not self._ring: - raise TypeError("The components are not defined on the same" + - " ring as the module.") + raise TypeError("the components are not defined on the same" + + " ring as the module") if comp._frame not in self._known_bases: - raise TypeError("The components are not defined on a basis of" + - " the module.") + raise TypeError("the components are not defined on a basis of" + + " the module") if comp._nid != tensor_type[0] + tensor_type[1]: - raise TypeError("Number of component indices not compatible with "+ - " the tensor type.") + raise TypeError("number of component indices not compatible with "+ + " the tensor type") # # 1/ Construction of the tensor: if tensor_type == (1,0): resu = self.element_class(self, name=name, latex_name=latex_name) - #!# the above is preferable to + #!# the above is preferable to # resu = FiniteRankFreeModuleElement(self, name=name, latex_name=latex_name) # because self.element_class is a (dynamically created) derived # class of FiniteRankFreeModuleElement elif tensor_type == (0,1): resu = FreeModuleLinForm(self, name=name, latex_name=latex_name) elif tensor_type == (1,1): - resu = FreeModuleEndomorphism(self, name=name, + resu = FreeModuleEndomorphism(self, name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and \ isinstance(comp, CompFullyAntiSym): - resu = FreeModuleAltForm(self, tensor_type[1], name=name, + resu = FreeModuleAltForm(self, tensor_type[1], name=name, latex_name=latex_name) else: - resu = self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, latex_name=latex_name) + resu = self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, latex_name=latex_name) # Tensor symmetries deduced from those of comp: if isinstance(comp, CompWithSym): resu._sym = comp._sym @@ -805,38 +795,40 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): def alternating_form(self, degree, name=None, latex_name=None): r""" - Construct an alternating form on the free module. - + Construct an alternating form on the free module. + INPUT: - - - ``degree`` -- the degree of the alternating form (i.e. its tensor rank) - - ``name`` -- (string; default: None) name given to the alternating - form - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - alternating form; if none is provided, the LaTeX symbol is set to - ``name`` - + + - ``degree`` -- the degree of the alternating form (i.e. its + tensor rank) + - ``name`` -- (default: ``None``) string; name given to the + alternating form + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the alternating form; if none is provided, the LaTeX symbol + is set to ``name`` + OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` - (``degree`` > 1) or - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + + - instance of + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` + (``degree`` > 1) or + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` (``degree`` = 1) EXAMPLES: - + Alternating forms on a rank-3 module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.alternating_form(2, 'a') ; a - alternating form a of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form a of degree 2 on the + Rank-3 free module M over the Integer Ring + + The nonzero components in a given basis have to be set in a second + step, thereby fully specifying the alternating form:: - The nonzero components in a given basis have to be set in a second step, - thereby fully specifying the alternating form:: - sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a.set_comp(e)[0,1] = 2 sage: a.set_comp(e)[1,2] = -3 sage: a.view(e) @@ -845,59 +837,59 @@ def alternating_form(self, degree, name=None, latex_name=None): An alternating form of degree 1 is a linear form:: sage: a = M.alternating_form(1, 'a') ; a - linear form a on the rank-3 free module M over the Integer Ring + Linear form a on the Rank-3 free module M over the Integer Ring - To construct such a form, it is preferable to call the method + To construct such a form, it is preferable to call the method :meth:`linear_form` instead:: - + sage: a = M.linear_form('a') ; a - linear form a on the rank-3 free module M over the Integer Ring - - See - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` - for further documentation. + Linear form a on the Rank-3 free module M over the Integer Ring + + See + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` + for further documentation. """ from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm if degree == 1: return FreeModuleLinForm(self, name=name, latex_name=latex_name) else: - return FreeModuleAltForm(self, degree, name=name, + return FreeModuleAltForm(self, degree, name=name, latex_name=latex_name) def linear_form(self, name=None, latex_name=None): r""" - Construct a linear form on the free module. - + Construct a linear form on the free module. + A *linear form* on a free module `M` over a ring `R` is a map - `M\rightarrow R` that is linear. It can be viewed as a tensor of type - (0,1) on `M`. + `M \rightarrow R` that is linear. It can be viewed as a tensor of type + `(0,1)` on `M`. INPUT: - - - ``name`` -- (string; default: None) name given to the linear + + - ``name`` -- (default: ``None``) string; name given to the linear form - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - linear form; if none is provided, the LaTeX symbol is set to - ``name`` - + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the linear form; if none is provided, the LaTeX symbol + is set to ``name`` + OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + + - instance of + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` EXAMPLES: - + Linear form on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') ; a - linear form A on the rank-3 free module M over the Integer Ring + Linear form A on the Rank-3 free module M over the Integer Ring sage: a[:] = [2,-1,3] # components w.r.t. the module's default basis (e) sage: a.view() A = 2 e^0 - e^1 + 3 e^2 - + A linear form maps module elements to ring elements:: sage: v = M([1,1,1]) @@ -905,14 +897,14 @@ def linear_form(self, name=None, latex_name=None): 4 Test of linearity:: - + sage: u = M([-5,-2,7]) sage: a(3*u - 4*v) == 3*a(u) - 4*a(v) True - See - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` - for further documentation. + See + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + for further documentation. """ from free_module_alt_form import FreeModuleLinForm @@ -920,44 +912,46 @@ def linear_form(self, name=None, latex_name=None): def endomorphism(self, name=None, latex_name=None): r""" - Construct an endomorphism on the free module. - + Construct an endomorphism on the free module. + INPUT: - - - ``name`` -- (string; default: None) name given to the endomorphism - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - endomorphism; if none is provided, the LaTeX symbol is set to - ``name`` - + + - ``name`` -- (default: ``None``) string; name given to the + endomorphism + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the endomorphism; if none is provided, the LaTeX symbol + is set to ``name`` + OUTPUT: - - - instance of + + - instance of :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` - + EXAMPLES: Endomorphism on a rank-3 module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.endomorphism('T') ; t - endomorphism T on the rank-3 free module M over the Integer Ring - + Endomorphism T on the Rank-3 free module M over the Integer Ring + An endomorphism is type-(1,1) tensor:: - + sage: t.parent() - free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,1) tensors on the + Rank-3 free module M over the Integer Ring sage: t.tensor_type() (1, 1) - Consequently, an endomorphism can also be created by the method + Consequently, an endomorphism can also be created by the method :meth:`tensor`:: - + sage: t = M.tensor((1,1), name='T') ; t - endomorphism T on the rank-3 free module M over the Integer Ring - - See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` - for further documentation. + Endomorphism T on the Rank-3 free module M over the Integer Ring + + See + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + for further documentation. """ from free_module_tensor_spec import FreeModuleEndomorphism @@ -966,176 +960,184 @@ def endomorphism(self, name=None, latex_name=None): def automorphism(self, name=None, latex_name=None): r""" - Construct an automorphism on the free module. - + Construct an automorphism on the free module. + INPUT: - - - ``name`` -- (string; default: None) name given to the automorphism - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - automorphism; if none is provided, the LaTeX symbol is set to - ``name`` - + + - ``name`` -- (default: ``None``) string; name given to the + automorphism + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the automorphism; if none is provided, the LaTeX symbol + is set to ``name`` + OUTPUT: - - - instance of + + - instance of :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` - + EXAMPLES: Automorphism on a rank-2 free module (vector space) on `\QQ`:: - + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: a = M.automorphism('A') ; a - automorphism A on the rank-2 free module M over the Rational Field - + Automorphism A on the Rank-2 free module M over the Rational Field + Automorphisms are tensors of type (1,1):: - + sage: a.parent() - free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field + Free module of type-(1,1) tensors on the + Rank-2 free module M over the Rational Field sage: a.tensor_type() (1, 1) See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` - for further documentation. - + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + for further documentation. + """ from free_module_tensor_spec import FreeModuleAutomorphism return FreeModuleAutomorphism(self, name=name, latex_name=latex_name) - + def identity_map(self, name='Id', latex_name=None): r""" - Construct the identity map on the free module. - + Construct the identity map on the free module. + INPUT: - - - ``name`` -- (string; default: 'Id') name given to the identity map - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - identity map; if none is provided, the LaTeX symbol is set to - ``name`` - + + - ``name`` -- (default: ``'Id'``) string; name given to the + identity map + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the identity map; if none is provided, the LaTeX symbol + is set to ``name`` + OUTPUT: - - - instance of + + - instance of :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` - + EXAMPLES: Identity map on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() ; a - identity map on the rank-3 free module M over the Integer Ring - - The LaTeX symbol is set by default to Id, but can be changed:: - + Identity map on the Rank-3 free module M over the Integer Ring + + The LaTeX symbol is set by default to `\mathrm{Id}`, but can + be changed:: + sage: latex(a) \mathrm{Id} sage: a = M.identity_map(latex_name=r'\mathrm{1}') sage: latex(a) \mathrm{1} - - The identity map is a tensor of type (1,1) on the free module:: - + + The identity map is a tensor of type `(1,1)` on the free module:: + sage: a.parent() - free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,1) tensors on the + Rank-3 free module M over the Integer Ring sage: a.tensor_type() (1, 1) See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` - for further documentation. - + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + for further documentation. + """ from free_module_tensor_spec import FreeModuleIdentityMap return FreeModuleIdentityMap(self, name=name, latex_name=latex_name) - + def sym_bilinear_form(self, name=None, latex_name=None): r""" - Construct a symmetric bilinear form on the free module. - + Construct a symmetric bilinear form on the free module. + INPUT: - - - ``name`` -- (string; default: None) name given to the symmetric + + - ``name`` -- (default: ``None``) string; name given to the symmetric bilinear form - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - symmetric bilinear form; if none is provided, the LaTeX symbol is set to - ``name`` - + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to + denote the symmetric bilinear form; if none is provided, the LaTeX + symbol is set to ``name`` + OUTPUT: - - - instance of + + - instance of :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` - of tensor type (0,2) and symmetric - + of tensor type `(0,2)` and symmetric + EXAMPLES: - + Symmetric bilinear form on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.sym_bilinear_form('A') ; a - symmetric bilinear form A on the rank-3 free module M over the Integer Ring - + Symmetric bilinear form A on the + Rank-3 free module M over the Integer Ring + A symmetric bilinear form is a type-(0,2) tensor that is symmetric:: - + sage: a.parent() - free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(0,2) tensors on the + Rank-3 free module M over the Integer Ring sage: a.tensor_type() (0, 2) sage: a.tensor_rank() 2 sage: a.symmetries() symmetry: (0, 1); no antisymmetry - + Components with respect to a given basis:: - + sage: e = M.basis('e') sage: a[0,0], a[0,1], a[0,2] = 1, 2, 3 sage: a[1,1], a[1,2] = 4, 5 sage: a[2,2] = 6 - - Only independent components have been set; the other ones are deduced by - symmetry:: - + + Only independent components have been set; the other ones are + deduced by symmetry:: + sage: a[1,0], a[2,0], a[2,1] (2, 3, 5) sage: a[:] [1 2 3] [2 4 5] [3 5 6] - + A symmetric bilinear form acts on pairs of module elements:: - + sage: u = M([2,-1,3]) ; v = M([-2,4,1]) sage: a(u,v) 61 sage: a(v,u) == a(u,v) True - - The sum of two symmetric bilinear forms is another symmetric bilinear + + The sum of two symmetric bilinear forms is another symmetric bilinear form:: - + sage: b = M.sym_bilinear_form('B') sage: b[0,0], b[0,1], b[1,2] = -2, 1, -3 sage: s = a + b ; s - symmetric bilinear form A+B on the rank-3 free module M over the Integer Ring + Symmetric bilinear form A+B on the + Rank-3 free module M over the Integer Ring sage: a[:], b[:], s[:] ( [1 2 3] [-2 1 0] [-1 3 3] [2 4 5] [ 1 0 -3] [ 3 4 2] [3 5 6], [ 0 -3 0], [ 3 2 6] ) - - Adding a symmetric bilinear from with a non-symmetric one results in a - generic type-(0,2) tensor:: - + + Adding a symmetric bilinear from with a non-symmetric one results in a + generic type-`(0,2)` tensor:: + sage: c = M.tensor((0,2), name='C') sage: c[0,1] = 4 sage: s = a + c ; s - type-(0,2) tensor A+C on the rank-3 free module M over the Integer Ring + Type-(0,2) tensor A+C on the Rank-3 free module M over the Integer Ring sage: s.symmetries() no symmetry; no antisymmetry sage: s[:] @@ -1143,21 +1145,21 @@ def sym_bilinear_form(self, name=None, latex_name=None): [2 4 5] [3 5 6] - See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + See :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` for more documentation. - + """ - return self.tensor_module(0,2).element_class(self, (0,2), name=name, - latex_name=latex_name, sym=(0,1)) + return self.tensor_module(0,2).element_class(self, (0,2), name=name, + latex_name=latex_name, sym=(0,1)) #### End of methods to be redefined by derived classes #### - + def _latex_(self): r""" LaTeX representation of the object. - + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M._latex_() 'M' @@ -1178,15 +1180,15 @@ def _latex_(self): def rank(self): r""" Return the rank of the free module ``self``. - - Since the ring over which ``self`` is built is assumed to be - commutative (and hence has the invariant basis number property), the - rank is defined uniquely, as the cardinality of any basis of ``self``. - + + Since the ring over which ``self`` is built is assumed to be + commutative (and hence has the invariant basis number property), the + rank is defined uniquely, as the cardinality of any basis of ``self``. + EXAMPLES: - + Rank of free modules over `\ZZ`:: - + sage: M = FiniteRankFreeModule(ZZ, 3) sage: M.rank() 3 @@ -1209,33 +1211,33 @@ def rank(self): def zero(self): r""" Return the zero element. - + EXAMPLES: - + Zero elements of free modules over `\ZZ`:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.zero() - element zero of the rank-3 free module M over the Integer Ring + Element zero of the Rank-3 free module M over the Integer Ring sage: M.zero().parent() is M True sage: M.zero() is M(0) True sage: T = M.tensor_module(1,1) sage: T.zero() - type-(1,1) tensor zero on the rank-3 free module M over the Integer Ring + Type-(1,1) tensor zero on the Rank-3 free module M over the Integer Ring sage: T.zero().parent() is T True sage: T.zero() is T(0) True Components of the zero element with respect to some basis:: - + sage: e = M.basis('e') sage: M.zero().comp(e)[:] [0, 0, 0] - sage: for i in M.irange(): print M.zero().comp(e)[i] == M.base_ring().zero(), - True True True + sage: all(M.zero().comp(e)[i] == M.base_ring().zero() for i in M.irange()) + True sage: T.zero().comp(e)[:] [0 0 0] [0 0 0] @@ -1251,36 +1253,36 @@ def zero(self): def dual(self): r""" Return the dual module. - + EXAMPLE: - + Dual of a free module over `\ZZ`:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: M.dual() - dual of the rank-3 free module M over the Integer Ring + Dual of the Rank-3 free module M over the Integer Ring sage: latex(M.dual()) M^* - + The dual is a free module of the same rank as M:: - + sage: isinstance(M.dual(), FiniteRankFreeModule) True sage: M.dual().rank() 3 It is formed by tensors of type (0,1), i.e. linear forms:: - + sage: M.dual() is M.tensor_module(0,1) True sage: M.dual().an_element() - type-(0,1) tensor on the rank-3 free module M over the Integer Ring + Type-(0,1) tensor on the Rank-3 free module M over the Integer Ring sage: a = M.linear_form() sage: a in M.dual() True The elements of a dual basis belong of course to the dual module:: - + sage: e = M.basis('e') sage: e.dual_basis()[0] in M.dual() True @@ -1291,21 +1293,21 @@ def dual(self): def irange(self, start=None): r""" Single index generator, labelling the elements of a basis. - + INPUT: - - - ``start`` -- (integer; default: None) initial value of the index; if none is - provided, ``self._sindex`` is assumed + + - ``start`` -- (default: ``None``) integer; initial value of the + index; if none is provided, ``self._sindex`` is assumed OUTPUT: - + - an iterable index, starting from ``start`` and ending at - ``self._sindex + self.rank() -1`` + ``self._sindex + self.rank() - 1`` EXAMPLES: - + Index range on a rank-3 module:: - + sage: M = FiniteRankFreeModule(ZZ, 3) sage: for i in M.irange(): print i, 0 1 2 @@ -1314,7 +1316,7 @@ def irange(self, start=None): The default starting value corresponds to the parameter ``start_index`` provided at the module construction (the default value being 0):: - + sage: M1 = FiniteRankFreeModule(ZZ, 3, start_index=1) sage: for i in M1.irange(): print i, 1 2 3 @@ -1335,167 +1337,174 @@ def irange(self, start=None): def default_basis(self): r""" - Return the default basis of the free module. - - The *default basis* is simply a basis whose name can be skipped in + Return the default basis of the free module. + + The *default basis* is simply a basis whose name can be skipped in methods requiring a basis as an argument. By default, it is the first - basis introduced on the module. It can be changed by the method - :meth:`set_default_basis`. - + basis introduced on the module. It can be changed by the method + :meth:`set_default_basis`. + OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` - + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + EXAMPLES: - + At the module construction, no default basis is assumed:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1) sage: M.default_basis() - No default basis has been defined on the rank-2 free module M over the Integer Ring + No default basis has been defined on the + Rank-2 free module M over the Integer Ring The first defined basis becomes the default one:: - + sage: e = M.basis('e') ; e - basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + Basis (e_1,e_2) on the Rank-2 free module M over the Integer Ring sage: M.default_basis() - basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + Basis (e_1,e_2) on the Rank-2 free module M over the Integer Ring sage: f = M.basis('f') ; f - basis (f_1,f_2) on the rank-2 free module M over the Integer Ring + Basis (f_1,f_2) on the Rank-2 free module M over the Integer Ring sage: M.default_basis() - basis (e_1,e_2) on the rank-2 free module M over the Integer Ring + Basis (e_1,e_2) on the Rank-2 free module M over the Integer Ring """ if self._def_basis is None: - print "No default basis has been defined on the " + str(self) + print("No default basis has been defined on the " + str(self)) return self._def_basis - + def set_default_basis(self, basis): r""" - Sets the default basis of the free module. - - The *default basis* is simply a basis whose name can be skipped in + Sets the default basis of ``self``. + + The *default basis* is simply a basis whose name can be skipped in methods requiring a basis as an argument. By default, it is the first - basis introduced on the module. - + basis introduced on the module. + INPUT: - - - ``basis`` -- instance of - :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + + - ``basis`` -- instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` representing a basis on ``self`` - + EXAMPLES: - + Changing the default basis on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: f = M.basis('f') ; f - basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring sage: M.default_basis() - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: M.set_default_basis(f) sage: M.default_basis() - basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring """ from free_module_basis import FreeModuleBasis if not isinstance(basis, FreeModuleBasis): - raise TypeError("The argument is not a free module basis.") + raise TypeError("the argument is not a free module basis") if basis._fmodule is not self: - raise ValueError("The basis is not defined on the current module.") + raise ValueError("the basis is not defined on the current module") self._def_basis = basis - - def view_bases(self): + + def print_bases(self): r""" Display the bases that have been defined on the free module. - Use the method :meth:`bases` to get the raw list of bases. - + Use the method :meth:`bases` to get the raw list of bases. + EXAMPLES: - + Bases on a rank-4 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 4, name='M', start_index=1) - sage: M.view_bases() - No basis has been defined on the rank-4 free module M over the Integer Ring + sage: M.print_bases() + No basis has been defined on the + Rank-4 free module M over the Integer Ring sage: e = M.basis('e') - sage: M.view_bases() - Bases defined on the rank-4 free module M over the Integer Ring: + sage: M.print_bases() + Bases defined on the Rank-4 free module M over the Integer Ring: - (e_1,e_2,e_3,e_4) (default basis) sage: f = M.basis('f') - sage: M.view_bases() - Bases defined on the rank-4 free module M over the Integer Ring: + sage: M.print_bases() + Bases defined on the Rank-4 free module M over the Integer Ring: - (e_1,e_2,e_3,e_4) (default basis) - (f_1,f_2,f_3,f_4) sage: M.set_default_basis(f) - sage: M.view_bases() - Bases defined on the rank-4 free module M over the Integer Ring: + sage: M.print_bases() + Bases defined on the Rank-4 free module M over the Integer Ring: - (e_1,e_2,e_3,e_4) - (f_1,f_2,f_3,f_4) (default basis) """ - if self._known_bases == []: - print "No basis has been defined on the " + str(self) + if not self._known_bases: + print("No basis has been defined on the " + str(self)) else: - print "Bases defined on the " + str(self) + ":" + print("Bases defined on the " + str(self) + ":") for basis in self._known_bases: item = " - " + basis._name if basis is self._def_basis: item += " (default basis)" - print item - + print(item) def bases(self): r""" Return the list of bases that have been defined on the free module. - - Use the method :meth:`view_bases` to get a formatted output with more - information. - + + Use the method :meth:`print_bases` to get a formatted output with more + information. + OUTPUT: - + - list of instances of class :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` - + EXAMPLES: - + Bases on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M_3', start_index=1) sage: M.bases() [] sage: e = M.basis('e') sage: M.bases() - [basis (e_1,e_2,e_3) on the rank-3 free module M_3 over the Integer Ring] + [Basis (e_1,e_2,e_3) on the Rank-3 free module M_3 over the Integer Ring] sage: f = M.basis('f') sage: M.bases() - [basis (e_1,e_2,e_3) on the rank-3 free module M_3 over the Integer Ring, - basis (f_1,f_2,f_3) on the rank-3 free module M_3 over the Integer Ring] + [Basis (e_1,e_2,e_3) on the Rank-3 free module M_3 over the Integer Ring, + Basis (f_1,f_2,f_3) on the Rank-3 free module M_3 over the Integer Ring] - """ - return self._known_bases + """ + return self._known_bases def basis_change(self, basis1, basis2): r""" Return a change of basis previously defined on the free module. - + INPUT: - - - ``basis1`` -- basis 1, denoted `(e_i)` below - - ``basis2`` -- basis 2, denoted `(f_i)` below - + + - ``basis1`` -- basis 1, denoted `(e_i)` below + - ``basis2`` -- basis 2, denoted `(f_i)` below + OUTPUT: - - - instance of + + - instance of :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` - + + .. TODO:: + + Suppose there exists change of bases + `A \leftrightarrow B \leftrightarrow C`, then do we want to + compute the change of basis `A \leftrightarrow C`? + EXAMPLES: - + Changes of basis on a rank-2 free module:: sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) @@ -1504,7 +1513,7 @@ def basis_change(self, basis1, basis2): sage: a[:] = [[1, 2], [-1, 3]] sage: f = e.new_basis(a, 'f') sage: M.basis_change(e,f) - automorphism on the rank-2 free module M over the Rational Field + Automorphism on the Rank-2 free module M over the Rational Field sage: M.basis_change(e,f)[:] [ 1 2] [-1 3] @@ -1514,30 +1523,35 @@ def basis_change(self, basis1, basis2): """ if (basis1, basis2) not in self._basis_changes: - raise TypeError("The change of basis from '" + repr(basis1) + - "' to '" + repr(basis2) + "' has not been " + - "defined on the " + repr(self)) + if (basis2, basis1) not in self._basis_changes: + raise TypeError(("the change of basis from '{!r}' to '{!r}'" + + "has not been defined on the {!r}" + ).format(basis1, basis2, self)) + inv = self._basis_changes[(basis2, basis1)].inverse() + self._basis_changes[(basis1, basis2)] = inv + return inv return self._basis_changes[(basis1, basis2)] - def set_basis_change(self, basis1, basis2, change_of_basis, + def set_basis_change(self, basis1, basis2, change_of_basis, compute_inverse=True): r""" Relates two bases by an automorphism. - - This updates the internal dictionary ``self._basis_changes``. - + + This updates the internal dictionary ``self._basis_changes``. + INPUT: - - - ``basis1`` -- basis 1, denoted `(e_i)` below - - ``basis2`` -- basis 2, denoted `(f_i)` below - - ``change_of_basis`` -- instance of class + + - ``basis1`` -- basis 1, denoted `(e_i)` below + - ``basis2`` -- basis 2, denoted `(f_i)` below + - ``change_of_basis`` -- instance of class :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` - describing the automorphism `P` that relates the basis `(e_i)` to + describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` - - ``compute_inverse`` (default: True) -- if set to True, the inverse - automorphism is computed and the change from basis `(f_i)` to `(e_i)` - is set to it in the internal dictionary ``self._basis_changes`` - + - ``compute_inverse`` (default: ``True``) -- if set to ``True``, the + inverse automorphism is computed and the change from basis `(f_i)` + to `(e_i)` is set to it in the internal dictionary + ``self._basis_changes`` + EXAMPLES: Defining a change of basis on a rank-2 free module:: @@ -1548,9 +1562,9 @@ def set_basis_change(self, basis1, basis2, change_of_basis, sage: a = M.automorphism() sage: a[:] = [[1, 2], [-1, 3]] sage: M.set_basis_change(e, f, a) - + The change of basis and its inverse have been recorded:: - + sage: M.basis_change(e,f)[:] [ 1 2] [-1 3] @@ -1559,7 +1573,7 @@ def set_basis_change(self, basis1, basis2, change_of_basis, [ 1/5 1/5] and are effective:: - + sage: f[0].view(e) f_0 = e_0 - e_1 sage: e[0].view(f) @@ -1568,9 +1582,9 @@ def set_basis_change(self, basis1, basis2, change_of_basis, """ from free_module_tensor_spec import FreeModuleAutomorphism if not isinstance(change_of_basis, FreeModuleAutomorphism): - raise TypeError("The argument change_of_basis must be some " + - "instance of FreeModuleAutomorphism.") + raise TypeError("the argument change_of_basis must be some " + + "instance of FreeModuleAutomorphism") self._basis_changes[(basis1, basis2)] = change_of_basis if compute_inverse: self._basis_changes[(basis2, basis1)] = change_of_basis.inverse() - + diff --git a/src/sage/tensor/modules/format_utilities.py b/src/sage/tensor/modules/format_utilities.py index 07098c28b27..42f5e889680 100644 --- a/src/sage/tensor/modules/format_utilities.py +++ b/src/sage/tensor/modules/format_utilities.py @@ -1,13 +1,12 @@ r""" -Formatting utilities. +Formatting utilities This module defines helper functions that are not class methods. - AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014) : initial version -- Joris Vankerschaver (2010): for the function is_atomic() +- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Joris Vankerschaver (2010): for the function :func:`is_atomic()` """ @@ -26,20 +25,21 @@ def is_atomic(expression): r""" Helper function to check whether some LaTeX expression is atomic. - - Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written - by Joris Vankerschaver (2010) - + + Adapted from function :meth:`DifferentialFormFormatter._is_atomic` + written by Joris Vankerschaver (2010) + INPUT: - + - ``expression`` -- string representing the expression (e.g. LaTeX string) - + OUTPUT: - - - True if additive operations are enclosed in parentheses, false otherwise. + + - ``True`` if additive operations are enclosed in parentheses and + ``False`` otherwise. EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import is_atomic sage: is_atomic("2*x") True @@ -50,7 +50,7 @@ def is_atomic(expression): """ if not isinstance(expression, basestring): - raise TypeError("The argument must be a string.") + raise TypeError("The argument must be a string") level = 0 for n, c in enumerate(expression): if c == '(': @@ -65,22 +65,23 @@ def is_atomic(expression): def is_atomic_wedge_txt(expression): r""" - Helper function to check whether some text-formatted expression is atomic - in terms of wedge products. - - Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written + Helper function to check whether some text-formatted expression is atomic + in terms of wedge products. + + Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written by Joris Vankerschaver (2010) - + INPUT: - + - ``expression`` -- string representing the text-formatted expression - + OUTPUT: - - - True if wedge products are enclosed in parentheses, false otherwise. + + - ``True`` if wedge products are enclosed in parentheses and + ``False`` otherwise. EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import is_atomic_wedge_txt sage: is_atomic_wedge_txt("a") True @@ -110,24 +111,25 @@ def is_atomic_wedge_txt(expression): def is_atomic_wedge_latex(expression): r""" - Helper function to check whether LaTeX-formatted expression is atomic in + Helper function to check whether LaTeX-formatted expression is atomic in terms of wedge products. - - Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written + + Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written by Joris Vankerschaver (2010) - + INPUT: - + - ``expression`` -- string representing the LaTeX expression - + OUTPUT: - - - True if wedge products are enclosed in parentheses, false otherwise. + + - ``True`` if wedge products are enclosed in parentheses and + ``False`` otherwise. EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import is_atomic_wedge_latex - sage: is_atomic_wedge_latex(r"a") + sage: is_atomic_wedge_latex(r"a") True sage: is_atomic_wedge_latex(r"a\wedge b") False @@ -137,9 +139,9 @@ def is_atomic_wedge_latex(expression): False sage: is_atomic_wedge_latex(r"((a\wedge b)\wedge c)") True - sage: is_atomic_wedge_latex(r"(a\wedge b\wedge c)") + sage: is_atomic_wedge_latex(r"(a\wedge b\wedge c)") True - sage: is_atomic_wedge_latex(r"\omega\wedge\theta") + sage: is_atomic_wedge_latex(r"\omega\wedge\theta") False sage: is_atomic_wedge_latex(r"(\omega\wedge\theta)") True @@ -163,11 +165,11 @@ def is_atomic_wedge_latex(expression): def format_mul_txt(name1, operator, name2): r""" - Helper function for text-formatted names of results of multiplication or - tensor product. - + Helper function for text-formatted names of results of multiplication or + tensor product. + EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import format_mul_txt sage: format_mul_txt('a', '*', 'b') 'a*b' @@ -179,7 +181,7 @@ def format_mul_txt(name1, operator, name2): '(a+b)*(c+d)' sage: format_mul_txt(None, '*', 'b') sage: format_mul_txt('a', '*', None) - + """ if name1 is None or name2 is None: return None @@ -187,16 +189,16 @@ def format_mul_txt(name1, operator, name2): name1 = '(' + name1 + ')' if not is_atomic(name2) or not is_atomic_wedge_txt(name2): name2 = '(' + name2 + ')' - return name1 + operator + name2 + return name1 + operator + name2 def format_mul_latex(name1, operator, name2): r""" - Helper function for LaTeX names of results of multiplication or tensor - product. - + Helper function for LaTeX names of results of multiplication or tensor + product. + EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import format_mul_latex sage: format_mul_latex('a', '*', 'b') 'a*b' @@ -216,15 +218,15 @@ def format_mul_latex(name1, operator, name2): name1 = r'\left(' + name1 + r'\right)' if not is_atomic(name2) or not is_atomic_wedge_latex(name2): name2 = r'\left(' + name2 + r'\right)' - return name1 + operator + name2 + return name1 + operator + name2 def format_unop_txt(operator, name): r""" Helper function for text-formatted names of results of unary operator. - + EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import format_unop_txt sage: format_unop_txt('-', 'a') '-a' @@ -233,7 +235,7 @@ def format_unop_txt(operator, name): sage: format_unop_txt('-', '(a+b)') '-(a+b)' sage: format_unop_txt('-', None) - + """ if name is None: return None @@ -246,9 +248,9 @@ def format_unop_txt(operator, name): def format_unop_latex(operator, name): r""" Helper function for LaTeX names of results of unary operator. - + EXAMPLES:: - + sage: from sage.tensor.modules.format_utilities import format_unop_latex sage: format_unop_latex('-', 'a') '-a' @@ -257,7 +259,7 @@ def format_unop_latex(operator, name): sage: format_unop_latex('-', '(a+b)') '-(a+b)' sage: format_unop_latex('-', None) - + """ if name is None: return None @@ -266,56 +268,59 @@ def format_unop_latex(operator, name): name = r'\left(' + name + r'\right)' return operator + name + class FormattedExpansion(SageObject): - r""" + r""" Helper class for displaying tensor expansions. - + + EXAMPLES:: + + sage: from sage.tensor.modules.format_utilities import FormattedExpansion + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: v = M.an_element() + sage: f = FormattedExpansion(v, 'v', r'\tilde v') + sage: f + v """ - def __init__(self, tensor): + def __init__(self, tensor, txt=None, latex=None): r""" - EXAMPLE:: - + TESTS:: + sage: from sage.tensor.modules.format_utilities import FormattedExpansion sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: v = M.an_element() - sage: f = FormattedExpansion(v) - sage: f.txt = 'v' ; f.latex = r'\tilde v' - sage: f - v - + sage: f = FormattedExpansion(v, 'v', r'\tilde v') """ self.tensor = tensor - self.txt = None - self.latex = None - + self.txt = txt + self.latex = latex + def _repr_(self): r""" - String representation of the object. + Return a string representation of ``self``. + + EXAMPLES:: - EXAMPLE:: - sage: from sage.tensor.modules.format_utilities import FormattedExpansion sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: v = M.an_element() - sage: f = FormattedExpansion(v) - sage: f.txt = 'v' ; f.latex = r'\tilde v' + sage: f = FormattedExpansion(v, 'v', r'\tilde v') sage: f._repr_() 'v' """ return self.txt - + def _latex_(self): r""" - LaTeX representation of the object. - + Return a LaTeX representation of ``self``. + EXAMPLE:: - + sage: from sage.tensor.modules.format_utilities import FormattedExpansion sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: v = M.an_element() - sage: f = FormattedExpansion(v) - sage: f.txt = 'v' ; f.latex = r'\tilde v' + sage: f = FormattedExpansion(v, 'v', r'\tilde v') sage: f._latex_() '\\tilde v' diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 5a21605e1f0..3e2e38bd302 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -1,28 +1,31 @@ r""" Alternating forms on free modules -The class :class:`FreeModuleAltForm` implement alternating forms on a free -module of finite rank over a commutative ring. +The class :class:`FreeModuleAltForm` implement alternating forms on a free +module of finite rank over a commutative ring. -It is a subclass of -:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`, alternating -forms being a special type of tensors. +It is a subclass of +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`, alternating +forms being a special type of tensors. -A subclass of :class:`FreeModuleAltForm` is :class:`FreeModuleLinForm` for -alternating forms of degree 1, i.e. linear forms. +A subclass of :class:`FreeModuleAltForm` is :class:`FreeModuleLinForm` for +alternating forms of degree 1, i.e. linear forms. AUTHORS: - Eric Gourgoulhon, Michal Bejger (2014): initial version -TODO: +.. TODO:: -* Implement a specific parent for alternating forms of a fixed degree p>1, with - element :class:`FreeModuleAltForm` and with coercion to tensor modules of - type (0,p). -* Implement a specific parent for linear forms, with element - :class:`FreeModuleLinForm` and with coercion to tensor modules of type (0,1). + Implement a specific parent for alternating forms of a fixed + degree `p > 1`, with element :class:`FreeModuleAltForm` and with + coercion to tensor modules of type `(0,p)`. +.. TODO:: + + Implement a specific parent for linear forms, with element + :class:`FreeModuleLinForm` and with coercion to tensor modules + of type `(0,1)`. """ #****************************************************************************** # Copyright (C) 2014 Eric Gourgoulhon @@ -34,40 +37,42 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement -from comp import Components, CompFullyAntiSym +from sage.tensor.modules.free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement +from sage.tensor.modules.comp import Components, CompFullyAntiSym class FreeModuleAltForm(FreeModuleTensor): r""" Alternating form over a free module `M`. - + INPUT: - - - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` + + - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - ``degree`` -- the degree of the alternating form (i.e. its tensor rank) - - ``name`` -- (default: None) name given to the alternating form - - ``latex_name`` -- (default: None) LaTeX symbol to denote the alternating - form; if none is provided, the LaTeX symbol is set to ``name`` - + - ``name`` -- (default: ``None``) name given to the alternating form + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + alternating form; if none is provided, the LaTeX symbol is ``name`` + EXAMPLES: - + Alternating form of degree 2 on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: a = M.alternating_form(2, name='a') ; a - alternating form a of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form a of degree 2 on the + Rank-3 free module M over the Integer Ring sage: type(a) sage: a.parent() - free module of type-(0,2) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(0,2) tensors on the + Rank-3 free module M over the Integer Ring sage: a[1,2], a[2,3] = 4, -3 sage: a.view() a = 4 e^1/\e^2 - 3 e^2/\e^3 - + The alternating form acting on the basis elements:: - + sage: a(e[1],e[2]) 4 sage: a(e[1],e[3]) @@ -80,69 +85,68 @@ class FreeModuleAltForm(FreeModuleTensor): """ def __init__(self, fmodule, degree, name=None, latex_name=None): r""" + Initialize ``self``. + TEST:: - + sage: from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: FreeModuleAltForm(M, 2, name='a') - alternating form a of degree 2 on the rank-3 free module M over the Integer Ring - + sage: A = FreeModuleAltForm(M, 2, name='a') + sage: TestSuite(A).run() """ - FreeModuleTensor.__init__(self, fmodule, (0,degree), name=name, + FreeModuleTensor.__init__(self, fmodule, (0,degree), name=name, latex_name=latex_name, antisym=range(degree)) FreeModuleAltForm._init_derived(self) # initialization of derived quantities def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.alternating_form(2) - sage: a._repr_() - 'alternating form of degree 2 on the rank-3 free module M over the Integer Ring' - sage: a = M.alternating_form(2, name='a') - sage: a._repr_() - 'alternating form a of degree 2 on the rank-3 free module M over the Integer Ring' + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.alternating_form(2) + Alternating form of degree 2 on the + Rank-3 free module M over the Integer Ring + sage: M.alternating_form(2, name='a') + Alternating form a of degree 2 on the + Rank-3 free module M over the Integer Ring """ - description = "alternating form " + description = "Alternating form " if self._name is not None: - description += self._name + " " - description += "of degree " + str(self._tensor_rank) + " on the " + \ - str(self._fmodule) + description += self._name + " " + description += "of degree {} on the {}".format(self._tensor_rank, self._fmodule) return description def _init_derived(self): r""" - Initialize the derived quantities - - EXAMPLE:: - + Initialize the derived quantities. + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.alternating_form(2) sage: a._init_derived() """ - FreeModuleTensor._init_derived(self) + FreeModuleTensor._init_derived(self) def _del_derived(self): r""" - Delete the derived quantities + Delete the derived quantities. + + EXAMPLES:: - EXAMPLE:: - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.alternating_form(2) sage: a._del_derived() """ - FreeModuleTensor._del_derived(self) + FreeModuleTensor._del_derived(self) def _new_instance(self): r""" - Create an instance of the same class as ``self``, on the same module + Create an instance of the same class as ``self``, on the same module and of the same degree. EXAMPLE:: @@ -150,45 +154,47 @@ def _new_instance(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.alternating_form(2, name='a') sage: a._new_instance() - alternating form of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form of degree 2 on the + Rank-3 free module M over the Integer Ring sage: a._new_instance().parent() is a.parent() True """ return self.__class__(self._fmodule, self._tensor_rank) - def _new_comp(self, basis): + def _new_comp(self, basis): r""" - Create some components in the given basis. + Create some components in the given basis ``basis`` of ``self``. - This method, which is already implemented in - :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency + This method, which is already implemented in + :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency. - EXAMPLE:: + EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.alternating_form(2, name='a') sage: a._new_comp(e) - fully antisymmetric 2-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Fully antisymmetric 2-indices components w.r.t. Basis (e_0,e_1,e_2) + on the Rank-3 free module M over the Integer Ring """ fmodule = self._fmodule # the base free module - if self._tensor_rank == 1: + if self._tensor_rank == 1: return Components(fmodule._ring, basis, 1, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) - else: - return CompFullyAntiSym(fmodule._ring, basis, self._tensor_rank, - start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) + + return CompFullyAntiSym(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) def degree(self): r""" Return the degree of the alternating form. - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.alternating_form(2, name='a') sage: a.degree() @@ -200,25 +206,25 @@ def degree(self): def view(self, basis=None, format_spec=None): r""" - Displays the alternating form in terms of its expansion onto a given - cobasis. + Display the alternating form ``self`` in terms of its expansion + onto a given cobasis. The output is either text-formatted (console mode) or LaTeX-formatted - (notebook mode). - - INPUT: - - - ``basis`` -- (default: None) basis of the free module with respect to - which the alternating form is expanded; if none is provided, the - module's default basis is assumed - - ``format_spec`` -- (default: None) format specification passed to - ``self._fmodule._output_formatter`` to format the output. - + (notebook mode). + + INPUT: + + - ``basis`` -- (default: ``None``) basis of the free module with + respect to which the alternating form is expanded; if none is + provided, the module's default basis is assumed + - ``format_spec`` -- (default: ``None``) format specification passed + to ``self._fmodule._output_formatter`` to format the output + EXAMPLES: - - Display of an alternating form of degree 1 (linear form) on a rank-3 + + Display of an alternating form of degree 1 (linear form) on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('a', latex_name=r'\alpha') @@ -229,7 +235,7 @@ def view(self, basis=None, format_spec=None): \alpha = e^0 -3 e^1 + 4 e^2 Display of an alternating form of degree 2 on a rank-3 free module:: - + sage: b = M.alternating_form(2, 'b', latex_name=r'\beta') sage: b[0,1], b[0,2], b[1,2] = 3, 2, -1 sage: b.view() @@ -238,7 +244,7 @@ def view(self, basis=None, format_spec=None): \beta = 3 e^0\wedge e^1 + 2 e^0\wedge e^2 -e^1\wedge e^2 Display of an alternating form of degree 3 on a rank-3 free module:: - + sage: c = M.alternating_form(3, 'c') sage: c[0,1,2] = 4 sage: c.view() @@ -247,7 +253,7 @@ def view(self, basis=None, format_spec=None): c = 4 e^0\wedge e^1\wedge e^2 Display of a vanishing alternating form:: - + sage: c[0,1,2] = 0 # the only independent component set to zero sage: c.is_zero() True @@ -256,9 +262,9 @@ def view(self, basis=None, format_spec=None): sage: latex(c.view()) c = 0 sage: c[0,1,2] = 4 # value restored for what follows - + Display in a basis which is not the default one:: - + sage: aut = M.automorphism() sage: aut[:] = [[0,1,0], [0,0,-1], [1,0,0]] sage: f = e.new_basis(aut, 'f') @@ -269,7 +275,7 @@ def view(self, basis=None, format_spec=None): sage: c.view(f) c = -4 f^0/\f^1/\f^2 - The output format can be set via the argument ``output_formatter`` + The output format can be set via the argument ``output_formatter`` passed at the module construction:: sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, output_formatter=Rational.numerical_approx) @@ -278,13 +284,13 @@ def view(self, basis=None, format_spec=None): sage: b[1,2], b[1,3], b[2,3] = 1/3, 5/2, 4 sage: b.view() # default format (53 bits of precision) b = 0.333333333333333 e^1/\e^2 + 2.50000000000000 e^1/\e^3 + 4.00000000000000 e^2/\e^3 - + The output format is then controled by the argument ``format_spec`` of the method :meth:`view`:: - + sage: b.view(format_spec=10) # 10 bits of precision b = 0.33 e^1/\e^2 + 2.5 e^1/\e^3 + 4.0 e^2/\e^3 - + """ from sage.misc.latex import latex from format_utilities import is_atomic, FormattedExpansion @@ -303,8 +309,8 @@ def view(self, basis=None, format_spec=None): for k in range(self._tensor_rank): bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) - basis_term_txt = "/\\".join(bases_txt) - basis_term_latex = r"\wedge ".join(bases_latex) + basis_term_txt = "/\\".join(bases_txt) + basis_term_latex = r"\wedge ".join(bases_latex) if coef == 1: terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) @@ -317,14 +323,14 @@ def view(self, basis=None, format_spec=None): if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: - terms_txt.append("(" + coef_txt + ") " + + terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: - terms_latex.append(r"\left(" + coef_latex + r"\right)" + + terms_latex.append(r"\left(" + coef_latex + r"\right)" + basis_term_latex) - if terms_txt == []: + if not terms_txt: expansion_txt = "0" else: expansion_txt = terms_txt[0] @@ -333,7 +339,7 @@ def view(self, basis=None, format_spec=None): expansion_txt += " - " + term[1:] else: expansion_txt += " + " + term - if terms_latex == []: + if not terms_latex: expansion_latex = "0" else: expansion_latex = terms_latex[0] @@ -342,7 +348,7 @@ def view(self, basis=None, format_spec=None): expansion_latex += term else: expansion_latex += "+" + term - result = FormattedExpansion(self) + result = FormattedExpansion(self) if self._name is None: result.txt = expansion_txt else: @@ -356,21 +362,21 @@ def view(self, basis=None, format_spec=None): def wedge(self, other): r""" - Exterior product with another alternating form. - + Exterior product of ``self`` with another alternating form ``other``. + INPUT: - - - ``other``: another alternating form - + + - ``other`` -- another alternating form + OUTPUT: - - - instance of :class:`FreeModuleAltForm` representing the exterior - product self/\\other. - + + - instance of :class:`FreeModuleAltForm` representing the exterior + product ``self/\other`` + EXAMPLES: - + Exterior product of two linear forms:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') @@ -378,7 +384,8 @@ def wedge(self, other): sage: b = M.linear_form('B') sage: b[:] = [2,-1,2] sage: c = a.wedge(b) ; c - alternating form A/\B of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form A/\B of degree 2 on the Rank-3 free module M + over the Integer Ring sage: c.view() A/\B = 5 e^0/\e^1 - 6 e^0/\e^2 - 2 e^1/\e^2 sage: latex(c) @@ -387,41 +394,42 @@ def wedge(self, other): A\wedge B = 5 e^0\wedge e^1 -6 e^0\wedge e^2 -2 e^1\wedge e^2 Test of the computation:: - + sage: a.wedge(b) == a*b - b*a True Exterior product of a linear form and an alternating form of degree 2:: - + sage: d = M.linear_form('D') sage: d[:] = [-1,2,4] sage: s = d.wedge(c) ; s - alternating form D/\A/\B of degree 3 on the rank-3 free module M over the Integer Ring + Alternating form D/\A/\B of degree 3 on the Rank-3 free module M + over the Integer Ring sage: s.view() D/\A/\B = 34 e^0/\e^1/\e^2 Test of the computation:: - + sage: s[0,1,2] == d[0]*c[1,2] + d[1]*c[2,0] + d[2]*c[0,1] True - + Let us check that the exterior product is associative:: - + sage: d.wedge(a.wedge(b)) == (d.wedge(a)).wedge(b) True - + and that it is graded anticommutative:: - - sage: a.wedge(b) == - b.wedge(a) + + sage: a.wedge(b) == - b.wedge(a) True sage: d.wedge(c) == c.wedge(d) True - + """ from format_utilities import is_atomic if not isinstance(other, FreeModuleAltForm): - raise TypeError("The second argument for the exterior product " + - "must be an alternating form.") + raise TypeError("the second argument for the exterior product " + + "must be an alternating form") if other._tensor_rank == 0: return other*self if self._tensor_rank == 0: @@ -429,11 +437,11 @@ def wedge(self, other): fmodule = self._fmodule basis = self.common_basis(other) if basis is None: - raise ValueError("No common basis for the exterior product.") + raise ValueError("no common basis for the exterior product") rank_r = self._tensor_rank + other._tensor_rank cmp_s = self._components[basis] cmp_o = other._components[basis] - cmp_r = CompFullyAntiSym(fmodule._ring, basis, rank_r, + cmp_r = CompFullyAntiSym(fmodule._ring, basis, rank_r, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) for ind_s, val_s in cmp_s._comp.iteritems(): @@ -468,40 +476,41 @@ class FreeModuleLinForm(FreeModuleAltForm): r""" Linear form on a free module `M` over a commutative ring `R`. - A *linear form* is a map `M\rightarrow R` that is linear. + A *linear form* is a map `M \rightarrow R` that is linear. INPUT: - - - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` + + - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: None) name given to the linear form - - ``latex_name`` -- (default: None) LaTeX symbol to denote the linear + - ``name`` -- (default: ``None``) name given to the linear form + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the linear form; if none is provided, the LaTeX symbol is set to ``name`` - + EXAMPLES: - + Linear form on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') ; a - linear form A on the rank-3 free module M over the Integer Ring + Linear form A on the Rank-3 free module M over the Integer Ring sage: a[:] = [2,-1,3] # components w.r.t. the module's default basis (e) The members of a dual basis are linear forms:: - + sage: e.dual_basis()[0] - linear form e^0 on the rank-3 free module M over the Integer Ring + Linear form e^0 on the Rank-3 free module M over the Integer Ring sage: e.dual_basis()[1] - linear form e^1 on the rank-3 free module M over the Integer Ring + Linear form e^1 on the Rank-3 free module M over the Integer Ring sage: e.dual_basis()[2] - linear form e^2 on the rank-3 free module M over the Integer Ring + Linear form e^2 on the Rank-3 free module M over the Integer Ring - Any linear form is expanded onto them:: - - sage: a.view(basis=e) # e being the default basis, it is equivalent to write a.view() + Any linear form is expanded onto them. In this example, the basis ``e`` + is the default basis and it is equivalent to ``a.view()``:: + + sage: a.view(basis=e) A = 2 e^0 - e^1 + 3 e^2 - + A linear form maps module elements to ring elements:: sage: v = M([1,1,1]) @@ -511,15 +520,15 @@ class FreeModuleLinForm(FreeModuleAltForm): True Test of linearity:: - + sage: u = M([-5,-2,7]) sage: a(3*u - 4*v) == 3*a(u) - 4*a(v) True A linear form is an element of the dual module:: - + sage: a.parent() - dual of the rank-3 free module M over the Integer Ring + Dual of the Rank-3 free module M over the Integer Ring As such, it is a tensor of type (0,1):: @@ -530,65 +539,64 @@ class FreeModuleLinForm(FreeModuleAltForm): def __init__(self, fmodule, name=None, latex_name=None): r""" TEST:: - + sage: from sage.tensor.modules.free_module_alt_form import FreeModuleLinForm sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: FreeModuleLinForm(M, name='a') - linear form a on the rank-3 free module M over the Integer Ring - + sage: L = FreeModuleLinForm(M, name='a') + sage: TestSuite(L).run() """ - FreeModuleAltForm.__init__(self, fmodule, 1, name=name, + FreeModuleAltForm.__init__(self, fmodule, 1, name=name, latex_name=latex_name) def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.linear_form('A') - sage: a._repr_() - 'linear form A on the rank-3 free module M over the Integer Ring' + sage: M.linear_form('A') + Linear form A on the Rank-3 free module M over the Integer Ring """ - description = "linear form " + description = "Linear form " if self._name is not None: - description += self._name + " " + description += self._name + " " description += "on the " + str(self._fmodule) return description def _new_instance(self): r""" - Create an instance of the same class as ``self`` and on the same - module. - + Create an instance of the same class as ``self`` and on the same + module. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.linear_form('A') sage: a._new_instance() - linear form on the rank-3 free module M over the Integer Ring + Linear form on the Rank-3 free module M over the Integer Ring sage: a._new_instance().parent() is a.parent() True - + """ return self.__class__(self._fmodule) - def _new_comp(self, basis): + def _new_comp(self, basis): r""" - Create some components in the given basis. - - This method, which is already implemented in + Create some components in the given basis ``basis`` of ``self``. + + This method, which is already implemented in :meth:`FreeModuleAltForm._new_comp`, is redefined here for efficiency - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') sage: a._new_comp(e) - 1-index components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + 1-index components w.r.t. Basis (e_0,e_1,e_2) + on the Rank-3 free module M over the Integer Ring """ fmodule = self._fmodule # the base free module @@ -598,18 +606,18 @@ def _new_comp(self, basis): def __call__(self, vector): r""" The linear form acting on an element of the module. - + INPUT: - - - ``vector`` -- an element of the module (instance of + + - ``vector`` -- an element of the module (instance of :class:`FiniteRankFreeModuleElement`) - + OUTPUT: - + - ring element `\langle \omega, v \rangle` - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form('A') @@ -619,40 +627,25 @@ def __call__(self, vector): -20 sage: a.__call__(v) == a(v) True - + """ if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("The argument must be a free module element.") + raise TypeError("the argument must be a free module element") basis = self.common_basis(vector) if basis is None: - raise ValueError("No common basis for the components.") + raise ValueError("no common basis for the components") omega = self._components[basis] vv = vector._components[basis] resu = 0 for i in self._fmodule.irange(): resu += omega[[i]]*vv[[i]] # Name and LaTeX symbol of the output: - if hasattr(resu, '_name'): + if hasattr(resu, '_name'): if self._name is not None and vector._name is not None: resu._name = self._name + "(" + vector._name + ")" - if hasattr(resu, '_latex_name'): + if hasattr(resu, '_latex_name'): if self._latex_name is not None and vector._latex_name is not None: resu._latex_name = self._latex_name + r"\left(" + \ vector._latex_name + r"\right)" return resu - - - - - - - - - - - - - - - diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 0bd69327264..123a1795e57 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -3,8 +3,8 @@ The class :class:`FreeModuleBasis` implements bases on a free module `M` of finite rank over a commutative ring, -while the class :class:`FreeModuleCoBasis` implements the dual bases (i.e. -bases of the dual module `M^*`). +while the class :class:`FreeModuleCoBasis` implements the dual bases (i.e. +bases of the dual module `M^*`). AUTHORS: @@ -27,90 +27,103 @@ from sage.structure.unique_representation import UniqueRepresentation class FreeModuleBasis(UniqueRepresentation, SageObject): - r""" + r""" Basis of a free module over a commutative ring `R`. - + INPUT: - - - ``fmodule`` -- free module `M` (must be an instance of + + - ``fmodule`` -- free module `M` (must be an instance of :class:`FiniteRankFreeModule`) - - ``symbol`` -- (string) a letter (of a few letters) to denote a generic + - ``symbol`` -- string; a letter (of a few letters) to denote a generic element of the basis - - ``latex_symbol`` -- (string; default: None) symbol to denote a generic - element of the basis; if None, the value of ``symbol`` is used. + - ``latex_symbol`` -- (default: ``None``) string; symbol to denote a + generic element of the basis; if ``None``, the value of ``symbol`` + is used EXAMPLES: - + A basis on a rank-3 free module over `\ZZ`:: - + sage: M0 = FiniteRankFreeModule(ZZ, 3, name='M_0') sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis sage: e = FreeModuleBasis(M0, 'e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M_0 over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M_0 over the Integer Ring - Instead of importing FreeModuleBasis in the global name space, one can + Instead of importing FreeModuleBasis in the global name space, one can use the module's method :meth:`basis`:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring - The individual elements constituting the basis are accessed via the + The individual elements constituting the basis are accessed via the square bracket operator:: - + sage: e[0] - element e_0 of the rank-3 free module M over the Integer Ring + Element e_0 of the Rank-3 free module M over the Integer Ring sage: e[0] in M True The LaTeX symbol can be set explicitely, as the second argument of :meth:`basis`:: - + sage: latex(e) \left(e_0,e_1,e_2\right) sage: eps = M.basis('eps', r'\epsilon') ; eps - basis (eps_0,eps_1,eps_2) on the rank-3 free module M over the Integer Ring + Basis (eps_0,eps_1,eps_2) on the Rank-3 free module M over the Integer Ring sage: latex(eps) \left(\epsilon_0,\epsilon_1,\epsilon_2\right) - - The individual elements of the basis are labelled according the + + The individual elements of the basis are labelled according the parameter ``start_index`` provided at the free module construction:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: e[1] - element e_1 of the rank-3 free module M over the Integer Ring - + Element e_1 of the Rank-3 free module M over the Integer Ring """ + @staticmethod + def __classcall_private__(cls, fmodule, symbol, latex_symbol=None): + """ + Normalize input to ensure a unique representation. + + TESTS:: + + sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = FreeModuleBasis(M, 'e', latex_symbol='e') + sage: e is FreeModuleBasis(M, 'e') + True + """ + if latex_symbol is None: + latex_symbol = symbol + return super(FreeModuleBasis, cls).__classcall__(cls, fmodule, symbol, latex_symbol) + def __init__(self, fmodule, symbol, latex_symbol=None): r""" + Initialize ``self``. + TESTS:: - + sage: FiniteRankFreeModule._clear_cache_() # for doctests only sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: FreeModuleBasis(M, 'e', latex_symbol=r'\epsilon') - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring sage: e = FreeModuleBasis(M, 'e', latex_symbol=r'\epsilon') - sage: e is FreeModuleBasis(M, 'e', latex_symbol=r'\epsilon') # unique representation - True - + sage: TestSuite(e).run() + """ self._fmodule = fmodule self._name = "(" + \ ",".join([symbol + "_" + str(i) for i in fmodule.irange()]) +")" - if latex_symbol is None: - latex_symbol = symbol - self._latex_name = r"\left(" + ",".join([latex_symbol + "_" + str(i) + self._latex_name = r"\left(" + ",".join([latex_symbol + "_" + str(i) for i in fmodule.irange()]) + r"\right)" self._symbol = symbol self._latex_symbol = latex_symbol # The basis is added to the module list of bases for other in fmodule._known_bases: if symbol == other._symbol: - raise ValueError("The " + str(other) + " already exist on the " + - str(fmodule)) + raise ValueError("the {} already exist on the {}".format(other, fmodule)) fmodule._known_bases.append(self) # The individual vectors: vl = list() @@ -126,129 +139,128 @@ def __init__(self, fmodule, symbol, latex_symbol=None): # The first defined basis is considered as the default one: if fmodule._def_basis is None: fmodule._def_basis = self - # Initialization of the components w.r.t the current basis of the zero - # elements of all tensor modules constructed up to now (including the - # base module itself, since it is considered as a type-(1,0) tensor - # module) + # Initialization of the components w.r.t the current basis of the zero + # elements of all tensor modules constructed up to now (including the + # base module itself, since it is considered as a type-(1,0) tensor + # module) for t in fmodule._tensor_modules.itervalues(): t._zero_element._components[self] = t._zero_element._new_comp(self) # (since new components are initialized to zero) # The dual basis: - self._dual_basis = self._init_dual_basis() + self._dual_basis = self._init_dual_basis() ###### Methods to be redefined by derived classes of FreeModuleBasis ###### def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: e._repr_() - 'basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring' + sage: e + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e1 = M1.basis('e') - sage: e1._repr_() - 'basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring' + sage: e1 + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring """ - return "basis " + self._name + " on the " + str(self._fmodule) + return "Basis {} on the {}".format(self._name, self._fmodule) def _init_dual_basis(self): - r""" + r""" Construct the basis dual to ``self``. - + OUTPUT: - + - instance of :class:`FreeModuleCoBasis` representing the dual of ``self`` - - EXAMPLE:: - + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e._init_dual_basis() - dual basis (e^0,e^1,e^2) on the rank-3 free module M over the Integer Ring - + Dual basis (e^0,e^1,e^2) on the Rank-3 free module M over the Integer Ring + """ - return FreeModuleCoBasis(self, self._symbol, - latex_symbol=self._latex_symbol) + return FreeModuleCoBasis(self, self._symbol, + latex_symbol=self._latex_symbol) def _new_instance(self, symbol, latex_symbol=None): r""" - Construct a new basis on the same module as ``self``. - + Construct a new basis on the same module as ``self``. + INPUT: - + - ``symbol`` -- (string) a letter (of a few letters) to denote a generic element of the basis - - ``latex_symbol`` -- (string; default: None) symbol to denote a - generic element of the basis; if None, the value of ``symbol`` is - used. + - ``latex_symbol`` -- (default: ``None``) string; symbol to denote a + generic element of the basis; if ``None``, the value of ``symbol`` + is used OUTPUT: - + - instance of :class:`FreeModuleBasis` - - EXAMPLE:: - + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e._new_instance('f') - basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring - + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring + """ return FreeModuleBasis(self._fmodule, symbol, latex_symbol=latex_symbol) - - ###### End of methods to be redefined by derived classes ###### + ###### End of methods to be redefined by derived classes ###### def dual_basis(self): - r""" + r""" Return the basis dual to ``self``. - + OUTPUT: - + - instance of :class:`FreeModuleCoBasis` representing the dual of ``self`` EXAMPLES: - + Dual basis on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: f = e.dual_basis() ; f - dual basis (e^1,e^2,e^3) on the rank-3 free module M over the Integer Ring - + Dual basis (e^1,e^2,e^3) on the Rank-3 free module M over the Integer Ring + Let us check that the elements of f are tensors of type (0,1) on M:: - + sage: f[1] in M.tensor_module(0,1) True sage: f[1] - linear form e^1 on the rank-3 free module M over the Integer Ring - + Linear form e^1 on the Rank-3 free module M over the Integer Ring + and that f is indeed the dual of e:: - + sage: f[1](e[1]), f[1](e[2]), f[1](e[3]) (1, 0, 0) sage: f[2](e[1]), f[2](e[2]), f[2](e[3]) (0, 1, 0) sage: f[3](e[1]), f[3](e[2]), f[3](e[3]) (0, 0, 1) - + """ return self._dual_basis def _latex_(self): r""" LaTeX representation of the object. - + EXAMPLES:: sage: FiniteRankFreeModule._clear_cache_() # for doctests only @@ -263,82 +275,28 @@ def _latex_(self): '\\left(\\epsilon_0,\\epsilon_1,\\epsilon_2\\right)' sage: latex(f) \left(\epsilon_0,\epsilon_1,\epsilon_2\right) - + """ return self._latex_name - def __hash__(self): + def __getitem__(self, index): r""" - Hash function (since instances of :class:`FreeModuleBasis` are used as - dictionary keys). - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: e.__hash__() # random - 140529558663376 - - """ - return id(self) + Return the basis element corresponding to the given index ``index``. - def __eq__(self, other): - r""" - Equality (comparison) operator - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: f = M.basis('f') - sage: e.__eq__(f) - False - sage: e.__eq__(e) - True - sage: e.__eq__(M.basis('e')) - True - sage: M2 = FiniteRankFreeModule(ZZ, 2, name='M2') - sage: e.__eq__(M2.basis('e')) - False + INPUT: - """ - return other is self + - ``index`` -- the index of the basis element - def __ne__(self, other): - r""" - Non-equality operator. - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: f = M.basis('f') - sage: e.__ne__(f) - True - sage: e.__ne__(e) - False - """ - return not self.__eq__(other) - - def __getitem__(self, index): - r""" - Returns the basis element corresponding to a given index. - - INPUT: - - - ``index`` -- the index of the basis element - - EXAMPLES:: - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e.__getitem__(0) - element e_0 of the rank-3 free module M over the Integer Ring + Element e_0 of the Rank-3 free module M over the Integer Ring sage: e.__getitem__(1) - element e_1 of the rank-3 free module M over the Integer Ring + Element e_1 of the Rank-3 free module M over the Integer Ring sage: e.__getitem__(2) - element e_2 of the rank-3 free module M over the Integer Ring + Element e_2 of the Rank-3 free module M over the Integer Ring sage: e[1] is e.__getitem__(1) True sage: e[1].parent() is M @@ -346,11 +304,11 @@ def __getitem__(self, index): sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e1 = M1.basis('e') sage: e1.__getitem__(1) - element e_1 of the rank-3 free module M over the Integer Ring + Element e_1 of the Rank-3 free module M over the Integer Ring sage: e1.__getitem__(2) - element e_2 of the rank-3 free module M over the Integer Ring + Element e_2 of the Rank-3 free module M over the Integer Ring sage: e1.__getitem__(3) - element e_3 of the rank-3 free module M over the Integer Ring + Element e_3 of the Rank-3 free module M over the Integer Ring """ n = self._fmodule._rank @@ -365,12 +323,12 @@ def __getitem__(self, index): def __len__(self): r""" Return the basis length, i.e. the rank of the free module. - - NB: the method __len__() is required for the basis to act as a - "frame" in the class :class:`~sage.tensor.modules.comp.Components`. - + + NB: the method ``__len__()`` is required for the basis to act as a + "frame" in the class :class:`~sage.tensor.modules.comp.Components`. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: e.__len__() @@ -383,37 +341,37 @@ def __len__(self): def new_basis(self, change_of_basis, symbol, latex_symbol=None): r""" - Define a new module basis from the current one. - - The new basis is defined by means of a module automorphism. - + Define a new module basis from ``self``. + + The new basis is defined by means of a module automorphism. + INPUT: - - - ``change_of_basis`` -- instance of + + - ``change_of_basis`` -- instance of :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` - describing the automorphism `P` that relates the current basis + describing the automorphism `P` that relates the current basis `(e_i)` (described by ``self``) to the new basis `(n_i)` according to `n_i = P(e_i)` - - ``symbol`` -- (string) a letter (of a few letters) to denote a + - ``symbol`` -- string; a letter (of a few letters) to denote a generic element of the basis - - ``latex_symbol`` -- (string; default: None) symbol to denote a - generic element of the basis; if None, the value of ``symbol`` is - used. - + - ``latex_symbol`` -- (default: ``None``) string; symbol to denote a + generic element of the basis; if ``None``, the value of ``symbol`` + is used + OUTPUT: - + - the new basis `(n_i)`, as an instance of :class:`FreeModuleBasis` - + EXAMPLES: - + Change of basis on a rank-2 free module:: - + sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') sage: a = M.automorphism() sage: a[:] = [[1, 2], [-1, 3]] sage: f = e.new_basis(a, 'f') ; f - basis (f_1,f_2) on the rank-2 free module M over the Rational Field + Basis (f_1,f_2) on the Rank-2 free module M over the Rational Field sage: f[1].view() f_1 = e_1 - e_2 sage: f[2].view() @@ -435,76 +393,77 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): transf = change_of_basis.copy() inv_transf = change_of_basis.inverse().copy() si = fmodule._sindex - # Components of the new basis vectors in the old basis: + # Components of the new basis vectors in the old basis: for i in fmodule.irange(): for j in fmodule.irange(): the_new_basis._vec[i-si].add_comp(self)[[j]] = \ transf.comp(self)[[j,i]] - # Components of the new dual-basis elements in the old dual basis: + # Components of the new dual-basis elements in the old dual basis: for i in fmodule.irange(): for j in fmodule.irange(): the_new_basis._dual_basis._form[i-si].add_comp(self)[[j]] = \ inv_transf.comp(self)[[i,j]] - # The components of the transformation and its inverse are the same in + # The components of the transformation and its inverse are the same in # the two bases: for i in fmodule.irange(): for j in fmodule.irange(): transf.add_comp(the_new_basis)[[i,j]] = transf.comp(self)[[i,j]] inv_transf.add_comp(the_new_basis)[[i,j]] = \ inv_transf.comp(self)[[i,j]] - # Components of the old basis vectors in the new basis: + # Components of the old basis vectors in the new basis: for i in fmodule.irange(): for j in fmodule.irange(): self._vec[i-si].add_comp(the_new_basis)[[j]] = \ inv_transf.comp(self)[[j,i]] - # Components of the old dual-basis elements in the new cobasis: + # Components of the old dual-basis elements in the new cobasis: for i in fmodule.irange(): for j in fmodule.irange(): self._dual_basis._form[i-si].add_comp(the_new_basis)[[j]] = \ transf.comp(self)[[i,j]] - # The automorphism and its inverse are added to the module's dictionary + # The automorphism and its inverse are added to the module's dictionary # of changes of bases: fmodule._basis_changes[(self, the_new_basis)] = transf fmodule._basis_changes[(the_new_basis, self)] = inv_transf # return the_new_basis - + #****************************************************************************** class FreeModuleCoBasis(SageObject): - r""" + r""" Dual basis of a free module over a commutative ring. - + INPUT: - - - ``basis`` -- basis of a free module `M` of which ``self`` is the dual + + - ``basis`` -- basis of a free module `M` of which ``self`` is the dual (must be an instance of :class:`FreeModuleBasis`) - ``symbol`` -- a letter (of a few letters) to denote a generic element of the cobasis - - ``latex_symbol`` -- (default: None) symbol to denote a generic element of - the cobasis; if None, the value of ``symbol`` is used. + - ``latex_symbol`` -- (default: ``None``) symbol to denote a generic + element of the cobasis; if ``None``, the value of ``symbol`` is used EXAMPLES: - + Dual basis on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') ; e - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring sage: from sage.tensor.modules.free_module_basis import FreeModuleCoBasis sage: f = FreeModuleCoBasis(e, 'f') ; f - dual basis (f^1,f^2,f^3) on the rank-3 free module M over the Integer Ring - - Let us check that the elements of f are tensors of type (0,1) on M:: + Dual basis (f^1,f^2,f^3) on the Rank-3 free module M over the Integer Ring + + Let us check that the elements of ``f`` are tensors of type `(0,1)` + on ``M``:: sage: f[1] in M.tensor_module(0,1) True sage: f[1] - linear form f^1 on the rank-3 free module M over the Integer Ring + Linear form f^1 on the Rank-3 free module M over the Integer Ring + + and that ``f`` is indeed the dual of ``e``:: - and that f is indeed the dual of e:: - sage: f[1](e[1]), f[1](e[2]), f[1](e[3]) (1, 0, 0) sage: f[2](e[1]), f[2](e[2]), f[2](e[3]) @@ -516,13 +475,13 @@ class FreeModuleCoBasis(SageObject): def __init__(self, basis, symbol, latex_symbol=None): r""" TEST:: - + sage: from sage.tensor.modules.free_module_basis import FreeModuleCoBasis sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: FreeModuleCoBasis(e, 'f') - dual basis (f^0,f^1,f^2) on the rank-3 free module M over the Integer Ring - + sage: f = FreeModuleCoBasis(e, 'f') + sage: TestSuite(f).run() + """ self._basis = basis self._fmodule = basis._fmodule @@ -531,7 +490,7 @@ def __init__(self, basis, symbol, latex_symbol=None): if latex_symbol is None: latex_symbol = symbol self._latex_name = r"\left(" + \ - ",".join([latex_symbol + "^" + str(i) + ",".join([latex_symbol + "^" + str(i) for i in self._fmodule.irange()]) + r"\right)" # The individual linear forms: vl = list() @@ -544,73 +503,74 @@ def __init__(self, basis, symbol, latex_symbol=None): v.set_comp(basis)[i] = 1 vl.append(v) self._form = tuple(vl) - + def _repr_(self): r""" - String representation of the object. - - EXAMPLE:: - + Return a string representation of ``self``. + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: f = e.dual_basis() - sage: f._repr_() - 'dual basis (e^0,e^1,e^2) on the rank-3 free module M over the Integer Ring' + sage: f + Dual basis (e^0,e^1,e^2) on the + Rank-3 free module M over the Integer Ring """ - return "dual basis " + self._name + " on the " + str(self._fmodule) + return "Dual basis {} on the {}".format(self._name, self._fmodule) def _latex_(self): r""" - LaTeX representation of the object. - - EXAMPLE:: - + Return a LaTeX representation of ``self``. + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: f = e.dual_basis() sage: f._latex_() '\\left(e^0,e^1,e^2\\right)' - + """ return self._latex_name def __getitem__(self, index): r""" - Returns the basis linear form corresponding to a given index. - + Return the basis linear form corresponding to a given index. + INPUT: - - - ``index`` -- the index of the linear form - - EXAMPLE:: - + + - ``index`` -- the index of the linear form + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: f = e.dual_basis() - sage: f.__getitem__(0) - linear form e^0 on the rank-3 free module M over the Integer Ring - sage: f.__getitem__(1) - linear form e^1 on the rank-3 free module M over the Integer Ring - sage: f.__getitem__(2) - linear form e^2 on the rank-3 free module M over the Integer Ring + sage: f[0] + Linear form e^0 on the Rank-3 free module M over the Integer Ring + sage: f[1] + Linear form e^1 on the Rank-3 free module M over the Integer Ring + sage: f[2] + Linear form e^2 on the Rank-3 free module M over the Integer Ring sage: f[1] is f.__getitem__(1) True sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: f1 = M1.basis('e').dual_basis() - sage: f1.__getitem__(1) - linear form e^1 on the rank-3 free module M over the Integer Ring - sage: f1.__getitem__(2) - linear form e^2 on the rank-3 free module M over the Integer Ring - sage: f1.__getitem__(3) - linear form e^3 on the rank-3 free module M over the Integer Ring + sage: f1[1] + Linear form e^1 on the Rank-3 free module M over the Integer Ring + sage: f1[2] + Linear form e^2 on the Rank-3 free module M over the Integer Ring + sage: f1[3] + Linear form e^3 on the Rank-3 free module M over the Integer Ring """ n = self._fmodule._rank si = self._fmodule._sindex i = index - si if i < 0 or i > n-1: - raise ValueError("Index out of range: " + - str(i+si) + " not in [" + str(si) + "," + - str(n-1+si) + "]") + raise IndexError("out of range: {} not in [{},{}]".format(i+si. si,n-1+si)) return self._form[i] + + diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index f21c7a1c807..f1808271195 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -3,7 +3,7 @@ The class :class:`FreeModuleTensor` implements tensors over a free module `M`, i.e. elements of the free module `T^{(k,l)}(M)` of tensors of type `(k,l)` -acting as multilinear forms on `M`. +acting as multilinear forms on `M`. A *tensor of type* `(k,l)` is a multilinear map: @@ -12,35 +12,35 @@ \underbrace{M^*\times\cdots\times M^*}_{k\ \; \mbox{times}} \times \underbrace{M\times\cdots\times M}_{l\ \; \mbox{times}} \longrightarrow R - -where `R` is the commutative ring over which the free module `M` is defined and -`M^*=\mathrm{Hom}_R(M,R)` is the dual of `M`. The integer `k+l` is called the -*tensor rank*. -Various derived classes of :class:`FreeModuleTensor` are devoted to specific +where `R` is the commutative ring over which the free module `M` is defined +and `M^* = \mathrm{Hom}_R(M,R)` is the dual of `M`. The integer `k + l` is +called the *tensor rank*. + +Various derived classes of :class:`FreeModuleTensor` are devoted to specific tensors: -* :class:`FiniteRankFreeModuleElement` for elements of `M`, considered as +* :class:`FiniteRankFreeModuleElement` for elements of `M`, considered as type-(1,0) tensors thanks to the canonical identification `M^{**}=M`, which holds since `M` is a free module of finite rank -* :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` for - fully antisymmetric type-`(0,l)` tensors (alternating forms) +* :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` for + fully antisymmetric type-`(0, l)` tensors (alternating forms) - * :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` for - type-(0,1) tensors (linear forms) + * :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` for + type-(0, 1) tensors (linear forms) -* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` - for type-(1,1) tensors (endomorphisms) +* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + for type-(1, 1) tensors (endomorphisms) - * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` for invertible endomorphisms - * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` for the identity map on a free module :class:`FreeModuleTensor` is a Sage *element* class, the corresponding *parent* -class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. +class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. AUTHORS: @@ -49,109 +49,112 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. EXAMPLES: - A tensor of type (1,1) on a rank-3 free module over `\ZZ`:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: t = M.tensor((1,1), name='t') ; t - endomorphism t on the rank-3 free module M over the Integer Ring - sage: t.parent() - free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring - sage: t.parent() is M.tensor_module(1,1) - True - sage: t in M.tensor_module(1,1) - True - - Setting some component of the tensor in a given basis:: - - sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring - sage: t.set_comp(e)[0,0] = -3 # the component [0,0] w.r.t. basis e is set to -3 - - The unset components are assumed to be zero:: - - sage: t.comp(e)[:] # list of all components w.r.t. basis e - [-3 0 0] - [ 0 0 0] - [ 0 0 0] - sage: t.view(e) # expansion of t on the basis e_i*e^j of T^(1,1)(M) - t = -3 e_0*e^0 - - The commands t.set_comp(e) and t.comp(e) can be abridged by providing - the basis as the first argument in the square brackets:: - - sage: t[e,0,0] = -3 - sage: t[e,:] - [-3 0 0] - [ 0 0 0] - [ 0 0 0] +A tensor of type `(1, 1)` on a rank-3 free module over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: t = M.tensor((1,1), name='t') ; t + Endomorphism t on the Rank-3 free module M over the Integer Ring + sage: t.parent() + Free module of type-(1,1) tensors on the Rank-3 free module M + over the Integer Ring + sage: t.parent() is M.tensor_module(1,1) + True + sage: t in M.tensor_module(1,1) + True + +Setting some component of the tensor in a given basis:: + + sage: e = M.basis('e') ; e + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: t.set_comp(e)[0,0] = -3 # the component [0,0] w.r.t. basis e is set to -3 + +The unset components are assumed to be zero:: + + sage: t.comp(e)[:] # list of all components w.r.t. basis e + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + sage: t.view(e) # expansion of t on the basis e_i*e^j of T^(1,1)(M) + t = -3 e_0*e^0 + +The commands ``t.set_comp(e)`` and ``t.comp(e)`` can be abridged by providing +the basis as the first argument in the square brackets:: + + sage: t[e,0,0] = -3 + sage: t[e,:] + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + +Actually, since ``e`` is ``M``'s default basis, the mention of ``e`` +can be omitted:: + + sage: t[0,0] = -3 + sage: t[:] + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + +Tensor components can be modified (reset) at any time:: + + sage: t[0,0] = 0 + sage: t[:] + [0 0 0] + [0 0 0] + [0 0 0] + +Checking that ``t`` is zero:: + + sage: t.is_zero() + True + sage: t == 0 + True + sage: t == M.tensor_module(1,1).zero() # the zero element of the module of all type-(1,1) tensors on M + True + +The components are managed by the +:class:`~sage.tensor.modules.comp.Components`:: + + sage: type(t.comp(e)) + + +Only non-zero components are actually stored, in the dictionary :attr:`_comp` +of class :class:`~sage.tensor.modules.comp.Components`, whose keys are +the indices:: + + sage: t.comp(e)._comp + {} + sage: t.set_comp(e)[0,0] = -3 ; t.set_comp(e)[1,2] = 2 + sage: t.comp(e)._comp # random output order (dictionary) + {(0, 0): -3, (1, 2): 2} + sage: t.view(e) + t = -3 e_0*e^0 + 2 e_1*e^2 + +Further tests of the comparison operator:: + + sage: t.is_zero() + False + sage: t == 0 + False + sage: t == M.tensor_module(1,1).zero() + False + sage: t1 = t.copy() + sage: t1 == t + True + sage: t1[2,0] = 4 + sage: t1 == t + False + +As a multilinear map `M^* \times M \rightarrow \ZZ`, the type-`(1,1)` +tensor ``t`` acts on pairs formed by a linear form and a module element:: + + sage: a = M.linear_form(name='a') ; a[:] = (2, 1, -3) ; a + Linear form a on the Rank-3 free module M over the Integer Ring + sage: b = M([1,-6,2], name='b') ; b + Element b of the Rank-3 free module M over the Integer Ring + sage: t(a,b) + -2 - Actually, since e is M's default basis, the mention of e can be omitted:: - - sage: t[0,0] = -3 - sage: t[:] - [-3 0 0] - [ 0 0 0] - [ 0 0 0] - - Tensor components can be modified (reset) at any time:: - - sage: t[0,0] = 0 - sage: t[:] - [0 0 0] - [0 0 0] - [0 0 0] - - Checking that t is zero:: - - sage: t.is_zero() - True - sage: t == 0 - True - sage: t == M.tensor_module(1,1).zero() # the zero element of the module of all type-(1,1) tensors on M - True - - The components are managed by the class :class:`~sage.tensor.modules.comp.Components`:: - - sage: type(t.comp(e)) - - - Only non-zero components are actually stored, in the dictionary :attr:`_comp` - of class :class:`~sage.tensor.modules.comp.Components`, whose keys are the indices:: - - sage: t.comp(e)._comp - {} - sage: t.set_comp(e)[0,0] = -3 ; t.set_comp(e)[1,2] = 2 - sage: t.comp(e)._comp # random output order (dictionary) - {(0, 0): -3, (1, 2): 2} - sage: t.view(e) - t = -3 e_0*e^0 + 2 e_1*e^2 - - Further tests of the comparison operator:: - - sage: t.is_zero() - False - sage: t == 0 - False - sage: t == M.tensor_module(1,1).zero() - False - sage: t1 = t.copy() - sage: t1 == t - True - sage: t1[2,0] = 4 - sage: t1 == t - False - - As a multilinear map `M^*\times M \rightarrow \ZZ`, the type-(1,1) tensor t - acts on pairs formed by a linear form and a module element:: - - sage: a = M.linear_form(name='a') ; a[:] = (2, 1, -3) ; a - linear form a on the rank-3 free module M over the Integer Ring - sage: b = M([1,-6,2], name='b') ; b - element b of the rank-3 free module M over the Integer Ring - sage: t(a,b) - -2 - - """ #****************************************************************************** # Copyright (C) 2014 Eric Gourgoulhon @@ -164,61 +167,63 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. #****************************************************************************** from sage.rings.integer import Integer -from sage.structure.element import ModuleElement -from comp import Components, CompWithSym, CompFullySym, CompFullyAntiSym -from tensor_with_indices import TensorWithIndices +from sage.structure.element import ModuleElement +from sage.tensor.modules.comp import (Components, CompWithSym, CompFullySym, + CompFullyAntiSym) +from sage.tensor.modules.tensor_with_indices import TensorWithIndices class FreeModuleTensor(ModuleElement): r""" Tensor over a free module of finite rank over a commutative ring. - + INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``tensor_type`` -- pair (k,l) with k being the contravariant rank and l - the covariant rank + - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the contravariant + rank and ``l`` the covariant rank - ``name`` -- (default: None) name given to the tensor - - ``latex_name`` -- (default: None) LaTeX symbol to denote the tensor; + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the tensor; if none is provided, the LaTeX symbol is set to ``name`` - - ``sym`` -- (default: None) a symmetry or a list of symmetries among the - tensor arguments: each symmetry is described by a tuple containing - the positions of the involved arguments, with the convention position=0 - for the first argument. For instance: + - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries among + the tensor arguments: each symmetry is described by a tuple containing + the positions of the involved arguments, with the convention + ``position=0`` for the first argument. For instance: - * sym=(0,1) for a symmetry between the 1st and 2nd arguments - * sym=[(0,2),(1,3,4)] for a symmetry between the 1st and 3rd - arguments and a symmetry between the 2nd, 4th and 5th arguments. + * ``sym = (0,1)`` for a symmetry between the 1st and 2nd arguments; + * ``sym = [(0,2), (1,3,4)]`` for a symmetry between the 1st and 3rd + arguments and a symmetry between the 2nd, 4th and 5th arguments. - - ``antisym`` -- (default: None) antisymmetry or list of antisymmetries - among the arguments, with the same convention as for ``sym``. + - ``antisym`` -- (default: ``None``) antisymmetry or list of antisymmetries + among the arguments, with the same convention as for ``sym`` EXAMPLES: - A tensor of type (1,1) on a rank-3 free module over `\ZZ`:: - + A tensor of type `(1,1)` on a rank-3 free module over `\ZZ`:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t - endomorphism t on the rank-3 free module M over the Integer Ring + Endomorphism t on the Rank-3 free module M over the Integer Ring Tensors are *Element* objects whose parents are tensor free modules:: - + sage: t.parent() - free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,1) tensors on the + Rank-3 free module M over the Integer Ring sage: t.parent() is M.tensor_module(1,1) True - + """ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, sym=None, antisym=None): r""" TEST:: - + sage: from sage.tensor.modules.free_module_tensor import FreeModuleTensor sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: FreeModuleTensor(M, (2,1), name='t', latex_name=r'\tau', sym=(0,1)) - type-(2,1) tensor t on the rank-3 free module M over the Integer Ring - + sage: t = FreeModuleTensor(M, (2,1), name='t', latex_name=r'\tau', sym=(0,1)) + sage: TestSuite(t).run() + """ ModuleElement.__init__(self, fmodule.tensor_module(*tensor_type)) self._fmodule = fmodule @@ -229,12 +234,13 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, self._latex_name = self._name else: self._latex_name = latex_name - self._components = {} # dict. of the sets of components on various + self._components = {} # dict. of the sets of components on various # bases, with the bases as keys (initially empty) + # Treatment of symmetry declarations: self._sym = [] if sym is not None and sym != []: - if isinstance(sym[0], (int, Integer)): + if isinstance(sym[0], (int, Integer)): # a single symmetry is provided as a tuple -> 1-item list: sym = [tuple(sym)] for isym in sym: @@ -243,10 +249,10 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, if i<0 or i>self._tensor_rank-1: raise IndexError("Invalid position: " + str(i) + " not in [0," + str(self._tensor_rank-1) + "]") - self._sym.append(tuple(isym)) + self._sym.append(tuple(isym)) self._antisym = [] if antisym is not None and antisym != []: - if isinstance(antisym[0], (int, Integer)): + if isinstance(antisym[0], (int, Integer)): # a single antisymmetry is provided as a tuple -> 1-item list: antisym = [tuple(antisym)] for isym in antisym: @@ -256,6 +262,7 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, raise IndexError("Invalid position: " + str(i) + " not in [0," + str(self._tensor_rank-1) + "]") self._antisym.append(tuple(isym)) + # Final consistency check: index_list = [] for isym in self._sym: @@ -264,26 +271,28 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, index_list += isym if len(index_list) != len(set(index_list)): # There is a repeated index position: - raise IndexError("Incompatible lists of symmetries: the same " + - "position appears more than once.") + raise IndexError("incompatible lists of symmetries: the same " + + "position appears more than once") + # Initialization of derived quantities: - FreeModuleTensor._init_derived(self) + FreeModuleTensor._init_derived(self) ####### Required methods for ModuleElement (beside arithmetic) ####### - + def __nonzero__(self): r""" - Return True if ``self`` is nonzero and False otherwise. - - This method is called by self.is_zero(). - + Return ``True`` if ``self`` is nonzero and ``False`` otherwise. + + This method is called by ``self.is_zero()``. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,1)) sage: t.add_comp(e) - 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + 3-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring sage: t.__nonzero__() # unitialized components are zero False sage: t == 0 @@ -306,28 +315,28 @@ def __nonzero__(self): """ basis = self.pick_a_basis() return not self._components[basis].is_zero() - + ####### End of required methods for ModuleElement (beside arithmetic) ####### - + def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') - sage: t._repr_() - 'type-(2,1) tensor t on the rank-3 free module M over the Integer Ring' + sage: t + Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring """ # Special cases if self._tensor_type == (0,2) and self._sym == [(0,1)]: - description = "symmetric bilinear form " + description = "Symmetric bilinear form " else: - # Generic case - description = "type-(%s,%s) tensor" % \ - (str(self._tensor_type[0]), str(self._tensor_type[1])) + # Generic case + description = "Type-({},{}) tensor".format( + self._tensor_type[0], self._tensor_type[1]) if self._name is not None: description += " " + self._name description += " on the " + str(self._fmodule) @@ -336,9 +345,9 @@ def _repr_(self): def _latex_(self): r""" LaTeX representation of the object. - + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') sage: t._latex_() @@ -352,20 +361,19 @@ def _latex_(self): \tau sage: t = M.tensor((2,1)) # unnamed tensor sage: t._latex_() - '\\mbox{type-(2,1) tensor on the rank-3 free module M over the Integer Ring}' + '\\mbox{Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring}' """ if self._latex_name is None: return r'\mbox{' + str(self) + r'}' - else: - return self._latex_name + return self._latex_name def _init_derived(self): r""" Initialize the derived quantities - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') sage: t._init_derived() @@ -376,9 +384,9 @@ def _init_derived(self): def _del_derived(self): r""" Delete the derived quantities - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') sage: t._del_derived() @@ -387,37 +395,37 @@ def _del_derived(self): pass # no derived quantities #### Simple accessors #### - + def tensor_type(self): r""" - Return the tensor type of ``self``. - + Return the tensor type of ``self``. + OUTPUT: - - - pair (k,l), where k is the contravariant rank and l is the covariant - rank - + + - pair ``(k, l)``, where ``k`` is the contravariant rank and ``l`` + is the covariant rank + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3) sage: M.an_element().tensor_type() (1, 0) sage: t = M.tensor((2,1)) sage: t.tensor_type() (2, 1) - + """ return self._tensor_type def tensor_rank(self): r""" - Return the tensor rank of ``self``. - + Return the tensor rank of ``self``. + OUTPUT: - - - integer k+l, where k is the contravariant rank and l is the covariant - rank - + + - integer ``k+l``, where ``k`` is the contravariant rank and ``l`` + is the covariant rank + EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3) @@ -433,11 +441,11 @@ def tensor_rank(self): def symmetries(self): r""" Print the list of symmetries and antisymmetries. - + EXAMPLES: - + Various symmetries / antisymmetries for a rank-4 tensor:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((4,0), name='T') # no symmetry declared sage: t.symmetries() @@ -451,43 +459,43 @@ def symmetries(self): sage: t = M.tensor((4,0), name='T', sym=(0,1), antisym=(2,3)) sage: t.symmetries() symmetry: (0, 1); antisymmetry: (2, 3) - + """ if len(self._sym) == 0: s = "no symmetry; " elif len(self._sym) == 1: s = "symmetry: " + str(self._sym[0]) + "; " else: - s = "symmetries: " + str(self._sym) + "; " + s = "symmetries: " + str(self._sym) + "; " if len(self._antisym) == 0: a = "no antisymmetry" elif len(self._antisym) == 1: a = "antisymmetry: " + str(self._antisym[0]) else: - a = "antisymmetries: " + str(self._antisym) + a = "antisymmetries: " + str(self._antisym) print s, a - + #### End of simple accessors ##### def view(self, basis=None, format_spec=None): r""" - Displays the tensor in terms of its expansion onto a given basis. - + Display the tensor in terms of its expansion onto a given basis. + The output is either text-formatted (console mode) or LaTeX-formatted - (notebook mode). - + (notebook mode). + INPUT: - - - ``basis`` -- (default: None) basis of the free module with respect to - which the tensor is expanded; if none is provided, the module's - default basis is assumed - - ``format_spec`` -- (default: None) format specification passed to - ``self._fmodule._output_formatter`` to format the output. + + - ``basis`` -- (default: ``None``) basis of the free module with + respect to which the tensor is expanded; if none is provided, + the module's default basis is assumed + - ``format_spec`` -- (default: ``None``) format specification passed + to ``self._fmodule._output_formatter`` to format the output EXAMPLES: - - Display of a module element (type-(1,0) tensor):: - + + Display of a module element (type-`(1,0)` tensor):: + sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') sage: v = M([1/3,-2], name='v') @@ -496,28 +504,28 @@ def view(self, basis=None, format_spec=None): sage: latex(v.view()) # display in the notebook v = \frac{1}{3} e_1 -2 e_2 - Display of a linear form (type-(0,1) tensor):: - + Display of a linear form (type-`(0,1)` tensor):: + sage: de = e.dual_basis() sage: w = - 3/4 * de[1] + de[2] ; w - linear form on the rank-2 free module M over the Rational Field + Linear form on the Rank-2 free module M over the Rational Field sage: w.set_name('w', latex_name='\omega') sage: w.view() w = -3/4 e^1 + e^2 sage: latex(w.view()) # display in the notebook \omega = -\frac{3}{4} e^1 +e^2 - Display of a type-(1,1) tensor:: - + Display of a type-`(1,1)` tensor:: + sage: t = v*w ; t # the type-(1,1) is formed as the tensor product of v by w - endomorphism v*w on the rank-2 free module M over the Rational Field + Endomorphism v*w on the Rank-2 free module M over the Rational Field sage: t.view() v*w = -1/4 e_1*e^1 + 1/3 e_1*e^2 + 3/2 e_2*e^1 - 2 e_2*e^2 sage: latex(t.view()) # display in the notebook v\otimes \omega = -\frac{1}{4} e_1\otimes e^1 + \frac{1}{3} e_1\otimes e^2 + \frac{3}{2} e_2\otimes e^1 -2 e_2\otimes e^2 Display in a basis which is not the default one:: - + sage: a = M.automorphism() sage: a[:] = [[1,2],[3,4]] sage: f = e.new_basis(a, 'f') @@ -528,7 +536,7 @@ def view(self, basis=None, format_spec=None): sage: t.view(f) v*w = -6 f_1*f^1 - 20/3 f_1*f^2 + 27/8 f_2*f^1 + 15/4 f_2*f^2 - The output format can be set via the argument ``output_formatter`` + The output format can be set via the argument ``output_formatter`` passed at the module construction:: sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1, output_formatter=Rational.numerical_approx) @@ -536,15 +544,15 @@ def view(self, basis=None, format_spec=None): sage: v = N([1/3,-2], name='v') sage: v.view() # default format (53 bits of precision) v = 0.333333333333333 e_1 - 2.00000000000000 e_2 - sage: latex(v.view()) + sage: latex(v.view()) v = 0.333333333333333 e_1 -2.00000000000000 e_2 - + The output format is then controled by the argument ``format_spec`` of the method :meth:`view`:: - + sage: v.view(format_spec=10) # 10 bits of precision v = 0.33 e_1 - 2.0 e_2 - + """ from sage.misc.latex import latex from format_utilities import is_atomic, FormattedExpansion @@ -567,8 +575,8 @@ def view(self, basis=None, format_spec=None): for k in range(n_con, self._tensor_rank): bases_txt.append(cobasis[ind[k]]._name) bases_latex.append(latex(cobasis[ind[k]])) - basis_term_txt = "*".join(bases_txt) - basis_term_latex = r"\otimes ".join(bases_latex) + basis_term_txt = "*".join(bases_txt) + basis_term_latex = r"\otimes ".join(bases_latex) if coef == 1: terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) @@ -581,12 +589,12 @@ def view(self, basis=None, format_spec=None): if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) else: - terms_txt.append("(" + coef_txt + ") " + + terms_txt.append("(" + coef_txt + ") " + basis_term_txt) if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: - terms_latex.append(r"\left(" + coef_latex + r"\right)" + + terms_latex.append(r"\left(" + coef_latex + r"\right)" + basis_term_latex) if terms_txt == []: expansion_txt = "0" @@ -606,7 +614,7 @@ def view(self, basis=None, format_spec=None): expansion_latex += term else: expansion_latex += "+" + term - result = FormattedExpansion(self) + result = FormattedExpansion(self) if self._name is None: result.txt = expansion_txt else: @@ -616,29 +624,29 @@ def view(self, basis=None, format_spec=None): else: result.latex = latex(self) + " = " + expansion_latex return result - + def set_name(self, name=None, latex_name=None): r""" - Set (or change) the text name and LaTeX name of the tensor. + Set (or change) the text name and LaTeX name of ``self``. INPUT: - - - ``name`` -- (string; default: None) name given to the tensor - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the - tensor; if None while ``name`` is provided, the LaTeX symbol is set - to ``name``. + + - ``name`` -- (default: ``None``) string; name given to the tensor + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the tensor; if None while ``name`` is provided, the LaTeX symbol + is set to ``name`` EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1)) ; t - type-(2,1) tensor on the rank-3 free module M over the Integer Ring + Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: t.set_name('t') ; t - type-(2,1) tensor t on the rank-3 free module M over the Integer Ring + Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring sage: latex(t) t sage: t.set_name(latex_name=r'\tau') ; t - type-(2,1) tensor t on the rank-3 free module M over the Integer Ring + Type-(2,1) tensor t on the Rank-3 free module M over the Integer Ring sage: latex(t) \tau @@ -649,51 +657,55 @@ def set_name(self, name=None, latex_name=None): self._latex_name = self._name if latex_name is not None: self._latex_name = latex_name - + def _new_instance(self): r""" - Create a tensor of the same tensor type and with the same symmetries - as ``self``. - + Create a tensor of the same tensor type and with the same symmetries + as ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') sage: t._new_instance() - type-(2,1) tensor on the rank-3 free module M over the Integer Ring + Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: t._new_instance().parent() is t.parent() True """ - return self.__class__(self._fmodule, self._tensor_type, sym=self._sym, + return self.__class__(self._fmodule, self._tensor_type, sym=self._sym, antisym=self._antisym) - def _new_comp(self, basis): + def _new_comp(self, basis): r""" - Create some components in the given basis. - - This method, to be called by :meth:`comp`, must be redefined by derived - classes to adapt the output to the relevant subclass of + Create some components in the given basis. + + This method, to be called by :meth:`comp`, must be redefined by derived + classes to adapt the output to the relevant subclass of :class:`~sage.tensor.modules.comp.Components`. - + OUTPUT: - - - an instance of :class:`~sage.tensor.modules.comp.Components` (or of one of its subclass) - + + - an instance of :class:`~sage.tensor.modules.comp.Components` + (or of one of its subclasses) + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') sage: e = M.basis('e') sage: t._new_comp(e) - 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + 3-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring sage: a = M.tensor((2,1), name='a', sym=(0,1)) sage: a._new_comp(e) - 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, with symmetry on the index positions (0, 1) + 3-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring, + with symmetry on the index positions (0, 1) """ fmodule = self._fmodule # the base free module - if self._sym == [] and self._antisym == []: + if not self._sym and not self._antisym: return Components(fmodule._ring, basis, self._tensor_rank, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) @@ -704,46 +716,47 @@ def _new_comp(self, basis): output_formatter=fmodule._output_formatter) for isym in self._antisym: if len(isym) == self._tensor_rank: - return CompFullyAntiSym(fmodule._ring, basis, self._tensor_rank, + return CompFullyAntiSym(fmodule._ring, basis, self._tensor_rank, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) - return CompWithSym(fmodule._ring, basis, self._tensor_rank, - start_index=fmodule._sindex, + return CompWithSym(fmodule._ring, basis, self._tensor_rank, + start_index=fmodule._sindex, output_formatter=fmodule._output_formatter, - sym=self._sym, antisym=self._antisym) + sym=self._sym, antisym=self._antisym) def comp(self, basis=None, from_basis=None): r""" Return the components in a given basis. - + If the components are not known already, they are computed by the tensor - change-of-basis formula from components in another basis. - + change-of-basis formula from components in another basis. + INPUT: - - - ``basis`` -- (default: None) basis in which the components are - required; if none is provided, the components are assumed to refer to - the module's default basis - - ``from_basis`` -- (default: None) basis from which the - required components are computed, via the tensor change-of-basis - formula, if they are not known already in the basis ``basis``; - if none, a basis is picked in ``self._components``. - - OUTPUT: - - - components in the basis ``basis``, as an instance of the - class :class:`~sage.tensor.modules.comp.Components` - + + - ``basis`` -- (default: ``None``) basis in which the components are + required; if none is provided, the components are assumed to refer + to the module's default basis + - ``from_basis`` -- (default: ``None``) basis from which the + required components are computed, via the tensor change-of-basis + formula, if they are not known already in the basis ``basis``; + if none, a basis is picked in ``self._components`` + + OUTPUT: + + - components in the basis ``basis``, as an instance of the + class :class:`~sage.tensor.modules.comp.Components` + EXAMPLES: - - Components of a tensor of type-(1,1):: - + + Components of a tensor of type-`(1,1)`:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 sage: t.comp() - 2-indices components w.r.t. basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + 2-indices components w.r.t. Basis (e_1,e_2,e_3) + on the Rank-3 free module M over the Integer Ring sage: t.comp() is t.comp(e) # since e is M's default basis True sage: t.comp()[:] @@ -762,50 +775,51 @@ class :class:`~sage.tensor.modules.comp.Components` [ 0 0 2] Components computed via a change-of-basis formula:: - + sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: f = e.new_basis(a, 'f') sage: t.comp(f) - 2-indices components w.r.t. basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring + 2-indices components w.r.t. Basis (f_1,f_2,f_3) + on the Rank-3 free module M over the Integer Ring sage: t.comp(f)[:] [ 0 0 0] [ 0 2 0] [-3 0 0] - + """ fmodule = self._fmodule - if basis is None: + if basis is None: basis = fmodule._def_basis if basis not in self._components: - # The components must be computed from + # The components must be computed from # those in the basis from_basis - if from_basis is None: + if from_basis is None: for known_basis in self._components: if (known_basis, basis) in self._fmodule._basis_changes \ and (basis, known_basis) in self._fmodule._basis_changes: from_basis = known_basis break if from_basis is None: - raise ValueError("No basis could be found for computing " + + raise ValueError("no basis could be found for computing " + "the components in the " + str(basis)) elif from_basis not in self._components: - raise ValueError("The tensor components are not known in the " + + raise ValueError("the tensor components are not known in the " + "basis "+ str(from_basis)) (n_con, n_cov) = self._tensor_type if n_cov > 0: if (from_basis, basis) not in fmodule._basis_changes: - raise ValueError("The change-of-basis matrix from the " + - str(from_basis) + " to the " + str(basis) - + " has not been set.") + raise ValueError("the change-of-basis matrix from the " + + "{} to the {}".format(from_basis, basis) + + " has not been set") pp = \ fmodule._basis_changes[(from_basis, basis)].comp(from_basis) # pp not used if n_cov = 0 (pure contravariant tensor) if n_con > 0: if (basis, from_basis) not in fmodule._basis_changes: - raise ValueError("The change-of-basis matrix from the " + - str(basis) + " to the " + str(from_basis) + - " has not been set.") + raise ValueError("the change-of-basis matrix from the " + + "{} to the {}".format(from_basis, basis) + + " has not been set") ppinv = \ fmodule._basis_changes[(basis, from_basis)].comp(from_basis) # ppinv not used if n_con = 0 (pure covariant tensor) @@ -813,11 +827,11 @@ class :class:`~sage.tensor.modules.comp.Components` new_comp = self._new_comp(basis) rank = self._tensor_rank # loop on the new components: - for ind_new in new_comp.non_redundant_index_generator(): - # Summation on the old components multiplied by the proper - # change-of-basis matrix elements (tensor formula): - res = 0 - for ind_old in old_comp.index_generator(): + for ind_new in new_comp.non_redundant_index_generator(): + # Summation on the old components multiplied by the proper + # change-of-basis matrix elements (tensor formula): + res = 0 + for ind_old in old_comp.index_generator(): t = old_comp[[ind_old]] for i in range(n_con): # loop on contravariant indices t *= ppinv[[ind_new[i], ind_old[i]]] @@ -832,27 +846,27 @@ class :class:`~sage.tensor.modules.comp.Components` def set_comp(self, basis=None): r""" Return the components in a given basis for assignment. - - The components with respect to other bases are deleted, in order to - avoid any inconsistency. To keep them, use the method :meth:`add_comp` + + The components with respect to other bases are deleted, in order to + avoid any inconsistency. To keep them, use the method :meth:`add_comp` instead. - + INPUT: - - - ``basis`` -- (default: None) basis in which the components are - defined; if none is provided, the components are assumed to refer to - the module's default basis. - - OUTPUT: - - - components in the given basis, as an instance of the + + - ``basis`` -- (default: ``None``) basis in which the components are + defined; if none is provided, the components are assumed to refer to + the module's default basis + + OUTPUT: + + - components in the given basis, as an instance of the class :class:`~sage.tensor.modules.comp.Components`; if such components did not exist - previously, they are created. - + previously, they are created. + EXAMPLES: - - Setting components of a type-(1,1) tensor:: - + + Setting components of a type-`(1,1)` tensor:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') @@ -863,35 +877,36 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not sage: t.view() t = -3 e_0*e^1 + 2 e_1*e^2 sage: t.set_comp(e) - 2-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring - + 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring + Setting components in a new basis:: - + sage: f = M.basis('f') sage: t.set_comp(f)[0,1] = 4 sage: t._components.keys() # the components w.r.t. basis e have been deleted - [basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + [Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] sage: t.view(f) t = 4 f_0*f^1 - + The components w.r.t. basis e can be deduced from those w.r.t. basis f, once a relation between the two bases has been set:: - + sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: M.set_basis_change(e, f, a) sage: t.view(e) t = -4 e_1*e^2 sage: t._components.keys() # random output (dictionary keys) - [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, - basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] """ - if basis is None: + if basis is None: basis = self._fmodule._def_basis if basis not in self._components: if basis not in self._fmodule._known_bases: - raise ValueError("The " + str(basis) + " has not been " + + raise ValueError("the " + str(basis) + " has not been " + "defined on the " + str(self._fmodule)) self._components[basis] = self._new_comp(basis) self._del_derived() # deletes the derived quantities @@ -901,33 +916,33 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not def add_comp(self, basis=None): r""" Return the components in a given basis for assignment, keeping the - components in other bases. - - To delete the components in other bases, use the method - :meth:`set_comp` instead. - + components in other bases. + + To delete the components in other bases, use the method + :meth:`set_comp` instead. + INPUT: - - - ``basis`` -- (default: None) basis in which the components are + + - ``basis`` -- (default: ``None``) basis in which the components are defined; if none is provided, the components are assumed to refer to - the module's default basis. - + the module's default basis + .. WARNING:: - - If the tensor has already components in other bases, it + + If the tensor has already components in other bases, it is the user's responsability to make sure that the components - to be added are consistent with them. - - OUTPUT: - - - components in the given basis, as an instance of the - class :class:`~sage.tensor.modules.comp.Components`; if such components did not exist - previously, they are created. - + to be added are consistent with them. + + OUTPUT: + + - components in the given basis, as an instance of the + class :class:`~sage.tensor.modules.comp.Components`; + if such components did not exist previously, they are created + EXAMPLES: - + Setting components of a type-(1,1) tensor:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') @@ -938,18 +953,19 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not sage: t.view() t = -3 e_0*e^1 + 2 e_1*e^2 sage: t.add_comp(e) - 2-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring - + 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring + Adding components in a new basis:: - + sage: f = M.basis('f') sage: t.add_comp(f)[0,1] = 4 - + The components w.r.t. basis e have been kept:: - - sage: t._components.keys() # # random output (dictionary keys) - [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring, - basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring] + + sage: t._components.keys() # # random output (dictionary keys) + [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] sage: t.view(f) t = 4 f_0*f^1 sage: t.view(e) @@ -969,43 +985,43 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not def del_other_comp(self, basis=None): r""" Delete all the components but those corresponding to ``basis``. - + INPUT: - - - ``basis`` -- (default: None) basis in which the components are + + - ``basis`` -- (default: ``None``) basis in which the components are kept; if none the module's default basis is assumed EXAMPLE: - + Deleting components of a module element:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: u = M([2,1,-5]) sage: f = M.basis('f') sage: u.add_comp(f)[:] = [0,4,2] sage: u._components.keys() # random output (dictionary keys) - [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring, - basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring, + Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring] sage: u.del_other_comp(f) sage: u._components.keys() - [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + [Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring] Let us restore the components w.r.t. e and delete those w.r.t. f:: - + sage: u.add_comp(e)[:] = [2,1,-5] sage: u._components.keys() # random output (dictionary keys) - [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring, - basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring, + Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring] sage: u.del_other_comp() # default argument: basis = e sage: u._components.keys() - [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] + [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring] """ if basis is None: basis = self._fmodule._def_basis if basis not in self._components: - raise ValueError("The components w.r.t. the " + - str(basis) + " have not been defined.") + raise ValueError("the components w.r.t. the " + + str(basis) + " have not been defined") to_be_deleted = [] for other_basis in self._components: if other_basis != basis: @@ -1016,25 +1032,26 @@ def del_other_comp(self, basis=None): def __getitem__(self, args): r""" Return a component w.r.t. some basis. - - NB: if ``args`` is a string, this method acts as a shortcut for - tensor contractions and symmetrizations, the string containing + + NB: if ``args`` is a string, this method acts as a shortcut for + tensor contractions and symmetrizations, the string containing abstract indices. INPUT: - - - ``args`` -- list of indices defining the component; if [:] is + + - ``args`` -- list of indices defining the component; if ``[:]`` is provided, all the components are returned. The basis can be passed - as the first item of ``args``; if not, the free module's default - basis is assumed. - + as the first item of ``args``; if not, the free module's default + basis is assumed. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,1), name='t') sage: e = M.basis('e') sage: t.add_comp(e) - 3-indices components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + 3-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring sage: t.__getitem__((1,2,0)) # uninitialized components are zero 0 sage: t.__getitem__((e,1,2,0)) # same as above since e in the default basis @@ -1065,32 +1082,32 @@ def __getitem__(self, args): elif not isinstance(args[0], (int, Integer, slice)): basis = args[0] args = args[1:] - if len(args)==1: - args = args[0] # to accommodate for [e,:] syntax + if len(args) == 1: + args = args[0] # to accommodate for [e,:] syntax else: basis = self._fmodule._def_basis return self.comp(basis)[args] - + def __setitem__(self, args, value): r""" Set a component w.r.t. some basis. INPUT: - - ``args`` -- list of indices defining the component; if [:] is + - ``args`` -- list of indices defining the component; if ``[:]`` is provided, all the components are set. The basis can be passed - as the first item of ``args``; if not, the free module's default - basis is assumed. - - ``value`` -- the value to be set or a list of values if ``args`` - == ``[:]`` - + as the first item of ``args``; if not, the free module's default + basis is assumed + - ``value`` -- the value to be set or a list of values if + ``args = [:]`` + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((0,2), name='t') sage: e = M.basis('e') - sage: t.__setitem__((e,0,1), 5) + sage: t.__setitem__((e,0,1), 5) sage: t.view() t = 5 e^0*e^1 sage: t.__setitem__((0,1), 5) # equivalent to above since e is the default basis @@ -1105,7 +1122,7 @@ def __setitem__(self, args, value): [-4 5 -6] [ 7 -8 9] - """ + """ if isinstance(args, list): # case of [[...]] syntax if isinstance(args[0], (int, Integer, slice, tuple)): basis = self._fmodule._def_basis @@ -1119,7 +1136,7 @@ def __setitem__(self, args, value): basis = args[0] args = args[1:] if len(args)==1: - args = args[0] # to accommodate for [e,:] syntax + args = args[0] # to accommodate for [e,:] syntax else: basis = self._fmodule._def_basis self.set_comp(basis)[args] = value @@ -1128,13 +1145,13 @@ def __setitem__(self, args, value): def copy(self): r""" Return an exact copy of ``self``. - - The name and the derived quantities are not copied. - + + The name and the derived quantities are not copied. + EXAMPLES: - - Copy of a tensor of type (1,1):: - + + Copy of a tensor of type `(1,1)`:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') @@ -1148,7 +1165,7 @@ def copy(self): True If the original tensor is modified, the copy is not:: - + sage: t[2,2] = 4 sage: t1[:] [ 0 -3 0] @@ -1165,82 +1182,83 @@ def copy(self): def common_basis(self, other): r""" - Find a common basis for the components of ``self`` and ``other``. - - In case of multiple common bases, the free module's default basis is - privileged. - If the current components of ``self`` and ``other`` are all relative to - different bases, a common basis is searched by performing a component - transformation, via the transformations listed in - ``self._fmodule._basis_changes``, still privileging transformations to - the free module's default basis. - + Find a common basis for the components of ``self`` and ``other``. + + In case of multiple common bases, the free module's default basis is + privileged. If the current components of ``self`` and ``other`` + are all relative to different bases, a common basis is searched + by performing a component transformation, via the transformations + listed in ``self._fmodule._basis_changes``, still privileging + transformations to the free module's default basis. + INPUT: - + - ``other`` -- a tensor (instance of :class:`FreeModuleTensor`) - + OUPUT: - - - instance of + + - instance of :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` - representing the common basis; if no common basis is found, None is - returned. - + representing the common basis; if no common basis is found, ``None`` + is returned + EXAMPLES: - + Common basis for the components of two module elements:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) sage: e = M.basis('e') sage: u = M([2,1,-5]) sage: f = M.basis('f') sage: M._basis_changes.clear() # to ensure that bases e and f are unrelated at this stage sage: v = M([0,4,2], basis=f) - sage: u.common_basis(v) - - The above result is None since u and v have been defined on different - bases and no connection between these bases have been set:: - + sage: u.common_basis(v) + + The above result is ``None`` since ``u`` and ``v`` have been defined + on different bases and no connection between these bases have + been set:: + sage: u._components.keys() - [basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] + [Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring] sage: v._components.keys() - [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring] + [Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring] + + Linking bases ``e`` and ``f`` changes the result:: - Linking bases e and f changes the result:: - sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: M.set_basis_change(e, f, a) sage: u.common_basis(v) - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring Indeed, v is now known in basis e:: - + sage: v._components.keys() # random output (dictionary keys) - [basis (f_1,f_2,f_3) on the rank-3 free module M over the Integer Ring, - basis (e_1,e_2,e_3) on the rank-3 free module M over the Integer Ring] + [Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring, + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring] """ # Compatibility checks: if not isinstance(other, FreeModuleTensor): - raise TypeError("The argument must be a tensor on a free module.") + raise TypeError("the argument must be a tensor on a free module") fmodule = self._fmodule if other._fmodule != fmodule: - raise TypeError("The two tensors are not defined on the same " + - "free module.") + raise TypeError("the two tensors are not defined on the same " + + "free module") def_basis = fmodule._def_basis - # - # 1/ Search for a common basis among the existing components, i.e. - # without performing any component transformation. + + # 1/ Search for a common basis among the existing components, i.e. + # without performing any component transformation. # ------------------------------------------------------------- if def_basis in self._components and def_basis in other._components: return def_basis # the module's default basis is privileged for basis1 in self._components: if basis1 in other._components: return basis1 + # 2/ Search for a common basis via one component transformation # ---------------------------------------------------------- - # If this point is reached, it is indeed necessary to perform at least + # If this point is reached, it is indeed necessary to perform at least # one component transformation to get a common basis if def_basis in self._components: for obasis in other._components: @@ -1262,7 +1280,7 @@ def common_basis(self, other): if (sbasis, obasis) in fmodule._basis_changes: self.comp(obasis, from_basis=sbasis) return obasis - # + # 3/ Search for a common basis via two component transformations # ----------------------------------------------------------- # If this point is reached, it is indeed necessary to perform at two @@ -1280,60 +1298,60 @@ def common_basis(self, other): self.comp(basis, from_basis=sbasis) other.comp(basis, from_basis=obasis) return basis - # - # If this point is reached, no common basis could be found, even at + + # If this point is reached, no common basis could be found, even at # the price of component transformations: return None - + def pick_a_basis(self): r""" - Return a basis in which the tensor components are defined. - - The free module's default basis is privileged. + Return a basis in which the tensor components are defined. + + The free module's default basis is privileged. OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` - representing the basis + + - instance of + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis` + representing the basis EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') sage: t[0,1] = 4 # component set in the default basis (e) sage: t.pick_a_basis() - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: f = M.basis('f') sage: t.add_comp(f)[2,1] = -4 # the components in basis e are not erased sage: t.pick_a_basis() - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: t.set_comp(f)[2,1] = -4 # the components in basis e not erased sage: t.pick_a_basis() - basis (f_0,f_1,f_2) on the rank-3 free module M over the Integer Ring + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring """ if self._fmodule._def_basis in self._components: return self._fmodule._def_basis # the default basis is privileged else: # a basis is picked arbitrarily: - return self._components.items()[0][0] + return self._components.items()[0][0] def __eq__(self, other): r""" - Comparison (equality) operator. - + Comparison (equality) operator. + INPUT: - + - ``other`` -- a tensor or 0 - + OUTPUT: - - - True if ``self`` is equal to ``other`` and False otherwise - + + - ``True`` if ``self`` is equal to ``other`` and ``False`` otherwise + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') @@ -1360,8 +1378,11 @@ def __eq__(self, other): True """ + if self is other: + return True + if self._tensor_rank == 0: - raise NotImplementedError("Scalar comparison not implemented.") + raise NotImplementedError("scalar comparison not implemented") if isinstance(other, (int, Integer)): # other should be 0 if other == 0: return self.is_zero() @@ -1376,23 +1397,24 @@ def __eq__(self, other): return False basis = self.common_basis(other) if basis is None: - raise ValueError("No common basis for the comparison.") + raise ValueError("no common basis for the comparison") return bool(self._components[basis] == other._components[basis]) def __ne__(self, other): r""" - Inequality operator. - + Inequality operator. + INPUT: - + - ``other`` -- a tensor or 0 - + OUTPUT: - - - True if ``self`` is different from ``other`` and False otherwise - + + - ``True`` if ``self`` is different from ``other`` and ``False`` + otherwise + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') @@ -1415,20 +1437,20 @@ def __ne__(self, other): def __pos__(self): r""" - Unary plus operator. - + Unary plus operator. + OUTPUT: - + - an exact copy of ``self`` - - EXAMPLE:: - + + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') sage: t[0,1] = 7 sage: p = t.__pos__() ; p - type-(2,0) tensor +t on the rank-3 free module M over the Integer Ring + Type-(2,0) tensor +t on the Rank-3 free module M over the Integer Ring sage: p.view() +t = 7 e_0*e_1 sage: p == t @@ -1441,22 +1463,21 @@ def __pos__(self): for basis in self._components: result._components[basis] = + self._components[basis] if self._name is not None: - result._name = '+' + self._name + result._name = '+' + self._name if self._latex_name is not None: result._latex_name = '+' + self._latex_name return result def __neg__(self): r""" - Unary minus operator. - + Unary minus operator. + OUTPUT: - + - the tensor `-T`, where `T` is ``self`` - - EXAMPLE:: - + EXAMPLES:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') @@ -1464,7 +1485,7 @@ def __neg__(self): sage: t.view() t = 7 e_0*e_1 - 4 e_1*e_2 sage: a = t.__neg__() ; a - type-(2,0) tensor -t on the rank-3 free module M over the Integer Ring + Type-(2,0) tensor -t on the Rank-3 free module M over the Integer Ring sage: a.view() -t = -7 e_0*e_1 + 4 e_1*e_2 sage: a == -t @@ -1475,27 +1496,27 @@ def __neg__(self): for basis in self._components: result._components[basis] = - self._components[basis] if self._name is not None: - result._name = '-' + self._name + result._name = '-' + self._name if self._latex_name is not None: result._latex_name = '-' + self._latex_name return result ######### ModuleElement arithmetic operators ######## - + def _add_(self, other): r""" - Tensor addition. - + Tensor addition. + INPUT: - + - ``other`` -- a tensor, of the same type as ``self`` - + OUPUT: - + - the tensor resulting from the addition of ``self`` and ``other`` - + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') @@ -1503,7 +1524,7 @@ def _add_(self, other): sage: b = M.tensor((2,0), name='b') sage: b[:] = [[0,1], [2,3]] sage: s = a._add_(b) ; s - type-(2,0) tensor a+b on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor a+b on the Rank-2 free module M over the Integer Ring sage: s[:] [4 1] [0 8] @@ -1519,7 +1540,7 @@ def _add_(self, other): return +self basis = self.common_basis(other) if basis is None: - raise ValueError("No common basis for the addition.") + raise ValueError("no common basis for the addition") comp_result = self._components[basis] + other._components[basis] result = self._fmodule.tensor_from_comp(self._tensor_type, comp_result) if self._name is not None and other._name is not None: @@ -1530,18 +1551,18 @@ def _add_(self, other): def _sub_(self, other): r""" - Tensor subtraction. - + Tensor subtraction. + INPUT: - + - ``other`` -- a tensor, of the same type as ``self`` - + OUPUT: - + - the tensor resulting from the subtraction of ``other`` from ``self`` - + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') @@ -1549,7 +1570,7 @@ def _sub_(self, other): sage: b = M.tensor((2,0), name='b') sage: b[:] = [[0,1], [2,3]] sage: s = a._sub_(b) ; s - type-(2,0) tensor a-b on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor a-b on the Rank-2 free module M over the Integer Ring sage: s[:] [ 4 -1] [-4 2] @@ -1560,6 +1581,15 @@ def _sub_(self, other): sage: a._sub_(-a) == 2*a True + TESTS: + + Check for when there is not a basis, but the same object:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: t == t + True + """ # No need for consistency check since self and other are guaranted # to belong to the same tensor module @@ -1567,7 +1597,7 @@ def _sub_(self, other): return +self basis = self.common_basis(other) if basis is None: - raise ValueError("No common basis for the subtraction.") + raise ValueError("no common basis for the subtraction") comp_result = self._components[basis] - other._components[basis] result = self._fmodule.tensor_from_comp(self._tensor_type, comp_result) if self._name is not None and other._name is not None: @@ -1578,23 +1608,23 @@ def _sub_(self, other): def _rmul_(self, other): r""" - Multiplication on the left by ``other``. - + Multiplication on the left by ``other``. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') sage: a[:] = [[4,0], [-2,5]] sage: s = a._rmul_(2) ; s - type-(2,0) tensor on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor on the Rank-2 free module M over the Integer Ring sage: s[:] [ 8 0] [-4 10] sage: s == a + a True sage: a._rmul_(0) - type-(2,0) tensor on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor on the Rank-2 free module M over the Integer Ring sage: a._rmul_(0) == 0 True sage: a._rmul_(1) == a @@ -1605,23 +1635,23 @@ def _rmul_(self, other): """ #!# The following test is probably not necessary: if isinstance(other, FreeModuleTensor): - raise NotImplementedError("Left tensor product not implemented.") - # Left multiplication by a scalar: + raise NotImplementedError("left tensor product not implemented") + # Left multiplication by a scalar: result = self._new_instance() for basis in self._components: result._components[basis] = other * self._components[basis] return result ######### End of ModuleElement arithmetic operators ######## - + def __radd__(self, other): r""" - Addition on the left with ``other``. - + Addition on the left with ``other``. + This allows to write "0 + t", where "t" is a tensor - + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') @@ -1629,31 +1659,30 @@ def __radd__(self, other): sage: b = M.tensor((2,0), name='b') sage: b[:] = [[0,1], [2,3]] sage: s = a.__radd__(b) ; s - type-(2,0) tensor a+b on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor a+b on the Rank-2 free module M over the Integer Ring sage: s[:] [4 1] [0 8] sage: s == a+b True sage: s = a.__radd__(0) ; s - type-(2,0) tensor +a on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor +a on the Rank-2 free module M over the Integer Ring sage: s == a True sage: 0 + a == a True - """ return self.__add__(other) def __rsub__(self, other): r""" - Subtraction from ``other``. + Subtraction from ``other``. + + This allows to write ``0 - t``, where ``t`` is a tensor. - This allows to write "0 - t", where "t" is a tensor - EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') @@ -1661,14 +1690,14 @@ def __rsub__(self, other): sage: b = M.tensor((2,0), name='b') sage: b[:] = [[0,1], [2,3]] sage: s = a.__rsub__(b) ; s - type-(2,0) tensor -a+b on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor -a+b on the Rank-2 free module M over the Integer Ring sage: s[:] [-4 1] [ 4 -2] sage: s == b - a True sage: s = a.__rsub__(0) ; s - type-(2,0) tensor +-a on the rank-2 free module M over the Integer Ring + Type-(2,0) tensor +-a on the Rank-2 free module M over the Integer Ring sage: s == -a True sage: 0 - a == -a @@ -1679,10 +1708,10 @@ def __rsub__(self, other): def __mul__(self, other): r""" - Tensor product. - + Tensor product. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') @@ -1690,54 +1719,54 @@ def __mul__(self, other): sage: b = M.tensor((0,2), name='b', antisym=(0,1)) sage: b[0,1] = 3 sage: s = a.__mul__(b) ; s - type-(2,2) tensor a*b on the rank-2 free module M over the Integer Ring + Type-(2,2) tensor a*b on the Rank-2 free module M over the Integer Ring sage: s.symmetries() no symmetry; antisymmetry: (2, 3) sage: s[:] [[[[0, 12], [-12, 0]], [[0, 0], [0, 0]]], [[[0, -6], [6, 0]], [[0, 15], [-15, 0]]]] - + """ from format_utilities import format_mul_txt, format_mul_latex if isinstance(other, FreeModuleTensor): basis = self.common_basis(other) if basis is None: - raise ValueError("No common basis for the tensor product.") + raise ValueError("no common basis for the tensor product") comp_prov = self._components[basis] * other._components[basis] # Reordering of the contravariant and covariant indices: k1, l1 = self._tensor_type k2, l2 = other._tensor_type if l1 != 0: - comp_result = comp_prov.swap_adjacent_indices(k1, - self._tensor_rank, + comp_result = comp_prov.swap_adjacent_indices(k1, + self._tensor_rank, self._tensor_rank+k2) else: comp_result = comp_prov # no reordering is necessary result = self._fmodule.tensor_from_comp((k1+k2, l1+l2), comp_result) result._name = format_mul_txt(self._name, '*', other._name) - result._latex_name = format_mul_latex(self._latex_name, r'\otimes ', + result._latex_name = format_mul_latex(self._latex_name, r'\otimes ', other._latex_name) return result - else: - # multiplication by a scalar: - result = self._new_instance() - for basis in self._components: - result._components[basis] = other * self._components[basis] - return result + + # multiplication by a scalar: + result = self._new_instance() + for basis in self._components: + result._components[basis] = other * self._components[basis] + return result def __div__(self, other): r""" - Division (by a scalar). - - EXAMPLE:: - + Division (by a scalar). + + EXAMPLES:: + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') sage: a[:] = [[4,0], [-2,5]] sage: s = a.__div__(4) ; s - type-(2,0) tensor on the rank-2 free module M over the Rational Field + Type-(2,0) tensor on the Rank-2 free module M over the Rational Field sage: s[:] [ 1 0] [-1/2 5/4] @@ -1751,20 +1780,20 @@ def __div__(self, other): for basis in self._components: result._components[basis] = self._components[basis] / other return result - + def __call__(self, *args): r""" - The tensor acting on linear forms and module elements as a multilinear + The tensor acting on linear forms and module elements as a multilinear map. - + INPUT: - - - ``*args`` -- list of k linear forms and l module elements, ``self`` - being a tensor of type (k,l). - + + - ``*args`` -- list of `k` linear forms and `l` module elements + with ``self`` being a tensor of type `(k, l)` + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,1), name='t', antisym=(0,1)) @@ -1796,20 +1825,20 @@ def __call__(self, *args): # Consistency checks: p = len(args) if p != self._tensor_rank: - raise TypeError(str(self._tensor_rank) + - " arguments must be provided.") + raise TypeError(str(self._tensor_rank) + + " arguments must be provided") for i in range(self._tensor_type[0]): if not isinstance(args[i], FreeModuleLinForm): - raise TypeError("The argument no. " + str(i+1) + - " must be a linear form.") + raise TypeError("the argument no. " + str(i+1) + + " must be a linear form") for i in range(self._tensor_type[0],p): if not isinstance(args[i], FiniteRankFreeModuleElement): - raise TypeError("The argument no. " + str(i+1) + - " must be a module element.") + raise TypeError("the argument no. " + str(i+1) + + " must be a module element") fmodule = self._fmodule # Search for a common basis basis = None - # First try with the module's default basis + # First try with the module's default basis def_basis = fmodule._def_basis if def_basis in self._components: basis = def_basis @@ -1825,10 +1854,10 @@ def __call__(self, *args): if bas not in arg._components: basis = None break - if basis is not None: # common basis found ! + if basis is not None: # common basis found ! break if basis is None: - # A last attempt to find a common basis, possibly via a + # A last attempt to find a common basis, possibly via a # change-of-components transformation for arg in args: self.common_basis(arg) # to trigger some change of components @@ -1838,10 +1867,10 @@ def __call__(self, *args): if bas not in arg._components: basis = None break - if basis is not None: # common basis found ! + if basis is not None: # common basis found ! break if basis is None: - raise ValueError("No common basis for the components.") + raise ValueError("no common basis for the components") t = self._components[basis] v = [args[i]._components[basis] for i in range(p)] res = 0 @@ -1851,7 +1880,7 @@ def __call__(self, *args): prod *= v[i][[ind[i]]] res += prod # Name of the output: - if hasattr(res, '_name'): + if hasattr(res, '_name'): res_name = None if self._name is not None: res_name = self._name + "(" @@ -1866,9 +1895,9 @@ def __call__(self, *args): res_name += args[p-1]._name + ")" else: res_name = None - res._name = res_name + res._name = res_name # LaTeX symbol of the output: - if hasattr(res, '_latex_name'): + if hasattr(res, '_latex_name'): res_latex = None if self._latex_name is not None: res_latex = self._latex_name + r"\left(" @@ -1887,79 +1916,86 @@ def __call__(self, *args): return res def trace(self, pos1=0, pos2=1): - r""" - Trace (contraction) on two slots of the tensor. - + r""" + Trace (contraction) on two slots of the tensor. + INPUT: - - - ``pos1`` -- (default: 0) position of the first index for the + + - ``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 + + - ``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`` - + OUTPUT: - - - tensor or scalar resulting from the (pos1, pos2) contraction - + + - tensor or scalar resulting from the ``(pos1, pos2)`` contraction + EXAMPLES: - - Trace of a type-(1,1) tensor:: + + Trace of a type-`(1,1)` tensor:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a = M.tensor((1,1), name='a') ; a - endomorphism a on the rank-3 free module M over the Integer Ring + Endomorphism a on the Rank-3 free module M over the Integer Ring sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] - sage: a.trace() - 15 + sage: a.trace() + 15 sage: a.trace(0,1) # equivalent to above (contraction of slot 0 with slot 1) 15 sage: a.trace(1,0) # the order of the slots does not matter 15 - + 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 square brackets:: - + sage: a['^i_i'] 15 The letter 'i' to denote the repeated index can be replaced by any other letter:: - + sage: a['^s_s'] 15 - Moreover, the symbol '^' can be omitted:: - + Moreover, the symbol ``^`` can be omitted:: + sage: a['i_i'] 15 The contraction on two slots having the same tensor type cannot occur:: - + sage: b = M.tensor((2,0), name='b') ; b - type-(2,0) tensor b on the rank-3 free module M over the Integer Ring + Type-(2,0) tensor b on the Rank-3 free module M over the Integer Ring sage: b[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: b.trace(0,1) Traceback (most recent call last): ... - IndexError: Contraction on two contravariant indices is not allowed. + IndexError: contraction on two contravariant indices is not allowed The contraction either preserves or destroys the symmetries:: - + sage: b = M.alternating_form(2, 'b') ; b - alternating form b of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form b of degree 2 on the Rank-3 free module M + over the Integer Ring sage: b[0,1], b[0,2], b[1,2] = 3, 2, 1 sage: t = a*b ; t - type-(1,3) tensor a*b on the rank-3 free module M over the Integer Ring - sage: # by construction, t is a tensor field antisymmetric w.r.t. its last two slots: + Type-(1,3) tensor a*b on the Rank-3 free module M + over the Integer Ring + + By construction, ``t`` is a tensor field antisymmetric w.r.t. its + last two slots:: + sage: t.symmetries() no symmetry; antisymmetry: (2, 3) sage: s = t.trace(0,1) ; s # contraction on the first two slots - alternating form of degree 2 on the rank-3 free module M over the Integer Ring + Alternating form of degree 2 on the + Rank-3 free module M over the Integer Ring sage: s.symmetries() # the antisymmetry is preserved no symmetry; antisymmetry: (0, 1) sage: s[:] @@ -1969,7 +2005,7 @@ def trace(self, pos1=0, pos2=1): sage: s == 15*b # check True sage: s = t.trace(0,2) ; s # contraction on the first and third slots - type-(0,2) tensor on the rank-3 free module M over the Integer Ring + Type-(0,2) tensor on the Rank-3 free module M over the Integer Ring sage: s.symmetries() # the antisymmetry has been destroyed by the above contraction: no symmetry; no antisymmetry sage: s[:] # indeed: @@ -1978,9 +2014,9 @@ def trace(self, pos1=0, pos2=1): [-36 0 12] sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) for j in M.irange()] for i in M.irange()] ) # check True - + Use of index notation instead of :meth:`trace`:: - + sage: t['^k_kij'] == t.trace(0,1) True sage: t['^k_{kij}'] == t.trace(0,1) # LaTeX notation @@ -1992,29 +2028,29 @@ def trace(self, pos1=0, pos2=1): Index symbols not involved in the contraction may be replaced by dots:: - + sage: t['^k_k..'] == t.trace(0,1) True sage: t['^k_.k.'] == t.trace(0,2) True sage: t['^k_..k'] == t.trace(0,3) True - + """ - # The indices at pos1 and pos2 must be of different types: + # The indices at pos1 and pos2 must be of different types: k_con = self._tensor_type[0] l_cov = self._tensor_type[1] if pos1 < k_con and pos2 < k_con: - raise IndexError("Contraction on two contravariant indices is " + - "not allowed.") + raise IndexError("contraction on two contravariant indices is " + + "not allowed") if pos1 >= k_con and pos2 >= k_con: - raise IndexError("Contraction on two covariant indices is " + - "not allowed.") - # Frame selection for the computation: + raise IndexError("contraction on two covariant indices is " + + "not allowed") + # Frame selection for the computation: if self._fmodule._def_basis in self._components: basis = self._fmodule._def_basis else: # a basis is picked arbitrarily: - basis = self.pick_a_basis() + basis = self.pick_a_basis() resu_comp = self._components[basis].trace(pos1, pos2) if self._tensor_rank == 2: # result is a scalar return resu_comp @@ -2022,31 +2058,31 @@ def trace(self, pos1=0, pos2=1): return self._fmodule.tensor_from_comp((k_con-1, l_cov-1), resu_comp) def contract(self, *args): - r""" - Contraction on one or more indices with another tensor. - + r""" + Contraction on one or more indices with another tensor. + INPUT: - + - ``pos1`` -- positions of the indices in ``self`` involved in the contraction; ``pos1`` must be a sequence of integers, with 0 standing - for the first index position, 1 for the second one, etc. If ``pos1`` + for the first index position, 1 for the second one, etc; if ``pos1`` is not provided, a single contraction on the last index position of ``self`` is assumed - ``other`` -- the tensor to contract with - ``pos2`` -- positions of the indices in ``other`` involved in the - contraction, with the same conventions as for ``pos1``. If ``pos2`` + contraction, with the same conventions as for ``pos1``; if ``pos2`` is not provided, a single contraction on the first index position of ``other`` is assumed - + OUTPUT: - + - tensor resulting from the contraction at the positions ``pos1`` and ``pos2`` of ``self`` with ``other`` - + EXAMPLES: - - Contraction of a tensor of type (0,1) with a tensor of type (1,0):: - + + Contraction of a tensor of type `(0,1)` with a tensor of type `(1,0)`:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.linear_form() # tensor of type (0,1) is a linear form @@ -2060,33 +2096,33 @@ def contract(self, *args): True The positions of the contraction indices can be set explicitely:: - + sage: s == a.contract(0, b, 0) True sage: s == a.contract(0, b) True sage: s == a.contract(b, 0) True - - Instead of the explicit call to the method :meth:`contract`, the index - notation can be used to specify the contraction, via Einstein - conventation (summation on repeated indices); it suffices to pass the + + Instead of the explicit call to the method :meth:`contract`, the index + notation can be used to specify the contraction, via Einstein + conventation (summation on repeated indices); it suffices to pass the indices as a string inside square brackets:: - + sage: s1 = a['_i']*b['^i'] ; s1 2 sage: s1 == s True - In the present case, performing the contraction is identical to + In the present case, performing the contraction is identical to applying the linear form to the module element:: - + sage: a.contract(b) == a(b) True or to applying the module element, considered as a tensor of type (1,0), to the linear form:: - + sage: a.contract(b) == b(a) True @@ -2095,96 +2131,95 @@ def contract(self, *args): sage: a.contract(b) == b.contract(a) True - Contraction of a tensor of type (1,1) with a tensor of type (1,0):: - + Contraction of a tensor of type `(1,1)` with a tensor of type `(1,0)`:: + sage: a = M.endomorphism() # tensor of type (1,1) sage: a[:] = [[-1,2,3],[4,-5,6],[7,8,9]] sage: s = a.contract(b) ; s - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: s.view() 2 e_0 - 29 e_1 + 36 e_2 - + Since the index positions have not been specified, the contraction takes place on the last position of a (i.e. no. 1) and the first - position of b (i.e. no. 0):: - + position of ``b`` (i.e. no. 0):: + sage: a.contract(b) == a.contract(1, b, 0) True sage: a.contract(b) == b.contract(0, a, 1) True - sage: a.contract(b) == b.contract(a, 1) + sage: a.contract(b) == b.contract(a, 1) True - + Using the index notation with Einstein convention:: - + sage: a['^i_j']*b['^j'] == a.contract(b) True - The index i can be replaced by a dot:: - + The index ``i`` can be replaced by a dot:: + sage: a['^._j']*b['^j'] == a.contract(b) True - and the symbol '^' may be omitted, the distinction between - contravariant and covariant indices being the position with respect to - the symbol '_':: - + and the symbol ``^`` may be omitted, the distinction between + contravariant and covariant indices being the position with respect to + the symbol ``_``:: + sage: a['._j']*b['j'] == a.contract(b) True - - Contraction is possible only between a contravariant index and a + + Contraction is possible only between a contravariant index and a covariant one:: - + sage: a.contract(0, b) Traceback (most recent call last): ... - TypeError: Contraction on two contravariant indices not permitted. + TypeError: contraction on two contravariant indices not permitted - In the present case, performing the contraction is identical to + In the present case, performing the contraction is identical to applying the endomorphism to the module element:: - + sage: a.contract(b) == a(b) True - Contraction of a tensor of type (2,1) with a tensor of type (0,2):: - + Contraction of a tensor of type `(2,1)` with a tensor of type `(0,2)`:: + sage: a = a*b ; a - type-(2,1) tensor on the rank-3 free module M over the Integer Ring + Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: b = M.tensor((0,2)) sage: b[:] = [[-2,3,1], [0,-2,3], [4,-7,6]] sage: s = a.contract(1, b, 1) ; s - type-(1,2) tensor on the rank-3 free module M over the Integer Ring + Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s[:] [[[-9, 16, 39], [18, -32, -78], [27, -48, -117]], [[36, -64, -156], [-45, 80, 195], [54, -96, -234]], [[63, -112, -273], [72, -128, -312], [81, -144, -351]]] - sage: # check of the computation: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == a[i,0,j]*b[k,0]+a[i,1,j]*b[k,1]+a[i,2,j]*b[k,2], - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + + Check of the computation:: + + sage: all(s[i,j,k] == a[i,0,j]*b[k,0]+a[i,1,j]*b[k,1]+a[i,2,j]*b[k,2] + ....: for i in range(3) for j in range(3) for k in range(3)) + True Using index notation:: - + sage: a['il_j']*b['_kl'] == a.contract(1, b, 1) True - + LaTeX notation are allowed:: - + sage: a['^{il}_j']*b['_{kl}'] == a.contract(1, b, 1) True Indices not involved in the contraction may be replaced by dots:: - + sage: a['.l_.']*b['_.l'] == a.contract(1, b, 1) True - - The two tensors do not have to be defined on the same basis for the - contraction to take place, reflecting the fact that the contraction is + + The two tensors do not have to be defined on the same basis for the + contraction to take place, reflecting the fact that the contraction is basis-independent:: - + sage: A = M.automorphism() sage: A[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: h = e.new_basis(A, 'h') @@ -2194,31 +2229,31 @@ def contract(self, *args): [ 3 -1 -2] sage: b.del_other_comp(h) # deletes components w.r.t. basis e sage: b._components.keys() # indeed: - [basis (h_0,h_1,h_2) on the rank-3 free module M over the Integer Ring] + [Basis (h_0,h_1,h_2) on the Rank-3 free module M over the Integer Ring] sage: a._components.keys() # while a is known only in basis e: - [basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring] + [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring] sage: s1 = a.contract(1, b, 1) ; s1 # yet the computation is possible - type-(1,2) tensor on the rank-3 free module M over the Integer Ring + Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s1 == s # ... and yields the same result as previously: True - The contraction can be performed on more than a single index; for - instance a 2-indices contraction of a type-(2,1) tensor with a - type-(1,2) one is:: - + The contraction can be performed on more than a single index; for + instance a `2`-indices contraction of a type-`(2,1)` tensor with a + type-`(1,2)` one is:: + sage: a # a is a tensor of type-(2,1) - type-(2,1) tensor on the rank-3 free module M over the Integer Ring + Type-(2,1) tensor on the Rank-3 free module M over the Integer Ring sage: b = M([1,-1,2])*b ; b # a tensor of type (1,2) - type-(1,2) tensor on the rank-3 free module M over the Integer Ring - sage: s = a.contract(1,2,b,1,0) ; s # the double contraction - endomorphism on the rank-3 free module M over the Integer Ring + Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring + sage: s = a.contract(1,2,b,1,0) ; s # the double contraction + Endomorphism on the Rank-3 free module M over the Integer Ring sage: s[:] [ -36 30 15] [-252 210 105] [-204 170 85] sage: s == a['^.k_l']*b['^l_k.'] # the same thing in index notation True - + """ # # Treatment of the input @@ -2230,7 +2265,7 @@ def contract(self, *args): it = i break else: - raise TypeError("A tensor must be provided in the argument list.") + raise TypeError("a tensor must be provided in the argument list") if it == 0: pos1 = (self._tensor_rank - 1,) else: @@ -2241,24 +2276,24 @@ def contract(self, *args): pos2 = args[it+1:] ncontr = len(pos1) # number of contractions if len(pos2) != ncontr: - raise TypeError("Different number of indices for the contraction.") + raise TypeError("different number of indices for the contraction") k1, l1 = self._tensor_type k2, l2 = other._tensor_type for i in range(ncontr): p1 = pos1[i] p2 = pos2[i] if p1 < k1 and p2 < k2: - raise TypeError("Contraction on two contravariant indices " + - "not permitted.") + raise TypeError("contraction on two contravariant indices " + + "not permitted") if p1 >= k1 and p2 >= k2: - raise TypeError("Contraction on two covariant indices " + - "not permitted.") + raise TypeError("contraction on two covariant indices " + + "not permitted") # # Contraction at the component level # basis = self.common_basis(other) if basis is None: - raise ValueError("No common basis for the contraction.") + raise ValueError("no common basis for the contraction") args = pos1 + (other._components[basis],) + pos2 cmp_res = self._components[basis].contract(*args) if self._tensor_rank + other._tensor_rank - 2*ncontr == 0: @@ -2272,14 +2307,14 @@ def contract(self, *args): for pos in range(k1,k1+l1): if pos not in pos1: nb_cov_s += 1 - nb_con_o = 0 # Number of contravariant indices of other not involved + nb_con_o = 0 # Number of contravariant indices of other not involved # in the contraction for pos in range(0,k2): if pos not in pos2: nb_con_o += 1 if nb_cov_s != 0 and nb_con_o !=0: # some reodering is necessary: - p2 = k1 + l1 - ncontr + p2 = k1 + l1 - ncontr p1 = p2 - nb_cov_s p3 = p2 + nb_con_o cmp_res = cmp_res.swap_adjacent_indices(p1, p2, p3) @@ -2289,33 +2324,33 @@ def contract(self, *args): def symmetrize(self, *pos, **kwargs): r""" Symmetrization over some arguments. - + INPUT: - - - ``pos`` -- list of argument positions involved in the - symmetrization (with the convention position=0 for the first - argument); if none, the symmetrization is performed over all the + + - ``pos`` -- list of argument positions involved in the + symmetrization (with the convention ``position=0`` for the first + argument); if none, the symmetrization is performed over all the arguments - - ``basis`` -- (default: None) module basis with respect to which the - component computation is to be performed; if none, the module's + - ``basis`` -- (default: ``None``) module basis with respect to which + the component computation is to be performed; if none, the module's default basis is used if the tensor field has already components - in it; otherwise another basis w.r.t. which the tensor has + in it; otherwise another basis w.r.t. which the tensor has components will be picked - + OUTPUT: - + - the symmetrized tensor (instance of :class:`FreeModuleTensor`) - + EXAMPLES: - - Symmetrization of a tensor of type (2,0):: - + + Symmetrization of a tensor of type `(2,0)`:: + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,0)) sage: t[:] = [[2,1,-3],[0,-4,5],[-1,4,2]] sage: s = t.symmetrize() ; s - type-(2,0) tensor on the rank-3 free module M over the Rational Field + Type-(2,0) tensor on the Rank-3 free module M over the Rational Field sage: t[:], s[:] ( [ 2 1 -3] [ 2 1/2 -2] @@ -2324,59 +2359,52 @@ def symmetrize(self, *pos, **kwargs): ) sage: s.symmetries() symmetry: (0, 1); no antisymmetry - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: print s[i,j] == 1/2*(t[i,j]+t[j,i]), - ....: - True True True True True True True True True - - Instead of invoking the method :meth:`symmetrize`, one may use the - index notation with parentheses to denote the symmetrization; it + sage: all(s[i,j] == 1/2*(t[i,j]+t[j,i]) # check: + ....: for i in range(3) for j in range(3)) + True + + Instead of invoking the method :meth:`symmetrize`, one may use the + index notation with parentheses to denote the symmetrization; it suffices to pass the indices as a string inside square brackets:: - + sage: t['(ij)'] - type-(2,0) tensor on the rank-3 free module M over the Rational Field + Type-(2,0) tensor on the Rank-3 free module M over the Rational Field sage: t['(ij)'].symmetries() symmetry: (0, 1); no antisymmetry sage: t['(ij)'] == t.symmetrize() True - The indices names are not significant; they can even be replaced by + The indices names are not significant; they can even be replaced by dots:: - + sage: t['(..)'] == t.symmetrize() True The LaTeX notation can be used as well:: - + sage: t['^{(ij)}'] == t.symmetrize() True - Symmetrization of a tensor of type (0,3) on the first two arguments:: - + Symmetrization of a tensor of type `(0,3)` on the first two arguments:: + sage: t = M.tensor((0,3)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.symmetrize(0,1) ; s # (0,1) = the first two arguments - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: s[:] [[[1, 2, 3], [3, -3, 9], [13, -6, -15]], [[3, -3, 9], [13, 14, -15], [-3, 20, 21]], [[13, -6, -15], [-3, 20, 21], [25, 26, -27]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[j,i,k]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[j,i,k]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.symmetrize(0,1) == s # another test True Again the index notation can be used:: - + sage: t['_(ij)k'] == t.symmetrize(0,1) True sage: t['_(..).'] == t.symmetrize(0,1) # no index name @@ -2386,47 +2414,41 @@ def symmetrize(self, *pos, **kwargs): sage: t['_{(..).}'] == t.symmetrize(0,1) # this also allowed True - Symmetrization of a tensor of type (0,3) on the first and last arguments:: + Symmetrization of a tensor of type `(0,3)` on the first and + last arguments:: sage: s = t.symmetrize(0,2) ; s # (0,2) = first and last arguments - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 2); no antisymmetry sage: s[:] [[[1, 6, 11], [-4, 9, -8], [7, 12, 8]], [[6, -11, -4], [9, 14, 4], [12, 17, 22]], [[11, -4, -21], [-8, 4, 24], [8, 22, -27]]] - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[k,j,i]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[k,j,i]) + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.symmetrize(0,2) == s # another test True - Symmetrization of a tensor of type (0,3) on the last two arguments:: - + Symmetrization of a tensor of type `(0,3)` on the last two arguments:: + sage: s = t.symmetrize(1,2) ; s # (1,2) = the last two arguments - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (1, 2); no antisymmetry sage: s[:] [[[1, -1, 5], [-1, 5, 7], [5, 7, -9]], [[10, 1, 14], [1, 14, 1], [14, 1, 18]], [[19, -21, 2], [-21, 23, 25], [2, 25, -27]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/2*(t[i,j,k]+t[i,k,j]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/2*(t[i,j,k]+t[i,k,j]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.symmetrize(1,2) == s # another test True - + Use of the index notation:: - + sage: t['_i(jk)'] == t.symmetrize(1,2) True sage: t['_.(..)'] == t.symmetrize(1,2) @@ -2434,64 +2456,60 @@ def symmetrize(self, *pos, **kwargs): sage: t['_{i(jk)}'] == t.symmetrize(1,2) # LaTeX notation True - Full symmetrization of a tensor of type (0,3):: - + Full symmetrization of a tensor of type `(0,3)`:: + sage: s = t.symmetrize() ; s - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 1, 2); no antisymmetry sage: s[:] [[[1, 8/3, 29/3], [8/3, 7/3, 0], [29/3, 0, -5/3]], [[8/3, 7/3, 0], [7/3, 14, 25/3], [0, 25/3, 68/3]], [[29/3, 0, -5/3], [0, 25/3, 68/3], [-5/3, 68/3, -27]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/6*(t[i,j,k]+t[i,k,j]+t[j,k,i]+t[j,i,k]+t[k,i,j]+t[k,j,i]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/6*(t[i,j,k]+t[i,k,j]+t[j,k,i]+t[j,i,k]+t[k,i,j]+t[k,j,i]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.symmetrize() == s # another test True - + Index notation for the full symmetrization:: - + sage: t['_(ijk)'] == t.symmetrize() True sage: t['_{(ijk)}'] == t.symmetrize() # LaTeX notation True - + Symmetrization can be performed only on arguments on the same type:: - + sage: t = M.tensor((1,2)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] - sage: s = t.symmetrize(0,1) + sage: s = t.symmetrize(0,1) Traceback (most recent call last): ... - TypeError: 0 is a contravariant position, while 1 is a covariant position; - symmetrization is meaningfull only on tensor arguments of the same type. + TypeError: 0 is a contravariant position, while 1 is a covariant position; + symmetrization is meaningfull only on tensor arguments of the same type sage: s = t.symmetrize(1,2) # OK: both 1 and 2 are covariant positions The order of positions does not matter:: - + sage: t.symmetrize(2,1) == t.symmetrize(1,2) True - + Use of the index notation:: - + sage: t['^i_(jk)'] == t.symmetrize(1,2) True sage: t['^._(..)'] == t.symmetrize(1,2) True - - The character '^' can be skipped, the character '_' being sufficient - to separate contravariant indices from covariant ones:: - + + The character ``^`` can be skipped, the character ``_`` being + sufficient to separate contravariant indices from covariant ones:: + sage: t['i_(jk)'] == t.symmetrize(1,2) True - + The LaTeX notation can be employed:: - + sage: t['^{i}_{(jk)}'] == t.symmetrize(1,2) True @@ -2499,24 +2517,24 @@ def symmetrize(self, *pos, **kwargs): if not pos: pos = range(self._tensor_rank) # check whether the symmetrization is possible: - pos_cov = self._tensor_type[0] # first covariant position + pos_cov = self._tensor_type[0] # first covariant position pos0 = pos[0] if pos0 < pos_cov: # pos0 is a contravariant position for k in range(1,len(pos)): if pos[k] >= pos_cov: raise TypeError( - str(pos[0]) + " is a contravariant position, while " + + str(pos[0]) + " is a contravariant position, while " + str(pos[k]) + " is a covariant position; \n" - "symmetrization is meaningfull only on tensor " + - "arguments of the same type.") + "symmetrization is meaningfull only on tensor " + + "arguments of the same type") else: # pos0 is a covariant position for k in range(1,len(pos)): if pos[k] < pos_cov: raise TypeError( str(pos[0]) + " is a covariant position, while " + \ str(pos[k]) + " is a contravariant position; \n" - "symmetrization is meaningfull only on tensor " + - "arguments of the same type.") + "symmetrization is meaningfull only on tensor " + + "arguments of the same type") if 'basis' in kwargs: basis = kwargs['basis'] else: @@ -2524,37 +2542,37 @@ def symmetrize(self, *pos, **kwargs): res_comp = self._components[basis].symmetrize(*pos) return self._fmodule.tensor_from_comp(self._tensor_type, res_comp) - + def antisymmetrize(self, *pos, **kwargs): r""" Antisymmetrization over some arguments. - + INPUT: - - - ``pos`` -- list of argument positions involved in the - antisymmetrization (with the convention position=0 for the first - argument); if none, the antisymmetrization is performed over all the + + - ``pos`` -- list of argument positions involved in the + antisymmetrization (with the convention ``position=0`` for the first + argument); if none, the antisymmetrization is performed over all the arguments - - ``basis`` -- (default: None) module basis with respect to which the - component computation is to be performed; if none, the module's + - ``basis`` -- (default: ``None``) module basis with respect to which + the component computation is to be performed; if none, the module's default basis is used if the tensor field has already components - in it; otherwise another basis w.r.t. which the tensor has + in it; otherwise another basis w.r.t. which the tensor has components will be picked - + OUTPUT: - + - the antisymmetrized tensor (instance of :class:`FreeModuleTensor`) - + EXAMPLES: - - Antisymmetrization of a tensor of type (2,0):: - + + Antisymmetrization of a tensor of type `(2,0)`:: + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,0)) sage: t[:] = [[1,-2,3], [4,5,6], [7,8,-9]] sage: s = t.antisymmetrize() ; s - type-(2,0) tensor on the rank-3 free module M over the Rational Field + Type-(2,0) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) sage: t[:], s[:] @@ -2563,136 +2581,118 @@ def antisymmetrize(self, *pos, **kwargs): [ 4 5 6] [ 3 0 -1] [ 7 8 -9], [ 2 1 0] ) - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: print s[i,j] == 1/2*(t[i,j]-t[j,i]), - ....: - True True True True True True True True True + sage: all(s[i,j] == 1/2*(t[i,j]-t[j,i]) # Check: + ....: for i in range(3) for j in range(3)) + True sage: s.antisymmetrize() == s # another test True sage: t.antisymmetrize() == t.antisymmetrize(0,1) True - Antisymmetrization of a tensor of type (0,3) over the first two + Antisymmetrization of a tensor of type `(0, 3)` over the first two arguments:: sage: t = M.tensor((0,3)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.antisymmetrize(0,1) ; s # (0,1) = the first two arguments - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) sage: s[:] [[[0, 0, 0], [-7, 8, -3], [-6, 14, 6]], [[7, -8, 3], [0, 0, 0], [19, -3, -3]], [[6, -14, -6], [-19, 3, 3], [0, 0, 0]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[j,i,k]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[j,i,k]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.antisymmetrize(0,1) == s # another test True sage: s.symmetrize(0,1) == 0 # of course True Instead of invoking the method :meth:`antisymmetrize`, one can use - the index notation with square brackets denoting the - antisymmetrization; it suffices to pass the indices as a string + the index notation with square brackets denoting the + antisymmetrization; it suffices to pass the indices as a string inside square brackets:: - + sage: s1 = t['_[ij]k'] ; s1 - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s1.symmetries() no symmetry; antisymmetry: (0, 1) sage: s1 == s True The LaTeX notation is recognized:: - + sage: t['_{[ij]k}'] == s True - - Note that in the index notation, the name of the indices is irrelevant; + + Note that in the index notation, the name of the indices is irrelevant; they can even be replaced by dots:: - + sage: t['_[..].'] == s True - Antisymmetrization of a tensor of type (0,3) over the first and last + Antisymmetrization of a tensor of type (0,3) over the first and last arguments:: sage: s = t.antisymmetrize(0,2) ; s # (0,2) = first and last arguments - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 2) sage: s[:] [[[0, -4, -8], [0, -4, 14], [0, -4, -17]], [[4, 0, 16], [4, 0, -19], [4, 0, -4]], [[8, -16, 0], [-14, 19, 0], [17, 4, 0]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[k,j,i]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[k,j,i]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.antisymmetrize(0,2) == s # another test True sage: s.symmetrize(0,2) == 0 # of course True sage: s.symmetrize(0,1) == 0 # no reason for this to hold False - - Antisymmetrization of a tensor of type (0,3) over the last two + + Antisymmetrization of a tensor of type `(0,3)` over the last two arguments:: sage: s = t.antisymmetrize(1,2) ; s # (1,2) = the last two arguments - type-(0,3) tensor on the rank-3 free module M over the Rational Field + Type-(0,3) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (1, 2) sage: s[:] [[[0, 3, -2], [-3, 0, -1], [2, 1, 0]], [[0, -12, -2], [12, 0, -16], [2, 16, 0]], [[0, 1, -23], [-1, 0, -1], [23, 1, 0]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/2*(t[i,j,k]-t[i,k,j]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/2*(t[i,j,k]-t[i,k,j]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.antisymmetrize(1,2) == s # another test True sage: s.symmetrize(1,2) == 0 # of course True - The index notation can be used instead of the explicit call to + The index notation can be used instead of the explicit call to :meth:`antisymmetrize`:: - + sage: t['_i[jk]'] == t.antisymmetrize(1,2) True Full antisymmetrization of a tensor of type (0,3):: - + sage: s = t.antisymmetrize() ; s - alternating form of degree 3 on the rank-3 free module M over the Rational Field + Alternating form of degree 3 on the + Rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1, 2) sage: s[:] [[[0, 0, 0], [0, 0, 2/3], [0, -2/3, 0]], [[0, 0, -2/3], [0, 0, 0], [2/3, 0, 0]], [[0, 2/3, 0], [-2/3, 0, 0], [0, 0, 0]]] - sage: # Check: - sage: for i in range(3): - ....: for j in range(3): - ....: for k in range(3): - ....: print s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k]+t[k,i,j]-t[k,j,i]), - ....: - True True True True True True True True True True True True True True True True True True True True True True True True True True True + sage: all(s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k]+t[k,i,j]-t[k,j,i]) # Check: + ....: for i in range(3) for j in range(3) for k in range(3)) + True sage: s.antisymmetrize() == s # another test True sage: s.symmetrize(0,1) == 0 # of course @@ -2704,9 +2704,9 @@ def antisymmetrize(self, *pos, **kwargs): sage: t.antisymmetrize() == t.antisymmetrize(0,1,2) True - The index notation can be used instead of the explicit call to + The index notation can be used instead of the explicit call to :meth:`antisymmetrize`:: - + sage: t['_[ijk]'] == t.antisymmetrize() True sage: t['_[abc]'] == t.antisymmetrize() @@ -2717,55 +2717,55 @@ def antisymmetrize(self, *pos, **kwargs): True Antisymmetrization can be performed only on arguments on the same type:: - + sage: t = M.tensor((1,2)) sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] - sage: s = t.antisymmetrize(0,1) + sage: s = t.antisymmetrize(0,1) Traceback (most recent call last): ... - TypeError: 0 is a contravariant position, while 1 is a covariant position; - antisymmetrization is meaningfull only on tensor arguments of the same type. + TypeError: 0 is a contravariant position, while 1 is a covariant position; + antisymmetrization is meaningfull only on tensor arguments of the same type sage: s = t.antisymmetrize(1,2) # OK: both 1 and 2 are covariant positions The order of positions does not matter:: - + sage: t.antisymmetrize(2,1) == t.antisymmetrize(1,2) True - + Again, the index notation can be used:: - + sage: t['^i_[jk]'] == t.antisymmetrize(1,2) True sage: t['^i_{[jk]}'] == t.antisymmetrize(1,2) # LaTeX notation True The character '^' can be skipped:: - + sage: t['i_[jk]'] == t.antisymmetrize(1,2) True - + """ if not pos: pos = range(self._tensor_rank) # check whether the antisymmetrization is possible: - pos_cov = self._tensor_type[0] # first covariant position + pos_cov = self._tensor_type[0] # first covariant position pos0 = pos[0] if pos0 < pos_cov: # pos0 is a contravariant position for k in range(1,len(pos)): if pos[k] >= pos_cov: raise TypeError( - str(pos[0]) + " is a contravariant position, while " + + str(pos[0]) + " is a contravariant position, while " + str(pos[k]) + " is a covariant position; \n" - "antisymmetrization is meaningfull only on tensor " + - "arguments of the same type.") + "antisymmetrization is meaningfull only on tensor " + + "arguments of the same type") else: # pos0 is a covariant position for k in range(1,len(pos)): if pos[k] < pos_cov: raise TypeError( str(pos[0]) + " is a covariant position, while " + \ str(pos[k]) + " is a contravariant position; \n" - "antisymmetrization is meaningfull only on tensor " + - "arguments of the same type.") + "antisymmetrization is meaningfull only on tensor " + + "arguments of the same type") if 'basis' in kwargs: basis = kwargs['basis'] else: @@ -2788,132 +2788,129 @@ def antisymmetrize(self, *pos, **kwargs): class FiniteRankFreeModuleElement(FreeModuleTensor): r""" Element of a free module of finite rank over a commutative ring. - - The class :class:`FiniteRankFreeModuleElement` inherits from - :class:`FreeModuleTensor` because the elements of a free module `M` of - finite rank over a commutative ring `R` are identified with tensors of - type (1,0) on `M` via the canonical map - + + The class :class:`FiniteRankFreeModuleElement` inherits from + :class:`FreeModuleTensor` because the elements of a free module `M` of + finite rank over a commutative ring `R` are identified with tensors of + type `(1,0)` on `M` via the canonical map + .. MATH:: \begin{array}{lllllll} \Phi: & M & \longrightarrow & M^{**} & & & \\ & v & \longmapsto & \bar v : & M^* & \longrightarrow & R \\ - & & & & a & \longmapsto & a(v) + & & & & a & \longmapsto & a(v) \end{array} - - Note that for free modules of finite rank, this map is actually an + + Note that for free modules of finite rank, this map is actually an isomorphism, enabling the canonical identification: `M^{**}= M`. - + INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: None) name given to the element - - ``latex_name`` -- (default: None) LaTeX symbol to denote the element; + - ``name`` -- (default: ``None``) name given to the element + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the element; if none is provided, the LaTeX symbol is set to ``name`` - + EXAMPLES: - - Let us consider a rank-3 free module over `\ZZ`:: - + + Let us consider a rank-3 free module `M` over `\ZZ`:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring - - There are three ways to construct an element of the free module M: the first - one (recommended) is via the operator __call__ acting on the free module:: - + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + + There are three ways to construct an element of the free module `M`: + the first one (recommended) is using the free module:: + sage: v = M([2,0,-1], basis=e, name='v') ; v - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: v.view() # expansion on the default basis (e) v = 2 e_0 - e_2 sage: v.parent() is M True + The second way is to construct a tensor of type `(1,0)` on `M` (cf. the + canonical identification `M^{**} = M` recalled above):: - The second way is to construct a tensor of type (1,0) on `M` (cf. the - canonical identification `M^{**}=M` recalled above):: - sage: v2 = M.tensor((1,0), name='v') sage: v2[0], v2[2] = 2, -1 ; v2 - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: v2.view() v = 2 e_0 - e_2 sage: v2 == v True - - Finally, the third way is via some linear combination of the basis + + Finally, the third way is via some linear combination of the basis elements:: - + sage: v3 = 2*e[0] - e[2] sage: v3.set_name('v') ; v3 # in this case, the name has to be set separately - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: v3.view() v = 2 e_0 - e_2 sage: v3 == v True - - The canonical identification `M^{**}=M` is implemented by letting the + + The canonical identification `M^{**} = M` is implemented by letting the module elements act on linear forms, providing the same result as the reverse operation (cf. the map `\Phi` defined above):: - + sage: a = M.linear_form(name='a') sage: a[:] = (2, 1, -3) ; a - linear form a on the rank-3 free module M over the Integer Ring + Linear form a on the Rank-3 free module M over the Integer Ring sage: v(a) 7 sage: a(v) 7 sage: a(v) == v(a) True - - ARITHMETIC EXAMPLES: - + + .. RUBRIC:: ARITHMETIC EXAMPLES + Addition:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a = M([0,1,3], name='a') ; a - element a of the rank-3 free module M over the Integer Ring + Element a of the Rank-3 free module M over the Integer Ring sage: a.view() a = e_1 + 3 e_2 sage: b = M([2,-2,1], name='b') ; b - element b of the rank-3 free module M over the Integer Ring + Element b of the Rank-3 free module M over the Integer Ring sage: b.view() b = 2 e_0 - 2 e_1 + e_2 sage: s = a + b ; s - element a+b of the rank-3 free module M over the Integer Ring + Element a+b of the Rank-3 free module M over the Integer Ring sage: s.view() a+b = 2 e_0 - e_1 + 4 e_2 - sage: # Test of the addition: - sage: for i in M.irange(): print s[i] == a[i] + b[i], - True True True + sage: all(s[i] == a[i] + b[i] for i in M.irange()) + True Subtraction:: - + sage: s = a - b ; s - element a-b of the rank-3 free module M over the Integer Ring + Element a-b of the Rank-3 free module M over the Integer Ring sage: s.view() a-b = -2 e_0 + 3 e_1 + 2 e_2 - sage: # Test of the substraction: - sage: for i in M.irange(): print s[i] == a[i] - b[i], - True True True - + sage: all(s[i] == a[i] - b[i] for i in M.irange()) + True + Multiplication by a scalar:: - + sage: s = 2*a ; s - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: s.view() 2 e_1 + 6 e_2 sage: a.view() a = e_1 + 3 e_2 Tensor product:: - + sage: s = a*b ; s - type-(2,0) tensor a*b on the rank-3 free module M over the Integer Ring + Type-(2,0) tensor a*b on the Rank-3 free module M over the Integer Ring sage: s.symmetries() no symmetry; no antisymmetry sage: s[:] @@ -2921,7 +2918,7 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): [ 2 -2 1] [ 6 -6 3] sage: s = a*s ; s - type-(3,0) tensor a*a*b on the rank-3 free module M over the Integer Ring + Type-(3,0) tensor a*a*b on the Rank-3 free module M over the Integer Ring sage: s[:] [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [2, -2, 1], [6, -6, 3]], @@ -2931,49 +2928,49 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): def __init__(self, fmodule, name=None, latex_name=None): r""" TEST:: - + sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: FiniteRankFreeModuleElement(M, name='v') - element v of the rank-3 free module M over the Integer Ring + sage: v = FiniteRankFreeModuleElement(M, name='v') + sage: TestSuite(v).run() """ - FreeModuleTensor.__init__(self, fmodule, (1,0), name=name, + FreeModuleTensor.__init__(self, fmodule, (1,0), name=name, latex_name=latex_name) def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: v = M([1,-2,3], name='v') - sage: v._repr_() - 'element v of the rank-3 free module M over the Integer Ring' + sage: M([1,-2,3], name='v') + Element v of the Rank-3 free module M over the Integer Ring """ - description = "element " + description = "Element " if self._name is not None: - description += self._name + " " + description += self._name + " " description += "of the " + str(self._fmodule) return description - def _new_comp(self, basis): + def _new_comp(self, basis): r""" - Create some components in the given basis. - - This method, which is already implemented in + Create some components in the given basis. + + This method, which is already implemented in :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: v = M([1,-2,3], name='v') sage: v._new_comp(e) - 1-index components w.r.t. basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + 1-index components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring sage: type(v._new_comp(e)) @@ -2985,15 +2982,15 @@ def _new_comp(self, basis): def _new_instance(self): r""" - Create an instance of the same class as ``self``. - + Create an instance of the same class as ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: v = M([1,-2,3], name='v') sage: v._new_instance() - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: v._new_instance().parent() is v.parent() True diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py index fce3e864d40..dd96dc85322 100644 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -1,8 +1,8 @@ """ Type-(1,1) tensors on free modules -Three derived classes of -:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` are devoted +Three derived classes of +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` are devoted to type-(1,1) tensors: * :class:`FreeModuleEndomorphism` for endomorphisms (generic type-(1,1) tensors) @@ -15,10 +15,10 @@ - Eric Gourgoulhon, Michal Bejger (2014): initial version -TODO: +.. TODO:: -* Implement these specific tensors as elements of a parent class for free - module endomorphisms, with coercion to the module of type-(1,1) tensors. + Implement these specific tensors as elements of a parent class for free + module endomorphisms, with coercion to the module of type-(1,1) tensors. """ #****************************************************************************** @@ -31,69 +31,69 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from free_module_tensor import FreeModuleTensor +from sage.tensor.modules.free_module_tensor import FreeModuleTensor class FreeModuleEndomorphism(FreeModuleTensor): r""" - Endomorphism (considered as a type-(1,1) tensor) on a free module. + Endomorphism (considered as a type-`(1,1)` tensor) on a free module. INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: None) name given to the endomorphism - - ``latex_name`` -- (default: None) LaTeX symbol to denote the + - ``name`` -- (default: ``None``) name given to the endomorphism + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the endomorphism; if none is provided, the LaTeX symbol is set to ``name`` - + EXAMPLES: - + Endomorphism on a rank-3 module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.endomorphism('T') ; t - endomorphism T on the rank-3 free module M over the Integer Ring + Endomorphism T on the Rank-3 free module M over the Integer Ring - An endomorphism is type-(1,1) tensor:: + An endomorphism is type-`(1,1)` tensor:: sage: t.parent() - free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,1) tensors on the + Rank-3 free module M over the Integer Ring sage: t.tensor_type() (1, 1) sage: t.tensor_rank() 2 - Consequently, an endomorphism can also be created by the module method + Consequently, an endomorphism can also be created by the module method :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor`:: - + sage: t = M.tensor((1,1), name='T') ; t - endomorphism T on the rank-3 free module M over the Integer Ring - + Endomorphism T on the Rank-3 free module M over the Integer Ring + Components of the endomorphism with respect to a given basis:: - + sage: e = M.basis('e') ; e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: t[:] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] sage: t[:] [1 2 3] [4 5 6] [7 8 9] sage: t.view() - T = e_0*e^0 + 2 e_0*e^1 + 3 e_0*e^2 + 4 e_1*e^0 + 5 e_1*e^1 + 6 e_1*e^2 + 7 e_2*e^0 + 8 e_2*e^1 + 9 e_2*e^2 + T = e_0*e^0 + 2 e_0*e^1 + 3 e_0*e^2 + 4 e_1*e^0 + 5 e_1*e^1 + + 6 e_1*e^2 + 7 e_2*e^0 + 8 e_2*e^1 + 9 e_2*e^2 The endomorphism acting on a module element:: - + sage: v = M([1,2,3], basis=e, name='v') ; v - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: w = t(v) ; w - element T(v) of the rank-3 free module M over the Integer Ring + Element T(v) of the Rank-3 free module M over the Integer Ring sage: w[:] [14, 32, 50] - sage: # check: - sage: for i in M.irange(): + sage: for i in M.irange(): # Check: ....: print sum( t[i,j]*v[j] for j in M.irange() ), - ....: 14 32 50 - + """ def __init__(self, fmodule, name=None, latex_name=None): r""" @@ -101,76 +101,74 @@ def __init__(self, fmodule, name=None, latex_name=None): sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleEndomorphism sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: FreeModuleEndomorphism(M, name='a') - endomorphism a on the rank-3 free module M over the Integer Ring - + sage: E = FreeModuleEndomorphism(M, name='a') + sage: TestSuite(E).run() + """ - FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, + FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, latex_name=latex_name) - + def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.endomorphism() - sage: a._repr_() - 'endomorphism on the rank-3 free module M over the Integer Ring' - sage: a = M.endomorphism(name='a') - sage: a._repr_() - 'endomorphism a on the rank-3 free module M over the Integer Ring' + sage: M.endomorphism() + Endomorphism on the Rank-3 free module M over the Integer Ring + sage: M.endomorphism(name='a') + Endomorphism a on the Rank-3 free module M over the Integer Ring """ - description = "endomorphism " + description = "Endomorphism " if self._name is not None: - description += self._name + " " + description += self._name + " " description += "on the " + str(self._fmodule) return description def _new_instance(self): r""" - Create an instance of the same class as ``self``. + Create an instance of the same class as ``self``. EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.endomorphism(name='a') sage: a._new_instance() - endomorphism on the rank-3 free module M over the Integer Ring - + Endomorphism on the Rank-3 free module M over the Integer Ring + """ return self.__class__(self._fmodule) def __call__(self, *arg): r""" - Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single - argument (module element). - + Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single + argument (module element). + EXAMPLES: - + Call with a single argument --> return a module element:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: a = M.endomorphism(name='a') sage: e = M.basis('e') sage: a[0,1], a[1,1], a[2,1] = 2, 4, -5 sage: v = M([2,1,4], name='v') sage: s = a.__call__(v) ; s - element a(v) of the rank-3 free module M over the Integer Ring + Element a(v) of the Rank-3 free module M over the Integer Ring sage: s.view() a(v) = 2 e_0 + 4 e_1 - 5 e_2 sage: s == a(v) True sage: s == a.contract(v) True - - Call with two arguments (FreeModuleTensor behaviour) --> return a - scalar:: - + + Call with two arguments (:class:`FreeModuleTensor` behaviour) + --> return a scalar:: + sage: b = M.linear_form(name='b') - sage: b[:] = 7, 0, 2 + sage: b[:] = 7, 0, 2 sage: a.__call__(b,v) 4 sage: a(b,v) == a.__call__(b,v) @@ -181,14 +179,14 @@ def __call__(self, *arg): """ from free_module_tensor import FiniteRankFreeModuleElement if len(arg) > 1: - # the endomorphism acting as a type (1,1) tensor on a pair + # the endomorphism acting as a type (1,1) tensor on a pair # (linear form, module element), returning a scalar: - return FreeModuleTensor.__call__(self, *arg) + return FreeModuleTensor.__call__(self, *arg) # the endomorphism acting as such, on a module element, returning a # module element: vector = arg[0] if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("The argument must be an element of a free module.") + raise TypeError("the argument must be an element of a free module") basis = self.common_basis(vector) t = self._components[basis] v = vector._components[basis] @@ -214,37 +212,38 @@ def __call__(self, *arg): class FreeModuleAutomorphism(FreeModuleEndomorphism): r""" - Automorphism (considered as a type-(1,1) tensor) on a free module. + Automorphism (considered as a type-`(1,1)` tensor) on a free module. INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: None) name given to the automorphism - - ``latex_name`` -- (default: None) LaTeX symbol to denote the + - ``name`` -- (default: ``None``) name given to the automorphism + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the automorphism; if none is provided, the LaTeX symbol is set to ``name`` - + EXAMPLES: - + Automorphism on a rank-2 free module (vector space) on `\QQ`:: - + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: a = M.automorphism('A') ; a - automorphism A on the rank-2 free module M over the Rational Field + Automorphism A on the Rank-2 free module M over the Rational Field + + Automorphisms are tensors of type `(1,1)`:: - Automorphisms are tensors of type (1,1):: - sage: a.parent() - free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field + Free module of type-(1,1) tensors on the + Rank-2 free module M over the Rational Field sage: a.tensor_type() (1, 1) sage: a.tensor_rank() 2 Setting the components in a basis:: - + sage: e = M.basis('e') ; e - basis (e_0,e_1) on the rank-2 free module M over the Rational Field + Basis (e_0,e_1) on the Rank-2 free module M over the Rational Field sage: a[:] = [[1, 2], [-1, 3]] sage: a[:] [ 1 2] @@ -253,9 +252,9 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): A = e_0*e^0 + 2 e_0*e^1 - e_1*e^0 + 3 e_1*e^1 The inverse automorphism is obtained via the method :meth:`inverse`:: - + sage: b = a.inverse() ; b - automorphism A^(-1) on the rank-2 free module M over the Rational Field + Automorphism A^(-1) on the Rank-2 free module M over the Rational Field sage: b.view(basis=e) A^(-1) = 3/5 e_0*e^0 - 2/5 e_0*e^1 + 1/5 e_1*e^0 + 1/5 e_1*e^1 sage: b[:] @@ -264,56 +263,54 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): sage: a[:] * b[:] # check that b is indeed the inverse of a [1 0] [0 1] - + """ def __init__(self, fmodule, name=None, latex_name=None): r""" TEST:: - + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleAutomorphism sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: FreeModuleAutomorphism(M, name='a') - automorphism a on the rank-3 free module M over the Rational Field + sage: a = FreeModuleAutomorphism(M, name='a') + sage: TestSuite(a).run() """ - FreeModuleEndomorphism.__init__(self, fmodule, name=name, + FreeModuleEndomorphism.__init__(self, fmodule, name=name, latex_name=latex_name) self._inverse = None # inverse automorphism not set yet def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: a = M.automorphism() - sage: a._repr_() - 'automorphism on the rank-3 free module M over the Rational Field' - sage: a = M.automorphism(name='a') - sage: a._repr_() - 'automorphism a on the rank-3 free module M over the Rational Field' - + sage: M.automorphism() + Automorphism on the Rank-3 free module M over the Rational Field + sage: M.automorphism(name='a') + Automorphism a on the Rank-3 free module M over the Rational Field + """ - description = "automorphism " + description = "Automorphism " if self._name is not None: - description += self._name + " " + description += self._name + " " description += "on the " + str(self._fmodule) return description - + def _del_derived(self): r""" - Delete the derived quantities - + Delete the derived quantities. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: a = M.automorphism(name='a') sage: e = M.basis('e') sage: a[e,:] = [[1,0,-1], [0,3,0], [0,0,2]] sage: b = a.inverse() - sage: a._inverse - automorphism a^(-1) on the rank-3 free module M over the Rational Field + sage: a._inverse + Automorphism a^(-1) on the Rank-3 free module M over the Rational Field sage: a._del_derived() sage: a._inverse # has been reset to None @@ -321,42 +318,42 @@ def _del_derived(self): # First delete the derived quantities pertaining to the mother class: FreeModuleEndomorphism._del_derived(self) # Then deletes the inverse automorphism: - self._inverse = None + self._inverse = None def inverse(self): r""" Return the inverse automorphism. - + OUTPUT: - - - instance of :class:`FreeModuleAutomorphism` representing the + + - instance of :class:`FreeModuleAutomorphism` representing the automorphism that is the inverse of ``self``. - + EXAMPLES: - + Inverse of an automorphism on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: a = M.automorphism('A') sage: e = M.basis('e') sage: a[:] = [[1,0,-1], [0,3,0], [0,0,2]] sage: b = a.inverse() ; b - automorphism A^(-1) on the rank-3 free module M over the Rational Field + Automorphism A^(-1) on the Rank-3 free module M over the Rational Field sage: b[:] [ 1 0 1/2] [ 0 1/3 0] [ 0 0 1/2] - - We may check that b is the inverse of a by performing the matrix - product of the components in the basis e:: - + + We may check that ``b`` is the inverse of ``a`` by performing the + matrix product of the components in the basis ``e``:: + sage: a[:] * b[:] [1 0 0] [0 1 0] [0 0 1] - + Another check is of course:: - + sage: b.inverse() == a True @@ -373,10 +370,11 @@ def inverse(self): else: inv_latex_name = self._latex_name + r'^{-1}' fmodule = self._fmodule - si = fmodule._sindex ; nsi = fmodule._rank + si + si = fmodule._sindex + nsi = fmodule._rank + si self._inverse = self.__class__(fmodule, inv_name, inv_latex_name) for basis in self._components: - try: + try: mat_self = matrix( [[self.comp(basis)[[i, j]] for j in range(si, nsi)] for i in range(si, nsi)]) @@ -386,7 +384,7 @@ def inverse(self): cinv = Components(fmodule._ring, basis, 2, start_index=si, output_formatter=fmodule._output_formatter) for i in range(si, nsi): - for j in range(si, nsi): + for j in range(si, nsi): cinv[i, j] = mat_inv[i-si,j-si] self._inverse._components[basis] = cinv return self._inverse @@ -399,41 +397,42 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): Identity map (considered as a type-(1,1) tensor) on a free module. INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` + + - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: 'Id') name given to the identity map. - - ``latex_name`` -- (default: None) LaTeX symbol to denote the identity + - ``name`` -- (default: 'Id') name given to the identity map. + - ``latex_name`` -- (default: None) LaTeX symbol to denote the identity map; if none is provided, the LaTeX symbol is set to ``name`` - + EXAMPLES: - + Identity map on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() ; a - identity map on the rank-3 free module M over the Integer Ring + Identity map on the Rank-3 free module M over the Integer Ring + + The LaTeX symbol is set by default to `\mathrm{Id}`, but can be changed:: - The LaTeX symbol is set by default to Id, but can be changed:: - sage: latex(a) \mathrm{Id} sage: a = M.identity_map(latex_name=r'\mathrm{1}') sage: latex(a) \mathrm{1} - The identity map is a tensor of type (1,1) on the free module:: - + The identity map is a tensor of type `(1,1)` on the free module:: + sage: a.parent() - free module of type-(1,1) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,1) tensors on the + Rank-3 free module M over the Integer Ring sage: a.tensor_type() (1, 1) sage: a.tensor_rank() 2 Its components are Kronecker deltas in any basis:: - + sage: a[:] [1 0 0] [0 1 0] @@ -449,88 +448,87 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): [1 0 0] [0 1 0] [0 0 1] - + The components can be read, but cannot be set:: - + sage: a[1,1] 1 sage: a[1,1] = 2 Traceback (most recent call last): ... - NotImplementedError: The components of the identity map cannot be changed. + TypeError: the components of the identity map cannot be changed The identity map acting on a module element:: - + sage: v = M([2,-3,1], basis=e, name='v') sage: v.view() v = 2 e_0 - 3 e_1 + e_2 sage: u = a(v) ; u - element v of the rank-3 free module M over the Integer Ring + Element v of the Rank-3 free module M over the Integer Ring sage: u is v True - The identity map acting as a type-(1,1) tensor on a pair (linear form, + The identity map acting as a type-`(1,1)` tensor on a pair (linear form, module element):: - + sage: w = M.tensor((0,1), name='w') ; w - linear form w on the rank-3 free module M over the Integer Ring + Linear form w on the Rank-3 free module M over the Integer Ring sage: w[:] = [0, 3, 2] sage: s = a(w,v) ; s -7 sage: s == w(v) True - + The identity map is its own inverse:: - + sage: a.inverse() == a True sage: a.inverse() is a True - + """ def __init__(self, fmodule, name='Id', latex_name=None): r""" - TEST:: + TESTs:: sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityMap sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: FreeModuleIdentityMap(M) - identity map on the rank-3 free module M over the Integer Ring - + sage: Id = FreeModuleIdentityMap(M) + sage: TestSuite(Id).run() + """ if latex_name is None and name == 'Id': latex_name = r'\mathrm{Id}' - FreeModuleAutomorphism.__init__(self, fmodule, name=name, + FreeModuleAutomorphism.__init__(self, fmodule, name=name, latex_name=latex_name) self._inverse = self # the identity is its own inverse self.comp() # Initializing the components in the module's default basis def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: id = M.identity_map() - sage: id._repr_() - 'identity map on the rank-3 free module M over the Integer Ring' - + sage: M.identity_map() + Identity map on the Rank-3 free module M over the Integer Ring + """ - description = "identity map " + description = "Identity map " if self._name != 'Id': - description += self._name + " " + description += self._name + " " description += "on the " + str(self._fmodule) return description def _del_derived(self): r""" Delete the derived quantities. - + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: id = M.identity_map() @@ -539,13 +537,13 @@ def _del_derived(self): """ # FreeModuleAutomorphism._del_derived is bypassed: FreeModuleEndomorphism._del_derived(self) - - def _new_comp(self, basis): + + def _new_comp(self, basis): r""" - Create some components in the given basis. - + Create some components in the given basis. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: id = M.identity_map() @@ -553,7 +551,7 @@ def _new_comp(self, basis): Kronecker delta of size 3x3 sage: type(id._new_comp(e)) - + """ from comp import KroneckerDelta fmodule = self._fmodule # the base free module @@ -562,42 +560,42 @@ def _new_comp(self, basis): def comp(self, basis=None, from_basis=None): r""" - Return the components in a given basis, as a Kronecker delta. - + Return the components in a given basis as a Kronecker delta. + INPUT: - - - ``basis`` -- (default: None) module basis in which the components - are required; if none is provided, the components are assumed to + + - ``basis`` -- (default: ``None``) module basis in which the components + are required; if none is provided, the components are assumed to refer to the module's default basis - - ``from_basis`` -- (default: None) unused (present just for ensuring - compatibility with FreeModuleTensor.comp calling list) - - OUTPUT: - - - components in the basis ``basis``, as an instance of the - class :class:`~sage.tensor.modules.comp.KroneckerDelta` - + - ``from_basis`` -- (default: ``None``) unused (present just for + ensuring compatibility with ``FreeModuleTensor.comp`` calling list) + + OUTPUT: + + - components in the basis ``basis``, as an instance of the + class :class:`~sage.tensor.modules.comp.KroneckerDelta` + EXAMPLES: Components of the identity map on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() - sage: a.comp(basis=e) + sage: a.comp(basis=e) Kronecker delta of size 3x3 - + For the module's default basis, the argument ``basis`` can be omitted:: - + sage: a.comp() is a.comp(basis=e) True sage: a.comp()[:] [1 0 0] [0 1 0] [0 0 1] - + """ - if basis is None: + if basis is None: basis = self._fmodule._def_basis if basis not in self._components: self._components[basis] = self._new_comp(basis) @@ -605,48 +603,46 @@ class :class:`~sage.tensor.modules.comp.KroneckerDelta` def set_comp(self, basis=None): r""" - Redefinition of the generic tensor method - :meth:`FreeModuleTensor.set_comp`: should not be called. + Redefinition of the generic tensor method + :meth:`FreeModuleTensor.set_comp`: should not be called. EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() sage: a.set_comp(e) Traceback (most recent call last): ... - NotImplementedError: The components of the identity map cannot be changed. - + TypeError: the components of the identity map cannot be changed + """ - raise NotImplementedError("The components of the identity map " + - "cannot be changed.") + raise TypeError("the components of the identity map cannot be changed") def add_comp(self, basis=None): r""" - Redefinition of the generic tensor method - :meth:`FreeModuleTensor.add_comp`: should not be called. + Redefinition of the generic tensor method + :meth:`FreeModuleTensor.add_comp`: should not be called. EXAMPLE:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: a = M.identity_map() sage: a.add_comp(e) Traceback (most recent call last): ... - NotImplementedError: The components of the identity map cannot be changed. - + TypeError: the components of the identity map cannot be changed + """ - raise NotImplementedError("The components of the identity map " + - "cannot be changed.") + raise TypeError("the components of the identity map cannot be changed") def __call__(self, *arg): r""" Redefinition of :meth:`FreeModuleEndomorphism.__call__`. EXAMPLES: - + Call with a single argument --> return a module element:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') @@ -654,7 +650,7 @@ def __call__(self, *arg): sage: id = M.identity_map() sage: v = M([-1,4,3]) sage: s = id.__call__(v) ; s - element of the rank-3 free module M over the Integer Ring + Element of the Rank-3 free module M over the Integer Ring sage: s == v True sage: s == id(v) @@ -662,8 +658,8 @@ def __call__(self, *arg): sage: s == id.contract(v) True - Call with two arguments (FreeModuleTensor behaviour) --> return a - scalar:: + Call with two arguments (:class:`FreeModuleTensor` behaviour) --> + return a scalar:: sage: b = M.linear_form(name='b') sage: b[:] = 7, 0, 2 @@ -681,19 +677,19 @@ def __call__(self, *arg): # the identity map acting as such, on a module element: vector = arg[0] if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("The argument must be a module element.") + raise TypeError("the argument must be a module element") return vector - #!# should it be return vector.copy() instead ? + #!# should it be return vector.copy() instead ? elif len(arg) == 2: - # the identity map acting as a type (1,1) tensor on a pair + # the identity map acting as a type (1,1) tensor on a pair # (1-form, vector), returning a scalar: linform = arg[0] if not isinstance(linform, FreeModuleLinForm): - raise TypeError("The first argument must be a linear form.") + raise TypeError("the first argument must be a linear form") vector = arg[1] if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("The second argument must be a module element.") + raise TypeError("the second argument must be a module element") return linform(vector) else: - raise TypeError("Wrong number of arguments.") + raise TypeError("wrong number of arguments") diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index ec8724fbbd0..08b672a4646 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -1,48 +1,47 @@ r""" Tensor free modules - -The class :class:`TensorFreeModule` implements the tensor products of the type +The class :class:`TensorFreeModule` implements the tensor products of the type .. MATH:: T^{(k,l)}(M) = \underbrace{M\otimes\cdots\otimes M}_{k\ \; \mbox{times}} \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}} - + where `M` is a free module of finite rank over a commutative ring `R` and -`M^*=\mathrm{Hom}_R(M,R)` is the dual of `M`. -`T^{(k,l)}(M)` can be canonically identified with the set of tensors of -type `(k,l)` acting as multilinear forms on `M`. -Note that `T^{(1,0)}(M) = M`. +`M^*=\mathrm{Hom}_R(M,R)` is the dual of `M`. +`T^{(k,l)}(M)` can be canonically identified with the set of tensors of +type `(k,l)` acting as multilinear forms on `M`. +Note that `T^{(1,0)}(M) = M`. `T^{(k,l)}(M)` is itself a free module over `R`, of rank `n^{k+l}`, `n` -being the rank of `M`. Accordingly the class :class:`TensorFreeModule` +being the rank of `M`. Accordingly the class :class:`TensorFreeModule` inherits from the class :class:`FiniteRankFreeModule` -Thanks to the canonical isomorphism `M^{**}\simeq M` (which holds because `M` -is a free module of finite rank), `T^{(k,l)}(M)` can be identified with the -set of tensors of type `(k,l)` defined as multilinear maps +Thanks to the canonical isomorphism `M^{**} \simeq M` (which holds because `M` +is a free module of finite rank), `T^{(k,l)}(M)` can be identified with the +set of tensors of type `(k,l)` defined as multilinear maps .. MATH:: \underbrace{M^*\times\cdots\times M^*}_{k\ \; \mbox{times}} \times \underbrace{M\times\cdots\times M}_{l\ \; \mbox{times}} \longrightarrow R - + Accordingly, :class:`TensorFreeModule` is a Sage *parent* class, whose *element* class is -:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`. +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`. -TODO: +.. TODO:: -- implement more general tensor products, i.e. tensor product of the type - `M_1\otimes\cdots\otimes M_n`, where the `M_i`'s are `n` free modules of - finite rank over the same ring `R`. + implement more general tensor products, i.e. tensor product of the type + `M_1\otimes\cdots\otimes M_n`, where the `M_i`'s are `n` free modules of + finite rank over the same ring `R`. AUTHORS: - Eric Gourgoulhon, Michal Bejger (2014): initial version - + """ #****************************************************************************** # Copyright (C) 2014 Eric Gourgoulhon @@ -54,57 +53,60 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from finite_rank_free_module import FiniteRankFreeModule -from free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement class TensorFreeModule(FiniteRankFreeModule): r""" - Class for the free modules over a commutative ring `R` that are - tensor products of a given free module `M` over `R` with itself and its + Class for the free modules over a commutative ring `R` that are + tensor products of a given free module `M` over `R` with itself and its dual `M^*`: - + .. MATH:: - + T^{(k,l)}(M) = \underbrace{M\otimes\cdots\otimes M}_{k\ \; \mbox{times}} \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}} - As recalled above, `T^{(k,l)}(M)` can be canonically identified with the - set of tensors of type `(k,l)` acting as multilinear forms on `M`. + As recalled above, `T^{(k,l)}(M)` can be canonically identified with the + set of tensors of type `(k,l)` acting as multilinear forms on `M`. INPUT: - - - ``fmodule`` -- free module `M` of finite rank (must be an instance of + + - ``fmodule`` -- free module `M` of finite rank (must be an instance of :class:`FiniteRankFreeModule`) - - ``tensor_type`` -- pair `(k,l)` with `k` being the contravariant rank and - `l` the covariant rank - - ``name`` -- (string; default: None) name given to the tensor module - - ``latex_name`` -- (string; default: None) LaTeX symbol to denote the + - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the contravariant + rank and ``l`` the covariant rank + - ``name`` -- (default: ``None``) string; name given to the tensor module + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the tensor module; if none is provided, it is set to ``name`` - + EXAMPLES: - - Set of tensors of type (1,2) on a free module of rank 3 over `\ZZ`:: - + + Set of tensors of type `(1,2)` on a free module of rank 3 over `\ZZ`:: + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') + sage: e = M.basis('e') sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule sage: T = TensorFreeModule(M, (1,2)) ; T - free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring - - Instead of importing TensorFreeModule in the global name space, it is - recommended to use the module's method + Free module of type-(1,2) tensors on the + Rank-3 free module M over the Integer Ring + + Instead of importing TensorFreeModule in the global name space, it is + recommended to use the module's method :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor_module`:: - + sage: T = M.tensor_module(1,2) ; T - free module of type-(1,2) tensors on the rank-3 free module M over the Integer Ring + Free module of type-(1,2) tensors on the + Rank-3 free module M over the Integer Ring + + The module ``M`` itself is considered as the set of tensors of + type `(1,0)`:: - The module `M` itself is considered as the set of tensors of type (1,0):: - sage: M is M.tensor_module(1,0) True - T is a module (actually a free module) over `\ZZ`:: - + ``T`` is a module (actually a free module) over `\ZZ`:: + sage: T.category() Category of modules over Integer Ring sage: T in Modules(ZZ) @@ -114,13 +116,13 @@ class TensorFreeModule(FiniteRankFreeModule): sage: T.base_ring() Integer Ring sage: T.base_module() - rank-3 free module M over the Integer Ring + Rank-3 free module M over the Integer Ring - T is a *parent* object, whose elements are instances of + ``T`` is a *parent* object, whose elements are instances of :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`:: sage: t = T.an_element() ; t - type-(1,2) tensor on the rank-3 free module M over the Integer Ring + Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: from sage.tensor.modules.free_module_tensor import FreeModuleTensor sage: isinstance(t, FreeModuleTensor) True @@ -129,92 +131,72 @@ class TensorFreeModule(FiniteRankFreeModule): sage: T.is_parent_of(t) True - Elements can be constructed by means of the __call__ operator acting - on the parent; 0 yields the zero element:: - + Elements can be constructed from ``T``. In particular, 0 yields + the zero element of ``T``:: + sage: T(0) - type-(1,2) tensor zero on the rank-3 free module M over the Integer Ring + Type-(1,2) tensor zero on the Rank-3 free module M over the Integer Ring sage: T(0) is T.zero() True - - while non-zero elements are constructed by providing their components in + + while non-zero elements are constructed by providing their components in a given basis:: - + sage: e - basis (e_0,e_1,e_2) on the rank-3 free module M over the Integer Ring + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: comp = [[[i-j+k for k in range(3)] for j in range(3)] for i in range(3)] sage: t = T(comp, basis=e, name='t') ; t - type-(1,2) tensor t on the rank-3 free module M over the Integer Ring + Type-(1,2) tensor t on the Rank-3 free module M over the Integer Ring sage: t.comp(e)[:] [[[0, 1, 2], [-1, 0, 1], [-2, -1, 0]], [[1, 2, 3], [0, 1, 2], [-1, 0, 1]], [[2, 3, 4], [1, 2, 3], [0, 1, 2]]] sage: t.view(e) - t = e_0*e^0*e^1 + 2 e_0*e^0*e^2 - e_0*e^1*e^0 + e_0*e^1*e^2 - 2 e_0*e^2*e^0 - e_0*e^2*e^1 + e_1*e^0*e^0 + 2 e_1*e^0*e^1 + 3 e_1*e^0*e^2 + e_1*e^1*e^1 + 2 e_1*e^1*e^2 - e_1*e^2*e^0 + e_1*e^2*e^2 + 2 e_2*e^0*e^0 + 3 e_2*e^0*e^1 + 4 e_2*e^0*e^2 + e_2*e^1*e^0 + 2 e_2*e^1*e^1 + 3 e_2*e^1*e^2 + e_2*e^2*e^1 + 2 e_2*e^2*e^2 + t = e_0*e^0*e^1 + 2 e_0*e^0*e^2 - e_0*e^1*e^0 + e_0*e^1*e^2 + - 2 e_0*e^2*e^0 - e_0*e^2*e^1 + e_1*e^0*e^0 + 2 e_1*e^0*e^1 + + 3 e_1*e^0*e^2 + e_1*e^1*e^1 + 2 e_1*e^1*e^2 - e_1*e^2*e^0 + + e_1*e^2*e^2 + 2 e_2*e^0*e^0 + 3 e_2*e^0*e^1 + 4 e_2*e^0*e^2 + + e_2*e^1*e^0 + 2 e_2*e^1*e^1 + 3 e_2*e^1*e^2 + e_2*e^2*e^1 + + 2 e_2*e^2*e^2 An alternative is to construct the tensor from an empty list of components and to set the nonzero components afterwards:: - + sage: t = T([], name='t') sage: t.set_comp(e)[0,1,1] = -3 sage: t.set_comp(e)[2,0,1] = 4 sage: t.view(e) t = -3 e_0*e^1*e^1 + 4 e_2*e^0*e^1 - - See the documentation of - :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` - for the full list of arguments that can be provided to the __call__ - operator. For instance, to contruct a tensor symmetric with respect to the + + See the documentation of + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + for the full list of arguments that can be provided to the __call__ + operator. For instance, to contruct a tensor symmetric with respect to the last two indices:: - + sage: t = T([], name='t', sym=(1,2)) sage: t.set_comp(e)[0,1,1] = -3 sage: t.set_comp(e)[2,0,1] = 4 sage: t.view(e) # notice that t^2_{10} has be set equal to t^2_{01} by symmetry t = -3 e_0*e^1*e^1 + 4 e_2*e^0*e^1 + 4 e_2*e^1*e^0 - + The tensor modules over a given module `M` are unique:: - + sage: T is M.tensor_module(1,2) True - All tests from the suite for the category - :class:`~sage.categories.modules.Modules` are passed:: - - sage: TestSuite(T).run(verbose=True) - running ._test_additive_associativity() . . . pass - running ._test_an_element() . . . pass - running ._test_category() . . . pass - running ._test_elements() . . . - Running the test suite of self.an_element() - running ._test_category() . . . pass - running ._test_eq() . . . pass - running ._test_nonzero_equal() . . . pass - running ._test_not_implemented_methods() . . . pass - running ._test_pickling() . . . pass - pass - running ._test_elements_eq_reflexive() . . . pass - running ._test_elements_eq_symmetric() . . . pass - running ._test_elements_eq_transitive() . . . pass - running ._test_elements_neq() . . . pass - running ._test_eq() . . . pass - running ._test_not_implemented_methods() . . . pass - running ._test_pickling() . . . pass - running ._test_some_elements() . . . pass - running ._test_zero() . . . pass - """ - + Element = FreeModuleTensor - + def __init__(self, fmodule, tensor_type, name=None, latex_name=None): r""" TEST:: - + sage: from sage.tensor.modules.tensor_free_module import TensorFreeModule sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: TensorFreeModule(M, (2,3), name='T23', latex_name=r'T^2_3') - free module T23 of type-(2,3) tensors on the rank-3 free module M over the Integer Ring + sage: T = TensorFreeModule(M, (2,3), name='T23', latex_name=r'T^2_3') + sage: TestSuite(T).run() """ self._fmodule = fmodule @@ -227,68 +209,69 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): name = fmodule._name + '*' if latex_name is None and fmodule._latex_name is not None: latex_name = fmodule._latex_name + r'^*' - FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, - latex_name=latex_name, + FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, + latex_name=latex_name, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) # Unique representation: if self._tensor_type in self._fmodule._tensor_modules: - raise TypeError("The module of tensors of type" + - str(self._tensor_type) + - " has already been created.") + raise ValueError("the module of tensors of type" + + str(self._tensor_type) + + " has already been created") else: self._fmodule._tensor_modules[self._tensor_type] = self - # Zero element - self._zero_element = self._element_constructor_(name='zero', + # Zero element + self._zero_element = self._element_constructor_(name='zero', latex_name='0') for basis in self._fmodule._known_bases: self._zero_element._components[basis] = \ self._zero_element._new_comp(basis) # (since new components are initialized to zero) - - #### Methods required for any Parent - def _element_constructor_(self, comp=[], basis=None, name=None, + + #### Methods required for any Parent + def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None, sym=None, antisym=None): r""" - Construct a tensor. - + Construct a tensor. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: T = M.tensor_module(1,1) sage: T._element_constructor_(0) is T.zero() True sage: e = M.basis('e') sage: t = T._element_constructor_(comp=[[2,0],[1/2,-3]], basis=e, name='t') ; t - type-(1,1) tensor t on the rank-2 free module M over the Rational Field + Type-(1,1) tensor t on the Rank-2 free module M over the Rational Field sage: t.view() t = 2 e_0*e^0 + 1/2 e_1*e^0 - 3 e_1*e^1 sage: t.parent() - free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field + Free module of type-(1,1) tensors on the + Rank-2 free module M over the Rational Field sage: t.parent() is T True """ if comp == 0: return self._zero_element - resu = self.element_class(self._fmodule, self._tensor_type, name=name, - latex_name=latex_name, sym=sym, + resu = self.element_class(self._fmodule, self._tensor_type, name=name, + latex_name=latex_name, sym=sym, antisym=antisym) - if comp != []: + if comp: resu.set_comp(basis)[:] = comp return resu def _an_element_(self): r""" - Construct some (unamed) tensor - + Construct some (unamed) tensor. + EXAMPLES:: - + sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: T = M.tensor_module(1,1) sage: e = M.basis('e') sage: t = T._an_element_() ; t - type-(1,1) tensor on the rank-2 free module M over the Rational Field + Type-(1,1) tensor on the Rank-2 free module M over the Rational Field sage: t.view() 1/2 e_0*e^0 sage: t.parent() is T @@ -303,52 +286,49 @@ def _an_element_(self): ind = [sindex for i in range(resu._tensor_rank)] resu.set_comp()[ind] = self._fmodule._ring.an_element() return resu - - #### End of methods required for any Parent + + #### End of methods required for any Parent def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLE:: - + sage: M = FiniteRankFreeModule(QQ, 2, name='M') - sage: T = M.tensor_module(1,1) - sage: T._repr_() - 'free module of type-(1,1) tensors on the rank-2 free module M over the Rational Field' - sage: T = M.tensor_module(0,1) - sage: T._repr_() - 'dual of the rank-2 free module M over the Rational Field' + sage: M.tensor_module(1,1) + Free module of type-(1,1) tensors on the Rank-2 free module M + over the Rational Field + sage: M.tensor_module(0,1) + Dual of the Rank-2 free module M over the Rational Field """ if self._tensor_type == (0,1): - return "dual of the " + str(self._fmodule) - else: - description = "free module " - if self._name is not None: - description += self._name + " " - description += "of type-(%s,%s)" % \ - (str(self._tensor_type[0]), str(self._tensor_type[1])) - description += " tensors on the " + str(self._fmodule) - return description + return "Dual of the " + str(self._fmodule) + description = "Free module " + if self._name is not None: + description += self._name + " " + description += "of type-({},{}) tensors on the {}".format( + self._tensor_type[0], self._tensor_type[1], self._fmodule) + return description def base_module(self): - r""" + r""" Return the free module on which ``self`` is constructed. - + OUTPUT: - - - instance of :class:`FiniteRankFreeModule` representing the free module - on which the tensor module is defined. - + + - instance of :class:`FiniteRankFreeModule` representing the free module + on which the tensor module is defined. + EXAMPLE: Base module of a type-(1,2) tensor module:: - + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: T = M.tensor_module(1,2) sage: T.base_module() - rank-3 free module M over the Integer Ring + Rank-3 free module M over the Integer Ring """ return self._fmodule diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py index 862d0c9d68f..ff9663a5609 100644 --- a/src/sage/tensor/modules/tensor_with_indices.py +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -1,5 +1,5 @@ r""" -Index notation for tensors. +Index notation for tensors AUTHORS: @@ -21,21 +21,21 @@ class TensorWithIndices(SageObject): r""" Index notation for tensors. - - This is a technical class to allow one to write some tensor operations + + This is a technical class to allow one to write some tensor operations (contractions and symmetrizations) in index notation. - + INPUT: - + - ``tensor`` -- a tensor (or a tensor field) - ``indices`` -- string containing the indices, as single letters; the - contravariant indices must be stated first and separated from the - covariant indices by the character '_'. - + contravariant indices must be stated first and separated from the + covariant indices by the character ``_`` + EXAMPLES: - + Index representation of tensors on a rank-3 free module:: - + sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') sage: a = M.tensor((2,0), name='a') @@ -43,15 +43,15 @@ class TensorWithIndices(SageObject): sage: b = M.tensor((0,2), name='b') sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] sage: t = a*b ; t.set_name('t') ; t - type-(2,2) tensor t on the rank-3 free module M over the Rational Field + Type-(2,2) tensor t on the Rank-3 free module M over the Rational Field sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: T = TensorWithIndices(t, '^ij_kl') ; T t^ij_kl - The :class:`TensorWithIndices` object is returned by the square + The :class:`TensorWithIndices` object is returned by the square bracket operator acting on the tensor and fed with the string specifying the indices:: - + sage: a['^ij'] a^ij sage: type(a['^ij']) @@ -60,80 +60,80 @@ class TensorWithIndices(SageObject): b_ef sage: t['^ij_kl'] t^ij_kl - - The symbol '^' may be omitted, since the distinction between covariant - and contravariant indices is performed by the index position relative to + + The symbol '^' may be omitted, since the distinction between covariant + and contravariant indices is performed by the index position relative to the symbol '_':: - + sage: t['ij_kl'] t^ij_kl Also, LaTeX notation may be used:: - + sage: t['^{ij}_{kl}'] t^ij_kl If some operation is asked in the index notation, the resulting tensor - is returned, not a :class:`TensorWithIndices` object; for instance, for + is returned, not a :class:`TensorWithIndices` object; for instance, for a symmetrization:: - + sage: s = t['^(ij)_kl'] ; s # the symmetrization on i,j is indicated by parentheses - type-(2,2) tensor on the rank-3 free module M over the Rational Field + Type-(2,2) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: s == t.symmetrize(0,1) True - + The letters denoting the indices can be chosen freely; since they carry no information, they can even be replaced by dots:: - + sage: t['^(..)_..'] == t.symmetrize(0,1) True Similarly, for an antisymmetrization:: - + sage: s = t['^ij_[kl]'] ; s # the symmetrization on k,l is indicated by square brackets - type-(2,2) tensor on the rank-3 free module M over the Rational Field + Type-(2,2) tensor on the Rank-3 free module M over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (2, 3) sage: s == t.antisymmetrize(2,3) True Another example of an operation indicated by indices is a contraction:: - + sage: s = t['^ki_kj'] ; s # contraction on the repeated index k - endomorphism on the rank-3 free module M over the Rational Field + Endomorphism on the Rank-3 free module M over the Rational Field sage: s == t.trace(0,2) True Indices not involved in the contraction may be replaced by dots:: - + sage: s == t['^k._k.'] True - The contraction of two tensors is indicated by repeated indices and - the * operator:: - - sage: s = a['^ik']*b['_kj'] ; s - endomorphism on the rank-3 free module M over the Rational Field + The contraction of two tensors is indicated by repeated indices and + the ``*`` operator:: + + sage: s = a['^ik'] * b['_kj'] ; s + Endomorphism on the Rank-3 free module M over the Rational Field sage: s == a.contract(1, b, 0) True - sage: s = t['^.k_..']*b['_.k'] ; s - type-(1,3) tensor on the rank-3 free module M over the Rational Field + sage: s = t['^.k_..'] * b['_.k'] ; s + Type-(1,3) tensor on the Rank-3 free module M over the Rational Field sage: s == t.contract(1, b, 1) True sage: t['^{ik}_{jl}']*b['_{mk}'] == s # LaTeX notation True - + Contraction on two indices:: - - sage: s = a['^kl']*b['_kl'] ; s + + sage: s = a['^kl'] * b['_kl'] ; s 105 sage: s == a.contract(0,1, b, 0,1) True Some minimal arithmetics:: - + sage: 2*a['^ij'] X^ij sage: (2*a['^ij'])._tensor == 2*a @@ -152,19 +152,30 @@ class TensorWithIndices(SageObject): """ def __init__(self, tensor, indices): r""" - TEST:: - + TESTS:: + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: t = M.tensor((2,1), name='t') - sage: TensorWithIndices(t, 'ab_c') - t^ab_c - + sage: ti = TensorWithIndices(t, 'ab_c') + + We need to skip the pickling test because we can't check equality + unless the tensor was defined w.r.t. a basis:: + + sage: TestSuite(ti).run(skip="_test_pickling") + + sage: e = M.basis('e') + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], + ....: [[10,-11,12], [13,14,-15], [16,17,18]], + ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: ti = TensorWithIndices(t, 'ab_c') + sage: TestSuite(ti).run() + """ self._tensor = tensor # may be changed below - self._changed = False # indicates whether self contains an altered - # version of the original tensor (True if - # symmetries or contractions are indicated in the + self._changed = False # indicates whether self contains an altered + # version of the original tensor (True if + # symmetries or contractions are indicated in the # indices) # Suppress all '{' and '}' comming from LaTeX notations: indices = indices.replace('{','').replace('}','') @@ -172,9 +183,10 @@ def __init__(self, tensor, indices): if indices[0] == '^': indices = indices[1:] if '^' in indices: - raise IndexError("The contravariant indices must be placed first.") + raise IndexError("the contravariant indices must be placed first") con_cov = indices.split('_') con = con_cov[0] + # Contravariant indices # --------------------- # Search for (anti)symmetries: @@ -182,7 +194,7 @@ def __init__(self, tensor, indices): sym1 = con.index('(') sym2 = con.index(')')-2 if con.find('(', sym1+1) != -1 or '[' in con: - raise NotImplementedError("Multiple symmetries are not " + + raise NotImplementedError("Multiple symmetries are not " + "treated yet.") self._tensor = self._tensor.symmetrize(*(range(sym1, sym2+1))) self._changed = True # self does no longer contain the original tensor @@ -191,21 +203,22 @@ def __init__(self, tensor, indices): sym1 = con.index('[') sym2 = con.index(']')-2 if con.find('[', sym1+1) != -1 or '(' in con: - raise NotImplementedError("Multiple symmetries are not " + - "treated yet.") + raise NotImplementedError("multiple symmetries are not " + + "treated yet") self._tensor = self._tensor.antisymmetrize(*(range(sym1, sym2+1))) self._changed = True # self does no longer contain the original tensor con = con.replace('[','').replace(']','') if len(con) != self._tensor._tensor_type[0]: - raise IndexError("Number of contravariant indices not compatible " + - "with the tensor type.") + raise IndexError("number of contravariant indices not compatible " + + "with the tensor type") self._con = con + # Covariant indices # ----------------- if len(con_cov) == 1: if tensor._tensor_type[1] != 0: - raise IndexError("Number of covariant indices not compatible " + - "with the tensor type.") + raise IndexError("number of covariant indices not compatible " + + "with the tensor type") self._cov = '' elif len(con_cov) == 2: cov = con_cov[1] @@ -214,34 +227,35 @@ def __init__(self, tensor, indices): sym1 = cov.index('(') sym2 = cov.index(')')-2 if cov.find('(', sym1+1) != -1 or '[' in cov: - raise NotImplementedError("Multiple symmetries are not " + - "treated yet.") + raise NotImplementedError("multiple symmetries are not " + + "treated yet") csym1 = sym1 + self._tensor._tensor_type[0] csym2 = sym2 + self._tensor._tensor_type[0] self._tensor = self._tensor.symmetrize( *(range(csym1, csym2+1))) - self._changed = True # self does no longer contain the original + self._changed = True # self does no longer contain the original # tensor cov = cov.replace('(','').replace(')','') if '[' in cov: sym1 = cov.index('[') sym2 = cov.index(']')-2 if cov.find('[', sym1+1) != -1 or '(' in cov: - raise NotImplementedError("Multiple symmetries are not " + - "treated yet.") + raise NotImplementedError("multiple symmetries are not " + + "treated yet") csym1 = sym1 + self._tensor._tensor_type[0] csym2 = sym2 + self._tensor._tensor_type[0] self._tensor = self._tensor.antisymmetrize( *(range(csym1, csym2+1))) - self._changed = True # self does no longer contain the original + self._changed = True # self does no longer contain the original # tensor cov = cov.replace('[','').replace(']','') if len(cov) != tensor._tensor_type[1]: - raise IndexError("Number of covariant indices not compatible " + - "with the tensor type.") + raise IndexError("number of covariant indices not compatible " + + "with the tensor type") self._cov = cov else: - raise IndexError("Two many '_' in the list of indices.") + raise IndexError("too many '_' in the list of indices") + # Treatment of possible self-contractions: # --------------------------------------- contraction_pairs = [] @@ -251,25 +265,24 @@ def __init__(self, tensor, indices): pos2 = self._tensor._tensor_type[0] + self._cov.index(ind) contraction_pairs.append((pos1, pos2)) if len(contraction_pairs) > 1: - raise NotImplementedError("Multiple self-contractions are not " + - "implemented yet.") + raise NotImplementedError("multiple self-contractions are not " + + "implemented yet") if len(contraction_pairs) == 1: pos1 = contraction_pairs[0][0] pos2 = contraction_pairs[0][1] self._tensor = self._tensor.trace(pos1, pos2) - self._changed = True # self does no longer contain the original + self._changed = True # self does no longer contain the original # tensor ind = self._con[pos1] self._con = self._con.replace(ind, '') self._cov = self._cov.replace(ind, '') - def _repr_(self): r""" - String representation of the object. - + Return a string representation of ``self``. + EXAMPLES:: - + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: t = M.tensor((2,1), name='t') @@ -299,10 +312,10 @@ def _repr_(self): def update(self): r""" Return the tensor contains in ``self`` if it differs from that used - for creating ``self``, otherwise return ``self``. - + for creating ``self``, otherwise return ``self``. + EXAMPLES:: - + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') @@ -325,12 +338,55 @@ def update(self): else: return self + def __eq__(self, other): + """ + Check equality. + + TESTS:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: ti = TensorWithIndices(t, 'ab_c') + sage: ti == TensorWithIndices(t, '^{ab}_c') + True + sage: ti == TensorWithIndices(t, 'ac_b') + False + sage: tp = M.tensor((2,1)) + sage: ti == TensorWithIndices(tp, 'ab_c') + Traceback (most recent call last): + ... + ValueError: no common basis for the comparison + """ + if not isinstance(other, TensorWithIndices): + return False + return (self._tensor == other._tensor + and self._con == other._con + and self._cov == other._cov) + + def __ne__(self, other): + """ + Check not equals. + + TESTS:: + + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: t = M.tensor((2,1), name='t') + sage: ti = TensorWithIndices(t, 'ab_c') + sage: ti != TensorWithIndices(t, '^{ab}_c') + False + sage: ti != TensorWithIndices(t, 'ac_b') + True + """ + return not self.__eq__(other) + def __mul__(self, other): r""" Tensor product or contraction on specified indices. - + EXAMPLES:: - + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') @@ -341,7 +397,7 @@ def __mul__(self, other): sage: ai = TensorWithIndices(a, '^ij') sage: bi = TensorWithIndices(b, '_k') sage: s = ai.__mul__(bi) ; s # no repeated indices ==> tensor product - type-(2,1) tensor a*b on the rank-3 free module M over the Rational Field + Type-(2,1) tensor a*b on the Rank-3 free module M over the Rational Field sage: s == a*b True sage: s[:] @@ -350,7 +406,7 @@ def __mul__(self, other): [[28, 14, 7], [-32, -16, -8], [36, 18, 9]]] sage: ai = TensorWithIndices(a, '^kj') sage: s = ai.__mul__(bi) ; s # repeated index k ==> contraction - element of the rank-3 free module M over the Rational Field + Element of the Rank-3 free module M over the Rational Field sage: s == a.contract(0, b) True sage: s[:] @@ -358,8 +414,8 @@ def __mul__(self, other): """ if not isinstance(other, TensorWithIndices): - raise TypeError("The second item of * must be a tensor with " + - "specified indices.") + raise TypeError("the second item of * must be a tensor with " + + "specified indices") contraction_pairs = [] for ind in self._con: if ind != '.': @@ -367,9 +423,9 @@ def __mul__(self, other): pos1 = self._con.index(ind) pos2 = other._tensor._tensor_type[0] + other._cov.index(ind) contraction_pairs.append((pos1, pos2)) - if ind in other._con: - raise IndexError("The index " + str(ind) + " appears twice " - + "in a contravariant position.") + if ind in other._con: + raise IndexError("the index {} appears twice ".format(ind) + + "in a contravariant position") for ind in self._cov: if ind != '.': if ind in other._con: @@ -377,9 +433,9 @@ def __mul__(self, other): pos2 = other._con.index(ind) contraction_pairs.append((pos1, pos2)) if ind in other._cov: - raise IndexError("The index " + str(ind) + " appears twice " - + "in a covariant position.") - if contraction_pairs == []: + raise IndexError("the index {} appears twice ".format(ind) + + "in a covariant position") + if not contraction_pairs: # No contraction is performed: the tensor product is returned return self._tensor * other._tensor ncontr = len(contraction_pairs) @@ -390,10 +446,10 @@ def __mul__(self, other): def __rmul__(self, other): r""" - Multiplication on the left by ``other``. - - EXAMPLE:: - + Multiplication on the left by ``other``. + + EXAMPLES:: + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') @@ -404,21 +460,21 @@ def __rmul__(self, other): X^ij_k sage: s._tensor == 3*a True - + """ - return TensorWithIndices(other*self._tensor, + return TensorWithIndices(other*self._tensor, self._con + '_' + self._cov) def __pos__(self): r""" - Unary plus operator. - + Unary plus operator. + OUTPUT: - + - an exact copy of ``self`` - - EXAMPLE:: - + + EXAMPLES:: + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') @@ -431,19 +487,19 @@ def __pos__(self): True """ - return TensorWithIndices(+self._tensor, + return TensorWithIndices(+self._tensor, self._con + '_' + self._cov) - + def __neg__(self): r""" - Unary minus operator. - + Unary minus operator. + OUTPUT: - - - - ``self`` - - EXAMPLE:: - + + - negative of ``self`` + + EXAMPLES:: + sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: M = FiniteRankFreeModule(QQ, 3, name='M') sage: e = M.basis('e') @@ -454,7 +510,8 @@ def __neg__(self): -a^ij_k sage: s._tensor == -a True - + """ - return TensorWithIndices(-self._tensor, + return TensorWithIndices(-self._tensor, self._con + '_' + self._cov) + From 6e53a8d3c92d405541e2a21dff5a867d402937e6 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Mon, 8 Dec 2014 09:43:57 +0100 Subject: [PATCH 051/129] Add free module homomorphisms, rename some classes and methods and make small improvements. Introduce free module homomorphisms, via - the parent class FreeModuleHomset (as a subclass of sage.categories.homset.Homset) - the element class FiniteRankFreeModuleMorphism (as a subclass of sage.categories.morphism.Morphism) with coercions to type-(1,1) tensors Rename some classes (to avoid any confusion with free module morphisms): - FreeModuleEndomorphism --> FreeModuleEndomorphismTensor - FreeModuleAutomorphism --> FreeModuleAutomorphismTensor - FreeModuleIdentityMap --> FreeModuleIdentityTensor Class FiniteRankFreeModule: rename some methods: - endomorphism() --> endomorphism_tensor() - automorphism() --> automorphism_tensor() - identity_map() --> identity_tensor() - basis_change() --> change_of_basis() - set_basis_change() --> set_change_of_basis() Class FreeModuleTensor: - rename comp() to components() and add comp() as an alias Class Components: - new method _matrix_() Class FreeModuleCoBasis now inherits from UniqueRepresentation Fix TestSuite issues on some elements --- .../reference/tensor_free_modules/index.rst | 4 + src/sage/tensor/modules/comp.py | 48 +- .../tensor/modules/finite_rank_free_module.py | 369 ++++- .../tensor/modules/free_module_alt_form.py | 30 +- src/sage/tensor/modules/free_module_basis.py | 18 +- src/sage/tensor/modules/free_module_homset.py | 413 ++++++ .../tensor/modules/free_module_morphism.py | 1280 +++++++++++++++++ src/sage/tensor/modules/free_module_tensor.py | 136 +- .../tensor/modules/free_module_tensor_spec.py | 201 +-- src/sage/tensor/modules/tensor_free_module.py | 145 +- .../tensor/modules/tensor_with_indices.py | 4 +- 11 files changed, 2424 insertions(+), 224 deletions(-) create mode 100644 src/sage/tensor/modules/free_module_homset.py create mode 100644 src/sage/tensor/modules/free_module_morphism.py diff --git a/src/doc/en/reference/tensor_free_modules/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst index a4a818484a1..69cb7a00082 100644 --- a/src/doc/en/reference/tensor_free_modules/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -27,6 +27,10 @@ __ http://creativecommons.org/licenses/by-sa/3.0/ sage/tensor/modules/free_module_alt_form + sage/tensor/modules/free_module_homset + + sage/tensor/modules/free_module_morphism + sage/tensor/modules/comp sage/tensor/modules/tensor_with_indices diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 6f0e1f14ac8..27e2634d3d2 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -1,5 +1,5 @@ r""" -Components as indexed sets of ring elements. +Components as indexed sets of ring elements The class :class:`Components` is a technical class to take in charge the storage and manipulation of **indexed elements of a commutative ring** that represent the @@ -106,6 +106,15 @@ class :class:`~sage.tensor.differential_form_element.DifferentialForm`) sage: v[0] -1 +Sets of components with 2 indices can be converted into a matrix:: + + sage: matrix(c) + [ 0 0 0] + [ 0 0 -3] + [ 0 0 0] + sage: matrix(c).parent() + Full MatrixSpace of 3 by 3 dense matrices over Rational Field + By default, the indices range from `0` to `n-1`, where `n` is the length of the frame. This can be changed via the argument ``start_index`` in the :class:`Components` constructor:: @@ -262,7 +271,7 @@ class Components(SageObject): - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound + - ``output_formatter`` -- (default: ``None``) function or unbound method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take 1 or 2 arguments: the 1st argument must be an element of ``ring`` and @@ -2220,6 +2229,33 @@ def antisymmetrize(self, *pos): result[[ind]] = sum / sym_group.order() return result + def _matrix_(self): + r""" + Convert a set of ring components with 2 indices into a matrix. + + EXAMPLE:: + + sage: from sage.tensor.modules.comp import Components + sage: V = VectorSpace(QQ, 3) + sage: c = Components(QQ, V.basis(), 2, start_index=1) + sage: c[:] = [[-1,2,3], [4,-5,6], [7,8,-9]] + sage: c._matrix_() + [-1 2 3] + [ 4 -5 6] + [ 7 8 -9] + + sage: matrix(c) == c._matrix_() + True + + """ + from sage.matrix.constructor import matrix + if self._nid != 2: + raise ValueError("the set of components must have 2 indices") + si = self._sindex + nsi = self._dim + si + tab = [[self[[i,j]] for j in range(si, nsi)] for i in range(si, nsi)] + return matrix(tab) + #****************************************************************************** @@ -2250,7 +2286,7 @@ class CompWithSym(Components): - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound + - ``output_formatter`` -- (default: ``None``) function or unbound method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and @@ -3900,7 +3936,7 @@ class CompFullySym(CompWithSym): - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound + - ``output_formatter`` -- (default: ``None``) function or unbound method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and @@ -4281,7 +4317,7 @@ class CompFullyAntiSym(CompWithSym): - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound + - ``output_formatter`` -- (default: ``None``) function or unbound method called to format the output of the component access operator ``[...]`` (method __getitem__); ``output_formatter`` must take 1 or 2 arguments: the 1st argument must be an instance of ``ring`` and @@ -4518,7 +4554,7 @@ class KroneckerDelta(CompFullySym): - ``start_index`` -- (default: 0) first value of a single index; accordingly a component index i must obey ``start_index <= i <= start_index + dim - 1``, where ``dim = len(frame)``. - - ``output_formatter`` -- (default: None) function or unbound + - ``output_formatter`` -- (default: ``None``) function or unbound method called to format the output of the component access operator ``[...]`` (method ``__getitem__``); ``output_formatter`` must take 1 or 2 arguments: the first argument must be an instance of diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index d319c5ea18c..30543ffe123 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -39,11 +39,6 @@ .. TODO:: - implement submodules - - implement free module homomorphisms (at the moment, only two specific - kinds of homomorphisms are implemented: endomorphisms, cf. - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism`, - and linear forms, cf. - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm`) - create a FreeModules category (cf. the *TODO* statement in the documentation of :class:`~sage.categories.modules.Modules`: *Implement a ``FreeModules(R)`` category, when so prompted by a concrete use case*) @@ -94,7 +89,7 @@ We define a second basis on M by linking it to ``e`` via a module automorphism:: - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a.set_comp(basis=e)[0,1] = -1 ; a.set_comp(basis=e)[1,0] = 1 # only the non-zero components have to be set sage: a[:] # a matrix view of the automorphism in the module's default basis [ 0 -1] @@ -393,6 +388,8 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, if not hasattr(self, '_zero_element'): self._zero_element = self._element_constructor_(name='zero', latex_name='0') + # Identity endomorphism: + self._identity_map = None # not defined yet #### Methods required for any Parent @@ -468,6 +465,38 @@ def _repr_(self): description += "over the " + str(self._ring) return description + def _Hom_(self, other, category=None): + r""" + Construct the set of homomorphisms self --> other. + + INPUT: + + - ``other`` -- another free module of finite rank over the same ring + as ``self`` + - ``category`` -- (default: ``None``) not used here (to ensure + compatibility with generic hook ``_Hom_``) + + OUTPUT: + + - the hom-set Hom(M,N), where M is ``self`` and N is ``other`` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: H = M._Hom_(N) ; H + Set of Morphisms from Rank-3 free module M over the Integer Ring + to Rank-2 free module N over the Integer Ring in Category of + modules over Integer Ring + sage: H = Hom(M,N) ; H # indirect doctest + Set of Morphisms from Rank-3 free module M over the Integer Ring + to Rank-2 free module N over the Integer Ring in Category of + modules over Integer Ring + + """ + from free_module_homset import FreeModuleHomset + return FreeModuleHomset(self, other) + def tensor_module(self, k, l): r""" Return the free module of all tensors of type `(k, l)` defined on @@ -637,7 +666,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, sage: t = M.tensor((0,1), name='t') ; t Linear form t on the Rank-3 free module M over the Integer Ring sage: t = M.tensor((1,1), name='t') ; t - Endomorphism t on the Rank-3 free module M over the Integer Ring + Endomorphism tensor t on the Rank-3 free module M over the Integer Ring sage: t = M.tensor((0,2), name='t', sym=(0,1)) ; t Symmetric bilinear form t on the Rank-3 free module M over the Integer Ring @@ -651,7 +680,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, for more examples and documentation. """ - from free_module_tensor_spec import FreeModuleEndomorphism + from free_module_tensor_spec import FreeModuleEndomorphismTensor from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) @@ -662,8 +691,8 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, elif tensor_type == (0,1): return FreeModuleLinForm(self, name=name, latex_name=latex_name) elif tensor_type == (1,1): - return FreeModuleEndomorphism(self, name=name, - latex_name=latex_name) + return FreeModuleEndomorphismTensor(self, name=name, + latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: if isinstance(antisym, list): antisym0 = antisym[0] @@ -749,7 +778,7 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): 4 e^0/\e^1 + 5 e^1/\e^2 """ - from free_module_tensor_spec import FreeModuleEndomorphism + from free_module_tensor_spec import FreeModuleEndomorphismTensor from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm from comp import CompWithSym, CompFullySym, CompFullyAntiSym # @@ -774,8 +803,8 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): elif tensor_type == (0,1): resu = FreeModuleLinForm(self, name=name, latex_name=latex_name) elif tensor_type == (1,1): - resu = FreeModuleEndomorphism(self, name=name, - latex_name=latex_name) + resu = FreeModuleEndomorphismTensor(self, name=name, + latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and \ isinstance(comp, CompFullyAntiSym): resu = FreeModuleAltForm(self, tensor_type[1], name=name, @@ -910,81 +939,81 @@ def linear_form(self, name=None, latex_name=None): from free_module_alt_form import FreeModuleLinForm return FreeModuleLinForm(self, name=name, latex_name=latex_name) - def endomorphism(self, name=None, latex_name=None): + def endomorphism_tensor(self, name=None, latex_name=None): r""" - Construct an endomorphism on the free module. + Construct a tensor of type-(1,1) on the free module ``self``. + + .. NOTE:: + + This method differs from :meth:`endomorphism` for it returns a + tensor, while :meth:`endomorphism` returns an instance of Sage's :class:`~sage.categories.map.Map` (actually an instance of the subclass + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism`). INPUT: - ``name`` -- (default: ``None``) string; name given to the - endomorphism + tensor - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to - denote the endomorphism; if none is provided, the LaTeX symbol + denote the tensor; if none is provided, the LaTeX symbol is set to ``name`` OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphismTensor` EXAMPLES: - Endomorphism on a rank-3 module:: + Endomorphism as a type-(1,1) tensor on a rank-3 module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: t = M.endomorphism('T') ; t - Endomorphism T on the Rank-3 free module M over the Integer Ring - - An endomorphism is type-(1,1) tensor:: - + sage: t = M.endomorphism_tensor('T') ; t + Endomorphism tensor T on the Rank-3 free module M over the Integer Ring sage: t.parent() Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring sage: t.tensor_type() (1, 1) - Consequently, an endomorphism can also be created by the method - :meth:`tensor`:: + The method :meth:`tensor` with the argument ``(1,1)`` can be used as + well:: sage: t = M.tensor((1,1), name='T') ; t - Endomorphism T on the Rank-3 free module M over the Integer Ring + Endomorphism tensor T on the Rank-3 free module M over the Integer Ring See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphismTensor` for further documentation. """ - from free_module_tensor_spec import FreeModuleEndomorphism - return FreeModuleEndomorphism(self, name=name, latex_name=latex_name) + from free_module_tensor_spec import FreeModuleEndomorphismTensor + return FreeModuleEndomorphismTensor(self, name=name, latex_name=latex_name) - def automorphism(self, name=None, latex_name=None): + def automorphism_tensor(self, name=None, latex_name=None): r""" - Construct an automorphism on the free module. + Construct an invertible type-(1,1) tensor on the free module ``self``. INPUT: - ``name`` -- (default: ``None``) string; name given to the - automorphism + tensor - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to - denote the automorphism; if none is provided, the LaTeX symbol + denote the tensor; if none is provided, the LaTeX symbol is set to ``name`` OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` EXAMPLES: - Automorphism on a rank-2 free module (vector space) on `\QQ`:: + Automorphism tensor on a rank-2 free module (vector space) on `\QQ`:: sage: M = FiniteRankFreeModule(QQ, 2, name='M') - sage: a = M.automorphism('A') ; a - Automorphism A on the Rank-2 free module M over the Rational Field - - Automorphisms are tensors of type (1,1):: - + sage: a = M.automorphism_tensor('A') ; a + Automorphism tensor A on the Rank-2 free module M over the Rational Field sage: a.parent() Free module of type-(1,1) tensors on the Rank-2 free module M over the Rational Field @@ -992,50 +1021,52 @@ def automorphism(self, name=None, latex_name=None): (1, 1) See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` for further documentation. """ - from free_module_tensor_spec import FreeModuleAutomorphism - return FreeModuleAutomorphism(self, name=name, latex_name=latex_name) + from free_module_tensor_spec import FreeModuleAutomorphismTensor + return FreeModuleAutomorphismTensor(self, name=name, + latex_name=latex_name) - def identity_map(self, name='Id', latex_name=None): + def identity_tensor(self, name='Id', latex_name=None): r""" - Construct the identity map on the free module. + Construct the type-(1,1) tensor representing the identity map of + the free module ``self`` INPUT: - ``name`` -- (default: ``'Id'``) string; name given to the - identity map + identity tensor - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to - denote the identity map; if none is provided, the LaTeX symbol + denote the identity tensor; if none is provided, the LaTeX symbol is set to ``name`` OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityTensor` EXAMPLES: - Identity map on a rank-3 free module:: + Identity tensor of a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: a = M.identity_map() ; a - Identity map on the Rank-3 free module M over the Integer Ring + sage: a = M.identity_tensor() ; a + Identity tensor on the Rank-3 free module M over the Integer Ring The LaTeX symbol is set by default to `\mathrm{Id}`, but can be changed:: sage: latex(a) \mathrm{Id} - sage: a = M.identity_map(latex_name=r'\mathrm{1}') + sage: a = M.identity_tensor(latex_name=r'\mathrm{1}') sage: latex(a) \mathrm{1} - The identity map is a tensor of type `(1,1)` on the free module:: + The identity is a tensor of type `(1,1)` on the free module:: sage: a.parent() Free module of type-(1,1) tensors on the @@ -1044,12 +1075,12 @@ def identity_map(self, name='Id', latex_name=None): (1, 1) See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityTensor` for further documentation. """ - from free_module_tensor_spec import FreeModuleIdentityMap - return FreeModuleIdentityMap(self, name=name, latex_name=latex_name) + from free_module_tensor_spec import FreeModuleIdentityTensor + return FreeModuleIdentityTensor(self, name=name, latex_name=latex_name) def sym_bilinear_form(self, name=None, latex_name=None): @@ -1481,7 +1512,7 @@ def bases(self): """ return self._known_bases - def basis_change(self, basis1, basis2): + def change_of_basis(self, basis1, basis2): r""" Return a change of basis previously defined on the free module. @@ -1493,7 +1524,7 @@ def basis_change(self, basis1, basis2): OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` @@ -1509,15 +1540,15 @@ def basis_change(self, basis1, basis2): sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[1, 2], [-1, 3]] sage: f = e.new_basis(a, 'f') - sage: M.basis_change(e,f) - Automorphism on the Rank-2 free module M over the Rational Field - sage: M.basis_change(e,f)[:] + sage: M.change_of_basis(e,f) + Automorphism tensor on the Rank-2 free module M over the Rational Field + sage: M.change_of_basis(e,f)[:] [ 1 2] [-1 3] - sage: M.basis_change(f,e)[:] + sage: M.change_of_basis(f,e)[:] [ 3/5 -2/5] [ 1/5 1/5] @@ -1532,7 +1563,7 @@ def basis_change(self, basis1, basis2): return inv return self._basis_changes[(basis1, basis2)] - def set_basis_change(self, basis1, basis2, change_of_basis, + def set_change_of_basis(self, basis1, basis2, change_of_basis, compute_inverse=True): r""" Relates two bases by an automorphism. @@ -1544,7 +1575,7 @@ def set_basis_change(self, basis1, basis2, change_of_basis, - ``basis1`` -- basis 1, denoted `(e_i)` below - ``basis2`` -- basis 2, denoted `(f_i)` below - ``change_of_basis`` -- instance of class - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` - ``compute_inverse`` (default: ``True``) -- if set to ``True``, the @@ -1559,16 +1590,16 @@ def set_basis_change(self, basis1, basis2, change_of_basis, sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: e = M.basis('e') sage: f = M.basis('f') - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[1, 2], [-1, 3]] - sage: M.set_basis_change(e, f, a) + sage: M.set_change_of_basis(e, f, a) The change of basis and its inverse have been recorded:: - sage: M.basis_change(e,f)[:] + sage: M.change_of_basis(e,f)[:] [ 1 2] [-1 3] - sage: M.basis_change(f,e)[:] + sage: M.change_of_basis(f,e)[:] [ 3/5 -2/5] [ 1/5 1/5] @@ -1580,11 +1611,203 @@ def set_basis_change(self, basis1, basis2, change_of_basis, e_0 = 3/5 f_0 + 1/5 f_1 """ - from free_module_tensor_spec import FreeModuleAutomorphism - if not isinstance(change_of_basis, FreeModuleAutomorphism): + from free_module_tensor_spec import FreeModuleAutomorphismTensor + if not isinstance(change_of_basis, FreeModuleAutomorphismTensor): raise TypeError("the argument change_of_basis must be some " + - "instance of FreeModuleAutomorphism") + "instance of FreeModuleAutomorphismTensor") self._basis_changes[(basis1, basis2)] = change_of_basis if compute_inverse: self._basis_changes[(basis2, basis1)] = change_of_basis.inverse() + def hom(self, codomain, matrix_rep, bases=None, name=None, + latex_name=None): + r""" + Homomorphism from ``self`` to a free module. + + Define a module homomorphism + + .. MATH:: + + \phi:\ M \longrightarrow N, + + where `M` is ``self`` and `N` is a free module of finite rank + over the same ring `R` as ``self``. + + .. NOTE:: + + This method is a redefinition of + :meth:`sage.structure.parent.Parent.hom` because the latter assumes + that ``self`` has some privileged generators, while an instance of + :class:`FiniteRankFreeModule` has no privileged basis. + + INPUT: + + - ``codomain`` -- the target module `N` + - ``matrix_rep`` -- matrix of size rank(N)*rank(M) representing the + homomorphism with respect to the pair of bases defined by ``bases``; + this entry can actually be any material from which a matrix of + elements of `R` can be constructed; the *columns* of + ``matrix_rep`` must be the components w.r.t. ``basis_N`` of + the images of the elements of ``basis_M``. + - ``bases`` -- (default: ``None``) pair ``(basis_M, basis_N)`` defining + the matrix representation, ``basis_M`` being a basis of ``self`` and + ``basis_N`` a basis of module `N` ; if None the pair formed by the + default bases of each module is assumed. + - ``name`` -- (default: ``None``) string; name given to the + homomorphism + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the homomorphism; if None, ``name`` will be used. + + OUTPUT: + + - the homomorphism `\phi: M \rightarrow N` corresponding to the given + specifications, as an instance of + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + + EXAMPLES: + + Homomorphism between two free modules over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') + sage: f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + + Homomorphism defined by a matrix w.r.t. bases that are not the + default ones:: + + sage: ep = M.basis('ep', latex_symbol=r"e'") + sage: fp = N.basis('fp', latex_symbol=r"f'") + sage: phi = M.hom(N, [[3,2,1], [1,2,3]], bases=(ep, fp)) ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + + Call with all arguments specified:: + + sage: phi = M.hom(N, [[3,2,1], [1,2,3]], bases=(ep, fp), + ....: name='phi', latex_name=r'\phi') + + The parent:: + + sage: phi.parent() is Hom(M,N) + True + + See class + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + for more documentation. + + """ + from sage.categories.homset import Hom + homset = Hom(self, codomain) + return homset(matrix_rep, bases=bases, name=name, + latex_name=latex_name) + + def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): + r""" + Contruct an endomorphism of the free module ``self``. + + The returned object is a module morphism `\phi: M \rightarrow M`, + where `M` is ``self``. + + .. NOTE:: + + This method differs from :meth:`endomorphism_tensor` for it + returns an instance of Sage's :class:`~sage.categories.map.Map` + (actually an instance of the subclass + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism`), + while :meth:`endomorphism_tensor` returns a tensor of type (1,1). + Note that there are coercions between these two types. + + INPUT: + + - ``matrix_rep`` -- matrix of size rank(M)*rank(M) representing the + endomorphism with respect to ``basis``; + this entry can actually be any material from which a matrix of + elements of ``self`` base ring can be constructed; the *columns* of + ``matrix_rep`` must be the components w.r.t. ``basis`` of + the images of the elements of ``basis``. + - ``basis`` -- (default: ``None``) basis of ``self`` defining the + matrix representation; if None the default basis of ``self`` is + assumed. + - ``name`` -- (default: ``None``) string; name given to the + endomorphism + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + the endomorphism; if none is provided, ``name`` will be used. + + OUTPUT: + + - the endomorphism `\phi: M \rightarrow M` corresponding to the given + specifications, as an instance of + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + + EXAMPLES: + + Construction of an endomorphism with minimal data (module's default + basis and no name):: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: phi = M.endomorphism([[1,-2], [-3,4]]) ; phi + Generic endomorphism of Rank-2 free module M over the Integer Ring + sage: phi.matrix() # matrix w.r.t the default basis + [ 1 -2] + [-3 4] + + Construction with full list of arguments (matrix given a basis + different from the default one):: + + sage: a = M.automorphism_tensor() ; a[0,1], a[1,0] = 1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: phi = M.endomorphism([[1,-2], [-3,4]], basis=ep, name='phi', + ....: latex_name=r'\phi') + sage: phi + Generic endomorphism of Rank-2 free module M over the Integer Ring + sage: phi.matrix(ep) # the input matrix + [ 1 -2] + [-3 4] + sage: phi.matrix() # matrix w.r.t the default basis + [4 3] + [2 1] + + """ + from sage.categories.homset import End + if basis is None: + basis = self.default_basis() + return End(self)(matrix_rep, bases=(basis,basis), name=name, + latex_name=latex_name) + + def identity_map(self): + r""" + Return the identity endomorphism of the free module ``self``. + + OUTPUT: + + - the identity map of ``self`` as an instance of + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + + EXAMPLES: + + Identity endomorphism of a rank-3 `\ZZ`-module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: Id = M.identity_map() ; Id + Identity endomorphism of Rank-3 free module M over the Integer Ring + sage: latex(Id) + \mathrm{Id} + + The identity endomorphism is actually the unit of End(M):: + + sage: Id is End(M).one() + True + + """ + from sage.categories.homset import End + if self._identity_map is None: + self._identity_map = End(self).one() + return self._identity_map diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 3e2e38bd302..32862a8da4d 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -1,7 +1,7 @@ r""" Alternating forms on free modules -The class :class:`FreeModuleAltForm` implement alternating forms on a free +The class :class:`FreeModuleAltForm` implements alternating forms on a free module of finite rank over a commutative ring. It is a subclass of @@ -87,12 +87,18 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): r""" Initialize ``self``. - TEST:: + TESTS:: sage: from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') sage: A = FreeModuleAltForm(M, 2, name='a') - sage: TestSuite(A).run() + sage: A[e,0,1] = 2 ; + sage: TestSuite(A).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because A is not an + instance of A.parent().category().element_class. + """ FreeModuleTensor.__init__(self, fmodule, (0,degree), name=name, latex_name=latex_name, antisym=range(degree)) @@ -265,7 +271,7 @@ def view(self, basis=None, format_spec=None): Display in a basis which is not the default one:: - sage: aut = M.automorphism() + sage: aut = M.automorphism_tensor() sage: aut[:] = [[0,1,0], [0,0,-1], [1,0,0]] sage: f = e.new_basis(aut, 'f') sage: a.view(f) @@ -311,14 +317,14 @@ def view(self, basis=None, format_spec=None): bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "/\\".join(bases_txt) basis_term_latex = r"\wedge ".join(bases_latex) - if coef == 1: + coef_txt = repr(coef) + if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) - elif coef == -1: + elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: - coef_txt = repr(coef) coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) @@ -538,12 +544,18 @@ class FreeModuleLinForm(FreeModuleAltForm): """ def __init__(self, fmodule, name=None, latex_name=None): r""" - TEST:: + TESTS:: sage: from sage.tensor.modules.free_module_alt_form import FreeModuleLinForm sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') sage: L = FreeModuleLinForm(M, name='a') - sage: TestSuite(L).run() + sage: L[e,0] = -3 + sage: TestSuite(L).run(skip='_test_category') # see below + + In the above test suite, _test_category fails because L is not an + instance of L.parent().category().element_class. + """ FreeModuleAltForm.__init__(self, fmodule, 1, name=name, latex_name=latex_name) diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index 123a1795e57..ad7b84ea3b8 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -114,6 +114,8 @@ def __init__(self, fmodule, symbol, latex_symbol=None): """ self._fmodule = fmodule + if latex_symbol is None: + latex_symbol = symbol self._name = "(" + \ ",".join([symbol + "_" + str(i) for i in fmodule.irange()]) +")" self._latex_name = r"\left(" + ",".join([latex_symbol + "_" + str(i) @@ -197,7 +199,7 @@ def _new_instance(self, symbol, latex_symbol=None): INPUT: - - ``symbol`` -- (string) a letter (of a few letters) to denote a + - ``symbol`` -- string; a letter (of a few letters) to denote a generic element of the basis - ``latex_symbol`` -- (default: ``None``) string; symbol to denote a generic element of the basis; if ``None``, the value of ``symbol`` @@ -348,7 +350,7 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): INPUT: - ``change_of_basis`` -- instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` + :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` describing the automorphism `P` that relates the current basis `(e_i)` (described by ``self``) to the new basis `(n_i)` according to `n_i = P(e_i)` @@ -368,7 +370,7 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[1, 2], [-1, 3]] sage: f = e.new_basis(a, 'f') ; f Basis (f_1,f_2) on the Rank-2 free module M over the Rational Field @@ -382,10 +384,10 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): e_2 = -2/5 f_1 + 1/5 f_2 """ - from free_module_tensor_spec import FreeModuleAutomorphism - if not isinstance(change_of_basis, FreeModuleAutomorphism): - raise TypeError("The argument change_of_basis must be some " + - "instance of FreeModuleAutomorphism.") + from free_module_tensor_spec import FreeModuleAutomorphismTensor + if not isinstance(change_of_basis, FreeModuleAutomorphismTensor): + raise TypeError("the argument change_of_basis must be some " + + "instance of FreeModuleAutomorphismTensor") fmodule = self._fmodule # self._new_instance used instead of FreeModuleBasis for a correct # construction in case of derived classes: @@ -430,7 +432,7 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): #****************************************************************************** -class FreeModuleCoBasis(SageObject): +class FreeModuleCoBasis(UniqueRepresentation, SageObject): r""" Dual basis of a free module over a commutative ring. diff --git a/src/sage/tensor/modules/free_module_homset.py b/src/sage/tensor/modules/free_module_homset.py new file mode 100644 index 00000000000..614684bd1ac --- /dev/null +++ b/src/sage/tensor/modules/free_module_homset.py @@ -0,0 +1,413 @@ +r""" +Sets of morphisms between free modules + +The class :class:`FreeModuleHomset` implements sets (actually free modules) of +homomorphisms between two free modules of finite rank over the same +commutative ring. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.categories.homset import Homset +from sage.tensor.modules.free_module_morphism import FiniteRankFreeModuleMorphism +from sage.tensor.modules.free_module_tensor import FreeModuleTensor +from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityTensor + +class FreeModuleHomset(Homset): + r""" + Set of homomorphisms between free modules of finite rank. + + Given two free modules `M` and `N` of respective ranks `m` and `n` over a + commutative ring `R`, the class :class:`FreeModuleHomset` implements the + set `\mathrm{Hom}(M,N)` of homomorphisms `M\rightarrow N`. This is a + *parent* class, whose *elements* are instances of + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + + The set `\mathrm{Hom}(M,N)` is actually a free module of rank `mn` over + `R`, but this aspect is not taken into account here. + + INPUT: + + - ``fmodule1`` -- free module `M` (domain of the homomorphisms); must be + an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + - ``fmodule2`` -- free module `N` (codomain of the homomorphisms); must be + an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + - ``name`` -- (default: ``None``) string; name given to the hom-set; if + none is provided, Hom(M,N) will be used + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the + hom-set; if none is provided, `\mathrm{Hom}(M,N)` will be used + + EXAMPLES: + + Set of homomorphisms between two free modules over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: H = Hom(M,N) ; H + Set of Morphisms from Rank-3 free module M over the Integer Ring to + Rank-2 free module N over the Integer Ring in Category of modules + over Integer Ring + sage: type(H) + + sage: H.category() + Category of homsets of modules over Integer Ring + + Hom-sets are cached:: + + sage: H is Hom(M,N) + True + + The LaTeX formatting is:: + + sage: latex(H) + \mathrm{Hom}\left(M,N\right) + + As usual, the construction of an element is performed by the ``__call__`` + method; the argument can be the matrix representing the morphism in the + default bases of the two modules:: + + sage: e = M.basis('e') + sage: f = N.basis('f') + sage: phi = H([[-1,2,0], [5,1,2]]) ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: phi.parent() is H + True + + An example of construction from a matrix w.r.t. bases that are not the + default ones:: + + sage: ep = M.basis('ep', latex_symbol=r"e'") + sage: fp = N.basis('fp', latex_symbol=r"f'") + sage: phi2 = H([[3,2,1], [1,2,3]], bases=(ep,fp)) ; phi2 + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + + The zero element:: + + sage: z = H.zero() ; z + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: z.matrix(e,f) + [0 0 0] + [0 0 0] + + The test suite for H is passed:: + + sage: TestSuite(H).run() + + The set of homomorphisms `M\rightarrow M`, i.e. endomorphisms, is + obtained by the function ``End``:: + + sage: End(M) + Set of Morphisms from Rank-3 free module M over the Integer Ring + to Rank-3 free module M over the Integer Ring in Category of modules + over Integer Ring + + ``End(M)`` is actually identical to ``Hom(M,M)``:: + + sage: End(M) is Hom(M,M) + True + + The unit of the endomorphism ring is the identity map:: + + sage: End(M).one() + Identity endomorphism of Rank-3 free module M over the Integer Ring + + whose matrix in any basis is of course the identity matrix:: + + sage: End(M).one().matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + + There is a canonical identification between endomorphisms of `M` and + tensors of type (1,1) on `M`. Accordingly, coercion maps have been + implemented between `\mathrm{End}(M)` and `T^{(1,1)}(M)` (the module of + all type-(1,1) tensors on `M`, see + :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`):: + + sage: T11 = M.tensor_module(1,1) ; T11 + Free module of type-(1,1) tensors on the Rank-3 free module M over + the Integer Ring + sage: End(M).has_coerce_map_from(T11) + True + sage: T11.has_coerce_map_from(End(M)) + True + + See :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` for + examples of the above coercions. + + """ + + Element = FiniteRankFreeModuleMorphism + + def __init__(self, fmodule1, fmodule2, name=None, latex_name=None): + r""" + TESTS:: + + sage: from sage.tensor.modules.free_module_homset import FreeModuleHomset + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: FreeModuleHomset(M, N) + Set of Morphisms from Rank-3 free module M over the Integer Ring + to Rank-2 free module N over the Integer Ring in Category of + modules over Integer Ring + sage: H = FreeModuleHomset(M, N, name='L(M,N)', + ....: latex_name=r'\mathcal{L}(M,N)') + sage: latex(H) + \mathcal{L}(M,N) + + """ + from finite_rank_free_module import FiniteRankFreeModule + if not isinstance(fmodule1, FiniteRankFreeModule): + raise TypeError("fmodule1 = " + str(fmodule1) + " is not an " + + "instance of FiniteRankFreeModule") + if not isinstance(fmodule2, FiniteRankFreeModule): + raise TypeError("fmodule1 = " + str(fmodule2) + " is not an " + + "instance of FiniteRankFreeModule") + if fmodule1.base_ring() != fmodule2.base_ring(): + raise TypeError("the domain and codomain are not defined over " + + "the same ring") + Homset.__init__(self, fmodule1, fmodule2) + if name is None: + self._name = "Hom(" + fmodule1._name + "," + fmodule2._name + ")" + else: + self._name = name + if latex_name is None: + self._latex_name = \ + r"\mathrm{Hom}\left(" + fmodule1._latex_name + "," + \ + fmodule2._latex_name + r"\right)" + else: + self._latex_name = latex_name + + def _latex_(self): + r""" + LaTeX representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: H = Hom(M,N) + sage: H._latex_() + '\\mathrm{Hom}\\left(M,N\\right)' + sage: latex(H) # indirect doctest + \mathrm{Hom}\left(M,N\right) + + """ + if self._latex_name is None: + return r'\mbox{' + str(self) + r'}' + else: + return self._latex_name + + def __call__(self, *args, **kwds): + r""" + To bypass Homset.__call__, enforcing Parent.__call__ instead. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: N = FiniteRankFreeModule(ZZ, 3, name='N') + sage: H = Hom(M,N) + sage: e = M.basis('e') ; f = N.basis('f') + sage: a = H.__call__(0) ; a + Generic morphism: + From: Rank-2 free module M over the Integer Ring + To: Rank-3 free module N over the Integer Ring + sage: a.matrix(e,f) + [0 0] + [0 0] + [0 0] + sage: a == H.zero() + True + sage: a == H(0) + True + sage: a = H.__call__([[1,2],[3,4],[5,6]], bases=(e,f), name='a') ; a + Generic morphism: + From: Rank-2 free module M over the Integer Ring + To: Rank-3 free module N over the Integer Ring + sage: a.matrix(e,f) + [1 2] + [3 4] + [5 6] + sage: a == H([[1,2],[3,4],[5,6]], bases=(e,f)) + True + + """ + from sage.structure.parent import Parent + return Parent.__call__(self, *args, **kwds) + + #### Methods required for any Parent + + def _element_constructor_(self, matrix_rep, bases=None, name=None, + latex_name=None, is_identity=False): + r""" + Construct an element of ``self``, i.e. a homomorphism M --> N, where + M is the domain of ``self`` and N its codomain. + + INPUT: + + - ``matrix_rep`` -- matrix representation of the homomorphism with + respect to the bases ``basis1`` and ``basis2``; this entry can + actually be any material from which a matrix of size rank(N)*rank(M) + can be constructed + - ``bases`` -- (default: ``None``) pair (basis_M, basis_N) defining the + matrix representation, basis_M being a basis of module `M` and + basis_N a basis of module `N` ; if none is provided the pair formed + by the default bases of each module is assumed. + - ``name`` -- (default: ``None``) string; name given to the + homomorphism + - ``latex_name`` -- (default: ``None``)string; LaTeX symbol to denote + the homomorphism; if none is provided, ``name`` will be used. + - ``is_identity`` -- (default: ``False``) determines whether the + constructed object is the identity endomorphism; if set to ``True``, + then N must be M and the entry ``matrix_rep`` is not used. + + EXAMPLES: + + Construction of a homomorphism between two free `\ZZ`-modules:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: H = Hom(M,N) + sage: phi = H._element_constructor_([[2,-1,3], [1,0,-4]], bases=(e,f), + ....: name='phi', latex_name=r'\phi') + sage: phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: phi.matrix(e,f) + [ 2 -1 3] + [ 1 0 -4] + sage: phi == H([[2,-1,3], [1,0,-4]], bases=(e,f), name='phi', + ....: latex_name=r'\phi') + True + + Construction of an endomorphism:: + + sage: EM = End(M) + sage: phi = EM._element_constructor_([[1,2,3],[4,5,6],[7,8,9]], name='phi', + ....: latex_name=r'\phi') + sage: phi + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: phi.matrix(e,e) + [1 2 3] + [4 5 6] + [7 8 9] + + Coercion of a type-(1,1) tensor to an endomorphism:: + + sage: a = M.tensor((1,1)) + sage: a[:] = [[1,2,3],[4,5,6],[7,8,9]] + sage: EM = End(M) + sage: phi_a = EM._element_constructor_(a) ; phi_a + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: phi_a.matrix(e,e) + [1 2 3] + [4 5 6] + [7 8 9] + sage: phi_a == phi + True + sage: phi_a1 = EM(a) ; phi_a1 # indirect doctest + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: phi_a1 == phi + True + + """ + if isinstance(matrix_rep, FreeModuleTensor): + # coercion of a type-(1,1) tensor to an endomorphism + tensor = matrix_rep # for readability + if tensor.tensor_type() == (1,1) and \ + self.is_endomorphism_set() and \ + tensor.base_module() is self.domain(): + basis = tensor.pick_a_basis() + tcomp = tensor.comp(basis) + fmodule = tensor.base_module() + mat = [[ tcomp[[i,j]] for j in fmodule.irange()] \ + for i in fmodule.irange()] + resu = self.element_class(self, mat, bases=(basis,basis), + name=tensor._name, latex_name=tensor._latex_name, + is_identity=isinstance(tensor, FreeModuleIdentityTensor)) + else: + raise TypeError("cannot coerce the " + str(tensor) + + " to an element of " + str(self)) + else: + # Standard construction: + resu = self.element_class(self, matrix_rep, bases=bases, name=name, + latex_name=latex_name, + is_identity=is_identity) + return resu + + def _an_element_(self): + r""" + Construct some (unamed) element. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = Hom(M,N)._an_element_() ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: phi.matrix(e,f) + [1 1 1] + [1 1 1] + sage: phi == Hom(M,N).an_element() + True + + """ + ring = self.base_ring() + m = self.domain().rank() + n = self.codomain().rank() + matrix_rep = [[ring.an_element() for i in range(m)] for j in range(n)] + return self.element_class(self, matrix_rep) + + def _coerce_map_from_(self, other): + r""" + Determine whether coercion to self exists from other parent. + + EXAMPLES: + + Only the module of type-(1,1) tensors coerce to self, if the latter + is some endomorphism set:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: End(M)._coerce_map_from_(M.tensor_module(1,1)) + True + sage: End(M).has_coerce_map_from(M.tensor_module(1,1)) + True + sage: End(M)._coerce_map_from_(M.tensor_module(1,2)) + False + + """ + from tensor_free_module import TensorFreeModule + if isinstance(other, TensorFreeModule): + # Coercion of a type-(1,1) tensor to an endomorphism: + if other.tensor_type() == (1,1): + if self.is_endomorphism_set() and \ + other.base_module() is self.domain(): + return True + return False + + #### End of methods required for any Parent diff --git a/src/sage/tensor/modules/free_module_morphism.py b/src/sage/tensor/modules/free_module_morphism.py new file mode 100644 index 00000000000..d5628c293fa --- /dev/null +++ b/src/sage/tensor/modules/free_module_morphism.py @@ -0,0 +1,1280 @@ +r""" +Morphisms between free modules + +The class :class:`FiniteRankFreeModuleMorphism` implements homomorphisms +between two free modules of finite rank over the same commutative ring. + +AUTHORS: + +- Eric Gourgoulhon, Michal Bejger (2014): initial version + +""" +#****************************************************************************** +# Copyright (C) 2014 Eric Gourgoulhon +# Copyright (C) 2014 Michal Bejger +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.rings.integer import Integer +from sage.categories.morphism import Morphism +from sage.categories.homset import Hom +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule + +class FiniteRankFreeModuleMorphism(Morphism): + r""" + Homomorphism between free modules of finite rank. + + This is an *element* class, whose *parent* class is + :class:`~sage.tensor.modules.free_module_homset.FreeModuleHomset`. + + An instance of this class is a homomorphism + + .. MATH:: + + \phi:\ M \longrightarrow N, + + where `M` and `N` are two free modules of finite rank over the same + commutative ring `R`. + + INPUT: + + - ``parent`` -- hom-set Hom(M,N) to which the homomorphism belongs + - ``matrix_rep`` -- matrix representation of the homomorphism with + respect to the bases ``bases``; this entry can actually + be any material from which a matrix of size rank(N)*rank(M) of + elements of `R` can be constructed; the *columns* of the matrix give + the images of the basis of `M` (see the convention in the example below) + - ``bases`` -- (default: ``None``) pair (basis_M, basis_N) defining the + matrix representation, basis_M being a basis of module `M` and + basis_N a basis of module `N` ; if None the pair formed by the + default bases of each module is assumed. + - ``name`` -- (default: ``None``) string; name given to the homomorphism + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the + homomorphism; if None, ``name`` will be used. + - ``is_identity`` -- (default: ``False``) determines whether the + constructed object is the identity endomorphism; if set to ``True``, then + N must be M and the entry ``matrix_rep`` is not used. + + EXAMPLES: + + A homomorphism between two free modules over `\ZZ` is contructed + as an element of the corresponding hom-set, by means of the function + ``__call__``:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: H = Hom(M,N) ; H + Set of Morphisms from Rank-3 free module M over the Integer Ring to + Rank-2 free module N over the Integer Ring in Category of modules + over Integer Ring + sage: phi = H([[2,-1,3], [1,0,-4]], name='phi', latex_name=r'\phi') ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + + Since no bases have been specified in the argument list, the provided + matrix is relative to the default bases of modules M and N, so that + the above is equivalent to:: + + sage: phi = H([[2,-1,3], [1,0,-4]], bases=(e,f), name='phi', + ....: latex_name=r'\phi') ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + + An alternative way to construct a homomorphism is to call the method + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.hom` + on the domain:: + + sage: phi = M.hom(N, [[2,-1,3], [1,0,-4]], bases=(e,f), name='phi', + ....: latex_name=r'\phi') ; phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + + The parent of a homomorphism is of course the corresponding hom-set:: + + sage: phi.parent() is H + True + sage: phi.parent() is Hom(M,N) + True + + Due to Sage's category scheme, the actual class of the homomorphism phi is + a derived class of :class:`FiniteRankFreeModuleMorphism`:: + + sage: type(phi) + + sage: isinstance(phi, sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism) + True + + The domain and codomain of the homomorphism are returned respectively by + the methods ``domain()`` and ``codomain()``, which are implemented as + Sage's constant functions:: + + sage: phi.domain() + Rank-3 free module M over the Integer Ring + sage: phi.codomain() + Rank-2 free module N over the Integer Ring + sage: type(phi.domain) + + + The matrix of the homomorphism with respect to a pair of bases is + returned by the method :meth:`matrix`:: + + sage: phi.matrix(e,f) + [ 2 -1 3] + [ 1 0 -4] + + The convention is that the columns of this matrix give the components of + the images of the elements of basis e w.r.t basis f:: + + sage: phi(e[0]).view() + phi(e_0) = 2 f_0 + f_1 + sage: phi(e[1]).view() + phi(e_1) = -f_0 + sage: phi(e[2]).view() + phi(e_2) = 3 f_0 - 4 f_1 + + Test of the module homomorphism laws:: + + sage: phi(M.zero()) == N.zero() + True + sage: u = M([1,2,3], basis=e, name='u') ; u.view() + u = e_0 + 2 e_1 + 3 e_2 + sage: v = M([-2,1,4], basis=e, name='v') ; v.view() + v = -2 e_0 + e_1 + 4 e_2 + sage: phi(u).view() + phi(u) = 9 f_0 - 11 f_1 + sage: phi(v).view() + phi(v) = 7 f_0 - 18 f_1 + sage: phi(3*u + v).view() + 34 f_0 - 51 f_1 + sage: phi(3*u + v) == 3*phi(u) + phi(v) + True + + The identity endomorphism:: + + sage: Id = M.identity_map() ; Id + Identity endomorphism of Rank-3 free module M over the Integer Ring + sage: Id.parent() + Set of Morphisms from Rank-3 free module M over the Integer Ring to + Rank-3 free module M over the Integer Ring in Category of modules + over Integer Ring + sage: Id.parent() is End(M) + True + + The identity endomorphism is actually the unit of End(M):: + + sage: Id is End(M).one() + True + + The matrix of Id with respect to the basis e is of course the identity + matrix:: + + sage: Id.matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + + The identity acting on a module element:: + + sage: Id(v) is v + True + + """ + def __init__(self, parent, matrix_rep, bases=None, name=None, + latex_name=None, is_identity=False): + r""" + TESTS: + + Generic homomorphism:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: from sage.tensor.modules.free_module_morphism import FiniteRankFreeModuleMorphism + sage: phi = FiniteRankFreeModuleMorphism(Hom(M,N), [[1,0,-3], [2,1,4]], + ....: name='phi', latex_name=r'\phi') + sage: phi + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: phi.matrix(e,f) + [ 1 0 -3] + [ 2 1 4] + sage: latex(phi) + \phi + + Generic endomorphism:: + + sage: phi = FiniteRankFreeModuleMorphism(End(M), [[1,0,-3], [2,1,4], [7,8,9]], + ....: name='phi', latex_name=r'\phi') + sage: phi + Generic endomorphism of Rank-3 free module M over the Integer Ring + + Identity endomorphism:: + + sage: phi = FiniteRankFreeModuleMorphism(End(M), 'whatever', is_identity=True) + sage: phi + Identity endomorphism of Rank-3 free module M over the Integer Ring + sage: phi.matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + sage: latex(phi) + \mathrm{Id} + + """ + from sage.matrix.constructor import matrix + from sage.misc.constant_function import ConstantFunction + Morphism.__init__(self, parent) + fmodule1 = parent.domain() + fmodule2 = parent.codomain() + if bases is None: + def_basis1 = fmodule1.default_basis() + if def_basis1 is None: + raise ValueError("the " + str(fmodule1) + " has no default " + + "basis") + def_basis2 = fmodule2.default_basis() + if def_basis2 is None: + raise ValueError("the " + str(fmodule2) + " has no default " + + "basis") + bases = (def_basis1, def_basis2) + else: + bases = tuple(bases) # insures bases is a tuple + if len(bases) != 2: + raise TypeError("the argument bases must contain 2 bases") + if bases[0] not in fmodule1.bases(): + raise TypeError(str(bases[0]) + " is not a basis on the " + \ + str(fmodule1)) + if bases[1] not in fmodule2.bases(): + raise TypeError(str(bases[1]) + " is not a basis on the " + \ + str(fmodule2)) + ring = parent.base_ring() + n1 = fmodule1.rank() + n2 = fmodule2.rank() + if is_identity: + # Construction of the identity endomorphism + if fmodule1 != fmodule2: + raise TypeError("the domain and codomain must coincide " + \ + "for the identity endomorphism.") + if bases[0] != bases[1]: + raise TypeError("the two bases must coincide for " + \ + "constructing the identity endomorphism.") + self._is_identity = True + zero = ring.zero() + one = ring.one() + matrix_rep = [] + for i in range(n1): + row = [zero]*n1 + row[i] = one + matrix_rep.append(row) + if name is None: + name = 'Id' + if latex_name is None and name == 'Id': + latex_name = r'\mathrm{Id}' + self._repr_type_str = 'Identity' + else: + # Construction of a generic morphism + self._is_identity = False + if isinstance(matrix_rep, ConstantFunction): + # the zero morphism + if matrix_rep().is_zero(): + matrix_rep = 0 + if matrix_rep == 1: + if fmodule1 == fmodule2: + # the identity endomorphism (again): + self._is_identity = True + self._repr_type_str = 'Identity' + name = 'Id' + latex_name = r'\mathrm{Id}' + self._matrices = {bases: matrix(ring, n2, n1, matrix_rep)} + self._name = name + if latex_name is None: + self._latex_name = self._name + else: + self._latex_name = latex_name + + # + # SageObject methods + # + + def _latex_(self): + r""" + LaTeX representation of the object. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\Phi') + sage: phi._latex_() + '\\Phi' + sage: latex(phi) # indirect doctest + \Phi + + :: + + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='F') + sage: phi._latex_() + 'F' + + :: + + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi._latex_() + '\\mbox{Generic morphism:\n From: Rank-3 free module M over the Integer Ring\n To: Rank-2 free module N over the Integer Ring}' + + """ + if self._latex_name is None: + return r'\mbox{' + str(self) + r'}' + else: + return self._latex_name + + def __eq__(self, other): + r""" + Comparison (equality) operator. + + INPUT: + + - ``other`` -- a free module morphism (or 0) + + OUTPUT: + + - ``True`` if ``self`` is equal to ``other`` and ``False`` otherwise + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: psi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.__eq__(psi) + True + sage: phi == psi + True + sage: phi.__eq__(phi) + True + sage: phi.__eq__(+phi) + True + sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) + sage: phi.__eq__(psi) + False + sage: phi.__eq__(-phi) + False + + Comparison of homomorphisms defined on different bases:: + + sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: psi = M.hom(N, [[-2,0,-1], [-1,-2, 5]], bases=(ep,f)) + sage: phi.__eq__(psi) + True + sage: phi.matrix(e,f) == psi.matrix(e,f) # check + True + + Comparison of homomorphisms having the same matrix but defined on + different modules:: + + sage: N1 = FiniteRankFreeModule(ZZ, 2, name='N1') + sage: f1 = N1.basis('f') + sage: phi1 = M.hom(N1, [[-1,2,0], [5,1,2]]) + sage: phi.matrix() == phi1.matrix() # same matrix in the default bases + True + sage: phi.__eq__(phi1) + False + + Comparison to zero:: + + sage: phi.__eq__(0) + False + sage: phi = M.hom(N, 0) + sage: phi.__eq__(0) + True + sage: phi == 0 + True + sage: phi.__eq__(Hom(M,N).zero()) + True + + """ + if isinstance(other, (int, Integer)): # other should be 0 + if other == 0: + return self.is_zero() + else: + return False + elif not isinstance(other, FiniteRankFreeModuleMorphism): + return False + elif self.parent() != other.parent(): + return False + else: + bases = self._common_bases(other) + if bases is None: + raise ValueError("no common pair of bases has been found to " + + "compare " + str(self) + " and " + str(other)) + return bool( self.matrix(*bases) == other.matrix(*bases) ) + + def __ne__(self, other): + r""" + Inequality operator. + + INPUT: + + - ``other`` -- a free module morphism (or 0) + + OUTPUT: + + - ``True`` if ``self`` is different from ``other`` and ``False`` + otherwise + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: psi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.__ne__(psi) + False + sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) + sage: phi.__ne__(psi) + True + sage: phi != psi + True + sage: phi.__ne__('junk') + True + sage: Hom(M,N).zero().__ne__(0) + False + + """ + return not self.__eq__(other) + + def __cmp__(self, other): + r""" + Old-style (Python 2) comparison operator. + + This is provisory, until migration to Python 3 is achieved. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: psi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.__cmp__(psi) + 0 + sage: phi.__cmp__(phi) + 0 + sage: phi.__cmp__(phi+phi) + -1 + sage: phi.__cmp__(2*psi) + -1 + sage: phi.__cmp__(-phi) + -1 + + """ + if self.__eq__(other): + return 0 + else: + return -1 + + # + # Required module methods + # + + def __nonzero__(self): + r""" + Return ``True`` if ``self`` is nonzero and ``False`` otherwise. + + This method is called by self.is_zero(). + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[2,-1,3], [1,0,-4]]) + sage: phi.__nonzero__() + True + sage: phi.is_zero() # indirect doctest + False + sage: phi = M.hom(N, 0) + sage: phi.__nonzero__() + False + sage: phi.is_zero() # indirect doctest + True + sage: Hom(M,N).zero().__nonzero__() + False + + """ + # Some matrix representation is picked at random: + matrix_rep = self._matrices.values()[0] + return not matrix_rep.is_zero() + + def _add_(self, other): + r""" + Homomorphism addition. + + INPUT: + + - ``other`` -- a free module morphism (same parent as ``self``) + + OUPUT: + + - the homomorphism resulting from the addition of ``self`` and ``other`` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) + sage: s = phi._add_(psi) ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s.matrix(e,f) + [0 3 0] + [9 2 5] + sage: s.matrix(e,f) == phi.matrix(e,f) + psi.matrix(e,f) # check + True + sage: s == phi + psi # indirect doctest + True + + Addition of homomorphisms defined on different bases:: + + sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: b = N.automorphism_tensor() ; b[0,1], b[1,0] = -1, 1 + sage: fp = f.new_basis(b, 'fp', latex_symbol="f'") + sage: psi = M.hom(N, [[-2,0,-1], [-1,-2, 5]], bases=(ep,fp)) + sage: s = phi._add_(psi) ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s.matrix(e,f) + [-6 1 -2] + [ 4 3 2] + sage: s.matrix(e,f) == phi.matrix(e,f) + psi.matrix(e,f) # check + True + sage: s == phi + psi # indirect doctest + True + + Other tests:: + + sage: phi._add_(Hom(M,N).zero()) == phi + True + + """ + # No need for consistency checks since self and other are guaranteed + # to have the same parents + bases = self._common_bases(other) + if bases is None: + raise ValueError("no common pair of bases has been found to " + + "add " + str(self) + " and " + str(other)) + # Addition at the matrix level: + resu_mat = self._matrices[bases] + other._matrices[bases] + if self._name is not None and other._name is not None: + resu_name = self._name + '+' + other._name + else: + resu_name = None + if self._latex_name is not None and other._latex_name is not None: + resu_latex_name = self._latex_name + '+' + other._latex_name + else: + resu_latex_name = None + return self.__class__(self.parent(), resu_mat, bases=bases, + name=resu_name, latex_name=resu_latex_name) + + def _sub_(self, other): + r""" + Homomorphism subtraction. + + INPUT: + + - ``other`` -- a free module morphism (same parent as ``self``) + + OUPUT: + + - the homomorphism resulting from the subtraction of ``other`` from + ``self`` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) + sage: s = phi._sub_(psi) ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s.matrix(e,f) + [-2 1 0] + [ 1 0 -1] + sage: s.matrix(e,f) == phi.matrix(e,f) - psi.matrix(e,f) # check + True + sage: s == phi - psi # indirect doctest + True + + Addition of homomorphisms defined on different bases:: + + sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: b = N.automorphism_tensor() ; b[0,1], b[1,0] = -1, 1 + sage: fp = f.new_basis(b, 'fp', latex_symbol="f'") + sage: psi = M.hom(N, [[-2,0,-1], [-1,-2, 5]], bases=(ep,fp)) + sage: s = phi._sub_(psi) ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s.matrix(e,f) + [ 4 3 2] + [ 6 -1 2] + sage: s.matrix(e,f) == phi.matrix(e,f) - psi.matrix(e,f) # check + True + sage: s == phi - psi # indirect doctest + True + + Other tests:: + + sage: phi._sub_(Hom(M,N).zero()) == phi + True + sage: Hom(M,N).zero()._sub_(phi) == -phi + True + sage: phi._sub_(phi).is_zero() + True + + """ + # No need for consistency checks since self and other are guaranteed + # to have the same parents + bases = self._common_bases(other) + if bases is None: + raise ValueError("no common pair of bases has been found to " + + "substract " + str(other) + " from " + + str(self)) + # Subtraction at the matrix level: + resu_mat = self._matrices[bases] - other._matrices[bases] + if self._name is not None and other._name is not None: + resu_name = self._name + '-' + other._name + else: + resu_name = None + if self._latex_name is not None and other._latex_name is not None: + resu_latex_name = self._latex_name + '-' + other._latex_name + else: + resu_latex_name = None + return self.__class__(self.parent(), resu_mat, bases=bases, + name=resu_name, latex_name=resu_latex_name) + + def _rmul_(self, scalar): + r""" + Multiplication on the left by ``scalar``. + + INPUT: + + - ``scalar`` -- element of the ring over which the parent of ``self`` + is a module. + + OUPUT: + + - the homomorphism resulting from the multiphication of ``self`` by + ``scalar`` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: s = phi._rmul_(7) ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s.matrix(e,f) + [-7 14 0] + [35 7 14] + sage: s == 7*phi # indirect doctest + True + + """ + resu = self.__class__(self.parent(), 0) # 0 = provisory value + for bases, mat in self._matrices.iteritems(): + resu._matrices[bases] = scalar * mat + return resu + + + # + # Other module methods + # + + def __pos__(self): + r""" + Unary plus operator. + + OUTPUT: + + - an exact copy of ``self`` + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: s = phi.__pos__() ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s == +phi + True + sage: s == phi + True + sage: s is phi + False + + """ + resu = self.__class__(self.parent(), 0, is_identity=self._is_identity) + # 0 = provisory value + for bases, mat in self._matrices.iteritems(): + resu._matrices[bases] = +mat + if self._name is not None: + resu._name = '+' + self._name + if self._latex_name is not None: + resu._latex_name = '+' + self._latex_name + return resu + + def __neg__(self): + r""" + Unary minus operator. + + OUTPUT: + + - the homomorphism `-f`, where `f` is ``self`` + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: s = phi.__neg__() ; s + Generic morphism: + From: Rank-3 free module M over the Integer Ring + To: Rank-2 free module N over the Integer Ring + sage: s == -phi + True + sage: s.matrix() + [ 1 -2 0] + [-5 -1 -2] + sage: s.matrix() == -phi.matrix() + True + + """ + resu = self.__class__(self.parent(), 0) # 0 = provisory value + for bases, mat in self._matrices.iteritems(): + resu._matrices[bases] = -mat + if self._name is not None: + resu._name = '-' + self._name + if self._latex_name is not None: + resu._latex_name = '-' + self._latex_name + return resu + + # + # Map methods + # + + def _call_(self, element): + r""" + Action of the homomorphism ``self`` on some free module element + + INPUT: + + - ``element`` -- element of the domain of ``self`` + + OUTPUT: + + - the image of ``element`` by ``self`` + + EXAMPLE: + + Images of a homomorphism between two `\ZZ`-modules:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: v = M([1,2,3], basis=e, name='v') + sage: w = phi(v) ; w + Element phi(v) of the Rank-2 free module N over the Integer Ring + sage: w.view() + phi(v) = 3 f_0 + 13 f_1 + + Tests:: + + sage: for i in range(2): + ....: print w[i] == sum( phi.matrix()[i,j]*v[j] for j in range(3) ), + ....: + True True + sage: phi.matrix(e,f) + [-1 2 0] + [ 5 1 2] + sage: phi(e[0]).view() + phi(e_0) = -f_0 + 5 f_1 + sage: phi(e[1]).view() + phi(e_1) = 2 f_0 + f_1 + sage: phi(e[2]).view() + phi(e_2) = 2 f_1 + + Image of an element that is not defined on the default basis:: + + sage: a = M.automorphism_tensor() + sage: a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: v = M([1,2,3], basis=ep, name='v') + sage: w = phi(v) ; w + Element phi(v) of the Rank-2 free module N over the Integer Ring + sage: w.view() + phi(v) = -5 f_0 + 10 f_1 + sage: for i in range(2): + ....: print w[i] == sum( phi.matrix(ep,f)[i,j]*v[ep,j] for j in range(3) ), + ....: + True True + + Check of homomorphism properties:: + + sage: phi(M.zero()) == N.zero() + True + + """ + if self._is_identity: + return element + dom = self.parent().domain() + sindex = dom._sindex + codom = self.parent().codomain() + basis_codom = codom.default_basis() + # Search for a common basis to compute the result + for basis in element._components: + try: + self.matrix(basis, basis_codom) + basis_dom = basis + break + except ValueError: + continue + else: + raise ValueError("no common basis found to evaluate the image " + + "of " + str(element) + " by " + str(self)) + # Components of the result obtained by matrix multiplication + mat = self.matrix(basis_dom, basis_codom) + vcomp = element._components[basis_dom] + tresu = [] + for i in range(codom.rank()): + s = 0 + for j in range(dom.rank()): + s += mat[i,j] * vcomp[[j+sindex]] + tresu.append(s) + # Name of the result + if self._name is not None and element._name is not None: + resu_name = self._name + '(' + element._name + ')' + else: + resu_name = None + if self._latex_name is not None and element._latex_name is not None: + resu_latex_name = self._latex_name + r'\left(' + \ + element._latex_name + r'\right)' + else: + resu_latex_name = None + # Creation of the result + return codom(tresu, basis=basis_codom, name=resu_name, + latex_name=resu_latex_name) + + def is_injective(self): + r""" + Determine whether ``self`` is injective. + + OUTPUT: + + - ``True`` if ``self`` is an injective homomorphism and ``False`` + otherwise + + EXAMPLES: + + Homomorphisms between two `\ZZ`-modules:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.matrix(e,f) + [-1 2 0] + [ 5 1 2] + sage: phi.is_injective() + False + + Indeed, phi has a non trivial kernel:: + + sage: phi(4*e[0] + 2*e[1] - 11*e[2]).view() + 0 + + An injective homomorphism:: + + sage: psi = N.hom(M, [[1,-1], [0,3], [4,-5]]) + sage: psi.matrix(f,e) + [ 1 -1] + [ 0 3] + [ 4 -5] + sage: psi.is_injective() + True + + Of course, the identity endomorphism is injective:: + + sage: M.identity_map().is_injective() + True + sage: N.identity_map().is_injective() + True + + """ + # Some matrix representation is picked at random: + matrix_rep = self._matrices.values()[0] + return matrix_rep.right_kernel().rank() == 0 + + def is_surjective(self): + r""" + Determine whether ``self`` is surjective. + + OUTPUT: + + - ``True`` if ``self`` is a surjective homomorphism and ``False`` + otherwise + + EXAMPLE: + + This method has not been implemented yet:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.is_surjective() + Traceback (most recent call last): + ... + NotImplementedError: FiniteRankFreeModuleMorphism.is_surjective() has not been implemented yet + + except for the identity map (!):: + + sage: M.identity_map().is_surjective() + True + sage: N.identity_map().is_surjective() + True + + """ + if self._is_identity: + return True + raise NotImplementedError( + "FiniteRankFreeModuleMorphism.is_surjective() " + + "has not been implemented yet") + # + # Morphism methods + # + + def is_identity(self): + r""" + Check whether ``self`` is the identity morphism. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: phi = M.endomorphism([[1,0], [0,1]]) + sage: phi.is_identity() + True + sage: (phi+phi).is_identity() + False + sage: End(M).zero().is_identity() + False + sage: a = M.automorphism_tensor() ; a[0,1], a[1,0] = 1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: phi = M.endomorphism([[1,0], [0,1]], basis=ep) + sage: phi.is_identity() + True + + Example illustrating that the identity can be constructed from a + matrix that is not the identity one, provided that it is relative to + different bases:: + + sage: phi = M.hom(M, [[0,1], [-1,0]], bases=(ep,e)) + sage: phi.is_identity() + True + + Of course, if we ask for the matrix in a single basis, it is the + identity matrix:: + + sage: phi.matrix(e) + [1 0] + [0 1] + sage: phi.matrix(ep) + [1 0] + [0 1] + + """ + if self._is_identity: + return True + # The identity must be an endomorphism: + fmodule = self.domain() + if fmodule != self.codomain(): + return False + # Some basis in which ``self`` has a representation is picked at + # random and the test is performed on the images of the basis + # elements: + basis = self._matrices.keys()[0][0] + for i in fmodule.irange(): + if self(basis[i]) != basis[i]: + return False + self._is_identity = True + return True + + # + # End of Morphism methods + # + + def matrix(self, basis1=None, basis2=None): + r""" + Return the matrix of ``self`` w.r.t to a pair of bases. + + If the matrix is not known already, it is computed from the matrix in + another pair of bases by means of the change-of-bases formula. + + INPUT: + + - ``basis1`` -- (default: ``None``) basis of the domain of ``self``; if + none is provided, the domain's default basis is assumed + - ``basis2`` -- (default: ``None``) basis of the codomain of ``self``; + if none is provided, ``basis2`` is set to ``basis1`` if ``self`` is + an endomorphism, otherwise, ``basis2`` is set to the codomain's + default basis. + + OUTPUT: + + - the matrix representing representing the homomorphism ``self`` w.r.t + to bases ``basis1`` and ``basis2``; more precisely, the columns of + this matrix are formed by the components w.r.t. ``basis2`` of + the images of the elements of ``basis1``. + + EXAMPLES: + + Matrix of a homomorphism between two `\ZZ`-modules:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: phi.matrix() # default bases + [-1 2 0] + [ 5 1 2] + sage: phi.matrix(e,f) # bases explicited + [-1 2 0] + [ 5 1 2] + sage: type(phi.matrix()) + + + Matrix in bases different from those in which the homomorphism has + been defined:: + + sage: a = M.automorphism_tensor() + sage: a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: a[:] + [ 0 0 1] + [-1 0 0] + [ 0 -1 0] + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: b = N.automorphism_tensor() + sage: b[0,1], b[1,0] = -1, 1 + sage: b[:] + [ 0 -1] + [ 1 0] + sage: fp = f.new_basis(b, 'fp', latex_symbol="f'") + sage: phi.matrix(ep, fp) + [-1 -2 5] + [ 2 0 1] + + Check of the change-of-bases formula:: + + sage: phi.matrix(ep, fp) == matrix(b.inverse()[:]) * phi.matrix(e,f) * matrix(a[:]) + True + + Single change of basis:: + + sage: phi.matrix(ep, f) + [-2 0 -1] + [-1 -2 5] + sage: phi.matrix(ep,f) == phi.matrix(e,f) * matrix(a[:]) + True + sage: phi.matrix(e, fp) + [ 5 1 2] + [ 1 -2 0] + sage: phi.matrix(e, fp) == matrix(b.inverse()[:]) * phi.matrix(e,f) + True + + Matrix of an endomorphism:: + + sage: phi = M.endomorphism([[1,2,3], [4,5,6], [7,8,9]], basis=ep) + sage: phi.matrix(ep) + [1 2 3] + [4 5 6] + [7 8 9] + sage: phi.matrix(ep,ep) # same as above + [1 2 3] + [4 5 6] + [7 8 9] + sage: phi.matrix() # matrix w.r.t to the module's default basis + [ 9 -7 -8] + [-3 1 2] + [-6 4 5] + + """ + from sage.matrix.constructor import matrix + fmodule1 = self.domain() + fmodule2 = self.codomain() + if basis1 is None: + basis1 = fmodule1.default_basis() + elif basis1 not in fmodule1.bases(): + raise TypeError(str(basis1) + " is not a basis on the " + \ + str(fmodule1) + ".") + if basis2 is None: + if self.is_endomorphism(): + basis2 = basis1 + else: + basis2 = fmodule2.default_basis() + elif basis2 not in fmodule2.bases(): + raise TypeError(str(basis2) + " is not a basis on the " + \ + str(fmodule2) + ".") + if (basis1, basis2) not in self._matrices: + if self._is_identity: + # The identity endomorphism + # ------------------------- + if basis1 == basis2: + # the matrix is the identity matrix: + ring = fmodule1.base_ring() + zero = ring.zero() + one = ring.one() + size = fmodule1.rank() + mat = [] + for i in range(size): + row = [zero]*size + row[i] = one + mat.append(row) + else: + # the matrix is the change-of-basis matrix: + change = fmodule1.change_of_basis(basis1, basis2) + mat = [[change[[i,j]] for j in fmodule1.irange()] + for i in fmodule1.irange()] + self._matrices[(basis1, basis2)] = matrix(mat) + else: + # Generic homomorphism + # -------------------- + b1_list = [bases[0] for bases in self._matrices] + b2_list = [bases[1] for bases in self._matrices] + if basis1 in b1_list: + for b2 in b2_list: + if (basis2, b2) in fmodule2._basis_changes: + nb2 = b2 + break + else: + raise ValueError("no start basis could be found for " + + "applying the change-of-bases formula") + change2 = fmodule2._basis_changes[(basis2, nb2)] + mat2 = matrix( [[change2[[i,j]] for j in fmodule2.irange()] + for i in fmodule2.irange()] ) + self._matrices[(basis1, basis2)] = \ + mat2 * self._matrices[(basis1,nb2)] + elif basis2 in b2_list: + for b1 in b1_list: + if (b1, basis1) in fmodule1._basis_changes: + nb1 = b1 + break + else: + raise ValueError("no start basis could be found for " + + "applying the change-of-bases formula") + change1 = fmodule1._basis_changes[(nb1, basis1)] + mat1 = matrix( [[change1[[i,j]] for j in fmodule1.irange()] + for i in fmodule1.irange()] ) + self._matrices[(basis1, basis2)] = \ + self._matrices[(nb1,basis2)] * mat1 + else: # most general change-of-basis formula + for (b1, b2) in self._matrices: + if (b1, basis1) in fmodule1._basis_changes and \ + (basis2, b2) in fmodule2._basis_changes: + nb1, nb2 = b1, b2 + break + else: + raise ValueError("no start basis could be found for " + + "applying the change-of-bases formula") + change1 = fmodule1._basis_changes[(nb1, basis1)] + change2 = fmodule2._basis_changes[(basis2, nb2)] + mat1 = matrix( [[change1[[i,j]] for j in fmodule1.irange()] + for i in fmodule1.irange()] ) + mat2 = matrix( [[change2[[i,j]] for j in fmodule2.irange()] + for i in fmodule2.irange()] ) + self._matrices[(basis1, basis2)] = \ + mat2 * self._matrices[(nb1,nb2)] * mat1 + return self._matrices[(basis1, basis2)] + + def _common_bases(self, other): + r""" + Return a pair of bases in which ``self`` and ``other`` have a known + matrix representation. + + INPUT: + + - ``other`` -- another homomorphism in the same hom-set + + OUTPUT: + + - a pair of bases in which ``self`` and ``other`` have a known + matrix representation. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: N = FiniteRankFreeModule(ZZ, 2, name='N') + sage: e = M.basis('e') ; f = N.basis('f') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]]) + sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) + sage: phi._common_bases(psi) # matrices of phi and psi both defined on (e,f) + (Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1) on the Rank-2 free module N over the Integer Ring) + sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") + sage: psi = M.hom(N, [[1,1,0], [4,1,3]], bases=(ep,f)) + sage: phi._common_bases(psi) # matrix of psi w.r.t. (e,f) computed + (Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1) on the Rank-2 free module N over the Integer Ring) + sage: psi = M.hom(N, [[1,1,0], [4,1,3]], bases=(ep,f)) + sage: psi._common_bases(phi) # matrix of phi w.r.t. (ep,f) computed + (Basis (ep_0,ep_1,ep_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1) on the Rank-2 free module N over the Integer Ring) + + """ + resu = None + for bases in self._matrices: + try: + other.matrix(*bases) + resu = bases + break + except ValueError: + continue + if resu is None: + for bases in other._matrices: + try: + self.matrix(*bases) + resu = bases + break + except ValueError: + continue + return resu diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index f1808271195..02a7fcccd38 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -30,14 +30,14 @@ * :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` for type-(0, 1) tensors (linear forms) -* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphism` - for type-(1, 1) tensors (endomorphisms) +* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphismTensor` + for type-(1,1) tensors - * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphism` - for invertible endomorphisms + * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` + for type-(1,1) tensors representing invertible endomorphisms - * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityMap` - for the identity map on a free module + * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityTensor` + for the type-(1,1) tensor representing the free module identity map :class:`FreeModuleTensor` is a Sage *element* class, the corresponding *parent* class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. @@ -53,7 +53,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t - Endomorphism t on the Rank-3 free module M over the Integer Ring + Endomorphism tensor t on the Rank-3 free module M over the Integer Ring sage: t.parent() Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring @@ -74,7 +74,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. [-3 0 0] [ 0 0 0] [ 0 0 0] - sage: t.view(e) # expansion of t on the basis e_i*e^j of T^(1,1)(M) + sage: t.view(e) # displays the expansion of t on the basis e_i*e^j of T^(1,1)(M) t = -3 e_0*e^0 The commands ``t.set_comp(e)`` and ``t.comp(e)`` can be abridged by providing @@ -95,6 +95,16 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. [ 0 0 0] [ 0 0 0] +For tensors of rank 2, the matrix of components w.r.t. a given basis is +obtained via the function ``matrix``:: + + sage: matrix(t.comp(e)) + [-3 0 0] + [ 0 0 0] + [ 0 0 0] + sage: matrix(t.comp(e)).parent() + Full MatrixSpace of 3 by 3 dense matrices over Integer Ring + Tensor components can be modified (reset) at any time:: sage: t[0,0] = 0 @@ -112,7 +122,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. sage: t == M.tensor_module(1,1).zero() # the zero element of the module of all type-(1,1) tensors on M True -The components are managed by the +The components are managed by the class :class:`~sage.tensor.modules.comp.Components`:: sage: type(t.comp(e)) @@ -182,7 +192,7 @@ class FreeModuleTensor(ModuleElement): instance of :class:`FiniteRankFreeModule`) - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the contravariant rank and ``l`` the covariant rank - - ``name`` -- (default: None) name given to the tensor + - ``name`` -- (default: ``None``) name given to the tensor - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the tensor; if none is provided, the LaTeX symbol is set to ``name`` - ``sym`` -- (default: ``None``) a symmetry or a list of symmetries among @@ -203,7 +213,7 @@ class FreeModuleTensor(ModuleElement): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t - Endomorphism t on the Rank-3 free module M over the Integer Ring + Endomorphism tensor t on the Rank-3 free module M over the Integer Ring Tensors are *Element* objects whose parents are tensor free modules:: @@ -217,12 +227,25 @@ class FreeModuleTensor(ModuleElement): def __init__(self, fmodule, tensor_type, name=None, latex_name=None, sym=None, antisym=None): r""" - TEST:: + TESTS:: sage: from sage.tensor.modules.free_module_tensor import FreeModuleTensor sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') sage: t = FreeModuleTensor(M, (2,1), name='t', latex_name=r'\tau', sym=(0,1)) - sage: TestSuite(t).run() + sage: t[e,0,0,0] = -3 + sage: TestSuite(t).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because t is not an + instance of t.parent().category().element_class. Actually tensors + must be constructed via TensorFreeModule.element_class and + not by a direct call to FreeModuleTensor:: + + sage: t1 = M.tensor_module(2,1).element_class(M, (2,1), name='t', + ....: latex_name=r'\tau', + ....: sym=(0,1)) + sage: t1[e,0,0,0] = -3 + sage: TestSuite(t1).run() """ ModuleElement.__init__(self, fmodule.tensor_module(*tensor_type)) @@ -247,7 +270,7 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, if len(isym) > 1: for i in isym: if i<0 or i>self._tensor_rank-1: - raise IndexError("Invalid position: " + str(i) + + raise IndexError("invalid position: " + str(i) + " not in [0," + str(self._tensor_rank-1) + "]") self._sym.append(tuple(isym)) self._antisym = [] @@ -259,7 +282,7 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, if len(isym) > 1: for i in isym: if i<0 or i>self._tensor_rank-1: - raise IndexError("Invalid position: " + str(i) + + raise IndexError("invalid position: " + str(i) + " not in [0," + str(self._tensor_rank-1) + "]") self._antisym.append(tuple(isym)) @@ -438,6 +461,29 @@ def tensor_rank(self): """ return self._tensor_rank + def base_module(self): + r""" + Return the module on which ``self`` is defined. + + OUTPUT: + + - instance of :class:`FiniteRankFreeModule` representing the free + module on which the tensor is defined. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.an_element().base_module() + Rank-3 free module M over the Integer Ring + sage: t = M.tensor((2,1)) + sage: t.base_module() + Rank-3 free module M over the Integer Ring + sage: t.base_module() is M + True + + """ + return self._fmodule + def symmetries(self): r""" Print the list of symmetries and antisymmetries. @@ -518,7 +564,7 @@ def view(self, basis=None, format_spec=None): Display of a type-`(1,1)` tensor:: sage: t = v*w ; t # the type-(1,1) is formed as the tensor product of v by w - Endomorphism v*w on the Rank-2 free module M over the Rational Field + Endomorphism tensor v*w on the Rank-2 free module M over the Rational Field sage: t.view() v*w = -1/4 e_1*e^1 + 1/3 e_1*e^2 + 3/2 e_2*e^1 - 2 e_2*e^2 sage: latex(t.view()) # display in the notebook @@ -526,7 +572,7 @@ def view(self, basis=None, format_spec=None): Display in a basis which is not the default one:: - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[1,2],[3,4]] sage: f = e.new_basis(a, 'f') sage: v.view(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a @@ -577,14 +623,14 @@ def view(self, basis=None, format_spec=None): bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "*".join(bases_txt) basis_term_latex = r"\otimes ".join(bases_latex) - if coef == 1: + coef_txt = repr(coef) + if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) - elif coef == -1: + elif coef_txt == "-1": terms_txt.append("-" + basis_term_txt) terms_latex.append("-" + basis_term_latex) else: - coef_txt = repr(coef) coef_latex = latex(coef) if is_atomic(coef_txt): terms_txt.append(coef_txt + " " + basis_term_txt) @@ -724,7 +770,7 @@ def _new_comp(self, basis): output_formatter=fmodule._output_formatter, sym=self._sym, antisym=self._antisym) - def comp(self, basis=None, from_basis=None): + def components(self, basis=None, from_basis=None): r""" Return the components in a given basis. @@ -754,16 +800,21 @@ class :class:`~sage.tensor.modules.comp.Components` sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t[1,2] = -3 ; t[3,3] = 2 - sage: t.comp() + sage: t.components() 2-indices components w.r.t. Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring - sage: t.comp() is t.comp(e) # since e is M's default basis + sage: t.components() is t.components(e) # since e is M's default basis True - sage: t.comp()[:] + sage: t.components()[:] [ 0 -3 0] [ 0 0 0] [ 0 0 2] + A shortcut is ``t.comp()``:: + + sage: t.comp() is t.components() + True + A direct access to the components w.r.t. the module's default basis is provided by the square brackets applied to the tensor itself:: @@ -776,7 +827,7 @@ class :class:`~sage.tensor.modules.comp.Components` Components computed via a change-of-basis formula:: - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: f = e.new_basis(a, 'f') sage: t.comp(f) @@ -843,6 +894,8 @@ class :class:`~sage.tensor.modules.comp.Components` # end of case where the computation was necessary return self._components[basis] + comp = components + def set_comp(self, basis=None): r""" Return the components in a given basis for assignment. @@ -892,9 +945,9 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not The components w.r.t. basis e can be deduced from those w.r.t. basis f, once a relation between the two bases has been set:: - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] - sage: M.set_basis_change(e, f, a) + sage: M.set_change_of_basis(e, f, a) sage: t.view(e) t = -4 e_1*e^2 sage: t._components.keys() # random output (dictionary keys) @@ -975,7 +1028,7 @@ class :class:`~sage.tensor.modules.comp.Components`; if basis is None: basis = self._fmodule._def_basis if basis not in self._components: if basis not in self._fmodule._known_bases: - raise ValueError("The " + str(basis) + " has not been " + + raise ValueError("the " + str(basis) + " has not been " + "defined on the " + str(self._fmodule)) self._components[basis] = self._new_comp(basis) self._del_derived() # deletes the derived quantities @@ -1225,9 +1278,9 @@ def common_basis(self, other): Linking bases ``e`` and ``f`` changes the result:: - sage: a = M.automorphism() + sage: a = M.automorphism_tensor() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] - sage: M.set_basis_change(e, f, a) + sage: M.set_change_of_basis(e, f, a) sage: u.common_basis(v) Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring @@ -1940,7 +1993,7 @@ def trace(self, pos1=0, pos2=1): sage: e = M.basis('e') ; e Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a = M.tensor((1,1), name='a') ; a - Endomorphism a on the Rank-3 free module M over the Integer Ring + Endomorphism tensor a on the Rank-3 free module M over the Integer Ring sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: a.trace() 15 @@ -2133,7 +2186,7 @@ def contract(self, *args): Contraction of a tensor of type `(1,1)` with a tensor of type `(1,0)`:: - sage: a = M.endomorphism() # tensor of type (1,1) + sage: a = M.endomorphism_tensor() # tensor of type (1,1) sage: a[:] = [[-1,2,3],[4,-5,6],[7,8,9]] sage: s = a.contract(b) ; s Element of the Rank-3 free module M over the Integer Ring @@ -2220,7 +2273,7 @@ def contract(self, *args): contraction to take place, reflecting the fact that the contraction is basis-independent:: - sage: A = M.automorphism() + sage: A = M.automorphism_tensor() sage: A[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: h = e.new_basis(A, 'h') sage: b.comp(h)[:] # forces the computation of b's components w.r.t. basis h @@ -2246,7 +2299,7 @@ def contract(self, *args): sage: b = M([1,-1,2])*b ; b # a tensor of type (1,2) Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s = a.contract(1,2,b,1,0) ; s # the double contraction - Endomorphism on the Rank-3 free module M over the Integer Ring + Endomorphism tensor on the Rank-3 free module M over the Integer Ring sage: s[:] [ -36 30 15] [-252 210 105] @@ -2927,12 +2980,23 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): """ def __init__(self, fmodule, name=None, latex_name=None): r""" - TEST:: + TESTS:: sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') sage: v = FiniteRankFreeModuleElement(M, name='v') - sage: TestSuite(v).run() + sage: v[e,:] = (-2, 1, 3) + sage: TestSuite(v).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because v is not an + instance of v.parent().category().element_class. Actually module + elements must be constructed via FiniteRankFreeModule.element_class and + not by a direct call to FiniteRankFreeModuleElement:: + + sage: v1 = M.element_class(M, name='v') + sage: v1[e,:] = (-2, 1, 3) + sage: TestSuite(v1).run() """ FreeModuleTensor.__init__(self, fmodule, (1,0), name=name, diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py index dd96dc85322..73ff1d66034 100644 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ b/src/sage/tensor/modules/free_module_tensor_spec.py @@ -5,11 +5,15 @@ :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` are devoted to type-(1,1) tensors: -* :class:`FreeModuleEndomorphism` for endomorphisms (generic type-(1,1) tensors) +* :class:`FreeModuleEndomorphismTensor` for endomorphisms viewed as type-(1,1) + tensors - * :class:`FreeModuleAutomorphism` for invertible endomorphisms + * :class:`FreeModuleAutomorphismTensor` for invertible endomorphisms viewed + as type-(1,1) tensors + + * :class:`FreeModuleIdentityTensor` for the identity map viewed as a + type-(1,1) tensor - * :class:`FreeModuleIdentityMap` for the identity map on a free module AUTHORS: @@ -17,8 +21,10 @@ .. TODO:: - Implement these specific tensors as elements of a parent class for free - module endomorphisms, with coercion to the module of type-(1,1) tensors. + Suppress :class:`FreeModuleEndomorphismTensor` ? (since the + coercion of type-(1,1) tensors to free module endomorphisms is implemented + now) This would leave only :class:`FreeModuleAutomorphismTensor` and + :class:`FreeModuleIdentityTensor`. """ #****************************************************************************** @@ -33,7 +39,7 @@ from sage.tensor.modules.free_module_tensor import FreeModuleTensor -class FreeModuleEndomorphism(FreeModuleTensor): +class FreeModuleEndomorphismTensor(FreeModuleTensor): r""" Endomorphism (considered as a type-`(1,1)` tensor) on a free module. @@ -47,14 +53,11 @@ class FreeModuleEndomorphism(FreeModuleTensor): EXAMPLES: - Endomorphism on a rank-3 module:: + Endomorphism tensor on a rank-3 module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: t = M.endomorphism('T') ; t - Endomorphism T on the Rank-3 free module M over the Integer Ring - - An endomorphism is type-`(1,1)` tensor:: - + sage: t = M.endomorphism_tensor('T') ; t + Endomorphism tensor T on the Rank-3 free module M over the Integer Ring sage: t.parent() Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring @@ -63,11 +66,12 @@ class FreeModuleEndomorphism(FreeModuleTensor): sage: t.tensor_rank() 2 - Consequently, an endomorphism can also be created by the module method - :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor`:: + The method + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor` + with the argument ``(1,1)`` can be used as well to create such a tensor:: sage: t = M.tensor((1,1), name='T') ; t - Endomorphism T on the Rank-3 free module M over the Integer Ring + Endomorphism tensor T on the Rank-3 free module M over the Integer Ring Components of the endomorphism with respect to a given basis:: @@ -82,6 +86,15 @@ class FreeModuleEndomorphism(FreeModuleTensor): T = e_0*e^0 + 2 e_0*e^1 + 3 e_0*e^2 + 4 e_1*e^0 + 5 e_1*e^1 + 6 e_1*e^2 + 7 e_2*e^0 + 8 e_2*e^1 + 9 e_2*e^2 + The matrix of components w.r.t. to a given basis:: + + sage: m = matrix(t.components(e)) ; m + [1 2 3] + [4 5 6] + [7 8 9] + sage: m.parent() + Full MatrixSpace of 3 by 3 dense matrices over Integer Ring + The endomorphism acting on a module element:: sage: v = M([1,2,3], basis=e, name='v') ; v @@ -97,12 +110,17 @@ class FreeModuleEndomorphism(FreeModuleTensor): """ def __init__(self, fmodule, name=None, latex_name=None): r""" - TEST:: + TESTS:: - sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleEndomorphism + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleEndomorphismTensor sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: E = FreeModuleEndomorphism(M, name='a') - sage: TestSuite(E).run() + sage: e = M.basis('e') + sage: E = FreeModuleEndomorphismTensor(M, name='a') + sage: E[e,0,1] = -3 + sage: TestSuite(E).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because E is not an + instance of E.parent().category().element_class. """ FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, @@ -115,13 +133,13 @@ def _repr_(self): EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M.endomorphism() - Endomorphism on the Rank-3 free module M over the Integer Ring - sage: M.endomorphism(name='a') - Endomorphism a on the Rank-3 free module M over the Integer Ring + sage: M.endomorphism_tensor() + Endomorphism tensor on the Rank-3 free module M over the Integer Ring + sage: M.endomorphism_tensor(name='a') + Endomorphism tensor a on the Rank-3 free module M over the Integer Ring """ - description = "Endomorphism " + description = "Endomorphism tensor " if self._name is not None: description += self._name + " " description += "on the " + str(self._fmodule) @@ -134,9 +152,9 @@ def _new_instance(self): EXAMPLE:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.endomorphism(name='a') + sage: a = M.endomorphism_tensor(name='a') sage: a._new_instance() - Endomorphism on the Rank-3 free module M over the Integer Ring + Endomorphism tensor on the Rank-3 free module M over the Integer Ring """ return self.__class__(self._fmodule) @@ -151,7 +169,7 @@ def __call__(self, *arg): Call with a single argument --> return a module element:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.endomorphism(name='a') + sage: a = M.endomorphism_tensor(name='a') sage: e = M.basis('e') sage: a[0,1], a[1,1], a[2,1] = 2, 4, -5 sage: v = M([2,1,4], name='v') @@ -179,7 +197,7 @@ def __call__(self, *arg): """ from free_module_tensor import FiniteRankFreeModuleElement if len(arg) > 1: - # the endomorphism acting as a type (1,1) tensor on a pair + # the endomorphism acting as a type-(1,1) tensor on a pair # (linear form, module element), returning a scalar: return FreeModuleTensor.__call__(self, *arg) # the endomorphism acting as such, on a module element, returning a @@ -210,7 +228,7 @@ def __call__(self, *arg): #****************************************************************************** -class FreeModuleAutomorphism(FreeModuleEndomorphism): +class FreeModuleAutomorphismTensor(FreeModuleEndomorphismTensor): r""" Automorphism (considered as a type-`(1,1)` tensor) on a free module. @@ -224,11 +242,11 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): EXAMPLES: - Automorphism on a rank-2 free module (vector space) on `\QQ`:: + Automorphism tensor on a rank-2 free module (vector space) on `\QQ`:: sage: M = FiniteRankFreeModule(QQ, 2, name='M') - sage: a = M.automorphism('A') ; a - Automorphism A on the Rank-2 free module M over the Rational Field + sage: a = M.automorphism_tensor('A') ; a + Automorphism tensor A on the Rank-2 free module M over the Rational Field Automorphisms are tensors of type `(1,1)`:: @@ -254,7 +272,7 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): The inverse automorphism is obtained via the method :meth:`inverse`:: sage: b = a.inverse() ; b - Automorphism A^(-1) on the Rank-2 free module M over the Rational Field + Automorphism tensor A^(-1) on the Rank-2 free module M over the Rational Field sage: b.view(basis=e) A^(-1) = 3/5 e_0*e^0 - 2/5 e_0*e^1 + 1/5 e_1*e^0 + 1/5 e_1*e^1 sage: b[:] @@ -267,15 +285,20 @@ class FreeModuleAutomorphism(FreeModuleEndomorphism): """ def __init__(self, fmodule, name=None, latex_name=None): r""" - TEST:: + TESTS:: - sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleAutomorphism + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleAutomorphismTensor sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: a = FreeModuleAutomorphism(M, name='a') - sage: TestSuite(a).run() + sage: e = M.basis('e') + sage: a = FreeModuleAutomorphismTensor(M, name='a') + sage: a[e,:] = [[1,0,1],[0,2,0],[0,0,-3]] + sage: TestSuite(a).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because a is not an + instance of a.parent().category().element_class. """ - FreeModuleEndomorphism.__init__(self, fmodule, name=name, + FreeModuleEndomorphismTensor.__init__(self, fmodule, name=name, latex_name=latex_name) self._inverse = None # inverse automorphism not set yet @@ -286,13 +309,13 @@ def _repr_(self): EXAMPLES:: sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: M.automorphism() - Automorphism on the Rank-3 free module M over the Rational Field - sage: M.automorphism(name='a') - Automorphism a on the Rank-3 free module M over the Rational Field + sage: M.automorphism_tensor() + Automorphism tensor on the Rank-3 free module M over the Rational Field + sage: M.automorphism_tensor(name='a') + Automorphism tensor a on the Rank-3 free module M over the Rational Field """ - description = "Automorphism " + description = "Automorphism tensor " if self._name is not None: description += self._name + " " description += "on the " + str(self._fmodule) @@ -305,18 +328,18 @@ def _del_derived(self): EXAMPLE:: sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: a = M.automorphism(name='a') + sage: a = M.automorphism_tensor(name='a') sage: e = M.basis('e') sage: a[e,:] = [[1,0,-1], [0,3,0], [0,0,2]] sage: b = a.inverse() sage: a._inverse - Automorphism a^(-1) on the Rank-3 free module M over the Rational Field + Automorphism tensor a^(-1) on the Rank-3 free module M over the Rational Field sage: a._del_derived() sage: a._inverse # has been reset to None """ # First delete the derived quantities pertaining to the mother class: - FreeModuleEndomorphism._del_derived(self) + FreeModuleEndomorphismTensor._del_derived(self) # Then deletes the inverse automorphism: self._inverse = None @@ -326,7 +349,7 @@ def inverse(self): OUTPUT: - - instance of :class:`FreeModuleAutomorphism` representing the + - instance of :class:`FreeModuleAutomorphismTensor` representing the automorphism that is the inverse of ``self``. EXAMPLES: @@ -334,11 +357,11 @@ def inverse(self): Inverse of an automorphism on a rank-3 free module:: sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: a = M.automorphism('A') + sage: a = M.automorphism_tensor('A') sage: e = M.basis('e') sage: a[:] = [[1,0,-1], [0,3,0], [0,0,2]] sage: b = a.inverse() ; b - Automorphism A^(-1) on the Rank-3 free module M over the Rational Field + Automorphism tensor A^(-1) on the Rank-3 free module M over the Rational Field sage: b[:] [ 1 0 1/2] [ 0 1/3 0] @@ -392,7 +415,7 @@ def inverse(self): #****************************************************************************** -class FreeModuleIdentityMap(FreeModuleAutomorphism): +class FreeModuleIdentityTensor(FreeModuleAutomorphismTensor): r""" Identity map (considered as a type-(1,1) tensor) on a free module. @@ -400,28 +423,28 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: 'Id') name given to the identity map. - - ``latex_name`` -- (default: None) LaTeX symbol to denote the identity - map; if none is provided, the LaTeX symbol is set to ``name`` + - ``name`` -- (default: 'Id') name given to the identity tensor. + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the identity + tensor; if none is provided, the LaTeX symbol is set to ``name`` EXAMPLES: - Identity map on a rank-3 free module:: + Identity tensor on a rank-3 free module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: a = M.identity_map() ; a - Identity map on the Rank-3 free module M over the Integer Ring + sage: a = M.identity_tensor() ; a + Identity tensor on the Rank-3 free module M over the Integer Ring The LaTeX symbol is set by default to `\mathrm{Id}`, but can be changed:: sage: latex(a) \mathrm{Id} - sage: a = M.identity_map(latex_name=r'\mathrm{1}') + sage: a = M.identity_tensor(latex_name=r'\mathrm{1}') sage: latex(a) \mathrm{1} - The identity map is a tensor of type `(1,1)` on the free module:: + The identity is a tensor of type `(1,1)` on the free module:: sage: a.parent() Free module of type-(1,1) tensors on the @@ -458,7 +481,7 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): ... TypeError: the components of the identity map cannot be changed - The identity map acting on a module element:: + The identity tensor acting on a module element:: sage: v = M([2,-3,1], basis=e, name='v') sage: v.view() @@ -468,7 +491,7 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): sage: u is v True - The identity map acting as a type-`(1,1)` tensor on a pair (linear form, + The identity tensor acting as a type-`(1,1)` tensor on a pair (linear form, module element):: sage: w = M.tensor((0,1), name='w') ; w @@ -479,7 +502,7 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): sage: s == w(v) True - The identity map is its own inverse:: + The identity tensor is its own inverse:: sage: a.inverse() == a True @@ -489,19 +512,22 @@ class FreeModuleIdentityMap(FreeModuleAutomorphism): """ def __init__(self, fmodule, name='Id', latex_name=None): r""" - TESTs:: + TESTS:: - sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityMap + sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityTensor sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: Id = FreeModuleIdentityMap(M) - sage: TestSuite(Id).run() + sage: Id = FreeModuleIdentityTensor(M) + sage: TestSuite(Id).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because Id is not an + instance of Id.parent().category().element_class. """ if latex_name is None and name == 'Id': latex_name = r'\mathrm{Id}' - FreeModuleAutomorphism.__init__(self, fmodule, name=name, - latex_name=latex_name) + FreeModuleAutomorphismTensor.__init__(self, fmodule, name=name, + latex_name=latex_name) self._inverse = self # the identity is its own inverse self.comp() # Initializing the components in the module's default basis @@ -513,11 +539,11 @@ def _repr_(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: M.identity_map() - Identity map on the Rank-3 free module M over the Integer Ring + sage: M.identity_tensor() + Identity tensor on the Rank-3 free module M over the Integer Ring """ - description = "Identity map " + description = "Identity tensor " if self._name != 'Id': description += self._name + " " description += "on the " + str(self._fmodule) @@ -531,12 +557,12 @@ def _del_derived(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: id = M.identity_map() + sage: id = M.identity_tensor() sage: id._del_derived() """ - # FreeModuleAutomorphism._del_derived is bypassed: - FreeModuleEndomorphism._del_derived(self) + # FreeModuleAutomorphismTensor._del_derived is bypassed: + FreeModuleEndomorphismTensor._del_derived(self) def _new_comp(self, basis): r""" @@ -546,7 +572,7 @@ def _new_comp(self, basis): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: id = M.identity_map() + sage: id = M.identity_tensor() sage: id._new_comp(e) Kronecker delta of size 3x3 sage: type(id._new_comp(e)) @@ -558,7 +584,7 @@ def _new_comp(self, basis): return KroneckerDelta(fmodule._ring, basis, start_index=fmodule._sindex, output_formatter=fmodule._output_formatter) - def comp(self, basis=None, from_basis=None): + def components(self, basis=None, from_basis=None): r""" Return the components in a given basis as a Kronecker delta. @@ -581,19 +607,24 @@ class :class:`~sage.tensor.modules.comp.KroneckerDelta` sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: a = M.identity_map() - sage: a.comp(basis=e) + sage: a = M.identity_tensor() + sage: a.components(basis=e) Kronecker delta of size 3x3 For the module's default basis, the argument ``basis`` can be omitted:: - sage: a.comp() is a.comp(basis=e) + sage: a.components() is a.components(basis=e) True - sage: a.comp()[:] + sage: a.components()[:] [1 0 0] [0 1 0] [0 0 1] + A shortcut is ``a.comp()``:: + + sage: a.comp() is a.components() + True + """ if basis is None: basis = self._fmodule._def_basis @@ -601,6 +632,8 @@ class :class:`~sage.tensor.modules.comp.KroneckerDelta` self._components[basis] = self._new_comp(basis) return self._components[basis] + comp = components + def set_comp(self, basis=None): r""" Redefinition of the generic tensor method @@ -610,7 +643,7 @@ def set_comp(self, basis=None): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: a = M.identity_map() + sage: a = M.identity_tensor() sage: a.set_comp(e) Traceback (most recent call last): ... @@ -628,7 +661,7 @@ def add_comp(self, basis=None): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: a = M.identity_map() + sage: a = M.identity_tensor() sage: a.add_comp(e) Traceback (most recent call last): ... @@ -639,7 +672,7 @@ def add_comp(self, basis=None): def __call__(self, *arg): r""" - Redefinition of :meth:`FreeModuleEndomorphism.__call__`. + Redefinition of :meth:`FreeModuleEndomorphismTensor.__call__`. EXAMPLES: @@ -647,7 +680,7 @@ def __call__(self, *arg): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: id = M.identity_map() + sage: id = M.identity_tensor() sage: v = M([-1,4,3]) sage: s = id.__call__(v) ; s Element of the Rank-3 free module M over the Integer Ring @@ -681,7 +714,7 @@ def __call__(self, *arg): return vector #!# should it be return vector.copy() instead ? elif len(arg) == 2: - # the identity map acting as a type (1,1) tensor on a pair + # the identity map acting as a type-(1,1) tensor on a pair # (1-form, vector), returning a scalar: linform = arg[0] if not isinstance(linform, FreeModuleLinForm): diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index 08b672a4646..8a776864b46 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -54,7 +54,9 @@ #****************************************************************************** from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule -from sage.tensor.modules.free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement +from sage.tensor.modules.free_module_tensor import (FreeModuleTensor, + FiniteRankFreeModuleElement) +from sage.tensor.modules.free_module_morphism import FiniteRankFreeModuleMorphism class TensorFreeModule(FiniteRankFreeModule): r""" @@ -185,6 +187,64 @@ class TensorFreeModule(FiniteRankFreeModule): sage: T is M.tensor_module(1,2) True + There is a canonical identification between tensors of type (1,1) and + endomorphisms of module `M`. Accordingly, coercion maps have been + implemented between `T^{(1,1)}(M)` and `\mathrm{End}(M)` (the module of + all endomorphisms of `M`, see + :class:`~sage.tensor.modules.free_module_homset.FreeModuleHomset`):: + + sage: T11 = M.tensor_module(1,1) ; T11 + Free module of type-(1,1) tensors on the Rank-3 free module M over the + Integer Ring + sage: End(M) + Set of Morphisms from Rank-3 free module M over the Integer Ring to + Rank-3 free module M over the Integer Ring in Category of modules + over Integer Ring + sage: T11.has_coerce_map_from(End(M)) + True + sage: End(M).has_coerce_map_from(T11) + True + + The coercion map `\mathrm{End}(M)\rightarrow T^{(1,1)}(M)` in action:: + + sage: phi = End(M).an_element() ; phi + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: phi.matrix(e) + [1 1 1] + [1 1 1] + [1 1 1] + sage: tphi = T11(phi) ; tphi # image of phi by the coercion map + Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring + sage: tphi[:] + [1 1 1] + [1 1 1] + [1 1 1] + sage: t = M.tensor((1,1)) + sage: t[0,0], t[1,1], t[2,2] = -1,-2,-3 + sage: t[:] + [-1 0 0] + [ 0 -2 0] + [ 0 0 -3] + sage: s = t + phi ; s # phi is coerced to a type-(1,1) tensor prior to the addition + Endomorphism tensor on the Rank-3 free module M over the Integer Ring + sage: s[:] + [ 0 1 1] + [ 1 -1 1] + [ 1 1 -2] + + The reverse coercion map in action:: + + sage: phi1 = End(M)(tphi) ; phi1 + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: phi1 == phi + True + sage: s = phi + t ; s # t is coerced to an endomorphism prior to the addition + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: s.matrix(e) + [ 0 1 1] + [ 1 -1 1] + [ 1 1 -2] + """ Element = FreeModuleTensor @@ -254,11 +314,26 @@ def _element_constructor_(self, comp=[], basis=None, name=None, """ if comp == 0: return self._zero_element - resu = self.element_class(self._fmodule, self._tensor_type, name=name, - latex_name=latex_name, sym=sym, - antisym=antisym) - if comp: - resu.set_comp(basis)[:] = comp + if isinstance(comp, FiniteRankFreeModuleMorphism): + # coercion of an endomorphism to a type-(1,1) tensor: + endo = comp # for readability + if self._tensor_type == (1,1) and endo.is_endomorphism() and \ + self._fmodule is endo.domain(): + resu = self.element_class(self._fmodule, (1,1), + name=endo._name, + latex_name=endo._latex_name) + for basis, mat in endo._matrices.iteritems(): + resu.add_comp(basis[0])[:] = mat + else: + raise TypeError("cannot coerce the " + str(endo) + + " to an element of " + str(self)) + else: + # Standard construction: + resu = self.element_class(self._fmodule, self._tensor_type, + name=name, latex_name=latex_name, + sym=sym, antisym=antisym) + if comp: + resu.set_comp(basis)[:] = comp return resu def _an_element_(self): @@ -287,6 +362,46 @@ def _an_element_(self): resu.set_comp()[ind] = self._fmodule._ring.an_element() return resu + def _coerce_map_from_(self, other): + r""" + Determine whether coercion to ``self`` exists from other parent. + + EXAMPLES: + + Sets of module endomorphisms coerces to type-(1,1) tensor modules:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: M.tensor_module(1,1)._coerce_map_from_(End(M)) + True + + but not to tensor modules of other types:: + + sage: M.tensor_module(0,1)._coerce_map_from_(End(M)) + False + + and not to type-(1,1) tensor modules defined on another free module:: + + sage: N = FiniteRankFreeModule(ZZ, 3, name='N') + sage: f = N.basis('f') + sage: M.tensor_module(1,1)._coerce_map_from_(End(N)) + False + + There is no coercion if the module morphisms are not endomorphisms:: + + sage: M.tensor_module(1,1)._coerce_map_from_(Hom(M,N)) + False + + """ + from free_module_homset import FreeModuleHomset + if isinstance(other, FreeModuleHomset): + # Coercion of an endomorphism to a type-(1,1) tensor: + if self._tensor_type == (1,1): + if other.is_endomorphism_set() and \ + self._fmodule is other.domain(): + return True + return False + #### End of methods required for any Parent def _repr_(self): @@ -333,3 +448,21 @@ def base_module(self): """ return self._fmodule + def tensor_type(self): + r""" + Return the tensor type of ``self``. + + OUTPUT: + + - pair `(k,l)` such that ``self`` is the module tensor product + `T^{(k,l)}(M)` + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 3) + sage: T = M.tensor_module(1,2) + sage: T.tensor_type() + (1, 2) + + """ + return self._tensor_type diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py index ff9663a5609..325f53dcc20 100644 --- a/src/sage/tensor/modules/tensor_with_indices.py +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -102,7 +102,7 @@ class TensorWithIndices(SageObject): Another example of an operation indicated by indices is a contraction:: sage: s = t['^ki_kj'] ; s # contraction on the repeated index k - Endomorphism on the Rank-3 free module M over the Rational Field + Endomorphism tensor on the Rank-3 free module M over the Rational Field sage: s == t.trace(0,2) True @@ -115,7 +115,7 @@ class TensorWithIndices(SageObject): the ``*`` operator:: sage: s = a['^ik'] * b['_kj'] ; s - Endomorphism on the Rank-3 free module M over the Rational Field + Endomorphism tensor on the Rank-3 free module M over the Rational Field sage: s == a.contract(1, b, 0) True sage: s = t['^.k_..'] * b['_.k'] ; s From 1e5305b97a409a652a8e9b6f0dbbdadc46e7d716 Mon Sep 17 00:00:00 2001 From: Thierry Monteil Date: Mon, 8 Dec 2014 12:45:13 +0100 Subject: [PATCH 052/129] #17306 removal of sagenb's mathjax repository can be removed once sagenb does not ship mathjax anymore. --- build/pkgs/sagenb/spkg-install | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/build/pkgs/sagenb/spkg-install b/build/pkgs/sagenb/spkg-install index 257f82857ea..a377d9a1482 100755 --- a/build/pkgs/sagenb/spkg-install +++ b/build/pkgs/sagenb/spkg-install @@ -49,11 +49,7 @@ easy_install -H None "src/$PKG" || die "Error installing sagenb !" # let sagenb use mathjax spkg cd $SAGE_LOCAL/lib/python/site-packages/sagenb-*.egg/sagenb/data -if [ -d mathjax ] ; then - rm -rf mathjax -fi -if [ -h mathjax ] ; then - rm mathjax -fi +# the following line can be removed once sagenb does not ship mathjax anymore. +rm -rf mathjax ln -s ../../../../../../share/mathjax/ From 7c574c7f4161443c317b3805ba2325f1bfff4623 Mon Sep 17 00:00:00 2001 From: Thierry Monteil Date: Mon, 8 Dec 2014 13:03:57 +0100 Subject: [PATCH 053/129] #17306 use the version number of sagenb and python packages in egg path --- build/pkgs/sagenb/spkg-install | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/build/pkgs/sagenb/spkg-install b/build/pkgs/sagenb/spkg-install index a377d9a1482..84127395b86 100755 --- a/build/pkgs/sagenb/spkg-install +++ b/build/pkgs/sagenb/spkg-install @@ -48,7 +48,9 @@ PKG=$(ls -1 src | GREP_OPTIONS= grep sagenb-) easy_install -H None "src/$PKG" || die "Error installing sagenb !" # let sagenb use mathjax spkg -cd $SAGE_LOCAL/lib/python/site-packages/sagenb-*.egg/sagenb/data +SAGENB_VERSION=$(cat ${SAGE_ROOT}/build/pkgs/sagenb/package-version.txt) +PYTHON_MAJOR_VERSION=$(cat ${SAGE_ROOT}/build/pkgs/python/package-version.txt | sed 's/\.[^.]*$//g') +cd ${SAGE_LOCAL}/lib/python/site-packages/sagenb-${SAGENB_VERSION}-py${PYTHON_MAJOR_VERSION}.egg/sagenb/data # the following line can be removed once sagenb does not ship mathjax anymore. rm -rf mathjax ln -s ../../../../../../share/mathjax/ From 968468796989dd7415c235a6895dadcab6d9e108 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Fri, 23 Jan 2015 16:03:07 +0100 Subject: [PATCH 054/129] Add parents for free module automorphisms and alternating forms; improve documentation. - Introduce the parent class ExtPowerFreeModule for alternating forms of a given degree on a free module. All alternating forms are now described by the element class FreeModuleAltForm. In particular, the class FreeModuleLinForm has been suppressed. - Introduce the parent class FreeModuleLinearGroup for the group GL(M) of a free module M. The corresponding element class is FreeModuleAutomorphism, which has been entirely rewritten (as both a group element class and a class of type-(1,1) tensors). - Add the following coercions: alternating forms of degree p ---> tensors of type (0,p) tensors of type (0,1) ---> alternating forms of degree 1 (linear form) automorphisms ---> tensors of type (1,1) automorphisms ---> endomorphisms and the following conversions: (invertible) endomorphisms ---> automorphisms (invertible) type-(1,1) tensors ---> automorphisms - Suppress the classes FreeModuleAutomorphismTensor and FreeModuleIdentityTensor, which were no longer needed thanks to the above coercions. - Suppress the class FreeModuleEndomorphismTensor, which was no longer needed thanks to the coercions endomorphisms <---> tensors of type-(1,1). - Rename method view() to display(); view() is deprecated - Transitivity in FiniteRankFreeModule.change_of_basis() - Add argument from_family in FiniteRankFreeModule.basis() - Systematic use of "...{}...".format(...) instead of "..." + str(...) + "..." - Reorganize documentation files in src/doc/en/reference/tensor_free_modules/*.rst - Add comparisons - between FiniteRankFreeModule and FreeModule, - between FiniteRankFreeModule and VectorSpace, - between FiniteRankFreeModule and CombinatorialFreeModule in the documentation of FiniteRankFreeModule - All test suites are passed --- .../tensor_free_modules/alt_forms.rst | 9 + .../reference/tensor_free_modules/index.rst | 25 +- .../tensor_free_modules/morphisms.rst | 13 + .../reference/tensor_free_modules/tensors.rst | 11 + src/sage/tensor/modules/comp.py | 25 +- .../tensor/modules/ext_pow_free_module.py | 487 ++++++ .../tensor/modules/finite_rank_free_module.py | 1441 ++++++++++++----- src/sage/tensor/modules/format_utilities.py | 8 +- .../tensor/modules/free_module_alt_form.py | 528 +++--- .../modules/free_module_automorphism.py | 1271 +++++++++++++++ src/sage/tensor/modules/free_module_basis.py | 68 +- src/sage/tensor/modules/free_module_homset.py | 124 +- .../modules/free_module_linear_group.py | 567 +++++++ .../tensor/modules/free_module_morphism.py | 267 +-- src/sage/tensor/modules/free_module_tensor.py | 473 ++++-- .../tensor/modules/free_module_tensor_spec.py | 728 --------- src/sage/tensor/modules/tensor_free_module.py | 300 +++- .../tensor/modules/tensor_with_indices.py | 34 +- 18 files changed, 4499 insertions(+), 1880 deletions(-) create mode 100644 src/doc/en/reference/tensor_free_modules/alt_forms.rst create mode 100644 src/doc/en/reference/tensor_free_modules/morphisms.rst create mode 100644 src/doc/en/reference/tensor_free_modules/tensors.rst create mode 100644 src/sage/tensor/modules/ext_pow_free_module.py create mode 100644 src/sage/tensor/modules/free_module_automorphism.py create mode 100644 src/sage/tensor/modules/free_module_linear_group.py delete mode 100644 src/sage/tensor/modules/free_module_tensor_spec.py diff --git a/src/doc/en/reference/tensor_free_modules/alt_forms.rst b/src/doc/en/reference/tensor_free_modules/alt_forms.rst new file mode 100644 index 00000000000..bbb0a9d966b --- /dev/null +++ b/src/doc/en/reference/tensor_free_modules/alt_forms.rst @@ -0,0 +1,9 @@ +Alternating forms +================= + +.. toctree:: + :maxdepth: 2 + + sage/tensor/modules/ext_pow_free_module + + sage/tensor/modules/free_module_alt_form diff --git a/src/doc/en/reference/tensor_free_modules/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst index 69cb7a00082..c5bdcf06abe 100644 --- a/src/doc/en/reference/tensor_free_modules/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -4,36 +4,21 @@ Tensors on free modules of finite rank This work is part of the `SageManifolds project `_ but it does not depend upon other SageManifolds classes. In other words, it constitutes -a self-consistent subset that can be used independently of SageManifolds. - - -This document is licensed under a `Creative Commons Attribution-Share Alike -3.0 License`__. - -__ http://creativecommons.org/licenses/by-sa/3.0/ +a self-consistent subset that can be used independently of SageManifolds. .. toctree:: :maxdepth: 2 sage/tensor/modules/finite_rank_free_module - + sage/tensor/modules/free_module_basis - - sage/tensor/modules/tensor_free_module - - sage/tensor/modules/free_module_tensor - sage/tensor/modules/free_module_tensor_spec - - sage/tensor/modules/free_module_alt_form + tensors - sage/tensor/modules/free_module_homset + alt_forms - sage/tensor/modules/free_module_morphism + morphisms sage/tensor/modules/comp - sage/tensor/modules/tensor_with_indices - .. include:: ../footer.txt - diff --git a/src/doc/en/reference/tensor_free_modules/morphisms.rst b/src/doc/en/reference/tensor_free_modules/morphisms.rst new file mode 100644 index 00000000000..fa661c8978d --- /dev/null +++ b/src/doc/en/reference/tensor_free_modules/morphisms.rst @@ -0,0 +1,13 @@ +Morphisms +========= + +.. toctree:: + :maxdepth: 2 + + sage/tensor/modules/free_module_homset + + sage/tensor/modules/free_module_morphism + + sage/tensor/modules/free_module_linear_group + + sage/tensor/modules/free_module_automorphism diff --git a/src/doc/en/reference/tensor_free_modules/tensors.rst b/src/doc/en/reference/tensor_free_modules/tensors.rst new file mode 100644 index 00000000000..be87fc68ad1 --- /dev/null +++ b/src/doc/en/reference/tensor_free_modules/tensors.rst @@ -0,0 +1,11 @@ +Tensors +======= + +.. toctree:: + :maxdepth: 2 + + sage/tensor/modules/tensor_free_module + + sage/tensor/modules/free_module_tensor + + sage/tensor/modules/tensor_with_indices diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 27e2634d3d2..8ed735ef9d0 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -2,9 +2,9 @@ Components as indexed sets of ring elements The class :class:`Components` is a technical class to take in charge the -storage and manipulation of **indexed elements of a commutative ring** that represent the -components of some "mathematical entity" with respect to some "frame". -Examples of *entity/frame* are *vector/vector-space basis* or +storage and manipulation of **indexed elements of a commutative ring** that +represent the components of some "mathematical entity" with respect to some +"frame". Examples of *entity/frame* are *vector/vector-space basis* or *vector field/vector frame on some manifold*. More generally, the components can be those of a tensor on a free module or those of a tensor field on a manifold. They can also be non-tensorial quantities, like connection @@ -36,7 +36,7 @@ AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version - Joris Vankerschaver (2010): for the idea of storing only the non-zero components as dictionaries, whose keys are the component indices (see class :class:`~sage.tensor.differential_form_element.DifferentialForm`) @@ -237,8 +237,8 @@ class :class:`~sage.tensor.differential_form_element.DifferentialForm`) """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -411,7 +411,7 @@ class Components(SageObject): {(0,): -1, (2,): 3} - ARITHMETIC EXAMPLES: + .. RUBRIC:: ARITHMETIC EXAMPLES: Unary plus operator:: @@ -1439,9 +1439,17 @@ def __mul__(self, other): antisym) elif self._nid == 1 and other._nid == 1: if self is other: # == would be dangerous here - # the result is symmetric: + # The result is symmetric: result = CompFullySym(self._ring, self._frame, 2, self._sindex, self._output_formatter) + # The loop below on self._comp.iteritems() and + # other._comp.iteritems() cannot be used in the present case + # (it would not deal correctly with redundant indices) + # So we use a loop specific to the current case and return the + # result: + for ind in result.non_redundant_index_generator(): + result[[ind]] = self[[ind[0]]] * self[[ind[1]]] + return result else: result = Components(self._ring, self._frame, 2, self._sindex, self._output_formatter) @@ -4641,4 +4649,3 @@ def __setitem__(self, args, value): """ raise TypeError("the components of a Kronecker delta cannot be changed") - diff --git a/src/sage/tensor/modules/ext_pow_free_module.py b/src/sage/tensor/modules/ext_pow_free_module.py new file mode 100644 index 00000000000..bac021cd0d9 --- /dev/null +++ b/src/sage/tensor/modules/ext_pow_free_module.py @@ -0,0 +1,487 @@ +r""" +Exterior powers of dual free modules + +Given a free module `M` of finite rank over a commutative ring `R` +and a positive integer `p`, the *p-th exterior power* of the dual of `M` is the +set `\Lambda^p(M^*)` of all alternating forms of degree `p` on `M`, i.e. of +all multilinear maps + +.. MATH:: + + \underbrace{M\times\cdots\times M}_{p\ \; \mbox{times}} + \longrightarrow R + +that vanish whenever any of two of their arguments are equal. +Note that `\Lambda^1(M^*) = M^*` (the dual of `M`). + +`\Lambda^p(M^*)` is a free module of rank `\left({n\atop p}\right)` over `R`, +where `n` is the rank of `M`. +Accordingly, exterior powers of free modules are implemented by a class, +:class:`ExtPowerFreeModule`, which inherits from the class +:class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule`. + +AUTHORS: + +- Eric Gourgoulhon (2015): initial version + +REFERENCES: + +- K. Conrad: *Exterior powers*, + `http://www.math.uconn.edu/~kconrad/blurbs/ `_ +- Chap. 19 of S. Lang: *Algebra*, 3rd ed., Springer (New York) (2002) + +""" +#****************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.free_module_tensor import FreeModuleTensor +from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm + +class ExtPowerFreeModule(FiniteRankFreeModule): + r""" + Class for the exterior powers of the dual of a free module of finite rank + over a commutative ring. + + Given a free module `M` of finite rank over a commutative ring `R` + and a positive integer `p`, the *p-th exterior power* of the dual of `M` is + the set `\Lambda^p(M^*)` of all alternating forms of degree `p` on `M`, + i.e. of all multilinear maps + + .. MATH:: + + \underbrace{M\times\cdots\times M}_{p\ \; \mbox{times}} + \longrightarrow R + + that vanish whenever any of two of their arguments are equal. + Note that `\Lambda^1(M^*) = M^*` (the dual of `M`). + + `\Lambda^p(M^*)` is a free module of rank `\left({n\atop p}\right)` over + `R`, where `n` is the rank of `M`. + Accordingly, the class :class:`ExtPowerFreeModule` inherits from the class + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule`. + + This is a Sage *parent* class, whose *element* class is + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + - ``degree`` -- positive integer; the degree `p` of the alternating forms + - ``name`` -- (default: ``None``) string; name given to `\Lambda^p(M^*)` + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote + `\Lambda^p(M^*)` + + EXAMPLES: + + 2nd exterior power of the dual of a free `\ZZ`-module of rank 3:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerFreeModule + sage: A = ExtPowerFreeModule(M, 2) ; A + 2nd exterior power of the dual of the Rank-3 free module M over the + Integer Ring + + Instead of importing ExtPowerFreeModule in the global name space, it is + recommended to use the module's method + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.dual_exterior_power`:: + + sage: A = M.dual_exterior_power(2) ; A + 2nd exterior power of the dual of the Rank-3 free module M over the + Integer Ring + sage: latex(A) + \Lambda^{2}\left(M^*\right) + + ``A`` is a module (actually a free module) over `\ZZ`:: + + sage: A.category() + Category of modules over Integer Ring + sage: A in Modules(ZZ) + True + sage: A.rank() + 3 + sage: A.base_ring() + Integer Ring + sage: A.base_module() + Rank-3 free module M over the Integer Ring + + ``A`` is a *parent* object, whose elements are alternating forms, + represented by instances of the class + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`:: + + sage: a = A.an_element() ; a + Alternating form of degree 2 on the Rank-3 free module M over the + Integer Ring + sage: a.display() # expansion with respect to M's default basis (e) + e^0/\e^1 + sage: from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm + sage: isinstance(a, FreeModuleAltForm) + True + sage: a in A + True + sage: A.is_parent_of(a) + True + + Elements can be constructed from ``A``. In particular, 0 yields + the zero element of ``A``:: + + sage: A(0) + Alternating form zero of degree 2 on the Rank-3 free module M over the + Integer Ring + sage: A(0) is A.zero() + True + + while non-zero elements are constructed by providing their components in a + given basis:: + + sage: e + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: comp = [[0,3,-1],[-3,0,4],[1,-4,0]] + sage: a = A(comp, basis=e, name='a') ; a + Alternating form a of degree 2 on the Rank-3 free module M over the + Integer Ring + sage: a.display(e) + a = 3 e^0/\e^1 - e^0/\e^2 + 4 e^1/\e^2 + + An alternative is to construct the alternating form from an empty list of + components and to set the nonzero components afterwards:: + + sage: a = A([], name='a') + sage: a.set_comp(e)[0,1] = 3 + sage: a.set_comp(e)[0,2] = -1 + sage: a.set_comp(e)[1,2] = 4 + sage: a.display(e) + a = 3 e^0/\e^1 - e^0/\e^2 + 4 e^1/\e^2 + + The exterior powers are unique:: + + sage: A is M.dual_exterior_power(2) + True + + The exterior power `\Lambda^1(M^*)` is nothing but `M^*`:: + + sage: M.dual_exterior_power(1) is M.dual() + True + sage: M.dual() + Dual of the Rank-3 free module M over the Integer Ring + sage: latex(M.dual()) + M^* + + Since any tensor of type (0,1) is a linear form, there is a coercion map + from the set `T^{(0,1)}(M)` of such tensors to `M^*`:: + + sage: T01 = M.tensor_module(0,1) ; T01 + Free module of type-(0,1) tensors on the Rank-3 free module M over the + Integer Ring + sage: M.dual().has_coerce_map_from(T01) + True + + There is also a coercion map in the reverse direction:: + + sage: T01.has_coerce_map_from(M.dual()) + True + + For a degree `p\geq 2`, the coercion holds only in the direction + `\Lambda^p(M^*)\rightarrow T^{(0,p)}(M)`:: + + sage: T02 = M.tensor_module(0,2) ; T02 + Free module of type-(0,2) tensors on the Rank-3 free module M over the + Integer Ring + sage: T02.has_coerce_map_from(A) + True + sage: A.has_coerce_map_from(T02) + False + + The coercion map `T^{(0,1)}(M) \rightarrow M^*` in action:: + + sage: b = T01([-2,1,4], basis=e, name='b') ; b + Type-(0,1) tensor b on the Rank-3 free module M over the Integer Ring + sage: b.display(e) + b = -2 e^0 + e^1 + 4 e^2 + sage: lb = M.dual()(b) ; lb + Linear form b on the Rank-3 free module M over the Integer Ring + sage: lb.display(e) + b = -2 e^0 + e^1 + 4 e^2 + + The coercion map `M^* \rightarrow T^{(0,1)}(M)` in action:: + + sage: tlb = T01(lb) ; tlb + Type-(0,1) tensor b on the Rank-3 free module M over the Integer Ring + sage: tlb == b + True + + The coercion map `\Lambda^2(M^*)\rightarrow T^{(0,2)}(M)` in action:: + + sage: ta = T02(a) ; ta + Type-(0,2) tensor a on the Rank-3 free module M over the Integer Ring + sage: ta.display(e) + a = 3 e^0*e^1 - e^0*e^2 - 3 e^1*e^0 + 4 e^1*e^2 + e^2*e^0 - 4 e^2*e^1 + sage: a.display(e) + a = 3 e^0/\e^1 - e^0/\e^2 + 4 e^1/\e^2 + sage: ta.symmetries() # the antisymmetry is of course preserved + no symmetry; antisymmetry: (0, 1) + + """ + + Element = FreeModuleAltForm + + def __init__(self, fmodule, degree, name=None, latex_name=None): + r""" + TEST:: + + sage: from sage.tensor.modules.ext_pow_free_module import ExtPowerFreeModule + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: A = ExtPowerFreeModule(M, 2) ; A + 2nd exterior power of the dual of the Rank-3 free module M over + the Integer Ring + sage: TestSuite(A).run() + + """ + from sage.functions.other import binomial + self._fmodule = fmodule + self._degree = degree + rank = binomial(fmodule._rank, degree) + self._zero_element = 0 # provisory (to avoid infinite recursion in what + # follows) + if degree == 1: # case of the dual + if name is None and fmodule._name is not None: + name = fmodule._name + '*' + if latex_name is None and fmodule._latex_name is not None: + latex_name = fmodule._latex_name + r'^*' + else: + if name is None and fmodule._name is not None: + name = '/\^{}('.format(degree) + fmodule._name + '*)' + if latex_name is None and fmodule._latex_name is not None: + latex_name = r'\Lambda^{' + str(degree) + r'}\left(' + \ + fmodule._latex_name + r'^*\right)' + FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, + latex_name=latex_name, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) + # Unique representation: + if self._degree in self._fmodule._dual_exterior_powers: + raise ValueError("the {}th exterior power of ".format(degree) + + "the dual of {}".format(self._fmodule) + + " has already been created") + else: + self._fmodule._dual_exterior_powers[self._degree] = self + # Zero element + self._zero_element = self._element_constructor_(name='zero', + latex_name='0') + for basis in self._fmodule._known_bases: + self._zero_element._components[basis] = \ + self._zero_element._new_comp(basis) + # (since new components are initialized to zero) + + #### Parent methods + + def _element_constructor_(self, comp=[], basis=None, name=None, + latex_name=None): + r""" + Construct an alternating form. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: A = M.dual_exterior_power(1) + sage: a = A._element_constructor_(0) ; a + Linear form zero on the Rank-3 free module M over the Integer Ring + sage: a = A._element_constructor_([2,0,-1], name='a') ; a + Linear form a on the Rank-3 free module M over the Integer Ring + sage: a.display() + a = 2 e^0 - e^2 + sage: A = M.dual_exterior_power(2) + sage: a = A._element_constructor_(0) ; a + Alternating form zero of degree 2 on the Rank-3 free module M over + the Integer Ring + sage: a = A._element_constructor_([], name='a') ; a + Alternating form a of degree 2 on the Rank-3 free module M over + the Integer Ring + sage: a[e,0,2], a[e,1,2] = 3, -1 + sage: a.display() + a = 3 e^0/\e^2 - e^1/\e^2 + + """ + if comp == 0: + return self._zero_element + if isinstance(comp, FreeModuleTensor): + # coercion of a tensor of type (0,1) to a linear form + tensor = comp # for readability + if tensor.tensor_type() == (0,1) and self._degree == 1 and \ + tensor.base_module() is self._fmodule: + resu = self.element_class(self._fmodule, 1, name=tensor._name, + latex_name=tensor._latex_name) + for basis, comp in tensor._components.iteritems(): + resu._components[basis] = comp.copy() + return resu + else: + raise TypeError("cannot coerce the {} ".format(tensor) + + "to an element of {}".format(self)) + # standard construction + resu = self.element_class(self._fmodule, self._degree, name=name, + latex_name=latex_name) + if comp: + resu.set_comp(basis)[:] = comp + return resu + + def _an_element_(self): + r""" + Construct some (unamed) alternating form. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 4, name='M') + sage: e = M.basis('e') + sage: a = M.dual_exterior_power(1)._an_element_() ; a + Linear form on the 4-dimensional vector space M over the Rational + Field + sage: a.display() + 1/2 e^0 + sage: a = M.dual_exterior_power(2)._an_element_() ; a + Alternating form of degree 2 on the 4-dimensional vector space M + over the Rational Field + sage: a.display() + 1/2 e^0/\e^1 + sage: a = M.dual_exterior_power(3)._an_element_() ; a + Alternating form of degree 3 on the 4-dimensional vector space M + over the Rational Field + sage: a.display() + 1/2 e^0/\e^1/\e^2 + sage: a = M.dual_exterior_power(4)._an_element_() ; a + Alternating form of degree 4 on the 4-dimensional vector space M + over the Rational Field + sage: a.display() + 1/2 e^0/\e^1/\e^2/\e^3 + + """ + resu = self.element_class(self._fmodule, self._degree) + if self._fmodule._def_basis is not None: + sindex = self._fmodule._sindex + ind = [sindex + i for i in range(resu._tensor_rank)] + resu.set_comp()[ind] = self._fmodule._ring.an_element() + return resu + + def _coerce_map_from_(self, other): + r""" + Determine whether coercion to ``self`` exists from other parent. + + EXAMPLES: + + Sets of type-(0,1) tensors coerce to ``self`` if the degree is 1:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: L1 = M.dual_exterior_power(1) ; L1 + Dual of the Rank-3 free module M over the Integer Ring + sage: T01 = M.tensor_module(0,1) ; T01 + Free module of type-(0,1) tensors on the Rank-3 free module M over + the Integer Ring + sage: L1._coerce_map_from_(T01) + True + + Of course, coercions from other tensor types are meaningless:: + + sage: L1._coerce_map_from_(M.tensor_module(1,0)) + False + sage: L1._coerce_map_from_(M.tensor_module(0,2)) + False + + If the degree is larger than 1, there is no coercion:: + + sage: L2 = M.dual_exterior_power(2) ; L2 + 2nd exterior power of the dual of the Rank-3 free module M over + the Integer Ring + sage: L2._coerce_map_from_(M.tensor_module(0,2)) + False + + """ + from sage.tensor.modules.tensor_free_module import TensorFreeModule + if isinstance(other, TensorFreeModule): + # coercion of a type-(0,1) tensor to a linear form + if self._fmodule is other._fmodule and self._degree == 1 and \ + other.tensor_type() == (0,1): + return True + return False + + #### End of parent methods + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 5, name='M') + sage: M.dual_exterior_power(1)._repr_() + 'Dual of the Rank-5 free module M over the Integer Ring' + sage: M.dual_exterior_power(2)._repr_() + '2nd exterior power of the dual of the Rank-5 free module M over the Integer Ring' + sage: M.dual_exterior_power(3)._repr_() + '3rd exterior power of the dual of the Rank-5 free module M over the Integer Ring' + sage: M.dual_exterior_power(4)._repr_() + '4th exterior power of the dual of the Rank-5 free module M over the Integer Ring' + sage: M.dual_exterior_power(5)._repr_() + '5th exterior power of the dual of the Rank-5 free module M over the Integer Ring' + + """ + if self._degree == 1: + return "Dual of the {}".format(self._fmodule) + description = "{}".format(self._degree) + if self._degree == 2: + description += "nd" + elif self._degree == 3: + description += "rd" + else: + description += "th" + description += " exterior power of the dual of the {}".format( + self._fmodule) + return description + + def base_module(self): + r""" + Return the free module on which ``self`` is constructed. + + OUTPUT: + + - instance of :class:`FiniteRankFreeModule` representing the free + module on which the exterior power is defined. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 5, name='M') + sage: A = M.dual_exterior_power(2) + sage: A.base_module() + Rank-5 free module M over the Integer Ring + sage: A.base_module() is M + True + + """ + return self._fmodule + + def degree(self): + r""" + Return the degree of ``self``. + + OUTPUT: + + - integer `p` such that ``self`` is the exterior power `\Lambda^p(M^*)` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 5, name='M') + sage: A = M.dual_exterior_power(2) + sage: A.degree() + 2 + sage: M.dual_exterior_power(4).degree() + 4 + + """ + return self._degree diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index 30543ffe123..ad1898459b7 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -18,23 +18,15 @@ .. NOTE:: The class :class:`FiniteRankFreeModule` does not inherit from - :class:`~sage.modules.free_module.FreeModule_generic` since the latter - is a derived class of :class:`~sage.modules.module.Module_old`, - which does not conform to the new coercion model. - Moreover, the class :class:`~sage.modules.free_module.FreeModule_generic` - seems to assume a distinguished basis (cf. its method - :meth:`~sage.modules.free_module.FreeModule_generic.basis`). - Besides, the class :class:`FiniteRankFreeModule` does not inherit - from the class - :class:`~sage.combinat.free_module.CombinatorialFreeModule` (which conforms - to the new coercion model) since this class is devoted to modules with a - distinguished basis. - -For the above reasons, the class :class:`FiniteRankFreeModule` inherits -directly from the generic class :class:`~sage.structure.parent.Parent` -with the category set to :class:`~sage.categories.modules.Modules` -and not to -:class:`~sage.categories.modules_with_basis.ModulesWithBasis`. + class :class:`~sage.modules.free_module.FreeModule_generic` + nor from class + :class:`~sage.combinat.free_module.CombinatorialFreeModule`, since + both classes deal with modules with a *distinguished basis* (see + details :ref:`below `). Accordingly, the class + :class:`FiniteRankFreeModule` inherits directly from the generic class + :class:`~sage.structure.parent.Parent` with the category set to + :class:`~sage.categories.modules.Modules` (and not to + :class:`~sage.categories.modules_with_basis.ModulesWithBasis`). .. TODO:: @@ -45,7 +37,13 @@ AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version + +REFERENCES: + +- Chap. 10 of R. Godement : *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) +- Chap. 3 of S. Lang : *Algebra*, 3rd ed., Springer (New York) (2002) EXAMPLES: @@ -53,6 +51,8 @@ sage: M = FiniteRankFreeModule(ZZ, 2, name='M') ; M Rank-2 free module M over the Integer Ring + sage: M.category() + Category of modules over Integer Ring We introduce a first basis on ``M``:: @@ -71,48 +71,68 @@ We define a module element by its components w.r.t. basis ``e``:: sage: u = M([2,-3], basis=e, name='u') - sage: u.view(basis=e) - u = 2 e_0 - 3 e_1 - -Since the first defined basis is considered as the default one on the module, -the above can be abridged to:: - - sage: u = M([2,-3], name='u') - sage: u.view() + sage: u.display(e) u = 2 e_0 - 3 e_1 -Module elements can be compared:: +Module elements can be also be created by arithmetic expressions:: + sage: v = -2*u + 4*e[0] ; v + Element of the Rank-2 free module M over the Integer Ring + sage: v.display(e) + 6 e_1 sage: u == 2*e[0] - 3*e[1] True -We define a second basis on M by linking it to ``e`` via a module -automorphism:: +We define a second basis on ``M`` from a family of linearly independent +elements:: - sage: a = M.automorphism_tensor() - sage: a.set_comp(basis=e)[0,1] = -1 ; a.set_comp(basis=e)[1,0] = 1 # only the non-zero components have to be set - sage: a[:] # a matrix view of the automorphism in the module's default basis - [ 0 -1] - [ 1 0] - sage: f = e.new_basis(a, 'f') ; f + sage: f = M.basis('f', from_family=(e[0]-e[1], -2*e[0]+3*e[1])) ; f Basis (f_0,f_1) on the Rank-2 free module M over the Integer Ring - sage: f[0].view() - f_0 = e_1 - sage: f[1].view() - f_1 = -e_0 + sage: f[0].display(e) + f_0 = e_0 - e_1 + sage: f[1].display(e) + f_1 = -2 e_0 + 3 e_1 -We may check that the basis ``f`` is the image of ``e`` by the -automorphism ``a``:: +We may of course express the elements of basis ``e`` in terms of basis ``f``:: + + sage: e[0].display(f) + e_0 = 3 f_0 + f_1 + sage: e[1].display(f) + e_1 = 2 f_0 + f_1 + +as well as any module element:: + + sage: u.display(f) + u = -f_1 + sage: v.display(f) + 12 f_0 + 6 f_1 + +The two bases are related by a module automorphism:: + + sage: a = M.change_of_basis(e,f) ; a + Automorphism of the Rank-2 free module M over the Integer Ring + sage: a.parent() + General linear group of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [ 1 -2] + [-1 3] + +Let us check that basis ``f`` is indeed the image of basis ``e`` by ``a``:: sage: f[0] == a(e[0]) True sage: f[1] == a(e[1]) True +The reverse change of basis is of course the inverse automorphism:: + + sage: M.change_of_basis(f,e) == a^(-1) + True + We introduce a new module element via its components w.r.t. basis ``f``:: sage: v = M([2,4], basis=f, name='v') - sage: v.view(basis=f) + sage: v.display(f) v = 2 f_0 + 4 f_1 The sum of the two module elements ``u`` and ``v`` can be performed even if @@ -122,53 +142,379 @@ sage: s = u + v ; s Element u+v of the Rank-2 free module M over the Integer Ring -We can view the result in either basis:: +We can display the result in either basis:: - sage: s.view(basis=e) # a shortcut is s.view(), e being the default basis - u+v = -2 e_0 - e_1 - sage: s.view(basis=f) - u+v = -f_0 + 2 f_1 + sage: s.display(e) + u+v = -4 e_0 + 7 e_1 + sage: s.display(f) + u+v = 2 f_0 + 3 f_1 -Of course, we can view each of the individual element in either basis:: - - sage: u.view(basis=f) # recall: u was introduced via basis e - u = -3 f_0 - 2 f_1 - sage: v.view(basis=e) # recall: v was introduced via basis f - v = -4 e_0 + 2 e_1 - -Tensor products are implemented:: +Tensor products of elements are implemented:: sage: t = u*v ; t Type-(2,0) tensor u*v on the Rank-2 free module M over the Integer Ring sage: t.parent() Free module of type-(2,0) tensors on the Rank-2 free module M over the Integer Ring - sage: t.view() - u*v = -8 e_0*e_0 + 4 e_0*e_1 + 12 e_1*e_0 - 6 e_1*e_1 - -The automorphism ``a`` is considered as a tensor of type `(1,1)` on ``M``:: + sage: t.display(e) + u*v = -12 e_0*e_0 + 20 e_0*e_1 + 18 e_1*e_0 - 30 e_1*e_1 + sage: t.display(f) + u*v = -2 f_1*f_0 - 4 f_1*f_1 + +We can access to tensor components w.r.t. to a given basis via the square +bracket operator:: + + sage: t[e,0,1] + 20 + sage: t[f,1,0] + -2 + sage: u[e,0] + 2 + sage: u[e,:] + [2, -3] + sage: u[f,:] + [0, -1] + +The parent of the automorphism ``a`` is the group `\mathrm{GL}(M)`, but +``a`` can also be considered as a tensor of type `(1,1)` on ``M``:: sage: a.parent() - Free module of type-(1,1) tensors on the - Rank-2 free module M over the Integer Ring - sage: a.view() - -e_0*e^1 + e_1*e^0 + General linear group of the Rank-2 free module M over the Integer Ring + sage: a.tensor_type() + (1, 1) + sage: a.display(e) + e_0*e^0 - 2 e_0*e^1 - e_1*e^0 + 3 e_1*e^1 + sage: a.display(f) + f_0*f^0 - 2 f_0*f^1 - f_1*f^0 + 3 f_1*f^1 As such, we can form its tensor product with ``t``, yielding a tensor of type `(3,1)`:: - sage: (t*a).parent() - Free module of type-(3,1) tensors on the - Rank-2 free module M over the Integer Ring - sage: (t*a).view() - 8 e_0*e_0*e_0*e^1 - 8 e_0*e_0*e_1*e^0 - 4 e_0*e_1*e_0*e^1 - + 4 e_0*e_1*e_1*e^0 - 12 e_1*e_0*e_0*e^1 + 12 e_1*e_0*e_1*e^0 - + 6 e_1*e_1*e_0*e^1 - 6 e_1*e_1*e_1*e^0 + sage: t*a + Type-(3,1) tensor on the Rank-2 free module M over the Integer Ring + sage: (t*a).display(e) + -12 e_0*e_0*e_0*e^0 + 24 e_0*e_0*e_0*e^1 + 12 e_0*e_0*e_1*e^0 + - 36 e_0*e_0*e_1*e^1 + 20 e_0*e_1*e_0*e^0 - 40 e_0*e_1*e_0*e^1 + - 20 e_0*e_1*e_1*e^0 + 60 e_0*e_1*e_1*e^1 + 18 e_1*e_0*e_0*e^0 + - 36 e_1*e_0*e_0*e^1 - 18 e_1*e_0*e_1*e^0 + 54 e_1*e_0*e_1*e^1 + - 30 e_1*e_1*e_0*e^0 + 60 e_1*e_1*e_0*e^1 + 30 e_1*e_1*e_1*e^0 + - 90 e_1*e_1*e_1*e^1 + +The parent of `t\otimes a` is itself a free module of finite rank over `\ZZ`:: + + sage: T = (t*a).parent() ; T + Free module of type-(3,1) tensors on the Rank-2 free module M over the + Integer Ring + sage: T.base_ring() + Integer Ring + sage: T.rank() + 16 + +.. _diff-FreeModule: + +.. RUBRIC:: Differences between ``FiniteRankFreeModule`` and ``FreeModule`` + (or ``VectorSpace``) + +To illustrate the differences, let us create two free modules of rank 3 over +`\ZZ`, one with ``FiniteRankFreeModule`` and the other one with +``FreeModule``:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') ; M + Rank-3 free module M over the Integer Ring + sage: N = FreeModule(ZZ, 3) ; N + Ambient free module of rank 3 over the principal ideal domain Integer Ring + +The main difference is that ``FreeModule`` returns a free module with a +distinguished basis, while ``FiniteRankFreeModule`` does not:: + + sage: N.basis() + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + sage: M.bases() + [] + sage: M.print_bases() + No basis has been defined on the Rank-3 free module M over the Integer Ring + +This is also revealed by the category of each module:: + + sage: M.category() + Category of modules over Integer Ring + sage: N.category() + Category of modules with basis over Integer Ring + +In other words, the module created by ``FreeModule`` is actually `\ZZ^3`, +while, in the absence of any distinguished basis, no *canonical* isomorphism +relates the module created by ``FiniteRankFreeModule`` to `\ZZ^3`:: + + sage: N is ZZ^3 + True + sage: M is ZZ^3 + False + sage: M == ZZ^3 + False + +Because it is `\ZZ^3`, ``N`` is unique, while there may be various modules +of the same rank over the same ring created by ``FiniteRankFreeModule``; +they are then distinguished by their names (actually by the complete +sequence of arguments of ``FiniteRankFreeModule``):: + + sage: N1 = FreeModule(ZZ, 3) ; N1 + Ambient free module of rank 3 over the principal ideal domain Integer Ring + sage: N1 is N # FreeModule(ZZ, 3) is unique + True + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M_1') ; M1 + Rank-3 free module M_1 over the Integer Ring + sage: M1 is M # M1 and M are different rank-3 modules over ZZ + False + sage: M1b = FiniteRankFreeModule(ZZ, 3, name='M_1') ; M1b + Rank-3 free module M_1 over the Integer Ring + sage: M1b is M1 # because M1b and M1 have the same name + True + +As illustrated above, various bases can be introduced on the module created by +``FiniteRankFreeModule``:: + + sage: e = M.basis('e') ; e + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: f = M.basis('f', from_family=(-e[0], e[1]-e[2], -2*e[1]+3*e[2])) ; f + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring + sage: M.bases() + [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] + +Each element of a basis is accessible via its index:: + + sage: e[0] + Element e_0 of the Rank-3 free module M over the Integer Ring + sage: e[0].parent() + Rank-3 free module M over the Integer Ring + sage: f[1] + Element f_1 of the Rank-3 free module M over the Integer Ring + sage: f[1].parent() + Rank-3 free module M over the Integer Ring + +while on module ``N``, the element of the (unique) basis is accessible +directly from the module symbol:: + + sage: N.0 + (1, 0, 0) + sage: N.1 + (0, 1, 0) + sage: N.0.parent() + Ambient free module of rank 3 over the principal ideal domain Integer Ring + +The arithmetic of elements is similar; the difference lies in the display: +a basis has to be specified for elements of ``M``, while elements of ``N`` are +displayed directly as elements of `\ZZ^3`:: + + sage: u = 2*e[0] - 3*e[2] ; u + Element of the Rank-3 free module M over the Integer Ring + sage: u.display(e) + 2 e_0 - 3 e_2 + sage: u.display(f) + -2 f_0 - 6 f_1 - 3 f_2 + sage: u[e,:] + [2, 0, -3] + sage: u[f,:] + [-2, -6, -3] + sage: v = 2*N.0 - 3*N.2 ; v + (2, 0, -3) + +For the case of ``M``, in order to avoid to specify the basis if the user is +always working with the same basis (e.g. only one basis has been defined), +the concept of *default basis* has been introduced:: + + sage: M.default_basis() + Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: M.print_bases() + Bases defined on the Rank-3 free module M over the Integer Ring: + - (e_0,e_1,e_2) (default basis) + - (f_0,f_1,f_2) + +This is different from the *distinguished basis* of ``N``: it simply means that +the mention of the basis can be omitted in function arguments:: + + sage: u.display() # equivalent to u.display(e) + 2 e_0 - 3 e_2 + sage: u[:] # equivalent to u[e,:] + [2, 0, -3] + +At any time, the default basis can be changed:: + + sage: M.set_default_basis(f) + sage: u.display() + -2 f_0 - 6 f_1 - 3 f_2 + +Another difference between ``FiniteRankFreeModule`` and ``FreeModule`` is that +for the former the range of indices can be specified (by default, it starts +from 0):: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) ; M + Rank-3 free module M over the Integer Ring + sage: e = M.basis('e') ; e # compare with (e_0,e_1,e_2) above + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring + sage: e[1], e[2], e[3] + (Element e_1 of the Rank-3 free module M over the Integer Ring, + Element e_2 of the Rank-3 free module M over the Integer Ring, + Element e_3 of the Rank-3 free module M over the Integer Ring) + +All the above holds for ``VectorSpace`` instead of ``FreeModule``: the object +created by ``VectorSpace`` is actually a Cartesian power of the base field:: + + sage: V = VectorSpace(QQ,3) ; V + Vector space of dimension 3 over Rational Field + sage: V.category() + Category of vector spaces over Rational Field + sage: V is QQ^3 + True + sage: V.basis() + [ + (1, 0, 0), + (0, 1, 0), + (0, 0, 1) + ] + +To create a vector space without any distinguished basis, one has to use +``FiniteRankFreeModule``:: + + sage: V = FiniteRankFreeModule(QQ, 3, name='V') ; V + 3-dimensional vector space V over the Rational Field + sage: V.category() + Category of vector spaces over Rational Field + sage: V.bases() + [] + sage: V.print_bases() + No basis has been defined on the 3-dimensional vector space V over the + Rational Field + +The class :class:`FiniteRankFreeModule` has been created for the needs +of the `SageManifolds project `_, where +free modules do not have any distinguished basis. Too kinds of free modules +occur in the context of differentiable manifolds (see +`here `_ for more +details): + +- the tangent vector space at any point of the manifold +- the set of vector fields on a parallelizable open subset `U` of the manifold, + which is a free module over the algebra of scalar fields on `U`. + +For instance, without any specific coordinate choice, no basis can be +distinguished in a tangent space. + +On the other side, the modules created by ``FreeModule`` have much more +algebraic functionalities than those created by ``FiniteRankFreeModule``. In +particular, submodules have not been implemented yet in +:class:`FiniteRankFreeModule`. Moreover, modules resulting from ``FreeModule`` +are tailored to the specific kind of their base ring: + +- free module over a commutative ring that is not an integral domain + (`\ZZ/6\ZZ`):: + + sage: R = IntegerModRing(6) ; R + Ring of integers modulo 6 + sage: FreeModule(R, 3) + Ambient free module of rank 3 over Ring of integers modulo 6 + sage: type(FreeModule(R, 3)) + + +- free module over an integral domain that is not principal (`\ZZ[X]`):: + + sage: R. = ZZ[] ; R + Univariate Polynomial Ring in X over Integer Ring + sage: FreeModule(R, 3) + Ambient free module of rank 3 over the integral domain Univariate + Polynomial Ring in X over Integer Ring + sage: type(FreeModule(R, 3)) + + +- free module over a principal ideal domain (`\ZZ`):: + + sage: R = ZZ ; R + Integer Ring + sage: FreeModule(R,3) + Ambient free module of rank 3 over the principal ideal domain Integer Ring + sage: type(FreeModule(R, 3)) + + +On the contrary, all objects constructed with ``FiniteRankFreeModule`` belong +to the same class:: + + sage: R = IntegerModRing(6) + sage: type(FiniteRankFreeModule(R, 3)) + + sage: R. = ZZ[] + sage: type(FiniteRankFreeModule(R, 3)) + + sage: R = ZZ + sage: type(FiniteRankFreeModule(R, 3)) + + + +.. RUBRIC:: Differences between ``FiniteRankFreeModule`` and + ``CombinatorialFreeModule`` + +An alternative to construct free modules in Sage is +:class:`~sage.combinat.free_module.CombinatorialFreeModule`. +However, as ``FreeModule``, it leads to a module with a distinguished basis:: + + sage: N = CombinatorialFreeModule(ZZ, [1,2,3]) ; N + Free module generated by {1, 2, 3} over Integer Ring + sage: N.category() + Category of modules with basis over Integer Ring + +The distinguished basis is returned by the method ``basis()``:: + + sage: b = N.basis() ; b + Finite family {1: B[1], 2: B[2], 3: B[3]} + sage: b[1] + B[1] + sage: b[1].parent() + Free module generated by {1, 2, 3} over Integer Ring + +For the free module ``M`` created above with ``FiniteRankFreeModule``, the +method ``basis`` has at least one argument: the symbol string that +specifies which basis is required:: + + sage: e = M.basis('e') ; e + Basis (e_1,e_2,e_3) on the Rank-3 free module M over the Integer Ring + sage: e[1] + Element e_1 of the Rank-3 free module M over the Integer Ring + sage: e[1].parent() + Rank-3 free module M over the Integer Ring + +The arithmetic of elements is similar:: + + sage: u = 2*e[1] - 5*e[3] ; u + Element of the Rank-3 free module M over the Integer Ring + sage: v = 2*b[1] - 5*b[3] ; v + 2*B[1] - 5*B[3] + +One notices that elements of ``N`` are displayed directly in terms of their +expansions on the distinguished basis. For elements of ``M``, one has to use +the method +:meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.display` +in order to specify the basis:: + + sage: u.display(e) + 2 e_1 - 5 e_3 + +The components on the basis are returned by the square bracket operator for +``M`` and by the method ``coefficient`` for ``N``:: + + sage: [u[e,i] for i in {1,2,3}] + [2, 0, -5] + sage: u[e,:] # a shortcut for the above + [2, 0, -5] + sage: [v.coefficient(i) for i in {1,2,3}] + [2, 0, -5] """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -180,32 +526,41 @@ from sage.structure.parent import Parent from sage.categories.modules import Modules from sage.categories.rings import Rings +from sage.categories.fields import Fields from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement class FiniteRankFreeModule(UniqueRepresentation, Parent): r""" - Free module of finite rank over a commutative ring `R`. + Free module of finite rank over a commutative ring. - .. NOTE:: + A *free module of finite rank* over a commutative ring `R` is a module `M` + over `R` that admits a *finite basis*, i.e. a finite familly of linearly + independent generators. Since `R` is commutative, it has the invariant + basis number property, so that the rank of the free module `M` is defined + uniquely, as the cardinality of any basis of `M`. - The class :class:`FiniteRankFreeModule` does not inherit from - :class:`~sage.modules.free_module.FreeModule_generic` since the latter - is a derived class of :class:`~sage.modules.module.Module_old`, - which does not conform to the new coercion model. - Besides, the class :class:`FiniteRankFreeModule` does not inherit - from the class :class:`CombinatorialFreeModule` since the latter is - devoted to modules *with a basis*. + No distinguished basis of `M` is assumed. On the contrary, many bases can be + introduced on the free module along with change-of-basis rules (as module + automorphisms). Each + module element has then various representations over the various bases. .. NOTE:: - Following the recommendation exposed in - `trac ticket 16427 `_ + The class :class:`FiniteRankFreeModule` does not inherit from + class :class:`~sage.modules.free_module.FreeModule_generic` + nor from class + :class:`~sage.combinat.free_module.CombinatorialFreeModule`, since + both classes deal with modules with a *distinguished basis* (see + details :ref:`above `). + Moreover, following the recommendation exposed in trac ticket + `#16427 `_ the class :class:`FiniteRankFreeModule` inherits directly from - :class:`~sage.structure.parent.Parent` and not from the Cython class - :class:`~sage.modules.module.Module`. + :class:`~sage.structure.parent.Parent` (with the category set to + :class:`~sage.categories.modules.Modules`) and not from the Cython + class :class:`~sage.modules.module.Module`. - The class :class:`FiniteRankFreeModule` is a Sage *Parent* class whose - elements belong to the class + The class :class:`FiniteRankFreeModule` is a Sage *parent* class, + the corresponding *element* class being :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`. INPUT: @@ -228,6 +583,7 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): Free module of rank 3 over `\ZZ`:: + sage: FiniteRankFreeModule._clear_cache_() # for doctests only sage: M = FiniteRankFreeModule(ZZ, 3) ; M Rank-3 free module over the Integer Ring sage: M = FiniteRankFreeModule(ZZ, 3, name='M') ; M # declaration with a name @@ -243,7 +599,7 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): spaces:: sage: V = FiniteRankFreeModule(QQ, 3, name='V') ; V - Rank-3 free module V over the Rational Field + 3-dimensional vector space V over the Rational Field sage: V.category() Category of vector spaces over Rational Field @@ -255,19 +611,6 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): sage: latex(M) \mathcal{M} - M is a *parent* object, whose elements are instances of - :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement`:: - - sage: v = M.an_element() ; v - Element of the Rank-3 free module M over the Integer Ring - sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement - sage: isinstance(v, FiniteRankFreeModuleElement) - True - sage: v in M - True - sage: M.is_parent_of(v) - True - The free module M has no distinguished basis:: sage: M in ModulesWithBasis(ZZ) @@ -275,6 +618,13 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): sage: M in Modules(ZZ) True + In particular, no basis is initialized at the module construction:: + + sage: M.print_bases() + No basis has been defined on the Rank-3 free module M over the Integer Ring + sage: M.bases() + [] + Bases have to be introduced by means of the method :meth:`basis`, the first defined basis being considered as the *default basis*, meaning it can be skipped in function arguments required a basis (this can @@ -285,6 +635,42 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): sage: M.default_basis() Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + A second basis can be created from a family of linearly independent + elements expressed in terms of basis ``e``:: + + sage: f = M.basis('f', from_family=(-e[0], e[1]+e[2], 2*e[1]+3*e[2])) + sage: f + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring + sage: M.print_bases() + Bases defined on the Rank-3 free module M over the Integer Ring: + - (e_0,e_1,e_2) (default basis) + - (f_0,f_1,f_2) + sage: M.bases() + [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] + + M is a *parent* object, whose elements are instances of + :class:`~sage.tensor.modules.free_module_tensor.FiniteRankFreeModuleElement` + (actually a dynamically generated subclass of it):: + + sage: v = M.an_element() ; v + Element of the Rank-3 free module M over the Integer Ring + sage: from sage.tensor.modules.free_module_tensor import FiniteRankFreeModuleElement + sage: isinstance(v, FiniteRankFreeModuleElement) + True + sage: v in M + True + sage: M.is_parent_of(v) + True + sage: v.display() # expansion w.r.t. the default basis (e) + e_0 + e_1 + e_2 + sage: v.display(f) + -f_0 + f_1 + + The test suite of the category of modules is passed:: + + sage: TestSuite(M).run() + Constructing an element of ``M`` from (the integer) 0 yields the zero element of ``M``:: @@ -298,25 +684,30 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): sage: v = M([-1,0,3]) ; v # components in the default basis (e) Element of the Rank-3 free module M over the Integer Ring - sage: v.view() + sage: v.display() # expansion w.r.t. the default basis (e) -e_0 + 3 e_2 - sage: f = M.basis('f') + sage: v.display(f) + f_0 - 6 f_1 + 3 f_2 sage: v = M([-1,0,3], basis=f) ; v # components in a specific basis Element of the Rank-3 free module M over the Integer Ring - sage: v.view(f) + sage: v.display(f) -f_0 + 3 f_2 + sage: v.display() + e_0 + 6 e_1 + 9 e_2 sage: v = M([-1,0,3], basis=f, name='v') ; v Element v of the Rank-3 free module M over the Integer Ring - sage: v.view(f) + sage: v.display(f) v = -f_0 + 3 f_2 + sage: v.display() + v = e_0 + 6 e_1 + 9 e_2 An alternative is to construct the element from an empty list of componentsand to set the nonzero components afterwards:: sage: v = M([], name='v') - sage: v.set_comp(e)[0] = -1 - sage: v.set_comp(e)[2] = 3 - sage: v.view(e) + sage: v[e,0] = -1 + sage: v[e,2] = 3 + sage: v.display(e) v = -e_0 + 3 e_2 Indices on the free module, such as indices labelling the element of a @@ -329,21 +720,21 @@ class FiniteRankFreeModule(UniqueRepresentation, Parent): This can be changed via the parameter ``start_index`` in the module construction:: - sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) - sage: for i in M.irange(): print i, + sage: M1 = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: for i in M1.irange(): print i, 1 2 3 The parameter ``output_formatter`` in the constructor of the free module is used to set the output format of tensor components:: - sage: M = FiniteRankFreeModule(QQ, 3, output_formatter=Rational.numerical_approx) - sage: e = M.basis('e') - sage: v = M([1/3, 0, -2], basis=e) - sage: v.comp(e)[:] + sage: N = FiniteRankFreeModule(QQ, 3, output_formatter=Rational.numerical_approx) + sage: e = N.basis('e') + sage: v = N([1/3, 0, -2], basis=e) + sage: v[e,:] [0.333333333333333, 0.000000000000000, -2.00000000000000] - sage: v.view(e) # default format (53 bits of precision) + sage: v.display(e) # default format (53 bits of precision) 0.333333333333333 e_0 - 2.00000000000000 e_2 - sage: v.view(e, format_spec=10) # 10 bits of precision + sage: v.display(e, format_spec=10) # 10 bits of precision 0.33 e_0 - 2.0 e_2 """ @@ -378,9 +769,12 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, self._sindex = start_index self._output_formatter = output_formatter # Dictionary of the tensor modules built on self - # (dict. keys = (k,l) --the tensor type) + # (keys = (k,l) --the tensor type) : self._tensor_modules = {(1,0): self} # self is considered as the set of # tensors of type (1,0) + # Dictionary of exterior powers of the dual of self + # (keys = p --the power degree) : + self._dual_exterior_powers = {} self._known_bases = [] # List of known bases on the free module self._def_basis = None # default basis self._basis_changes = {} # Dictionary of the changes of bases @@ -389,10 +783,12 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, self._zero_element = self._element_constructor_(name='zero', latex_name='0') # Identity endomorphism: - self._identity_map = None # not defined yet - + self._identity_map = None # to be set by self.identity_map() + # General linear group: + self._general_linear_group = None # to be set by + # self.general_linear_group() - #### Methods required for any Parent + #### Parent methods def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None): @@ -401,11 +797,12 @@ def _element_constructor_(self, comp=[], basis=None, name=None, EXAMPLES:: + sage: FiniteRankFreeModule._clear_cache_() # for doctests only sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: v = M._element_constructor_(comp=[1,0,-2], basis=e, name='v') ; v Element v of the Rank-3 free module M over the Integer Ring - sage: v.view() + sage: v.display() v = e_0 - 2 e_2 sage: v == M([1,0,-2]) True @@ -424,14 +821,15 @@ def _element_constructor_(self, comp=[], basis=None, name=None, def _an_element_(self): r""" - Construct some (unamed) element of the module. + Construct some (unamed) element of ``self``. EXAMPLE:: + sage: FiniteRankFreeModule._clear_cache_() # for doctests only sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: v = M._an_element_(); v Element of the Rank-3 free module M over the Integer Ring - sage: v.view() + sage: v.display() e_0 + e_1 + e_2 sage: v == M.an_element() True @@ -440,12 +838,12 @@ def _an_element_(self): """ if self._def_basis is None: - self.basis('e') + self.basis('e') resu = self.element_class(self) resu.set_comp()[:] = [self._ring.an_element() for i in range(self._rank)] return resu - #### End of methods required for any Parent + #### End of parent methods #### Methods to be redefined by derived classes #### @@ -459,15 +857,18 @@ def _repr_(self): Rank-3 free module M over the Integer Ring """ - description = "Rank-{} free module ".format(self._rank) + if self._ring in Fields(): + description = "{}-dimensional vector space ".format(self._rank) + else: + description = "Rank-{} free module ".format(self._rank) if self._name is not None: description += self._name + " " - description += "over the " + str(self._ring) + description += "over the {}".format(self._ring) return description def _Hom_(self, other, category=None): r""" - Construct the set of homomorphisms self --> other. + Construct the set of homomorphisms ``self`` --> ``other``. INPUT: @@ -489,8 +890,8 @@ def _Hom_(self, other, category=None): to Rank-2 free module N over the Integer Ring in Category of modules over Integer Ring sage: H = Hom(M,N) ; H # indirect doctest - Set of Morphisms from Rank-3 free module M over the Integer Ring - to Rank-2 free module N over the Integer Ring in Category of + Set of Morphisms from Rank-3 free module M over the Integer Ring + to Rank-2 free module N over the Integer Ring in Category of modules over Integer Ring """ @@ -537,27 +938,174 @@ def tensor_module(self, k, l): sage: M.tensor_module(1,0) is M True + See :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` + for more documentation. + """ from sage.tensor.modules.tensor_free_module import TensorFreeModule if (k,l) not in self._tensor_modules: self._tensor_modules[(k,l)] = TensorFreeModule(self, (k,l)) return self._tensor_modules[(k,l)] - def basis(self, symbol=None, latex_symbol=None): + def dual_exterior_power(self, p): r""" - Define a basis of the free module. + Return the `p`-th exterior power of the dual of ``self``. + + If `M` stands for the free module ``self``, the *p-th exterior power* + of the dual of `M` is the set + `\Lambda^p(M^*)` of all *alternating forms of degree* `p` on `M`, i.e. + of all multilinear maps + + .. MATH:: + + \underbrace{M\times\cdots\times M}_{p\ \; \mbox{times}} + \longrightarrow R + + that vanish whenever any of two of their arguments are equal. + `\Lambda^p(M^*)` is a free module of rank `\left({n\atop p}\right)` + over the same ring as `M`, where `n` is the rank of `M`. + + INPUT: + + - ``p`` -- non-negative integer + + OUTPUT: + + - for `p\geq 1`, instance of + :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerFreeModule` + representing the free module `\Lambda^p(M^*)`; for `p=0`, the + base ring `R` is returned instead + + EXAMPLES: + + Exterior powers of the dual of a free `\ZZ`-module of rank 3:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: M.dual_exterior_power(0) # return the base ring + Integer Ring + sage: M.dual_exterior_power(1) # return the dual module + Dual of the Rank-3 free module M over the Integer Ring + sage: M.dual_exterior_power(1) is M.dual() + True + sage: M.dual_exterior_power(2) + 2nd exterior power of the dual of the Rank-3 free module M over the Integer Ring + sage: M.dual_exterior_power(2).an_element() + Alternating form of degree 2 on the Rank-3 free module M over the Integer Ring + sage: M.dual_exterior_power(2).an_element().display() + e^0/\e^1 + sage: M.dual_exterior_power(3) + 3rd exterior power of the dual of the Rank-3 free module M over the Integer Ring + sage: M.dual_exterior_power(3).an_element() + Alternating form of degree 3 on the Rank-3 free module M over the Integer Ring + sage: M.dual_exterior_power(3).an_element().display() + e^0/\e^1/\e^2 + + See + :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerFreeModule` + for more documentation. + + """ + from sage.tensor.modules.ext_pow_free_module import ExtPowerFreeModule + if p == 0: + return self._ring + if p not in self._dual_exterior_powers: + self._dual_exterior_powers[p] = ExtPowerFreeModule(self, p) + return self._dual_exterior_powers[p] + + def general_linear_group(self): + r""" + Return the general linear group of ``self``. + + If ``self`` is the free module `M`, the *general linear group* is the + group `\mathrm{GL}(M)` of automorphisms of `M`. + + OUTPUT: + + - instance of class + :class:`~sage.tensor.modules.free_module_linear_group.FreeModuleLinearGroup` + representing `\mathrm{GL}(M)` + + EXAMPLES: + + The general linear group of a rank-3 free module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: GL = M.general_linear_group() ; GL + General linear group of the Rank-3 free module M over the Integer Ring + sage: GL.category() + Category of groups + sage: type(GL) + + + There is a unique instance of the general linear group:: + + sage: M.general_linear_group() is GL + True + + The group identity element:: + + sage: GL.one() + Identity map of the Rank-3 free module M over the Integer Ring + sage: GL.one().matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + + An element:: + + sage: GL.an_element() + Automorphism of the Rank-3 free module M over the Integer Ring + sage: GL.an_element().matrix(e) + [ 1 0 0] + [ 0 -1 0] + [ 0 0 1] + + See + :class:`~sage.tensor.modules.free_module_linear_group.FreeModuleLinearGroup` + for more documentation. + + """ + from sage.tensor.modules.free_module_linear_group import \ + FreeModuleLinearGroup + if self._general_linear_group is None: + self._general_linear_group = FreeModuleLinearGroup(self) + return self._general_linear_group + + def basis(self, symbol, latex_symbol=None, from_family=None): + r""" + Define or return a basis of the free module ``self``. + + Let `M` denotes the free module ``self`` and `n` its rank. + + The basis can be defined from a set of `n` linearly independent + elements of `M` by means of the argument ``from_family``. + If ``from_family`` is not specified, the basis is created from + scratch and, at this stage, is unrelated to bases that could have been + defined previously on `M`. It can be related afterwards by means of + the method :meth:`set_change_of_basis`. If the basis specified by the given symbol already exists, it is - simply returned. + simply returned, whatever the value of the arguments ``latex_symbol`` + or ``from_family``. + + Note that another way to construct a basis of ``self`` is to use + the method + :meth:`~sage.tensor.modules.free_module_basis.FreeModuleBasis.new_basis` + on an existing basis, with the automorphism relating the two bases as + an argument. INPUT: - - ``symbol`` -- (default: ``None``) string; a letter (of a few letters) - to denote a generic element of the basis; if ``None``, the module's - default basis is returned + - ``symbol`` -- string; a letter (of a few letters) to denote a generic + element of the basis - ``latex_symbol`` -- (default: ``None``) string; symbol to denote a generic element of the basis; if ``None``, the value of ``symbol`` is used + - ``from_family`` -- (default: ``None``) a tuple of `n` linearly + independent elements of the free module ``self`` (`n` being the + rank of ``self``) OUTPUT: @@ -594,15 +1142,6 @@ def basis(self, symbol=None, latex_symbol=None): sage: M.basis('eps') is eps True - If no symbol is provided, the module's default basis is returned:: - - sage: M.basis() - Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring - sage: M.basis() is e - True - sage: M.basis() is M.default_basis() - True - The individual elements of the basis are labelled according the parameter ``start_index`` provided at the free module construction:: @@ -612,21 +1151,76 @@ def basis(self, symbol=None, latex_symbol=None): sage: e[1] Element e_1 of the Rank-3 free module M over the Integer Ring + Construction of a basis from a family of linearly independent module + elements:: + + sage: f1 = -e[2] + sage: f2 = 4*e[1] + 3*e[3] + sage: f3 = 7*e[1] + 5*e[3] + sage: f = M.basis('f', from_family=(f1,f2,f3)) + sage: f[1].display() + f_1 = -e_2 + sage: f[2].display() + f_2 = 4 e_1 + 3 e_3 + sage: f[3].display() + f_3 = 7 e_1 + 5 e_3 + + The change-of-basis automorphisms have been registered:: + + sage: M.change_of_basis(e,f).matrix(e) + [ 0 4 7] + [-1 0 0] + [ 0 3 5] + sage: M.change_of_basis(f,e).matrix(e) + [ 0 -1 0] + [-5 0 7] + [ 3 0 -4] + sage: M.change_of_basis(f,e) == M.change_of_basis(e,f).inverse() + True + + Check of the change-of-basis e --> f:: + + sage: a = M.change_of_basis(e,f) ; a + Automorphism of the Rank-3 free module M over the Integer Ring + sage: all( f[i] == a(e[i]) for i in M.irange() ) + True + + For more documentation on bases see + :class:`~sage.tensor.modules.free_module_basis.FreeModuleBasis`. + """ from free_module_basis import FreeModuleBasis - if symbol is None: - return self.default_basis() - for other in self._known_bases: if symbol == other._symbol: return other - return FreeModuleBasis(self, symbol, latex_symbol) - + resu = FreeModuleBasis(self, symbol, latex_symbol) + if from_family: + n = self._rank + if len(from_family) != n: + raise ValueError("the size of the family is not {}".format(n)) + for ff in from_family: + if ff not in self: + raise TypeError("{} is not an element of {}".format(ff, + self)) + # The automorphisms relating the family to previously defined + # bases are registered: + ff0 = from_family[0] + for basis in ff0._components: + try: + comp = [ff.components(basis) for ff in from_family] + except ValueError: + continue + mat = [[comp_ff[[i]] for comp_ff in comp] + for i in self.irange()] + aut = self.automorphism() + aut.set_comp(basis)[:] = mat + self.set_change_of_basis(basis, resu, aut) + return resu def tensor(self, tensor_type, name=None, latex_name=None, sym=None, antisym=None): r""" - Construct a tensor on the free module. + Construct a tensor on the free module ``self``. INPUT: @@ -666,7 +1260,7 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, sage: t = M.tensor((0,1), name='t') ; t Linear form t on the Rank-3 free module M over the Integer Ring sage: t = M.tensor((1,1), name='t') ; t - Endomorphism tensor t on the Rank-3 free module M over the Integer Ring + Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring sage: t = M.tensor((0,2), name='t', sym=(0,1)) ; t Symmetric bilinear form t on the Rank-3 free module M over the Integer Ring @@ -680,42 +1274,27 @@ def tensor(self, tensor_type, name=None, latex_name=None, sym=None, for more examples and documentation. """ - from free_module_tensor_spec import FreeModuleEndomorphismTensor - from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm + # Special cases: if tensor_type == (1,0): return self.element_class(self, name=name, latex_name=latex_name) - #!# the above is preferable to - # return FiniteRankFreeModuleElement(self, name=name, latex_name=latex_name) - # because self.element_class is a (dynamically created) derived - # class of FiniteRankFreeModuleElement elif tensor_type == (0,1): - return FreeModuleLinForm(self, name=name, latex_name=latex_name) - elif tensor_type == (1,1): - return FreeModuleEndomorphismTensor(self, name=name, - latex_name=latex_name) + return self.linear_form(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and antisym: if isinstance(antisym, list): antisym0 = antisym[0] else: antisym0 = antisym if len(antisym0) == tensor_type[1]: - return FreeModuleAltForm(self, tensor_type[1], name=name, - latex_name=latex_name) - else: - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, - latex_name=latex_name, sym=sym, - antisym=antisym) - else: - return self.tensor_module(*tensor_type).element_class(self, - tensor_type, name=name, - latex_name=latex_name, sym=sym, - antisym=antisym) - + return self.alternating_form(tensor_type[1], name=name, + latex_name=latex_name) + # Generic case: + return self.tensor_module(*tensor_type).element_class(self, + tensor_type, name=name, latex_name=latex_name, + sym=sym, antisym=antisym) def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): r""" - Construct a tensor on the free module from a set of components. + Construct a tensor on ``self`` from a set of components. The tensor symmetries are deduced from those of the components. @@ -751,11 +1330,11 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): sage: t = M.tensor_from_comp((1,0), c) sage: t Element of the Rank-3 free module M over the Integer Ring - sage: t.view(e) + sage: t.display(e) -e_0 + 4 e_1 + 2 e_2 sage: t = M.tensor_from_comp((0,1), c) ; t Linear form on the Rank-3 free module M over the Integer Ring - sage: t.view(e) + sage: t.display(e) -e^0 + 4 e^1 + 2 e^2 Construction of a tensor of rank 2:: @@ -767,19 +1346,17 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): Rank-3 free module M over the Integer Ring sage: t.symmetries() symmetry: (0, 1); no antisymmetry - sage: t.view(e) + sage: t.display(e) 4 e^0*e^0 + 5 e^1*e^2 + 5 e^2*e^1 sage: c = CompFullyAntiSym(ZZ, e, 2) sage: c[0,1], c[1,2] = 4, 5 sage: t = M.tensor_from_comp((0,2), c) ; t Alternating form of degree 2 on the Rank-3 free module M over the Integer Ring - sage: t.view(e) + sage: t.display(e) 4 e^0/\e^1 + 5 e^1/\e^2 """ - from free_module_tensor_spec import FreeModuleEndomorphismTensor - from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm from comp import CompWithSym, CompFullySym, CompFullyAntiSym # # 0/ Compatibility checks: @@ -796,19 +1373,12 @@ def tensor_from_comp(self, tensor_type, comp, name=None, latex_name=None): # 1/ Construction of the tensor: if tensor_type == (1,0): resu = self.element_class(self, name=name, latex_name=latex_name) - #!# the above is preferable to - # resu = FiniteRankFreeModuleElement(self, name=name, latex_name=latex_name) - # because self.element_class is a (dynamically created) derived - # class of FiniteRankFreeModuleElement elif tensor_type == (0,1): - resu = FreeModuleLinForm(self, name=name, latex_name=latex_name) - elif tensor_type == (1,1): - resu = FreeModuleEndomorphismTensor(self, name=name, - latex_name=latex_name) + resu = self.linear_form(name=name, latex_name=latex_name) elif tensor_type[0] == 0 and tensor_type[1] > 1 and \ isinstance(comp, CompFullyAntiSym): - resu = FreeModuleAltForm(self, tensor_type[1], name=name, - latex_name=latex_name) + resu = self.alternating_form(tensor_type[1], name=name, + latex_name=latex_name) else: resu = self.tensor_module(*tensor_type).element_class(self, tensor_type, name=name, latex_name=latex_name) @@ -840,9 +1410,6 @@ def alternating_form(self, degree, name=None, latex_name=None): - instance of :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` - (``degree`` > 1) or - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` - (``degree`` = 1) EXAMPLES: @@ -860,7 +1427,7 @@ def alternating_form(self, degree, name=None, latex_name=None): Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a.set_comp(e)[0,1] = 2 sage: a.set_comp(e)[1,2] = -3 - sage: a.view(e) + sage: a.display(e) a = 2 e^0/\e^1 - 3 e^1/\e^2 An alternating form of degree 1 is a linear form:: @@ -876,19 +1443,15 @@ def alternating_form(self, degree, name=None, latex_name=None): See :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` - for further documentation. + for more documentation. """ - from free_module_alt_form import FreeModuleAltForm, FreeModuleLinForm - if degree == 1: - return FreeModuleLinForm(self, name=name, latex_name=latex_name) - else: - return FreeModuleAltForm(self, degree, name=name, - latex_name=latex_name) + return self.dual_exterior_power(degree).element_class(self, degree, + name=name, latex_name=latex_name) def linear_form(self, name=None, latex_name=None): r""" - Construct a linear form on the free module. + Construct a linear form on the free module ``self``. A *linear form* on a free module `M` over a ring `R` is a map `M \rightarrow R` that is linear. It can be viewed as a tensor of type @@ -905,7 +1468,7 @@ def linear_form(self, name=None, latex_name=None): OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` EXAMPLES: @@ -916,7 +1479,7 @@ def linear_form(self, name=None, latex_name=None): sage: a = M.linear_form('A') ; a Linear form A on the Rank-3 free module M over the Integer Ring sage: a[:] = [2,-1,3] # components w.r.t. the module's default basis (e) - sage: a.view() + sage: a.display() A = 2 e^0 - e^1 + 3 e^2 A linear form maps module elements to ring elements:: @@ -932,160 +1495,107 @@ def linear_form(self, name=None, latex_name=None): True See - :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` - for further documentation. + :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` + for more documentation. """ - from free_module_alt_form import FreeModuleLinForm - return FreeModuleLinForm(self, name=name, latex_name=latex_name) + return self.dual_exterior_power(1).element_class(self, 1, name=name, + latex_name=latex_name) - def endomorphism_tensor(self, name=None, latex_name=None): + def automorphism(self, matrix=None, basis=None, name=None, + latex_name=None): r""" - Construct a tensor of type-(1,1) on the free module ``self``. - - .. NOTE:: + Construct a module automorphism of ``self``. - This method differs from :meth:`endomorphism` for it returns a - tensor, while :meth:`endomorphism` returns an instance of Sage's :class:`~sage.categories.map.Map` (actually an instance of the subclass - :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism`). + Denoting ``self`` by `M`, an automorphism of ``self`` is an element + of the general linear group `\mathrm{GL}(M)`. INPUT: + - ``matrix`` -- (default: ``None``) matrix of size rank(M)*rank(M) + representing the automorphism with respect to ``basis``; + this entry can actually be any material from which a matrix of + elements of ``self`` base ring can be constructed; the *columns* of + ``matrix`` must be the components w.r.t. ``basis`` of + the images of the elements of ``basis``. If ``matrix`` is ``None``, + the automorphism has to be initialized afterwards by + method :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.set_comp` + or via the operator []. + - ``basis`` -- (default: ``None``) basis of ``self`` defining the + matrix representation; if ``None`` the default basis of ``self`` is + assumed. - ``name`` -- (default: ``None``) string; name given to the - tensor - - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to - denote the tensor; if none is provided, the LaTeX symbol - is set to ``name`` - - OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphismTensor` - - EXAMPLES: - - Endomorphism as a type-(1,1) tensor on a rank-3 module:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: t = M.endomorphism_tensor('T') ; t - Endomorphism tensor T on the Rank-3 free module M over the Integer Ring - sage: t.parent() - Free module of type-(1,1) tensors on the - Rank-3 free module M over the Integer Ring - sage: t.tensor_type() - (1, 1) - - The method :meth:`tensor` with the argument ``(1,1)`` can be used as - well:: - - sage: t = M.tensor((1,1), name='T') ; t - Endomorphism tensor T on the Rank-3 free module M over the Integer Ring - - See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphismTensor` - for further documentation. - - """ - from free_module_tensor_spec import FreeModuleEndomorphismTensor - return FreeModuleEndomorphismTensor(self, name=name, latex_name=latex_name) - - - def automorphism_tensor(self, name=None, latex_name=None): - r""" - Construct an invertible type-(1,1) tensor on the free module ``self``. - - INPUT: - - - ``name`` -- (default: ``None``) string; name given to the - tensor - - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to - denote the tensor; if none is provided, the LaTeX symbol - is set to ``name`` - - OUTPUT: - - - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` - - EXAMPLES: - - Automorphism tensor on a rank-2 free module (vector space) on `\QQ`:: - - sage: M = FiniteRankFreeModule(QQ, 2, name='M') - sage: a = M.automorphism_tensor('A') ; a - Automorphism tensor A on the Rank-2 free module M over the Rational Field - sage: a.parent() - Free module of type-(1,1) tensors on the - Rank-2 free module M over the Rational Field - sage: a.tensor_type() - (1, 1) - - See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` - for further documentation. - - """ - from free_module_tensor_spec import FreeModuleAutomorphismTensor - return FreeModuleAutomorphismTensor(self, name=name, - latex_name=latex_name) - - - def identity_tensor(self, name='Id', latex_name=None): - r""" - Construct the type-(1,1) tensor representing the identity map of - the free module ``self`` - - INPUT: - - - ``name`` -- (default: ``'Id'``) string; name given to the - identity tensor + automorphism - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to - denote the identity tensor; if none is provided, the LaTeX symbol + denote the automorphism; if none is provided, the LaTeX symbol is set to ``name`` OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityTensor` + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` EXAMPLES: - Identity tensor of a rank-3 free module:: + Automorphism of a rank-2 free `\ZZ`-module:: - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') - sage: a = M.identity_tensor() ; a - Identity tensor on the Rank-3 free module M over the Integer Ring - - The LaTeX symbol is set by default to `\mathrm{Id}`, but can - be changed:: - - sage: latex(a) - \mathrm{Id} - sage: a = M.identity_tensor(latex_name=r'\mathrm{1}') - sage: latex(a) - \mathrm{1} + sage: a = M.automorphism(matrix=[[1,2],[1,3]], basis=e, name='a') ; a + Automorphism a of the Rank-2 free module M over the Integer Ring + sage: a.parent() + General linear group of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [1 2] + [1 3] - The identity is a tensor of type `(1,1)` on the free module:: + An automorphism is a tensor of type (1,1):: - sage: a.parent() - Free module of type-(1,1) tensors on the - Rank-3 free module M over the Integer Ring sage: a.tensor_type() (1, 1) + sage: a.display(e) + a = e_0*e^0 + 2 e_0*e^1 + e_1*e^0 + 3 e_1*e^1 + + The automorphism components can be specified in a second step, as + components of a type-(1,1) tensor:: + + sage: a1 = M.automorphism(name='a') + sage: a1[e,:] = [[1,2],[1,3]] + sage: a1.matrix(e) + [1 2] + [1 3] + sage: a1 == a + True + + Component by component specification:: + + sage: a2 = M.automorphism(name='a') + sage: a2[0,0] = 1 # component set in the module's default basis (e) + sage: a2[0,1] = 2 + sage: a2[1,0] = 1 + sage: a2[1,1] = 3 + sage: a2.matrix(e) + [1 2] + [1 3] + sage: a2 == a + True See - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityTensor` - for further documentation. + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` + for more documentation. """ - from free_module_tensor_spec import FreeModuleIdentityTensor - return FreeModuleIdentityTensor(self, name=name, latex_name=latex_name) - + resu = self.general_linear_group().element_class(self, name=name, + latex_name=latex_name) + if matrix: + if basis is None: + basis = self.default_basis() + resu.set_comp(basis)[:] = matrix + return resu def sym_bilinear_form(self, name=None, latex_name=None): r""" - Construct a symmetric bilinear form on the free module. + Construct a symmetric bilinear form on the free module ``self``. INPUT: @@ -1187,7 +1697,7 @@ def sym_bilinear_form(self, name=None, latex_name=None): def _latex_(self): r""" - LaTeX representation of the object. + LaTeX representation of ``self``. EXAMPLES:: @@ -1241,7 +1751,7 @@ def rank(self): def zero(self): r""" - Return the zero element. + Return the zero element of ``self``. EXAMPLES: @@ -1265,15 +1775,15 @@ def zero(self): Components of the zero element with respect to some basis:: sage: e = M.basis('e') - sage: M.zero().comp(e)[:] + sage: M.zero()[e,:] [0, 0, 0] - sage: all(M.zero().comp(e)[i] == M.base_ring().zero() for i in M.irange()) + sage: all(M.zero()[e,i] == M.base_ring().zero() for i in M.irange()) True - sage: T.zero().comp(e)[:] + sage: T.zero()[e,:] [0 0 0] [0 0 0] [0 0 0] - sage: M.tensor_module(1,2).zero().comp(e)[:] + sage: M.tensor_module(1,2).zero()[e,:] [[[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]]] @@ -1283,7 +1793,7 @@ def zero(self): def dual(self): r""" - Return the dual module. + Return the dual module of ``self``. EXAMPLE: @@ -1302,12 +1812,12 @@ def dual(self): sage: M.dual().rank() 3 - It is formed by tensors of type (0,1), i.e. linear forms:: + It is formed by alternating forms of degree 1, i.e. linear forms:: - sage: M.dual() is M.tensor_module(0,1) + sage: M.dual() is M.dual_exterior_power(1) True sage: M.dual().an_element() - Type-(0,1) tensor on the Rank-3 free module M over the Integer Ring + Linear form on the Rank-3 free module M over the Integer Ring sage: a = M.linear_form() sage: a in M.dual() True @@ -1319,11 +1829,11 @@ def dual(self): True """ - return self.tensor_module(0,1) + return self.dual_exterior_power(1) def irange(self, start=None): r""" - Single index generator, labelling the elements of a basis. + Single index generator, labelling the elements of a basis of ``self``. INPUT: @@ -1368,7 +1878,7 @@ def irange(self, start=None): def default_basis(self): r""" - Return the default basis of the free module. + Return the default basis of the free module ``self``. The *default basis* is simply a basis whose name can be skipped in methods requiring a basis as an argument. By default, it is the first @@ -1402,7 +1912,7 @@ def default_basis(self): """ if self._def_basis is None: - print("No default basis has been defined on the " + str(self)) + print("No default basis has been defined on the {}".format(self)) return self._def_basis def set_default_basis(self, basis): @@ -1444,7 +1954,7 @@ def set_default_basis(self, basis): def print_bases(self): r""" - Display the bases that have been defined on the free module. + Display the bases that have been defined on the free module ``self``. Use the method :meth:`bases` to get the raw list of bases. @@ -1473,9 +1983,9 @@ def print_bases(self): """ if not self._known_bases: - print("No basis has been defined on the " + str(self)) + print("No basis has been defined on the {}".format(self)) else: - print("Bases defined on the " + str(self) + ":") + print("Bases defined on the {}:".format(self)) for basis in self._known_bases: item = " - " + basis._name if basis is self._def_basis: @@ -1484,7 +1994,8 @@ def print_bases(self): def bases(self): r""" - Return the list of bases that have been defined on the free module. + Return the list of bases that have been defined on the free module + ``self``. Use the method :meth:`print_bases` to get a formatted output with more information. @@ -1514,59 +2025,148 @@ def bases(self): def change_of_basis(self, basis1, basis2): r""" - Return a change of basis previously defined on the free module. + Return a module automorphism linking two bases defined on the free + module ``self``. + + If the automorphism has not been recorded yet (in the internal + dictionary ``self._basis_changes``), it is computed by transitivity, + i.e. by performing products of recorded changes of basis. INPUT: - - ``basis1`` -- basis 1, denoted `(e_i)` below - - ``basis2`` -- basis 2, denoted `(f_i)` below + - ``basis1`` -- a basis of ``self``, denoted `(e_i)` below + - ``basis2`` -- a basis of ``self``, denoted `(f_i)` below OUTPUT: - instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` - .. TODO:: - - Suppose there exists change of bases - `A \leftrightarrow B \leftrightarrow C`, then do we want to - compute the change of basis `A \leftrightarrow C`? - EXAMPLES: Changes of basis on a rank-2 free module:: - sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) + sage: FiniteRankFreeModule._clear_cache_() # for doctests only + sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1) sage: e = M.basis('e') - sage: a = M.automorphism_tensor() - sage: a[:] = [[1, 2], [-1, 3]] - sage: f = e.new_basis(a, 'f') - sage: M.change_of_basis(e,f) - Automorphism tensor on the Rank-2 free module M over the Rational Field - sage: M.change_of_basis(e,f)[:] - [ 1 2] - [-1 3] - sage: M.change_of_basis(f,e)[:] - [ 3/5 -2/5] - [ 1/5 1/5] + sage: f = M.basis('f', from_family=(e[1]+2*e[2], e[1]+3*e[2])) + sage: P = M.change_of_basis(e,f) ; P + Automorphism of the Rank-2 free module M over the Integer Ring + sage: P.matrix(e) + [1 1] + [2 3] + + Note that the columns of this matrix contain the components of the + elements of basis ``f`` w.r.t. to basis ``e``:: + + sage: f[1].display(e) + f_1 = e_1 + 2 e_2 + sage: f[2].display(e) + f_2 = e_1 + 3 e_2 + + The change of basis is cached:: + + sage: P is M.change_of_basis(e,f) + True + + Check of the change-of-basis automorphism:: + + sage: f[1] == P(e[1]) + True + sage: f[2] == P(e[2]) + True + + Check of the reverse change of basis:: + + sage: M.change_of_basis(f,e) == P^(-1) + True + + We have of course:: + + sage: M.change_of_basis(e,e) + Identity map of the Rank-2 free module M over the Integer Ring + sage: M.change_of_basis(e,e) is M.identity_map() + True + + Let us introduce a third basis on ``M``:: + + sage: h = M.basis('h', from_family=(3*e[1]+4*e[2], 5*e[1]+7*e[2])) + + The change of basis ``e`` --> ``h`` has been recorded directly from the + definition of ``h``:: + + sage: Q = M.change_of_basis(e,h) ; Q.matrix(e) + [3 5] + [4 7] + + The change of basis ``f`` --> ``h`` is computed by transitivity, i.e. + from the changes of basis ``f`` --> ``e`` and ``e`` --> ``h``:: + + sage: R = M.change_of_basis(f,h) ; R + Automorphism of the Rank-2 free module M over the Integer Ring + sage: R.matrix(e) + [-1 2] + [-2 3] + sage: R.matrix(f) + [ 5 8] + [-2 -3] + + Let us check that ``R`` is indeed the change of basis ``f`` --> ``h``:: + + sage: h[1] == R(f[1]) + True + sage: h[2] == R(f[2]) + True + + A related check is:: + + sage: R == Q*P^(-1) + True """ - if (basis1, basis2) not in self._basis_changes: - if (basis2, basis1) not in self._basis_changes: - raise TypeError(("the change of basis from '{!r}' to '{!r}'" - + "has not been defined on the {!r}" - ).format(basis1, basis2, self)) - inv = self._basis_changes[(basis2, basis1)].inverse() - self._basis_changes[(basis1, basis2)] = inv - return inv - return self._basis_changes[(basis1, basis2)] + if basis1 == basis2: + return self.identity_map() + bc = self._basis_changes + if (basis1, basis2) not in bc: + if basis1 not in self._known_bases: + raise TypeError("{} is not a basis of the {}".format(basis1, + self)) + if basis2 not in self._known_bases: + raise TypeError("{} is not a basis of the {}".format(basis2, + self)) + # Is the inverse already registred ? + if (basis2, basis1) in bc: + inv = bc[(basis2, basis1)].inverse() + bc[(basis1, basis2)] = inv + return inv + # Search for a third basis, basis say, such that either the changes + # basis1 --> basis and basis --> basis2 + # or + # basis2 --> basis and basis --> basis1 + # are known: + for basis in self._known_bases: + if (basis1, basis) in bc and (basis, basis2) in bc: + transf = bc[(basis, basis2)] * bc[(basis1, basis)] + bc[(basis1, basis2)] = transf + bc[(basis2, basis1)] = transf.inverse() + break + if (basis2, basis) in bc and (basis, basis1) in bc: + inv = bc[(basis, basis1)] * bc[(basis2, basis)] + bc[(basis2, basis1)] = inv + bc[(basis1, basis2)] = inv.inverse() + break + else: + raise ValueError(("the change of basis from '{!r}' to '{!r}'" + + " cannot be computed" + ).format(basis1, basis2)) + return bc[(basis1, basis2)] def set_change_of_basis(self, basis1, basis2, change_of_basis, - compute_inverse=True): + compute_inverse=True): r""" - Relates two bases by an automorphism. + Relates two bases by an automorphism of ``self``. This updates the internal dictionary ``self._basis_changes``. @@ -1575,7 +2175,7 @@ def set_change_of_basis(self, basis1, basis2, change_of_basis, - ``basis1`` -- basis 1, denoted `(e_i)` below - ``basis2`` -- basis 2, denoted `(f_i)` below - ``change_of_basis`` -- instance of class - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` describing the automorphism `P` that relates the basis `(e_i)` to the basis `(f_i)` according to `f_i = P(e_i)` - ``compute_inverse`` (default: ``True``) -- if set to ``True``, the @@ -1590,31 +2190,36 @@ def set_change_of_basis(self, basis1, basis2, change_of_basis, sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: e = M.basis('e') sage: f = M.basis('f') - sage: a = M.automorphism_tensor() + sage: a = M.automorphism() sage: a[:] = [[1, 2], [-1, 3]] sage: M.set_change_of_basis(e, f, a) The change of basis and its inverse have been recorded:: - sage: M.change_of_basis(e,f)[:] + sage: M.change_of_basis(e,f).matrix(e) [ 1 2] [-1 3] - sage: M.change_of_basis(f,e)[:] + sage: M.change_of_basis(f,e).matrix(e) [ 3/5 -2/5] [ 1/5 1/5] and are effective:: - sage: f[0].view(e) + sage: f[0].display(e) f_0 = e_0 - e_1 - sage: e[0].view(f) + sage: e[0].display(f) e_0 = 3/5 f_0 + 1/5 f_1 """ - from free_module_tensor_spec import FreeModuleAutomorphismTensor - if not isinstance(change_of_basis, FreeModuleAutomorphismTensor): - raise TypeError("the argument change_of_basis must be some " + - "instance of FreeModuleAutomorphismTensor") + if basis1 not in self._known_bases: + raise TypeError("{} is not a basis of the {}".format(basis1, + self)) + if basis2 not in self._known_bases: + raise TypeError("{} is not a basis of the {}".format(basis2, + self)) + if change_of_basis not in self.general_linear_group(): + raise TypeError("{} is not an automorphism of the {}".format( + change_of_basis, self)) self._basis_changes[(basis1, basis2)] = change_of_basis if compute_inverse: self._basis_changes[(basis2, basis1)] = change_of_basis.inverse() @@ -1653,7 +2258,7 @@ def hom(self, codomain, matrix_rep, bases=None, name=None, the matrix representation, ``basis_M`` being a basis of ``self`` and ``basis_N`` a basis of module `N` ; if None the pair formed by the default bases of each module is assumed. - - ``name`` -- (default: ``None``) string; name given to the + - ``name`` -- (default: ``None``) string; name given to the homomorphism - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the homomorphism; if None, ``name`` will be used. @@ -1689,7 +2294,7 @@ def hom(self, codomain, matrix_rep, bases=None, name=None, Call with all arguments specified:: - sage: phi = M.hom(N, [[3,2,1], [1,2,3]], bases=(ep, fp), + sage: phi = M.hom(N, [[3,2,1], [1,2,3]], bases=(ep, fp), ....: name='phi', latex_name=r'\phi') The parent:: @@ -1714,15 +2319,6 @@ def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): The returned object is a module morphism `\phi: M \rightarrow M`, where `M` is ``self``. - .. NOTE:: - - This method differs from :meth:`endomorphism_tensor` for it - returns an instance of Sage's :class:`~sage.categories.map.Map` - (actually an instance of the subclass - :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism`), - while :meth:`endomorphism_tensor` returns a tensor of type (1,1). - Note that there are coercions between these two types. - INPUT: - ``matrix_rep`` -- matrix of size rank(M)*rank(M) representing the @@ -1734,7 +2330,7 @@ def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): - ``basis`` -- (default: ``None``) basis of ``self`` defining the matrix representation; if None the default basis of ``self`` is assumed. - - ``name`` -- (default: ``None``) string; name given to the + - ``name`` -- (default: ``None``) string; name given to the endomorphism - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the endomorphism; if none is provided, ``name`` will be used. @@ -1761,7 +2357,7 @@ def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): Construction with full list of arguments (matrix given a basis different from the default one):: - sage: a = M.automorphism_tensor() ; a[0,1], a[1,0] = 1, -1 + sage: a = M.automorphism() ; a[0,1], a[1,0] = 1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") sage: phi = M.endomorphism([[1,-2], [-3,4]], basis=ep, name='phi', ....: latex_name=r'\phi') @@ -1774,40 +2370,59 @@ def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): [4 3] [2 1] + See :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + for more documentation. + """ from sage.categories.homset import End if basis is None: basis = self.default_basis() - return End(self)(matrix_rep, bases=(basis,basis), name=name, + return End(self)(matrix_rep, bases=(basis,basis), name=name, latex_name=latex_name) def identity_map(self): r""" - Return the identity endomorphism of the free module ``self``. + Return the identity map of the free module ``self``. OUTPUT: - the identity map of ``self`` as an instance of - :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` EXAMPLES: - Identity endomorphism of a rank-3 `\ZZ`-module:: + Identity map of a rank-3 `\ZZ`-module:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') sage: Id = M.identity_map() ; Id - Identity endomorphism of Rank-3 free module M over the Integer Ring + Identity map of the Rank-3 free module M over the Integer Ring + sage: Id.parent() + General linear group of the Rank-3 free module M over the Integer Ring sage: latex(Id) \mathrm{Id} + sage: Id.matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] - The identity endomorphism is actually the unit of End(M):: + The identity map is actually the identity element of GL(M):: - sage: Id is End(M).one() + sage: Id is M.general_linear_group().one() True - """ - from sage.categories.homset import End + It is also a tensor of type-(1,1) on M:: + + sage: Id.tensor_type() + (1, 1) + sage: Id.comp(e) + Kronecker delta of size 3x3 + sage: Id[:] + [1 0 0] + [0 1 0] + [0 0 1] + + """ if self._identity_map is None: - self._identity_map = End(self).one() + self._identity_map = self.general_linear_group().one() return self._identity_map diff --git a/src/sage/tensor/modules/format_utilities.py b/src/sage/tensor/modules/format_utilities.py index 42f5e889680..597761ee9ae 100644 --- a/src/sage/tensor/modules/format_utilities.py +++ b/src/sage/tensor/modules/format_utilities.py @@ -5,14 +5,14 @@ AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version - Joris Vankerschaver (2010): for the function :func:`is_atomic()` """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -326,5 +326,3 @@ def _latex_(self): """ return self.latex - - diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index 32862a8da4d..f68697cd06b 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -1,35 +1,37 @@ r""" Alternating forms on free modules -The class :class:`FreeModuleAltForm` implements alternating forms on a free -module of finite rank over a commutative ring. +Given a free module `M` of finite rank over a commutative ring `R` +and a positive integer `p`, an *alternating form of degree* `p` on `M` is +a map -It is a subclass of -:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`, alternating -forms being a special type of tensors. +.. MATH:: -A subclass of :class:`FreeModuleAltForm` is :class:`FreeModuleLinForm` for -alternating forms of degree 1, i.e. linear forms. + a:\ \underbrace{M\times\cdots\times M}_{p\ \; \mbox{times}} + \longrightarrow R -AUTHORS: +that (i) is multilinear and (ii) vanishes whenever any of two of its +arguments are equal. +An alternating form of degree `p` is a tensor on `M` of type `(0,p)`. + +Alternating forms are implemented via the class :class:`FreeModuleAltForm`, +which is a subclass of the generic tensor class +:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`. -- Eric Gourgoulhon, Michal Bejger (2014): initial version +AUTHORS: -.. TODO:: +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version - Implement a specific parent for alternating forms of a fixed - degree `p > 1`, with element :class:`FreeModuleAltForm` and with - coercion to tensor modules of type `(0,p)`. +REFERENCES: -.. TODO:: +- Chap. 23 of R. Godement: *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) +- Chap. 15 of S. Lang: *Algebra*, 3rd ed., Springer (New York) (2002) - Implement a specific parent for linear forms, with element - :class:`FreeModuleLinForm` and with coercion to tensor modules - of type `(0,1)`. """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -37,21 +39,27 @@ # http://www.gnu.org/licenses/ #****************************************************************************** -from sage.tensor.modules.free_module_tensor import FreeModuleTensor, FiniteRankFreeModuleElement +from sage.tensor.modules.free_module_tensor import FreeModuleTensor from sage.tensor.modules.comp import Components, CompFullyAntiSym class FreeModuleAltForm(FreeModuleTensor): r""" - Alternating form over a free module `M`. + Alternating form on a free module of finite rank over a commutative ring. + + This is a Sage *element* class, the corresponding *parent* class being + :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerFreeModule`. INPUT: - - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` - (must be an instance of :class:`FiniteRankFreeModule`) - - ``degree`` -- the degree of the alternating form (i.e. its tensor rank) - - ``name`` -- (default: ``None``) name given to the alternating form - - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the - alternating form; if none is provided, the LaTeX symbol is ``name`` + - ``fmodule`` -- free module `M` of finite rank over a commutative ring + `R`, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + - ``degree`` -- positive integer; the degree `p` of the + alternating form (i.e. its tensor rank) + - ``name`` -- (default: ``None``) string; name given to the alternating + form + - ``latex_name`` -- (default: ``None``) string; LaTeX symbol to denote the + alternating form; if none is provided, ``name`` is used EXAMPLES: @@ -63,12 +71,11 @@ class FreeModuleAltForm(FreeModuleTensor): Alternating form a of degree 2 on the Rank-3 free module M over the Integer Ring sage: type(a) - + sage: a.parent() - Free module of type-(0,2) tensors on the - Rank-3 free module M over the Integer Ring + 2nd exterior power of the dual of the Rank-3 free module M over the Integer Ring sage: a[1,2], a[2,3] = 4, -3 - sage: a.view() + sage: a.display(e) a = 4 e^1/\e^2 - 3 e^2/\e^3 The alternating form acting on the basis elements:: @@ -82,6 +89,130 @@ class FreeModuleAltForm(FreeModuleTensor): sage: a(e[2],e[1]) -4 + An alternating form of degree 1 is a linear form:: + + sage: b = M.linear_form('b') ; b + Linear form b on the Rank-3 free module M over the Integer Ring + sage: b[:] = [2,-1,3] # components w.r.t. the module's default basis (e) + + A linear form is a tensor of type (0,1):: + + sage: b.tensor_type() + (0, 1) + + It is an element of the dual module:: + + sage: b.parent() + Dual of the Rank-3 free module M over the Integer Ring + sage: b.parent() is M.dual() + True + + The members of a dual basis are linear forms:: + + sage: e.dual_basis()[1] + Linear form e^1 on the Rank-3 free module M over the Integer Ring + sage: e.dual_basis()[2] + Linear form e^2 on the Rank-3 free module M over the Integer Ring + sage: e.dual_basis()[3] + Linear form e^3 on the Rank-3 free module M over the Integer Ring + + Any linear form is expanded onto them:: + + sage: b.display(e) + b = 2 e^1 - e^2 + 3 e^3 + + In the above example, an equivalent writing would have been + ``b.display()``, since the basis ``e`` is the module's default basis. + A linear form maps module elements to ring elements:: + + sage: v = M([1,1,1]) + sage: b(v) + 4 + sage: b(v) in M.base_ring() + True + + Test of linearity:: + + sage: u = M([-5,-2,7]) + sage: b(3*u - 4*v) == 3*b(u) - 4*b(v) + True + + The standard tensor operations apply to alternating forms, like the + extraction of components with respect to a given basis:: + + sage: a[e,1,2] + 4 + sage: a[1,2] # since e is the module's default basis + 4 + sage: all( a[i,j] == - a[j,i] for i in {1,2,3} for j in {1,2,3} ) + True + + the tensor product:: + + sage: c = b*b ; c + Symmetric bilinear form b*b on the Rank-3 free module M over the + Integer Ring + sage: c.parent() + Free module of type-(0,2) tensors on the Rank-3 free module M over the + Integer Ring + sage: c.display(e) + b*b = 4 e^1*e^1 - 2 e^1*e^2 + 6 e^1*e^3 - 2 e^2*e^1 + e^2*e^2 + - 3 e^2*e^3 + 6 e^3*e^1 - 3 e^3*e^2 + 9 e^3*e^3 + + the contractions:: + + sage: s = a.contract(v) ; s + Linear form on the Rank-3 free module M over the Integer Ring + sage: s.parent() + Dual of the Rank-3 free module M over the Integer Ring + sage: s.display(e) + 4 e^1 - 7 e^2 + 3 e^3 + + or tensor arithmetics:: + + sage: s = 3*a + c ; s + Type-(0,2) tensor on the Rank-3 free module M over the Integer Ring + sage: s.parent() + Free module of type-(0,2) tensors on the Rank-3 free module M over the + Integer Ring + sage: s.display(e) + 4 e^1*e^1 + 10 e^1*e^2 + 6 e^1*e^3 - 14 e^2*e^1 + e^2*e^2 + - 12 e^2*e^3 + 6 e^3*e^1 + 6 e^3*e^2 + 9 e^3*e^3 + + Note that tensor arithmetics preserves the alternating character if both + operands are alternating:: + + sage: s = a - 2*a ; s + Alternating form of degree 2 on the Rank-3 free module M over the + Integer Ring + sage: s.parent() # note the difference with s = 3*a + c above + 2nd exterior power of the dual of the Rank-3 free module M over the + Integer Ring + sage: s == -a + True + + An operation specific to alternating forms is of course the exterior + product:: + + sage: s = a.wedge(b) ; s + Alternating form a/\b of degree 3 on the Rank-3 free module M over the + Integer Ring + sage: s.parent() + 3rd exterior power of the dual of the Rank-3 free module M over the + Integer Ring + sage: s.display(e) + a/\b = 6 e^1/\e^2/\e^3 + sage: s[1,2,3] == a[1,2]*b[3] + a[2,3]*b[1] + a[3,1]*b[2] + True + + The exterior product is nilpotent on linear forms:: + + sage: s = b.wedge(b) ; s + Alternating form b/\b of degree 2 on the Rank-3 free module M over the + Integer Ring + sage: s.display(e) + b/\b = 0 + """ def __init__(self, fmodule, degree, name=None, latex_name=None): r""" @@ -92,17 +223,25 @@ def __init__(self, fmodule, degree, name=None, latex_name=None): sage: from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') - sage: A = FreeModuleAltForm(M, 2, name='a') - sage: A[e,0,1] = 2 ; - sage: TestSuite(A).run(skip="_test_category") # see below + sage: a = FreeModuleAltForm(M, 2, name='a') + sage: a[e,0,1] = 2 + sage: TestSuite(a).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because a is not an + instance of a.parent().category().element_class. Actually alternating + forms must be constructed via ExtPowerFreeModule.element_class and + not by a direct call to FreeModuleAltForm:: + + sage: a1 = M.dual_exterior_power(2).element_class(M, 2, name='a') + sage: a1[e,0,1] = 2 + sage: TestSuite(a1).run() - In the above test suite, _test_category fails because A is not an - instance of A.parent().category().element_class. - """ FreeModuleTensor.__init__(self, fmodule, (0,degree), name=name, - latex_name=latex_name, antisym=range(degree)) - FreeModuleAltForm._init_derived(self) # initialization of derived quantities + latex_name=latex_name, antisym=range(degree), + parent=fmodule.dual_exterior_power(degree)) + FreeModuleAltForm._init_derived(self) # initialization of derived + # quantities def _repr_(self): r""" @@ -111,6 +250,10 @@ def _repr_(self): EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: M.alternating_form(1) + Linear form on the Rank-3 free module M over the Integer Ring + sage: M.alternating_form(1, name='a') + Linear form a on the Rank-3 free module M over the Integer Ring sage: M.alternating_form(2) Alternating form of degree 2 on the Rank-3 free module M over the Integer Ring @@ -118,10 +261,16 @@ def _repr_(self): Alternating form a of degree 2 on the Rank-3 free module M over the Integer Ring """ - description = "Alternating form " - if self._name is not None: - description += self._name + " " - description += "of degree {} on the {}".format(self._tensor_rank, self._fmodule) + if self._tensor_rank == 1: + description = "Linear form " + if self._name is not None: + description += self._name + " " + else: + description = "Alternating form " + if self._name is not None: + description += self._name + " " + description += "of degree {} ".format(self._tensor_rank) + description += "on the {}".format(self._fmodule) return description def _init_derived(self): @@ -155,14 +304,19 @@ def _new_instance(self): Create an instance of the same class as ``self``, on the same module and of the same degree. - EXAMPLE:: + EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.alternating_form(2, name='a') + sage: a = M.alternating_form(1) sage: a._new_instance() + Linear form on the Rank-3 free module M over the Integer Ring + sage: a._new_instance().parent() is a.parent() + True + sage: b = M.alternating_form(2, name='b') + sage: b._new_instance() Alternating form of degree 2 on the Rank-3 free module M over the Integer Ring - sage: a._new_instance().parent() is a.parent() + sage: b._new_instance().parent() is b.parent() True """ @@ -170,11 +324,21 @@ def _new_instance(self): def _new_comp(self, basis): r""" - Create some components in the given basis ``basis`` of ``self``. + Create some (uninitialized) components of ``self`` in a given basis. This method, which is already implemented in :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency. + INPUT: + + - ``basis`` -- basis of the free module on which ``self`` is defined + + OUTPUT: + + - an instance of :class:`~sage.tensor.modules.comp.CompFullyAntiSym`, + or of :class:`~sage.tensor.modules.comp.Components` if + the degree of ``self`` is 1. + EXAMPLES:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') @@ -183,6 +347,10 @@ def _new_comp(self, basis): sage: a._new_comp(e) Fully antisymmetric 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring + sage: a = M.alternating_form(1) + sage: a._new_comp(e) + 1-index components w.r.t. Basis (e_0,e_1,e_2) on the Rank-3 free + module M over the Integer Ring """ fmodule = self._fmodule # the base free module @@ -197,7 +365,7 @@ def _new_comp(self, basis): def degree(self): r""" - Return the degree of the alternating form. + Return the degree of ``self``. EXAMPLE:: @@ -210,13 +378,15 @@ def degree(self): return self._tensor_rank - def view(self, basis=None, format_spec=None): + def display(self, basis=None, format_spec=None): r""" - Display the alternating form ``self`` in terms of its expansion - onto a given cobasis. + Display the alternating form ``self`` in terms of its expansion w.r.t. + a given module basis. - The output is either text-formatted (console mode) or LaTeX-formatted - (notebook mode). + The expansion is actually performed onto exterior products of elements + of the cobasis (dual basis) associated with ``basis`` (see examples + below). The output is either text-formatted (console mode) or + LaTeX-formatted (notebook mode). INPUT: @@ -233,29 +403,38 @@ def view(self, basis=None, format_spec=None): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') + sage: e.dual_basis() + Dual basis (e^0,e^1,e^2) on the Rank-3 free module M over the Integer Ring sage: a = M.linear_form('a', latex_name=r'\alpha') sage: a[:] = [1,-3,4] - sage: a.view() + sage: a.display(e) a = e^0 - 3 e^1 + 4 e^2 - sage: latex(a.view()) # display in the notebook + sage: a.display() # a shortcut since e is M's default basis + a = e^0 - 3 e^1 + 4 e^2 + sage: latex(a.display()) # display in the notebook \alpha = e^0 -3 e^1 + 4 e^2 + A shortcut is ``disp()``:: + + sage: a.disp() + a = e^0 - 3 e^1 + 4 e^2 + Display of an alternating form of degree 2 on a rank-3 free module:: sage: b = M.alternating_form(2, 'b', latex_name=r'\beta') sage: b[0,1], b[0,2], b[1,2] = 3, 2, -1 - sage: b.view() + sage: b.display() b = 3 e^0/\e^1 + 2 e^0/\e^2 - e^1/\e^2 - sage: latex(b.view()) # display in the notebook + sage: latex(b.display()) # display in the notebook \beta = 3 e^0\wedge e^1 + 2 e^0\wedge e^2 -e^1\wedge e^2 Display of an alternating form of degree 3 on a rank-3 free module:: sage: c = M.alternating_form(3, 'c') sage: c[0,1,2] = 4 - sage: c.view() + sage: c.display() c = 4 e^0/\e^1/\e^2 - sage: latex(c.view()) + sage: latex(c.display()) c = 4 e^0\wedge e^1\wedge e^2 Display of a vanishing alternating form:: @@ -263,38 +442,42 @@ def view(self, basis=None, format_spec=None): sage: c[0,1,2] = 0 # the only independent component set to zero sage: c.is_zero() True - sage: c.view() + sage: c.display() c = 0 - sage: latex(c.view()) + sage: latex(c.display()) c = 0 sage: c[0,1,2] = 4 # value restored for what follows Display in a basis which is not the default one:: - sage: aut = M.automorphism_tensor() - sage: aut[:] = [[0,1,0], [0,0,-1], [1,0,0]] + sage: aut = M.automorphism(matrix=[[0,1,0], [0,0,-1], [1,0,0]], + ....: basis=e) sage: f = e.new_basis(aut, 'f') - sage: a.view(f) + sage: a.display(f) + a = 4 f^0 + f^1 + 3 f^2 + sage: a.disp(f) # shortcut notation a = 4 f^0 + f^1 + 3 f^2 - sage: b.view(f) + sage: b.display(f) b = -2 f^0/\f^1 - f^0/\f^2 - 3 f^1/\f^2 - sage: c.view(f) + sage: c.display(f) c = -4 f^0/\f^1/\f^2 The output format can be set via the argument ``output_formatter`` passed at the module construction:: - sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, output_formatter=Rational.numerical_approx) + sage: N = FiniteRankFreeModule(QQ, 3, name='N', start_index=1, + ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: b = N.alternating_form(2, 'b') sage: b[1,2], b[1,3], b[2,3] = 1/3, 5/2, 4 - sage: b.view() # default format (53 bits of precision) - b = 0.333333333333333 e^1/\e^2 + 2.50000000000000 e^1/\e^3 + 4.00000000000000 e^2/\e^3 + sage: b.display() # default format (53 bits of precision) + b = 0.333333333333333 e^1/\e^2 + 2.50000000000000 e^1/\e^3 + + 4.00000000000000 e^2/\e^3 The output format is then controled by the argument ``format_spec`` of - the method :meth:`view`:: + the method :meth:`display`:: - sage: b.view(format_spec=10) # 10 bits of precision + sage: b.display(format_spec=10) # 10 bits of precision b = 0.33 e^1/\e^2 + 2.5 e^1/\e^3 + 4.0 e^2/\e^3 """ @@ -334,8 +517,8 @@ def view(self, basis=None, format_spec=None): if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: - terms_latex.append(r"\left(" + coef_latex + r"\right)" + - basis_term_latex) + terms_latex.append(r"\left(" + coef_latex + \ + r"\right)" + basis_term_latex) if not terms_txt: expansion_txt = "0" else: @@ -365,14 +548,16 @@ def view(self, basis=None, format_spec=None): result.latex = latex(self) + " = " + expansion_latex return result + disp = display + def wedge(self, other): r""" - Exterior product of ``self`` with another alternating form ``other``. + Exterior product of ``self`` with the alternating form ``other``. INPUT: - - ``other`` -- another alternating form + - ``other`` -- an alternating form OUTPUT: @@ -392,11 +577,11 @@ def wedge(self, other): sage: c = a.wedge(b) ; c Alternating form A/\B of degree 2 on the Rank-3 free module M over the Integer Ring - sage: c.view() + sage: c.display() A/\B = 5 e^0/\e^1 - 6 e^0/\e^2 - 2 e^1/\e^2 sage: latex(c) A\wedge B - sage: latex(c.view()) + sage: latex(c.display()) A\wedge B = 5 e^0\wedge e^1 -6 e^0\wedge e^2 -2 e^1\wedge e^2 Test of the computation:: @@ -411,7 +596,7 @@ def wedge(self, other): sage: s = d.wedge(c) ; s Alternating form D/\A/\B of degree 3 on the Rank-3 free module M over the Integer Ring - sage: s.view() + sage: s.display() D/\A/\B = 34 e^0/\e^1/\e^2 Test of the computation:: @@ -474,190 +659,3 @@ def wedge(self, other): olname = '(' + olname + ')' result._latex_name = slname + r'\wedge ' + olname return result - - -#****************************************************************************** - -class FreeModuleLinForm(FreeModuleAltForm): - r""" - Linear form on a free module `M` over a commutative ring `R`. - - A *linear form* is a map `M \rightarrow R` that is linear. - - INPUT: - - - ``fmodule`` -- free module `M` of finite rank over a commutative ring `R` - (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: ``None``) name given to the linear form - - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the linear - form; if none is provided, the LaTeX symbol is set to ``name`` - - EXAMPLES: - - Linear form on a rank-3 free module:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.linear_form('A') ; a - Linear form A on the Rank-3 free module M over the Integer Ring - sage: a[:] = [2,-1,3] # components w.r.t. the module's default basis (e) - - The members of a dual basis are linear forms:: - - sage: e.dual_basis()[0] - Linear form e^0 on the Rank-3 free module M over the Integer Ring - sage: e.dual_basis()[1] - Linear form e^1 on the Rank-3 free module M over the Integer Ring - sage: e.dual_basis()[2] - Linear form e^2 on the Rank-3 free module M over the Integer Ring - - Any linear form is expanded onto them. In this example, the basis ``e`` - is the default basis and it is equivalent to ``a.view()``:: - - sage: a.view(basis=e) - A = 2 e^0 - e^1 + 3 e^2 - - A linear form maps module elements to ring elements:: - - sage: v = M([1,1,1]) - sage: a(v) - 4 - sage: a(v) in M.base_ring() - True - - Test of linearity:: - - sage: u = M([-5,-2,7]) - sage: a(3*u - 4*v) == 3*a(u) - 4*a(v) - True - - A linear form is an element of the dual module:: - - sage: a.parent() - Dual of the Rank-3 free module M over the Integer Ring - - As such, it is a tensor of type (0,1):: - - sage: a.tensor_type() - (0, 1) - - """ - def __init__(self, fmodule, name=None, latex_name=None): - r""" - TESTS:: - - sage: from sage.tensor.modules.free_module_alt_form import FreeModuleLinForm - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: L = FreeModuleLinForm(M, name='a') - sage: L[e,0] = -3 - sage: TestSuite(L).run(skip='_test_category') # see below - - In the above test suite, _test_category fails because L is not an - instance of L.parent().category().element_class. - - """ - FreeModuleAltForm.__init__(self, fmodule, 1, name=name, - latex_name=latex_name) - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M.linear_form('A') - Linear form A on the Rank-3 free module M over the Integer Ring - - """ - description = "Linear form " - if self._name is not None: - description += self._name + " " - description += "on the " + str(self._fmodule) - return description - - def _new_instance(self): - r""" - Create an instance of the same class as ``self`` and on the same - module. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.linear_form('A') - sage: a._new_instance() - Linear form on the Rank-3 free module M over the Integer Ring - sage: a._new_instance().parent() is a.parent() - True - - """ - return self.__class__(self._fmodule) - - def _new_comp(self, basis): - r""" - Create some components in the given basis ``basis`` of ``self``. - - This method, which is already implemented in - :meth:`FreeModuleAltForm._new_comp`, is redefined here for efficiency - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.linear_form('A') - sage: a._new_comp(e) - 1-index components w.r.t. Basis (e_0,e_1,e_2) - on the Rank-3 free module M over the Integer Ring - - """ - fmodule = self._fmodule # the base free module - return Components(fmodule._ring, basis, 1, start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) - - def __call__(self, vector): - r""" - The linear form acting on an element of the module. - - INPUT: - - - ``vector`` -- an element of the module (instance of - :class:`FiniteRankFreeModuleElement`) - - OUTPUT: - - - ring element `\langle \omega, v \rangle` - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.linear_form('A') - sage: a[:] = 2, -4, 1 - sage: v = M([1,5,-2]) - sage: a.__call__(v) - -20 - sage: a.__call__(v) == a(v) - True - - """ - if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("the argument must be a free module element") - basis = self.common_basis(vector) - if basis is None: - raise ValueError("no common basis for the components") - omega = self._components[basis] - vv = vector._components[basis] - resu = 0 - for i in self._fmodule.irange(): - resu += omega[[i]]*vv[[i]] - # Name and LaTeX symbol of the output: - if hasattr(resu, '_name'): - if self._name is not None and vector._name is not None: - resu._name = self._name + "(" + vector._name + ")" - if hasattr(resu, '_latex_name'): - if self._latex_name is not None and vector._latex_name is not None: - resu._latex_name = self._latex_name + r"\left(" + \ - vector._latex_name + r"\right)" - return resu - diff --git a/src/sage/tensor/modules/free_module_automorphism.py b/src/sage/tensor/modules/free_module_automorphism.py new file mode 100644 index 00000000000..e7ccb8c519a --- /dev/null +++ b/src/sage/tensor/modules/free_module_automorphism.py @@ -0,0 +1,1271 @@ +r""" +Free module automorphisms + +Given a free module `M` of finite rank over a commutative ring `R`, an +*automorphism* of `M` is a map + +.. MATH:: + + \phi:\ M \longrightarrow M + +that is linear (i.e. is a module homomorphism) and bijective. + +Automorphisms of a free module of finite rank are implemented via the class +:class:`FreeModuleAutomorphism`. + +AUTHORS: + +- Eric Gourgoulhon (2015): initial version + +REFERENCES: + +- Chaps. 15, 24 of R. Godement: *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) + +""" +#****************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.element import MultiplicativeGroupElement +from sage.tensor.modules.free_module_tensor import FreeModuleTensor + +class FreeModuleAutomorphism(FreeModuleTensor, MultiplicativeGroupElement): + r""" + Automorphism of a free module of finite rank over a commutative ring. + + This is a Sage *element* class, the corresponding *parent* class being + :class:`~sage.tensor.modules.free_module_linear_group.FreeModuleLinearGroup`. + + This class inherits from the classes + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` + and + :class:`~sage.structure.element.MultiplicativeGroupElement`. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank over a commutative ring + `R`, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + - ``name`` -- (default: ``None``) name given to the automorphism + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + automorphism; if none is provided, the LaTeX symbol is set to ``name`` + - ``is_identity`` -- (default: ``False``) determines whether the + constructed object is the identity automorphism, i.e. the identity map + of `M` considered as an automorphism (the identity element of the + general linear group) + + EXAMPLES: + + Automorphism of a rank-2 free module over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1) + sage: a = M.automorphism(name='a', latex_name=r'\alpha') ; a + Automorphism a of the Rank-2 free module M over the Integer Ring + sage: a.parent() + General linear group of the Rank-2 free module M over the Integer Ring + sage: a.parent() is M.general_linear_group() + True + sage: latex(a) + \alpha + + Setting the components of ``a`` w.r.t. a basis of module ``M``:: + + sage: e = M.basis('e') ; e + Basis (e_1,e_2) on the Rank-2 free module M over the Integer Ring + sage: a[:] = [[1,2],[1,3]] + sage: a.matrix(e) + [1 2] + [1 3] + sage: a(e[1]).display() + a(e_1) = e_1 + e_2 + sage: a(e[2]).display() + a(e_2) = 2 e_1 + 3 e_2 + + Actually, the components w.r.t. a given basis can be specified at the + construction of the object:: + + sage: a = M.automorphism(matrix=[[1,2],[1,3]], basis=e, name='a', + ....: latex_name=r'\alpha') ; a + Automorphism a of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [1 2] + [1 3] + + Since e is the module's default basis, it can be omitted in the argument + list:: + + sage: a == M.automorphism(matrix=[[1,2],[1,3]], name='a', + ....: latex_name=r'\alpha') + True + + The matrix of the automorphism can be obtained in any basis:: + + sage: f = M.basis('f', from_family=(3*e[1]+4*e[2], 5*e[1]+7*e[2])) ; f + Basis (f_1,f_2) on the Rank-2 free module M over the Integer Ring + sage: a.matrix(f) + [2 3] + [1 2] + + Automorphisms are tensors of type `(1,1)`:: + + sage: a.tensor_type() + (1, 1) + sage: a.tensor_rank() + 2 + + In particular, they can be displayed as such:: + + sage: a.display(e) + a = e_1*e^1 + 2 e_1*e^2 + e_2*e^1 + 3 e_2*e^2 + sage: a.display(f) + a = 2 f_1*f^1 + 3 f_1*f^2 + f_2*f^1 + 2 f_2*f^2 + + The automorphism acting on a module element:: + + sage: v = M([-2,3], name='v') ; v + Element v of the Rank-2 free module M over the Integer Ring + sage: a(v) + Element a(v) of the Rank-2 free module M over the Integer Ring + sage: a(v).display() + a(v) = 4 e_1 + 7 e_2 + + A second automorphism of the module ``M``:: + + sage: b = M.automorphism([[0,1],[-1,0]], name='b') ; b + Automorphism b of the Rank-2 free module M over the Integer Ring + sage: b.matrix(e) + [ 0 1] + [-1 0] + sage: b(e[1]).display() + b(e_1) = -e_2 + sage: b(e[2]).display() + b(e_2) = e_1 + + The composition of automorphisms is performed via the multiplication + operator:: + + sage: s = a*b ; s + Automorphism of the Rank-2 free module M over the Integer Ring + sage: s(v) == a(b(v)) + True + sage: s.matrix(f) + [ 11 19] + [ -7 -12] + sage: s.matrix(f) == a.matrix(f) * b.matrix(f) + True + + It is not commutative:: + + sage: a*b != b*a + True + + In other words, the parent of ``a`` and ``b``, i.e. the group + `\mathrm{GL}(M)`, is not abelian:: + + sage: M.general_linear_group() in CommutativeAdditiveGroups() + False + + The neutral element for the composition law is the module identity map:: + + sage: id = M.identity_map() ; id + Identity map of the Rank-2 free module M over the Integer Ring + sage: id.parent() + General linear group of the Rank-2 free module M over the Integer Ring + sage: id(v) == v + True + sage: id.matrix(f) + [1 0] + [0 1] + sage: id*a == a + True + sage: a*id == a + True + + The inverse of an automorphism is obtained via the method :meth:`inverse`, + or the operator ~, or the exponent -1:: + + sage: a.inverse() + Automorphism a^(-1) of the Rank-2 free module M over the Integer Ring + sage: a.inverse() is ~a + True + sage: a.inverse() is a^(-1) + True + sage: (a^(-1)).matrix(e) + [ 3 -2] + [-1 1] + sage: a*a^(-1) == id + True + sage: a^(-1)*a == id + True + sage: a^(-1)*s == b + True + sage: (a^(-1))(a(v)) == v + True + + The module's changes of basis are stored as automorphisms:: + + sage: M.change_of_basis(e,f) + Automorphism of the Rank-2 free module M over the Integer Ring + sage: M.change_of_basis(e,f).parent() + General linear group of the Rank-2 free module M over the Integer Ring + sage: M.change_of_basis(e,f).matrix(e) + [3 5] + [4 7] + sage: M.change_of_basis(f,e) == M.change_of_basis(e,f).inverse() + True + + The opposite of an automorphism is still an automorphism:: + + sage: -a + Automorphism -a of the Rank-2 free module M over the Integer Ring + sage: (-a).parent() + General linear group of the Rank-2 free module M over the Integer Ring + sage: (-a).matrix(e) == - (a.matrix(e)) + True + + Adding two automorphisms results in a generic type-(1,1) tensor:: + + sage: s = a + b ; s + Type-(1,1) tensor a+b on the Rank-2 free module M over the Integer Ring + sage: s.parent() + Free module of type-(1,1) tensors on the Rank-2 free module M over the + Integer Ring + sage: a[:], b[:], s[:] + ( + [1 2] [ 0 1] [1 3] + [1 3], [-1 0], [0 3] + ) + + To get the result as an endomorphism, one has to explicitely convert it via + the parent of endormophisms, `\mathrm{End}(M)`:: + + sage: s = End(M)(a+b) ; s + Generic endomorphism of Rank-2 free module M over the Integer Ring + sage: s(v) == a(v) + b(v) + True + sage: s.matrix(e) == a.matrix(e) + b.matrix(e) + True + sage: s.matrix(f) == a.matrix(f) + b.matrix(f) + True + + """ + def __init__(self, fmodule, name=None, latex_name=None, is_identity=False): + r""" + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism + sage: a = FreeModuleAutomorphism(M, name='a') + sage: a[e,:] = [[-1,0,0],[0,1,2],[0,1,3]] + sage: TestSuite(a).run(skip="_test_category") # see below + + In the above test suite, _test_category fails because a is not an + instance of a.parent().category().element_class. Actually automorphism + must be constructed via FreeModuleLinearGroup.element_class and + not by a direct call to FreeModuleAutomorphism:: + + sage: a = M.general_linear_group().element_class(M, name='a') + sage: a[e,:] = [[-1,0,0],[0,1,2],[0,1,3]] + sage: TestSuite(a).run() + + Test suite on the identity map:: + + sage: id = M.general_linear_group().one() + sage: TestSuite(id).run() + + Test suite on the automorphism obtained as GL.an_element():: + + sage: b = M.general_linear_group().an_element() + sage: TestSuite(b).run() + + """ + if is_identity: + if name is None: + name = 'Id' + if latex_name is None and name == 'Id': + latex_name = r'\mathrm{Id}' + FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, + latex_name=latex_name, + parent=fmodule.general_linear_group()) + # MultiplicativeGroupElement attributes: + # - none + # Local attributes: + self._is_identity = is_identity + self._inverse = None # inverse automorphism not set yet + self._matrices = {} + + #### SageObject methods #### + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: M.automorphism() + Automorphism of the 3-dimensional vector space M over the Rational Field + sage: M.automorphism(name='a') + Automorphism a of the 3-dimensional vector space M over the Rational Field + sage: M.identity_map() + Identity map of the 3-dimensional vector space M over the Rational Field + + """ + if self._is_identity: + description = "Identity map " + else: + description = "Automorphism " + if self._name is not None: + description += self._name + " " + description += "of the {}".format(self._fmodule) + return description + + #### End of SageObject methods #### + + #### FreeModuleTensor methods #### + + def _new_instance(self): + r""" + Create an instance of the same class as ``self``. + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: a = M.automorphism(name='a') + sage: a._new_instance() + Automorphism of the Rank-3 free module M over the Integer Ring + sage: Id = M.identity_map() + sage: Id._new_instance() + Automorphism of the Rank-3 free module M over the Integer Ring + + """ + return self.__class__(self._fmodule) + + def _del_derived(self): + r""" + Delete the derived quantities. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(QQ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism(name='a') + sage: a[e,:] = [[1,0,-1], [0,3,0], [0,0,2]] + sage: b = a.inverse() + sage: a._inverse + Automorphism a^(-1) of the 3-dimensional vector space M over the + Rational Field + sage: a._del_derived() + sage: a._inverse # has been reset to None + + """ + # First delete the derived quantities pertaining to FreeModuleTensor: + FreeModuleTensor._del_derived(self) + # Then reset the inverse automorphism to None: + if self._inverse is not None: + self._inverse._inverse = None # (it was set to self) + self._inverse = None + # and delete the matrices: + self._matrices.clear() + + def _new_comp(self, basis): + r""" + Create some (uninitialized) components of ``self`` in a given basis. + + INPUT: + + - ``basis`` -- basis of the free module on which ``self`` is defined + + OUTPUT: + + - an instance of :class:`~sage.tensor.modules.comp.Components` or, + if ``self`` is the identity, of the subclass + :class:`~sage.tensor.modules.comp.KroneckerDelta` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism() + sage: a._new_comp(e) + 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the Rank-3 free + module M over the Integer Ring + sage: id = M.identity_map() + sage: id._new_comp(e) + Kronecker delta of size 3x3 + sage: type(id._new_comp(e)) + + + """ + from comp import KroneckerDelta + if self._is_identity: + fmodule = self._fmodule + return KroneckerDelta(fmodule._ring, basis, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) + return FreeModuleTensor._new_comp(self, basis) + + + def components(self, basis=None, from_basis=None): + r""" + Return the components of ``self`` w.r.t to a given module basis. + + If the components are not known already, they are computed by the + tensor change-of-basis formula from components in another basis. + + INPUT: + + - ``basis`` -- (default: ``None``) basis in which the components are + required; if none is provided, the components are assumed to refer + to the module's default basis + - ``from_basis`` -- (default: ``None``) basis from which the + required components are computed, via the tensor change-of-basis + formula, if they are not known already in the basis ``basis``; + if none, a basis from which both the components and a change-of-basis + to ``basis`` are known is selected. + + OUTPUT: + + - components in the basis ``basis``, as an instance of the + class :class:`~sage.tensor.modules.comp.Components`, + or, for the identity automorphism, of the subclass + :class:`~sage.tensor.modules.comp.KroneckerDelta` + + EXAMPLES: + + Components of an automorphism on a rank-3 free `\ZZ`-module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.automorphism([[-1,0,0],[0,1,2],[0,1,3]], name='a') + sage: a.components(e) + 2-indices components w.r.t. Basis (e_1,e_2,e_3) on the Rank-3 free + module M over the Integer Ring + sage: a.components(e)[:] + [-1 0 0] + [ 0 1 2] + [ 0 1 3] + + Since e is the module's default basis, it can be omitted:: + + sage: a.components() is a.components(e) + True + + A shortcut is ``a.comp()``:: + + sage: a.comp() is a.components() + True + sage: a.comp(e) is a.components() + True + + Components in another basis:: + + sage: f1 = -e[2] + sage: f2 = 4*e[1] + 3*e[3] + sage: f3 = 7*e[1] + 5*e[3] + sage: f = M.basis('f', from_family=(f1,f2,f3)) + sage: a.components(f) + 2-indices components w.r.t. Basis (f_1,f_2,f_3) on the Rank-3 free + module M over the Integer Ring + sage: a.components(f)[:] + [ 1 -6 -10] + [ -7 83 140] + [ 4 -48 -81] + + Some check of the above matrix:: + + sage: a(f[1]).display(f) + a(f_1) = f_1 - 7 f_2 + 4 f_3 + sage: a(f[2]).display(f) + a(f_2) = -6 f_1 + 83 f_2 - 48 f_3 + sage: a(f[3]).display(f) + a(f_3) = -10 f_1 + 140 f_2 - 81 f_3 + + Components of the identity map:: + + sage: id = M.identity_map() + sage: id.components(e) + Kronecker delta of size 3x3 + sage: id.components(e)[:] + [1 0 0] + [0 1 0] + [0 0 1] + sage: id.components(f) + Kronecker delta of size 3x3 + sage: id.components(f)[:] + [1 0 0] + [0 1 0] + [0 0 1] + + """ + if self._is_identity: + if basis is None: + basis = self._fmodule._def_basis + if basis not in self._components: + self._components[basis] = self._new_comp(basis) + return self._components[basis] + else: + return FreeModuleTensor.components(self, basis=basis, + from_basis=from_basis) + + comp = components + + def set_comp(self, basis=None): + r""" + Return the components of ``self`` w.r.t. a given module basis for + assignment. + + The components with respect to other bases are deleted, in order to + avoid any inconsistency. To keep them, use the method :meth:`add_comp` + instead. + + INPUT: + + - ``basis`` -- (default: ``None``) basis in which the components are + defined; if none is provided, the components are assumed to refer to + the module's default basis + + OUTPUT: + + - components in the given basis, as an instance of the + class :class:`~sage.tensor.modules.comp.Components`; if such + components did not exist previously, they are created. + + EXAMPLE: + + Setting the components of an automorphism of a rank-3 free + `\ZZ`-module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism(name='a') + sage: a.set_comp(e) + 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the Rank-3 free + module M over the Integer Ring + sage: a.set_comp(e)[:] = [[1,0,0],[0,1,2],[0,1,3]] + sage: a.matrix(e) + [1 0 0] + [0 1 2] + [0 1 3] + + Since ``e`` is the module's default basis, one has:: + + sage: a.set_comp() is a.set_comp(e) + True + + The method :meth:`set_comp` can be used to modify a single component:: + + sage: a.set_comp(e)[0,0] = -1 + sage: a.matrix(e) + [-1 0 0] + [ 0 1 2] + [ 0 1 3] + + A short cut to :meth:`set_comp` is the bracket operator, with the basis + as first argument:: + + sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]] + sage: a.matrix(e) + [ 1 0 0] + [ 0 -1 2] + [ 0 1 -3] + sage: a[e,0,0] = -1 + sage: a.matrix(e) + [-1 0 0] + [ 0 -1 2] + [ 0 1 -3] + + The call to :meth:`set_comp` erases the components previously defined + in other bases; to keep them, use the method :meth:`add_comp` instead:: + + sage: f = M.basis('f', from_family=(-e[0], 3*e[1]+4*e[2], + ....: 5*e[1]+7*e[2])) ; f + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer + Ring + sage: a._components + {Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer + Ring: 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring} + sage: a.set_comp(f)[:] = [[-1,0,0], [0,1,0], [0,0,-1]] + + The components w.r.t. basis ``e`` have been erased:: + + sage: a._components + {Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer + Ring: 2-indices components w.r.t. Basis (f_0,f_1,f_2) on the + Rank-3 free module M over the Integer Ring} + + Of course, they can be computed from those in basis ``f`` by means of + a change-of-basis formula, via the method :meth:`comp` or + :meth:`matrix`:: + + sage: a.matrix(e) + [ -1 0 0] + [ 0 41 -30] + [ 0 56 -41] + + For the identity map, it is not permitted to set components:: + + sage: id = M.identity_map() + sage: id.set_comp(e) + Traceback (most recent call last): + ... + TypeError: the components of the identity map cannot be changed + + Indeed, the components are automatically set by a call to + :meth:`comp`:: + + sage: id.comp(e) + Kronecker delta of size 3x3 + sage: id.comp(f) + Kronecker delta of size 3x3 + + """ + if self._is_identity: + raise TypeError("the components of the identity map cannot be " + + "changed") + else: + return FreeModuleTensor.set_comp(self, basis=basis) + + def add_comp(self, basis=None): + r""" + + Return the components of ``self`` w.r.t. a given module basis for + assignment, keeping the components w.r.t. other bases. + + To delete the components w.r.t. other bases, use the method + :meth:`set_comp` instead. + + INPUT: + + - ``basis`` -- (default: ``None``) basis in which the components are + defined; if none is provided, the components are assumed to refer to + the module's default basis + + .. WARNING:: + + If the automorphism has already components in other bases, it + is the user's responsability to make sure that the components + to be added are consistent with them. + + OUTPUT: + + - components in the given basis, as an instance of the + class :class:`~sage.tensor.modules.comp.Components`; + if such components did not exist previously, they are created + + EXAMPLE: + + Adding components to an automorphism of a rank-3 free + `\ZZ`-module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism(name='a') + sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]] + sage: f = M.basis('f', from_family=(-e[0], 3*e[1]+4*e[2], + ....: 5*e[1]+7*e[2])) ; f + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer + Ring + sage: a.add_comp(f)[:] = [[1,0,0], [0, 80, 143], [0, -47, -84]] + + The components in basis ``e`` have been kept:: + + sage: a._components # random (dictionary output) + {Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer + Ring: 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the + Rank-3 free module M over the Integer Ring, + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer + Ring: 2-indices components w.r.t. Basis (f_0,f_1,f_2) on the + Rank-3 free module M over the Integer Ring} + + For the identity map, it is not permitted to invoke :meth:`add_comp`:: + + sage: id = M.identity_map() + sage: id.add_comp(e) + Traceback (most recent call last): + ... + TypeError: the components of the identity map cannot be changed + + Indeed, the components are automatically set by a call to + :meth:`comp`:: + + sage: id.comp(e) + Kronecker delta of size 3x3 + sage: id.comp(f) + Kronecker delta of size 3x3 + + """ + if self._is_identity: + raise TypeError("the components of the identity map cannot be " + + "changed") + else: + return FreeModuleTensor.add_comp(self, basis=basis) + + def __call__(self, *arg): + r""" + Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single + argument (module element). + + EXAMPLES: + + Call with a single argument: return a module element:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.automorphism([[-1,0,0],[0,1,2],[0,1,3]], name='a') + sage: v = M([2,1,4], name='v') + sage: s = a.__call__(v) ; s + Element a(v) of the Rank-3 free module M over the Integer Ring + sage: s.display() + a(v) = -2 e_1 + 9 e_2 + 13 e_3 + sage: s == a(v) + True + sage: s == a.contract(v) + True + + Call with two arguments (:class:`FreeModuleTensor` behaviour): return a + scalar:: + + sage: b = M.linear_form(name='b') + sage: b[:] = 7, 0, 2 + sage: a.__call__(b,v) + 12 + sage: a(b,v) == a.__call__(b,v) + True + sage: a(b,v) == s(b) + True + + Identity map with a single argument: return a module element:: + + sage: id = M.identity_map() + sage: s = id.__call__(v) ; s + Element v of the Rank-3 free module M over the Integer Ring + sage: s == v + True + sage: s == id(v) + True + sage: s == id.contract(v) + True + + Identity map with two arguments (:class:`FreeModuleTensor` behaviour): + return a scalar:: + + sage: id.__call__(b,v) + 22 + sage: id(b,v) == id.__call__(b,v) + True + sage: id(b,v) == b(v) + True + + """ + from free_module_tensor import FiniteRankFreeModuleElement + if len(arg) > 1: + # The automorphism acting as a type-(1,1) tensor on a pair + # (linear form, module element), returning a scalar: + if self._is_identity: + if len(arg) != 2: + raise TypeError("wrong number of arguments") + linform = arg[0] + if linform._tensor_type != (0,1): + raise TypeError("the first argument must be a linear form") + vector = arg[1] + if not isinstance(vector, FiniteRankFreeModuleElement): + raise TypeError("the second argument must be a module" + + " element") + return linform(vector) + else: # self is not the identity automorphism: + return FreeModuleTensor.__call__(self, *arg) + # The automorphism acting as such, on a module element, returning a + # module element: + vector = arg[0] + if not isinstance(vector, FiniteRankFreeModuleElement): + raise TypeError("the argument must be an element of a free module") + if self._is_identity: + return vector + basis = self.common_basis(vector) + t = self._components[basis] + v = vector._components[basis] + fmodule = self._fmodule + result = vector._new_instance() + for i in fmodule.irange(): + res = 0 + for j in fmodule.irange(): + res += t[[i,j]]*v[[j]] + result.set_comp(basis)[i] = res + # Name of the output: + result._name = None + if self._name is not None and vector._name is not None: + result._name = self._name + "(" + vector._name + ")" + # LaTeX symbol for the output: + result._latex_name = None + if self._latex_name is not None and vector._latex_name is not None: + result._latex_name = self._latex_name + r"\left(" + \ + vector._latex_name + r"\right)" + return result + + #### End of FreeModuleTensor methods #### + + #### MultiplicativeGroupElement methods #### + + def __invert__(self): + r""" + Return the inverse automorphism. + + OUTPUT: + + - instance of :class:`FreeModuleAutomorphism` representing the + automorphism that is the inverse of ``self``. + + EXAMPLES: + + Inverse of an automorphism of a rank-3 free module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism(name='a') + sage: a[e,:] = [[1,0,0],[0,-1,2],[0,1,-3]] + sage: a.inverse() + Automorphism a^(-1) of the Rank-3 free module M over the Integer + Ring + sage: a.inverse().parent() + General linear group of the Rank-3 free module M over the Integer + Ring + + Check that ``a.inverse()`` is indeed the inverse automorphism:: + + sage: a.inverse() * a + Identity map of the Rank-3 free module M over the Integer Ring + sage: a * a.inverse() + Identity map of the Rank-3 free module M over the Integer Ring + sage: a.inverse().inverse() == a + True + + Another check is:: + + sage: a.inverse().matrix(e) + [ 1 0 0] + [ 0 -3 -2] + [ 0 -1 -1] + sage: a.inverse().matrix(e) == (a.matrix(e))^(-1) + True + + The inverse is cached (as long as ``a`` is not modified):: + + sage: a.inverse() is a.inverse() + True + + If ``a`` is modified, the inverse is automatically recomputed:: + + sage: a[0,0] = -1 + sage: a.matrix(e) + [-1 0 0] + [ 0 -1 2] + [ 0 1 -3] + sage: a.inverse().matrix(e) # compare with above + [-1 0 0] + [ 0 -3 -2] + [ 0 -1 -1] + + Shortcuts for :meth:`inverse` are the operator ``~`` and the exponent + ``-1``:: + + sage: ~a is a.inverse() + True + sage: a^(-1) is a.inverse() + True + + The inverse of the identity map is of course itself:: + + sage: id = M.identity_map() + sage: id.inverse() is id + True + + and we have:: + + sage: a*a^(-1) == id + True + sage: a^(-1)*a == id + True + + """ + from sage.matrix.constructor import matrix + from comp import Components + if self._is_identity: + return self + if self._inverse is None: + if self._name is None: + inv_name = None + else: + inv_name = self._name + '^(-1)' + if self._latex_name is None: + inv_latex_name = None + else: + inv_latex_name = self._latex_name + r'^{-1}' + fmodule = self._fmodule + si = fmodule._sindex + nsi = fmodule._rank + si + self._inverse = self.__class__(fmodule, inv_name, inv_latex_name) + for basis in self._components: + try: + mat = self.matrix(basis) + except (KeyError, ValueError): + continue + mat_inv = mat.inverse() + cinv = Components(fmodule._ring, basis, 2, start_index=si, + output_formatter=fmodule._output_formatter) + for i in range(si, nsi): + for j in range(si, nsi): + cinv[i, j] = mat_inv[i-si,j-si] + self._inverse._components[basis] = cinv + self._inverse._inverse = self + return self._inverse + + inverse = __invert__ + + def _mul_(self, other): + r""" + Automorphism composition. + + This implements the group law of GL(M), M being the module of ``self``. + + INPUT: + + - ``other`` -- an automorphism of the same module as ``self`` + + OUPUT: + + - the automorphism resulting from the composition of ``other`` and + ``self.`` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[1,2],[1,3]]) + sage: b = M.automorphism([[3,4],[5,7]]) + sage: c = a._mul_(b) ; c + Automorphism of the Rank-2 free module M over the Integer Ring + sage: c.matrix() + [13 18] + [18 25] + + TESTS:: + + sage: c.parent() is a.parent() + True + sage: c.matrix() == a.matrix() * b.matrix() + True + sage: c(e[0]) == a(b(e[0])) + True + sage: c(e[1]) == a(b(e[1])) + True + sage: a.inverse()._mul_(c) == b + True + sage: c._mul_(b.inverse()) == a + True + sage: id = M.identity_map() + sage: id._mul_(a) == a + True + sage: a._mul_(id) == a + True + sage: a._mul_(a.inverse()) == id + True + sage: a.inverse()._mul_(a) == id + True + + """ + # No need for consistency check since self and other are guaranted + # to have the same parent. In particular, they are defined on the same + # free module. + # + # Special cases: + if self._is_identity: + return other + if other._is_identity: + return self + if other is self._inverse or self is other._inverse: + return self._fmodule.identity_map() + # General case: + fmodule = self._fmodule + resu = self.__class__(fmodule) + basis = self.common_basis(other) + if basis is None: + raise ValueError("no common basis for the composition") + # The composition is performed as a tensor contraction of the last + # index of self (position=1) and the first index of other (position=0): + resu._components[basis] = self._components[basis].contract(1, + other._components[basis],0) + return resu + + #### End of MultiplicativeGroupElement methods #### + + def __mul__(self, other): + r""" + Redefinition of + :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.__mul__` + so that * dispatches either to automorphism composition or to the + tensor product. + + EXAMPLES: + + Automorphism composition:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[1,2],[1,3]]) + sage: b = M.automorphism([[3,4],[5,7]]) + sage: s = a*b ; s + Automorphism of the Rank-2 free module M over the Integer Ring + sage: s.matrix() + [13 18] + [18 25] + sage: s.matrix() == a.matrix() * b.matrix() + True + sage: s(e[0]) == a(b(e[0])) + True + sage: s(e[1]) == a(b(e[1])) + True + sage: s.display() + 13 e_0*e^0 + 18 e_0*e^1 + 18 e_1*e^0 + 25 e_1*e^1 + + Tensor product:: + + sage: c = M.tensor((1,1)) ; c + Type-(1,1) tensor on the Rank-2 free module M over the Integer Ring + sage: c[:] = [[3,4],[5,7]] + sage: c[:] == b[:] # c and b have the same components + True + sage: s = a*c ; s + Type-(2,2) tensor on the Rank-2 free module M over the Integer Ring + sage: s.display() + 3 e_0*e_0*e^0*e^0 + 4 e_0*e_0*e^0*e^1 + 6 e_0*e_0*e^1*e^0 + + 8 e_0*e_0*e^1*e^1 + 5 e_0*e_1*e^0*e^0 + 7 e_0*e_1*e^0*e^1 + + 10 e_0*e_1*e^1*e^0 + 14 e_0*e_1*e^1*e^1 + 3 e_1*e_0*e^0*e^0 + + 4 e_1*e_0*e^0*e^1 + 9 e_1*e_0*e^1*e^0 + 12 e_1*e_0*e^1*e^1 + + 5 e_1*e_1*e^0*e^0 + 7 e_1*e_1*e^0*e^1 + 15 e_1*e_1*e^1*e^0 + + 21 e_1*e_1*e^1*e^1 + + """ + if isinstance(other, FreeModuleAutomorphism): + return self._mul_(other) # general linear group law + else: + return FreeModuleTensor.__mul__(self, other) # tensor product + + def __imul__(self, other): + r""" + Redefinition of + :meth:`sage.structure.element.ModuleElement.__imul__` + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.automorphism([[1,2],[1,3]], name='a') + sage: b = M.automorphism([[0,1],[-1,0]], name='b') + sage: mat_a0 = a.matrix(e) + sage: a.__imul__(b) + Automorphism of the Rank-2 free module M over the Integer Ring + sage: a *= b + sage: a.matrix(e) == mat_a0 * b.matrix(e) + True + + """ + return self.__mul__(other) + + def matrix(self, basis1=None, basis2=None): + r""" + Return the matrix of ``self`` w.r.t to a pair of bases. + + If the matrix is not known already, it is computed from the matrix in + another pair of bases by means of the change-of-basis formula. + + INPUT: + + - ``basis1`` -- (default: ``None``) basis of the free module on which + ``self`` is defined; if none is provided, the module's default basis + is assumed + - ``basis2`` -- (default: ``None``) basis of the free module on which + ``self`` is defined; if none is provided, ``basis2`` is set to + ``basis1`` + + OUTPUT: + + - the matrix representing representing the automorphism ``self`` w.r.t + to bases ``basis1`` and ``basis2``; more precisely, the columns of + this matrix are formed by the components w.r.t. ``basis2`` of + the images of the elements of ``basis1``. + + EXAMPLES: + + Matrices of an automorphism of a rank-3 free `\ZZ`-module:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M', start_index=1) + sage: e = M.basis('e') + sage: a = M.automorphism([[-1,0,0],[0,1,2],[0,1,3]], name='a') + sage: a.matrix(e) + [-1 0 0] + [ 0 1 2] + [ 0 1 3] + sage: a.matrix() + [-1 0 0] + [ 0 1 2] + [ 0 1 3] + sage: f = M.basis('f', from_family=(-e[2], 4*e[1]+3*e[3], 7*e[1]+5*e[3])) ; f + Basis (f_1,f_2,f_3) on the Rank-3 free module M over the Integer Ring + sage: a.matrix(f) + [ 1 -6 -10] + [ -7 83 140] + [ 4 -48 -81] + + Check of the above matrix:: + + sage: a(f[1]).display(f) + a(f_1) = f_1 - 7 f_2 + 4 f_3 + sage: a(f[2]).display(f) + a(f_2) = -6 f_1 + 83 f_2 - 48 f_3 + sage: a(f[3]).display(f) + a(f_3) = -10 f_1 + 140 f_2 - 81 f_3 + + Check of the change-of-basis formula:: + + sage: P = M.change_of_basis(e,f).matrix(e) + sage: a.matrix(f) == P^(-1) * a.matrix(e) * P + True + + Check that the matrix of the product of two automorphisms is the + product of their matrices:: + + sage: b = M.change_of_basis(e,f) ; b + Automorphism of the Rank-3 free module M over the Integer Ring + sage: b.matrix(e) + [ 0 4 7] + [-1 0 0] + [ 0 3 5] + sage: (a*b).matrix(e) == a.matrix(e) * b.matrix(e) + True + + Check that the matrix of the inverse automorphism is the inverse of the + automorphism's matrix:: + + sage: (~a).matrix(e) + [-1 0 0] + [ 0 3 -2] + [ 0 -1 1] + sage: (~a).matrix(e) == ~(a.matrix(e)) + True + + Matrices of the identity map:: + + sage: id = M.identity_map() + sage: id.matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + sage: id.matrix(f) + [1 0 0] + [0 1 0] + [0 0 1] + + """ + from sage.matrix.constructor import matrix + fmodule = self._fmodule + if basis1 is None: + basis1 = fmodule.default_basis() + elif basis1 not in fmodule.bases(): + raise TypeError("{} is not a basis on the {}".format(basis1, + fmodule)) + if basis2 is None: + basis2 = basis1 + elif basis2 not in fmodule.bases(): + raise TypeError("{} is not a basis on the {}".format(basis2, + fmodule)) + if (basis1, basis2) not in self._matrices: + if basis2 == basis1: + comp = self.components(basis1) + mat = [[comp[[i,j]] for j in fmodule.irange()] + for i in fmodule.irange()] + self._matrices[(basis1, basis1)] = matrix(mat) + else: + # 1/ determine the matrix w.r.t. basis1: + self.matrix(basis1) + # 2/ perform the change (basis1, basis1) --> (basis1, basis2): + raise NotImplementedError("basis1 != basis2 not implemented yet") + return self._matrices[(basis1, basis2)] + + def det(self): + r""" + Return the determinant of ``self``. + + OUTPUT: + + - element of the base ring of the module on which ``self`` is defined, + equal to the determinant of ``self``. + + EXAMPLES: + + Determinant of an automorphism on a `\ZZ`-module of rank 2:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[4,7],[3,5]], name='a') + sage: a.matrix(e) + [4 7] + [3 5] + sage: a.det() + -1 + sage: det(a) + -1 + sage: ~a.det() # determinant of the inverse automorphism + -1 + sage: id = M.identity_map() + sage: id.det() + 1 + + """ + self.matrix() # forces the update of the matrix in the module's default + # basis, to make sure that the dictionary self._matrices + # is not empty + return self._matrices.values()[0].det() # pick a random value in the + # dictionary self._matrices + # and compute the determinant + + def trace(self): + r""" + Return the trace of ``self``. + + OUTPUT: + + - element of the base ring of the module on which ``self`` is defined, + equal to the trace of ``self``. + + EXAMPLES: + + Trace of an automorphism on a `\ZZ`-module of rank 2:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: a = M.automorphism([[4,7],[3,5]], name='a') + sage: a.matrix(e) + [4 7] + [3 5] + sage: a.trace() + 9 + sage: id = M.identity_map() + sage: id.trace() + 2 + + """ + self.matrix() # forces the update of the matrix in the module's default + # basis, to make sure that the dictionary self._matrices + # is not empty + return self._matrices.values()[0].trace() # pick a random value in the + # dictionary self._matrices + # and compute the trace diff --git a/src/sage/tensor/modules/free_module_basis.py b/src/sage/tensor/modules/free_module_basis.py index ad7b84ea3b8..26ddb6a4e33 100644 --- a/src/sage/tensor/modules/free_module_basis.py +++ b/src/sage/tensor/modules/free_module_basis.py @@ -6,16 +6,20 @@ while the class :class:`FreeModuleCoBasis` implements the dual bases (i.e. bases of the dual module `M^*`). - AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version + +REFERENCES: +- Chap. 10 of R. Godement : *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) +- Chap. 3 of S. Lang : *Algebra*, 3rd ed., Springer (New York) (2002) """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -32,7 +36,7 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): INPUT: - - ``fmodule`` -- free module `M` (must be an instance of + - ``fmodule`` -- free module `M` (as an instance of :class:`FiniteRankFreeModule`) - ``symbol`` -- string; a letter (of a few letters) to denote a generic element of the basis @@ -49,8 +53,9 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): sage: e = FreeModuleBasis(M0, 'e') ; e Basis (e_0,e_1,e_2) on the Rank-3 free module M_0 over the Integer Ring - Instead of importing FreeModuleBasis in the global name space, one can - use the module's method :meth:`basis`:: + Instead of importing FreeModuleBasis in the global name space, it is + recommended to use the module's method + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.basis`:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') ; e @@ -65,12 +70,13 @@ class FreeModuleBasis(UniqueRepresentation, SageObject): True The LaTeX symbol can be set explicitely, as the second argument of - :meth:`basis`:: + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.basis`:: sage: latex(e) \left(e_0,e_1,e_2\right) sage: eps = M.basis('eps', r'\epsilon') ; eps - Basis (eps_0,eps_1,eps_2) on the Rank-3 free module M over the Integer Ring + Basis (eps_0,eps_1,eps_2) on the Rank-3 free module M over the Integer + Ring sage: latex(eps) \left(\epsilon_0,\epsilon_1,\epsilon_2\right) @@ -89,7 +95,7 @@ def __classcall_private__(cls, fmodule, symbol, latex_symbol=None): Normalize input to ensure a unique representation. TESTS:: - + sage: from sage.tensor.modules.free_module_basis import FreeModuleBasis sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = FreeModuleBasis(M, 'e', latex_symbol='e') @@ -148,6 +154,11 @@ def __init__(self, fmodule, symbol, latex_symbol=None): for t in fmodule._tensor_modules.itervalues(): t._zero_element._components[self] = t._zero_element._new_comp(self) # (since new components are initialized to zero) + # Initialization of the components w.r.t the current basis of the zero + # elements of all exterior powers constructed up to now + for t in fmodule._dual_exterior_powers.itervalues(): + t._zero_element._components[self] = t._zero_element._new_comp(self) + # (since new components are initialized to zero) # The dual basis: self._dual_basis = self._init_dual_basis() @@ -240,9 +251,9 @@ def dual_basis(self): sage: f = e.dual_basis() ; f Dual basis (e^1,e^2,e^3) on the Rank-3 free module M over the Integer Ring - Let us check that the elements of f are tensors of type (0,1) on M:: + Let us check that the elements of f are elements of the dual of M:: - sage: f[1] in M.tensor_module(0,1) + sage: f[1] in M.dual() True sage: f[1] Linear form e^1 on the Rank-3 free module M over the Integer Ring @@ -350,7 +361,7 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): INPUT: - ``change_of_basis`` -- instance of - :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` describing the automorphism `P` that relates the current basis `(e_i)` (described by ``self``) to the new basis `(n_i)` according to `n_i = P(e_i)` @@ -366,28 +377,29 @@ def new_basis(self, change_of_basis, symbol, latex_symbol=None): EXAMPLES: - Change of basis on a rank-2 free module:: + Change of basis on a vector space of dimension 2:: sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) sage: e = M.basis('e') - sage: a = M.automorphism_tensor() + sage: a = M.automorphism() sage: a[:] = [[1, 2], [-1, 3]] sage: f = e.new_basis(a, 'f') ; f - Basis (f_1,f_2) on the Rank-2 free module M over the Rational Field - sage: f[1].view() + Basis (f_1,f_2) on the 2-dimensional vector space M over the + Rational Field + sage: f[1].display() f_1 = e_1 - e_2 - sage: f[2].view() + sage: f[2].display() f_2 = 2 e_1 + 3 e_2 - sage: e[1].view(f) + sage: e[1].display(f) e_1 = 3/5 f_1 + 1/5 f_2 - sage: e[2].view(f) + sage: e[2].display(f) e_2 = -2/5 f_1 + 1/5 f_2 """ - from free_module_tensor_spec import FreeModuleAutomorphismTensor - if not isinstance(change_of_basis, FreeModuleAutomorphismTensor): + from free_module_automorphism import FreeModuleAutomorphism + if not isinstance(change_of_basis, FreeModuleAutomorphism): raise TypeError("the argument change_of_basis must be some " + - "instance of FreeModuleAutomorphismTensor") + "instance of FreeModuleAutomorphism") fmodule = self._fmodule # self._new_instance used instead of FreeModuleBasis for a correct # construction in case of derived classes: @@ -456,10 +468,9 @@ class FreeModuleCoBasis(UniqueRepresentation, SageObject): sage: f = FreeModuleCoBasis(e, 'f') ; f Dual basis (f^1,f^2,f^3) on the Rank-3 free module M over the Integer Ring - Let us check that the elements of ``f`` are tensors of type `(0,1)` - on ``M``:: + Let us check that the elements of ``f`` are in the dual of ``M``:: - sage: f[1] in M.tensor_module(0,1) + sage: f[1] in M.dual() True sage: f[1] Linear form f^1 on the Rank-3 free module M over the Integer Ring @@ -572,7 +583,6 @@ def __getitem__(self, index): si = self._fmodule._sindex i = index - si if i < 0 or i > n-1: - raise IndexError("out of range: {} not in [{},{}]".format(i+si. si,n-1+si)) + raise IndexError("out of range: {} not in [{},{}]".format(i+si, + si, n-1+si)) return self._form[i] - - diff --git a/src/sage/tensor/modules/free_module_homset.py b/src/sage/tensor/modules/free_module_homset.py index 614684bd1ac..889056e9658 100644 --- a/src/sage/tensor/modules/free_module_homset.py +++ b/src/sage/tensor/modules/free_module_homset.py @@ -1,18 +1,23 @@ r""" Sets of morphisms between free modules -The class :class:`FreeModuleHomset` implements sets (actually free modules) of -homomorphisms between two free modules of finite rank over the same -commutative ring. +The class :class:`FreeModuleHomset` implements sets of homomorphisms between +two free modules of finite rank over the same commutative ring. AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version + +REFERENCES: + +- Chaps. 13, 14 of R. Godement : *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) +- Chap. 3 of S. Lang : *Algebra*, 3rd ed., Springer (New York) (2002) """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -22,29 +27,30 @@ from sage.categories.homset import Homset from sage.tensor.modules.free_module_morphism import FiniteRankFreeModuleMorphism +from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism from sage.tensor.modules.free_module_tensor import FreeModuleTensor -from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityTensor class FreeModuleHomset(Homset): r""" - Set of homomorphisms between free modules of finite rank. + Set of homomorphisms between free modules of finite rank over a + commutative ring. Given two free modules `M` and `N` of respective ranks `m` and `n` over a commutative ring `R`, the class :class:`FreeModuleHomset` implements the - set `\mathrm{Hom}(M,N)` of homomorphisms `M\rightarrow N`. This is a - *parent* class, whose *elements* are instances of - :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` - + set `\mathrm{Hom}(M,N)` of homomorphisms `M\rightarrow N`. The set `\mathrm{Hom}(M,N)` is actually a free module of rank `mn` over `R`, but this aspect is not taken into account here. + This is a Sage *parent* class, whose *element* class is + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism`. + INPUT: - - ``fmodule1`` -- free module `M` (domain of the homomorphisms); must be - an instance of + - ``fmodule1`` -- free module `M` (domain of the homomorphisms), as an + instance of :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` - - ``fmodule2`` -- free module `N` (codomain of the homomorphisms); must be - an instance of + - ``fmodule2`` -- free module `N` (codomain of the homomorphisms), as an + instance of :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` - ``name`` -- (default: ``None``) string; name given to the hom-set; if none is provided, Hom(M,N) will be used @@ -72,7 +78,7 @@ class FreeModuleHomset(Homset): True The LaTeX formatting is:: - + sage: latex(H) \mathrm{Hom}\left(M,N\right) @@ -155,6 +161,35 @@ class FreeModuleHomset(Homset): See :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` for examples of the above coercions. + There is a coercion `\mathrm{GL}(M) \rightarrow \mathrm{End}(M)`, since + every automorphism is an endomorphism:: + + sage: GL = M.general_linear_group() ; GL + General linear group of the Rank-3 free module M over the Integer Ring + sage: End(M).has_coerce_map_from(GL) + True + + Of course, there is no coercion in the reverse direction, since only + bijective endomorphisms are automorphisms:: + + sage: GL.has_coerce_map_from(End(M)) + False + + The coercion `\mathrm{GL}(M) \rightarrow \mathrm{End}(M)` in action:: + + sage: a = GL.an_element() ; a + Automorphism of the Rank-3 free module M over the Integer Ring + sage: a.matrix(e) + [ 1 0 0] + [ 0 -1 0] + [ 0 0 1] + sage: ea = End(M)(a) ; ea + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: ea.matrix(e) + [ 1 0 0] + [ 0 -1 0] + [ 0 0 1] + """ Element = FiniteRankFreeModuleMorphism @@ -162,7 +197,7 @@ class FreeModuleHomset(Homset): def __init__(self, fmodule1, fmodule2, name=None, latex_name=None): r""" TESTS:: - + sage: from sage.tensor.modules.free_module_homset import FreeModuleHomset sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') @@ -178,13 +213,13 @@ def __init__(self, fmodule1, fmodule2, name=None, latex_name=None): """ from finite_rank_free_module import FiniteRankFreeModule if not isinstance(fmodule1, FiniteRankFreeModule): - raise TypeError("fmodule1 = " + str(fmodule1) + " is not an " + + raise TypeError("fmodule1 = {} is not an ".format(fmodule1) + "instance of FiniteRankFreeModule") if not isinstance(fmodule2, FiniteRankFreeModule): - raise TypeError("fmodule1 = " + str(fmodule2) + " is not an " + + raise TypeError("fmodule2 = {} is not an ".format(fmodule2) + "instance of FiniteRankFreeModule") if fmodule1.base_ring() != fmodule2.base_ring(): - raise TypeError("the domain and codomain are not defined over " + + raise TypeError("the domain and codomain are not defined over " + "the same ring") Homset.__init__(self, fmodule1, fmodule2) if name is None: @@ -255,7 +290,7 @@ def __call__(self, *args, **kwds): from sage.structure.parent import Parent return Parent.__call__(self, *args, **kwds) - #### Methods required for any Parent + #### Methods required for any Parent def _element_constructor_(self, matrix_rep, bases=None, name=None, latex_name=None, is_identity=False): @@ -289,7 +324,7 @@ def _element_constructor_(self, matrix_rep, bases=None, name=None, sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') sage: H = Hom(M,N) - sage: phi = H._element_constructor_([[2,-1,3], [1,0,-4]], bases=(e,f), + sage: phi = H._element_constructor_([[2,-1,3], [1,0,-4]], bases=(e,f), ....: name='phi', latex_name=r'\phi') sage: phi Generic morphism: @@ -305,8 +340,8 @@ def _element_constructor_(self, matrix_rep, bases=None, name=None, Construction of an endomorphism:: sage: EM = End(M) - sage: phi = EM._element_constructor_([[1,2,3],[4,5,6],[7,8,9]], name='phi', - ....: latex_name=r'\phi') + sage: phi = EM._element_constructor_([[1,2,3],[4,5,6],[7,8,9]], + ....: name='phi', latex_name=r'\phi') sage: phi Generic endomorphism of Rank-3 free module M over the Integer Ring sage: phi.matrix(e,e) @@ -318,7 +353,7 @@ def _element_constructor_(self, matrix_rep, bases=None, name=None, sage: a = M.tensor((1,1)) sage: a[:] = [[1,2,3],[4,5,6],[7,8,9]] - sage: EM = End(M) + sage: EM = End(M) sage: phi_a = EM._element_constructor_(a) ; phi_a Generic endomorphism of Rank-3 free module M over the Integer Ring sage: phi_a.matrix(e,e) @@ -335,6 +370,8 @@ def _element_constructor_(self, matrix_rep, bases=None, name=None, """ if isinstance(matrix_rep, FreeModuleTensor): # coercion of a type-(1,1) tensor to an endomorphism + # (this includes automorphisms, since the class + # FreeModuleAutomorphism inherits from FreeModuleTensor) tensor = matrix_rep # for readability if tensor.tensor_type() == (1,1) and \ self.is_endomorphism_set() and \ @@ -344,12 +381,16 @@ def _element_constructor_(self, matrix_rep, bases=None, name=None, fmodule = tensor.base_module() mat = [[ tcomp[[i,j]] for j in fmodule.irange()] \ for i in fmodule.irange()] - resu = self.element_class(self, mat, bases=(basis,basis), + if isinstance(tensor, FreeModuleAutomorphism): + is_identity = tensor._is_identity + else: + is_identity = False + resu = self.element_class(self, mat, bases=(basis,basis), name=tensor._name, latex_name=tensor._latex_name, - is_identity=isinstance(tensor, FreeModuleIdentityTensor)) + is_identity=is_identity) else: - raise TypeError("cannot coerce the " + str(tensor) + - " to an element of " + str(self)) + raise TypeError("cannot coerce the {}".format(tensor) + + " to an element of {}".format(self)) else: # Standard construction: resu = self.element_class(self, matrix_rep, bases=bases, name=name, @@ -389,10 +430,11 @@ def _coerce_map_from_(self, other): EXAMPLES: - Only the module of type-(1,1) tensors coerce to self, if the latter + The module of type-(1,1) tensors coerces to ``self``, if the latter is some endomorphism set:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') sage: End(M)._coerce_map_from_(M.tensor_module(1,1)) True sage: End(M).has_coerce_map_from(M.tensor_module(1,1)) @@ -400,14 +442,24 @@ def _coerce_map_from_(self, other): sage: End(M)._coerce_map_from_(M.tensor_module(1,2)) False + The general linear group coerces to the endomorphism ring:: + + sage: End(M)._coerce_map_from_(M.general_linear_group()) + True + """ - from tensor_free_module import TensorFreeModule + from sage.tensor.modules.tensor_free_module import TensorFreeModule + from sage.tensor.modules.free_module_linear_group import \ + FreeModuleLinearGroup if isinstance(other, TensorFreeModule): # Coercion of a type-(1,1) tensor to an endomorphism: if other.tensor_type() == (1,1): - if self.is_endomorphism_set() and \ - other.base_module() is self.domain(): - return True + return self.is_endomorphism_set() and \ + other.base_module() is self.domain() + if isinstance(other, FreeModuleLinearGroup): + # Coercion of an automorphism to an endomorphism: + return self.is_endomorphism_set() and \ + other.base_module() is self.domain() return False - #### End of methods required for any Parent + #### End of methods required for any Parent diff --git a/src/sage/tensor/modules/free_module_linear_group.py b/src/sage/tensor/modules/free_module_linear_group.py new file mode 100644 index 00000000000..49297d09bfc --- /dev/null +++ b/src/sage/tensor/modules/free_module_linear_group.py @@ -0,0 +1,567 @@ +r""" +General linear group of a free module + +The set `\mathrm{GL}(M)` of automorphisms (i.e. invertible endomorphims) of a +free module of finite rank `M` is a group under composition of automorphisms, +named the *general linear group* of `M`. In other words, `\mathrm{GL}(M)` is +the group of units (i.e. invertible elements) of `\mathrm{End}(M)`, the +endomorphism ring of `M`. + +The group `\mathrm{GL}(M)` is implemented via the class +:class:`FreeModuleLinearGroup`. + +AUTHORS: + +- Eric Gourgoulhon (2015): initial version + +REFERENCES: + +- Chap. 15 of R. Godement: *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) + +""" +#****************************************************************************** +# Copyright (C) 2015 Eric Gourgoulhon +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#****************************************************************************** + +from sage.structure.unique_representation import UniqueRepresentation +from sage.structure.parent import Parent +from sage.categories.groups import Groups +from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule +from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism + + +class FreeModuleLinearGroup(UniqueRepresentation, Parent): + r""" + General linear group of a free module of finite rank over a commutative + ring. + + Given a free module of finite rank `M` over a commutative ring `R`, the + *general linear group* of `M` is the group `\mathrm{GL}(M)` of + automorphisms (i.e. invertible endomorphims) of `M`. It is the group of + units (i.e. invertible elements) of `\mathrm{End}(M)`, the endomorphism + ring of `M`. + + This is a Sage *parent* class, whose *element* class is + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism`. + + INPUT: + + - ``fmodule`` -- free module `M` of finite rank over a commutative ring + `R`, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` + + EXAMPLES: + + General linear group of a free `\ZZ`-module of rank 3:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: from sage.tensor.modules.free_module_linear_group import FreeModuleLinearGroup + sage: GL = FreeModuleLinearGroup(M) ; GL + General linear group of the Rank-3 free module M over the Integer Ring + + Instead of importing FreeModuleLinearGroup in the global name space, it is + recommended to use the module's method + :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.general_linear_group`:: + + sage: GL = M.general_linear_group() ; GL + General linear group of the Rank-3 free module M over the Integer Ring + sage: latex(GL) + \mathrm{GL}\left( M \right) + + As most parents, the general linear group has a unique instance:: + + sage: GL is M.general_linear_group() + True + + `\mathrm{GL}(M)` is in the category of groups:: + + sage: GL.category() + Category of groups + sage: GL in Groups() + True + + ``GL`` is a *parent* object, whose elements are automorphisms of `M`, + represented by instances of the class + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism`:: + + sage: GL.Element + + sage: a = GL.an_element() ; a + Automorphism of the Rank-3 free module M over the Integer Ring + sage: a.matrix(e) + [ 1 0 0] + [ 0 -1 0] + [ 0 0 1] + sage: a in GL + True + sage: GL.is_parent_of(a) + True + + As an endomorphism, ``a`` maps elements of `M` to elements of `M`:: + + sage: v = M.an_element() ; v + Element of the Rank-3 free module M over the Integer Ring + sage: v.display() + e_0 + e_1 + e_2 + sage: a(v) + Element of the Rank-3 free module M over the Integer Ring + sage: a(v).display() + e_0 - e_1 + e_2 + + An automorphism can also be viewed as a tensor of type (1,1) on `M`:: + + sage: a.tensor_type() + (1, 1) + sage: a.display(e) + e_0*e^0 - e_1*e^1 + e_2*e^2 + sage: type(a) + + + As for any group, the identity element is obtained by the method + :meth:`one`:: + + sage: id = GL.one() ; id + Identity map of the Rank-3 free module M over the Integer Ring + sage: id*a == a + True + sage: a*id == a + True + sage: a*a^(-1) == id + True + sage: a^(-1)*a == id + True + + The identity element is of course the identity map of the module `M`:: + + sage: id(v) == v + True + sage: id.matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + + The module's changes of basis are stored as elements of the general linear + group:: + + sage: f = M.basis('f', from_family=(-e[1], 4*e[0]+3*e[2], 7*e[0]+5*e[2])) + sage: f + Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring + sage: M.change_of_basis(e,f) + Automorphism of the Rank-3 free module M over the Integer Ring + sage: M.change_of_basis(e,f) in GL + True + sage: M.change_of_basis(e,f).parent() + General linear group of the Rank-3 free module M over the Integer Ring + sage: M.change_of_basis(e,f).matrix(e) + [ 0 4 7] + [-1 0 0] + [ 0 3 5] + sage: M.change_of_basis(e,f) == M.change_of_basis(f,e).inverse() + True + + Since every automorphism is an endomorphism, there is a coercion + `\mathrm{GL}(M) \rightarrow \mathrm{End}(M)` (the endomorphism ring of + module `M`):: + + sage: End(M).has_coerce_map_from(GL) + True + + (see :class:`~sage.tensor.modules.free_module_homset.FreeModuleHomset` for + details), but not in the reverse direction, since only bijective + endomorphisms are automorphisms:: + + sage: GL.has_coerce_map_from(End(M)) + False + + A bijective endomorphism can be converted to an element of + `\mathrm{GL}(M)`:: + + sage: h = M.endomorphism([[1,0,0], [0,-1,2], [0,1,-3]]) ; h + Generic endomorphism of Rank-3 free module M over the Integer Ring + sage: h.parent() is End(M) + True + sage: ah = GL(h) ; ah + Automorphism of the Rank-3 free module M over the Integer Ring + sage: ah.parent() is GL + True + + As maps `M\rightarrow M`, ``ah`` and ``h`` are identical:: + + sage: v # recall + Element of the Rank-3 free module M over the Integer Ring + sage: ah(v) == h(v) + True + sage: ah.matrix(e) == h.matrix(e) + True + + Of course, non-invertible endomorphisms cannot be converted to elements of + `\mathrm{GL}(M)`:: + + sage: GL(M.endomorphism([[0,0,0], [0,-1,2], [0,1,-3]])) + Traceback (most recent call last): + ... + TypeError: the Generic endomorphism of Rank-3 free module M over the + Integer Ring is not invertible + + Similarly, there is a coercion `\mathrm{GL}(M)\rightarrow T^{(1,1)}(M)` + (module of type-(1,1) tensors):: + + sage: M.tensor_module(1,1).has_coerce_map_from(GL) + True + + (see :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` for + details), but not in the reverse direction, since not every type-(1,1) + tensor can be considered as an automorphism:: + + sage: GL.has_coerce_map_from(M.tensor_module(1,1)) + False + + Invertible type-(1,1) tensors can be converted to automorphisms:: + + sage: t = M.tensor((1,1), name='t') + sage: t[e,:] = [[-1,0,0], [0,1,2], [0,1,3]] + sage: at = GL(t) ; at + Automorphism t of the Rank-3 free module M over the Integer Ring + sage: at.matrix(e) + [-1 0 0] + [ 0 1 2] + [ 0 1 3] + sage: at.matrix(e) == t[e,:] + True + + Non-invertible ones cannot:: + + sage: t0 = M.tensor((1,1), name='t_0') + sage: t0[e,0,0] = 1 + sage: t0[e,:] # the matrix is clearly not invertible + [1 0 0] + [0 0 0] + [0 0 0] + sage: GL(t0) + Traceback (most recent call last): + ... + TypeError: the Type-(1,1) tensor t_0 on the Rank-3 free module M over + the Integer Ring is not invertible + sage: t0[e,1,1], t0[e,2,2] = 2, 3 + sage: t0[e,:] # the matrix is not invertible in Mat_3(ZZ) + [1 0 0] + [0 2 0] + [0 0 3] + sage: GL(t0) + Traceback (most recent call last): + ... + TypeError: the Type-(1,1) tensor t_0 on the Rank-3 free module M over + the Integer Ring is not invertible + + """ + + Element = FreeModuleAutomorphism + + def __init__(self, fmodule): + r""" + See :class:`FreeModuleLinearGroup` for documentation and examples. + + TESTS:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: from sage.tensor.modules.free_module_linear_group import FreeModuleLinearGroup + sage: GL = FreeModuleLinearGroup(M) ; GL + General linear group of the Rank-3 free module M over the Integer Ring + sage: GL.category() + Category of groups + sage: TestSuite(GL).run() + + """ + if not isinstance(fmodule, FiniteRankFreeModule): + raise TypeError("{} is not a free module of finite rank".format( + fmodule)) + Parent.__init__(self, category=Groups()) + self._fmodule = fmodule + self._one = None # to be set by self.one() + + #### Parent methods #### + + def _element_constructor_(self, comp=[], basis=None, name=None, + latex_name=None): + r""" + Construct a free module automorphism. + + INPUT: + + - ``comp`` -- (default: ``[]``) components representing the + automorphism with respect to ``basis``; this entry can actually be + any array of size rank(M)*rank(M) from which a matrix of elements + of ``self`` base ring can be constructed; the *columns* of ``comp`` + must be the components w.r.t. ``basis`` of the images of the elements + of ``basis``. If ``comp`` is ``[]``, the automorphism has to be + initialized afterwards by method + :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.set_comp` + or via the operator []. + - ``basis`` -- (default: ``None``) basis of ``self`` defining the + matrix representation; if ``None`` the default basis of ``self`` is + assumed. + - ``name`` -- (default: ``None``) name given to the automorphism + - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the + automorphism; if none is provided, the LaTeX symbol is set to ``name`` + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` + + EXAMPLES: + + Generic construction:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: GL = M.general_linear_group() + sage: a = GL._element_constructor_(comp=[[1,2],[1,3]], basis=e, + ....: name='a') + sage: a + Automorphism a of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [1 2] + [1 3] + + Identity map constructed from integer 1:: + + sage: GL._element_constructor_(1) + Identity map of the Rank-2 free module M over the Integer Ring + sage: GL._element_constructor_(1).matrix(e) + [1 0] + [0 1] + + Construction from an invertible endomorphism:: + + sage: phi = M.endomorphism([[1,1], [2,3]]) + sage: a = GL._element_constructor_(phi) ; a + Automorphism of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [1 1] + [2 3] + sage: a.matrix(e) == phi.matrix(e) + True + + Construction from an invertible tensor of type (1,1):: + + sage: t = M.tensor((1,1), name='t') + sage: t[e,:] = [[1,1], [2,3]] + sage: a = GL._element_constructor_(t) ; a + Automorphism t of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) == t[e,:] + True + + """ + from sage.tensor.modules.free_module_tensor import FreeModuleTensor + from sage.tensor.modules.free_module_morphism import \ + FiniteRankFreeModuleMorphism + if comp == 1: + return self.one() + if isinstance(comp, FreeModuleTensor): + tens = comp # for readability + # Conversion of a type-(1,1) tensor to an automorphism + if tens.tensor_type() == (1,1): + resu = self.element_class(self._fmodule, name=tens._name, + latex_name=tens._latex_name) + for basis, comp in tens._components.iteritems(): + resu._components[basis] = comp.copy() + # Check whether the tensor is invertible: + try: + resu.inverse() + except (ZeroDivisionError, TypeError): + raise TypeError("the {} is not invertible ".format(tens)) + return resu + else: + raise TypeError("the {} cannot be converted ".format(tens) + + "to an automorphism.") + if isinstance(comp, FiniteRankFreeModuleMorphism): + # Conversion of an endomorphism to an automorphism + endo = comp # for readability + if endo.is_endomorphism() and self._fmodule is endo.domain(): + resu = self.element_class(self._fmodule, name=endo._name, + latex_name=endo._latex_name) + for basis, mat in endo._matrices.iteritems(): + resu.add_comp(basis[0])[:] = mat + # Check whether the endomorphism is invertible: + try: + resu.inverse() + except (ZeroDivisionError, TypeError): + raise TypeError("the {} is not invertible ".format(endo)) + return resu + else: + raise TypeError("cannot coerce the {}".format(endo) + + " to an element of {}".format(self)) + + # standard construction + resu = self.element_class(self._fmodule, name=name, + latex_name=latex_name) + if comp: + resu.set_comp(basis)[:] = comp + return resu + + + def _an_element_(self): + r""" + Construct some specific free module automorphism. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` + + EXAMPLES:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: e = M.basis('e') + sage: GL = M.general_linear_group() + sage: a = GL._an_element_() ; a + Automorphism of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [ 1 0] + [ 0 -1] + + """ + resu = self.element_class(self._fmodule) + if self._fmodule._def_basis is not None: + comp = resu.set_comp() + for i in self._fmodule.irange(): + if i%2 == 0: + comp[[i,i]] = self._fmodule._ring.one() + else: + comp[[i,i]] = -(self._fmodule._ring.one()) + return resu + + #### End of parent methods #### + + #### Monoid methods #### + + def one(self): + r""" + Return the group identity element of ``self``. + + The group identity element is nothing but the module identity map. + + OUTPUT: + + - instance of + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` + representing the identity element. + + EXAMPLES: + + Identity element of the general linear group of a rank-2 free module:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M', start_index=1) + sage: GL = M.general_linear_group() + sage: GL.one() + Identity map of the Rank-2 free module M over the Integer Ring + + The identity element is cached:: + + sage: GL.one() is GL.one() + True + + Check that the element returned is indeed the neutral element for + the group law:: + + sage: e = M.basis('e') + sage: a = GL([[3,4],[5,7]], basis=e) ; a + Automorphism of the Rank-2 free module M over the Integer Ring + sage: a.matrix(e) + [3 4] + [5 7] + sage: GL.one() * a == a + True + sage: a * GL.one() == a + True + sage: a * a^(-1) == GL.one() + True + sage: a^(-1) * a == GL.one() + True + + The unit element of `\mathrm{GL}(M)` is the identity map of `M`:: + + sage: GL.one()(e[1]) + Element e_1 of the Rank-2 free module M over the Integer Ring + sage: GL.one()(e[2]) + Element e_2 of the Rank-2 free module M over the Integer Ring + + Its matrix is the identity matrix in any basis:: + + sage: GL.one().matrix(e) + [1 0] + [0 1] + sage: f = M.basis('f', from_family=(e[1]+2*e[2], e[1]+3*e[2])) + sage: GL.one().matrix(f) + [1 0] + [0 1] + + """ + if self._one is None: + self._one = self.element_class(self._fmodule, is_identity=True) + # Initialization of the components (Kronecker delta) in some basis: + if self._fmodule.bases(): + self._one.components(self._fmodule.bases()[0]) + return self._one + + #### End of monoid methods #### + + def _repr_(self): + r""" + Return a string representation of ``self``. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: GL = M.general_linear_group() + sage: GL._repr_() + 'General linear group of the Rank-2 free module M over the Integer Ring' + + """ + return "General linear group of the {}".format(self._fmodule) + + def _latex_(self): + r""" + Return a string representation of ``self``. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: GL = M.general_linear_group() + sage: GL._latex_() + \mathrm{GL}\left( M \right) + + """ + from sage.misc.latex import latex + return r"\mathrm{GL}\left("+ latex(self._fmodule)+ r"\right)" + + + def base_module(self): + r""" + Return the free module of which ``self`` is the general linear group. + + OUTPUT: + + - instance of :class:`FiniteRankFreeModule` representing the free + module of which ``self`` is the general linear group + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 2, name='M') + sage: GL = M.general_linear_group() + sage: GL.base_module() + Rank-2 free module M over the Integer Ring + sage: GL.base_module() is M + True + + """ + return self._fmodule diff --git a/src/sage/tensor/modules/free_module_morphism.py b/src/sage/tensor/modules/free_module_morphism.py index d5628c293fa..e4fa5efa6a2 100644 --- a/src/sage/tensor/modules/free_module_morphism.py +++ b/src/sage/tensor/modules/free_module_morphism.py @@ -1,17 +1,23 @@ r""" -Morphisms between free modules +Free module morphisms The class :class:`FiniteRankFreeModuleMorphism` implements homomorphisms -between two free modules of finite rank over the same commutative ring. +between two free modules of finite rank over the same commutative ring. AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version + +REFERENCES: + +- Chaps. 13, 14 of R. Godement: *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) +- Chap. 3 of S. Lang: *Algebra*, 3rd ed., Springer (New York) (2002) """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -26,26 +32,26 @@ class FiniteRankFreeModuleMorphism(Morphism): r""" - Homomorphism between free modules of finite rank. - - This is an *element* class, whose *parent* class is - :class:`~sage.tensor.modules.free_module_homset.FreeModuleHomset`. + Homomorphism between free modules of finite rank over a commutative ring. An instance of this class is a homomorphism .. MATH:: - \phi:\ M \longrightarrow N, + \phi:\ M \longrightarrow N, - where `M` and `N` are two free modules of finite rank over the same - commutative ring `R`. + where `M` and `N` are two free modules of finite rank over the same + commutative ring `R`. + + This is a Sage *element* class, the corresponding *parent* class being + :class:`~sage.tensor.modules.free_module_homset.FreeModuleHomset`. INPUT: - ``parent`` -- hom-set Hom(M,N) to which the homomorphism belongs - - ``matrix_rep`` -- matrix representation of the homomorphism with + - ``matrix_rep`` -- matrix representation of the homomorphism with respect to the bases ``bases``; this entry can actually - be any material from which a matrix of size rank(N)*rank(M) of + be any material from which a matrix of size rank(N)*rank(M) of elements of `R` can be constructed; the *columns* of the matrix give the images of the basis of `M` (see the convention in the example below) - ``bases`` -- (default: ``None``) pair (basis_M, basis_N) defining the @@ -104,8 +110,8 @@ class FiniteRankFreeModuleMorphism(Morphism): sage: phi.parent() is Hom(M,N) True - Due to Sage's category scheme, the actual class of the homomorphism phi is - a derived class of :class:`FiniteRankFreeModuleMorphism`:: + Due to Sage's category scheme, the actual class of the homomorphism ``phi`` + is a derived class of :class:`FiniteRankFreeModuleMorphism`:: sage: type(phi) @@ -131,35 +137,35 @@ class FiniteRankFreeModuleMorphism(Morphism): [ 1 0 -4] The convention is that the columns of this matrix give the components of - the images of the elements of basis e w.r.t basis f:: + the images of the elements of basis ``e`` w.r.t basis ``f``:: - sage: phi(e[0]).view() + sage: phi(e[0]).display() phi(e_0) = 2 f_0 + f_1 - sage: phi(e[1]).view() + sage: phi(e[1]).display() phi(e_1) = -f_0 - sage: phi(e[2]).view() + sage: phi(e[2]).display() phi(e_2) = 3 f_0 - 4 f_1 Test of the module homomorphism laws:: sage: phi(M.zero()) == N.zero() True - sage: u = M([1,2,3], basis=e, name='u') ; u.view() + sage: u = M([1,2,3], basis=e, name='u') ; u.display() u = e_0 + 2 e_1 + 3 e_2 - sage: v = M([-2,1,4], basis=e, name='v') ; v.view() + sage: v = M([-2,1,4], basis=e, name='v') ; v.display() v = -2 e_0 + e_1 + 4 e_2 - sage: phi(u).view() + sage: phi(u).display() phi(u) = 9 f_0 - 11 f_1 - sage: phi(v).view() + sage: phi(v).display() phi(v) = 7 f_0 - 18 f_1 - sage: phi(3*u + v).view() + sage: phi(3*u + v).display() 34 f_0 - 51 f_1 sage: phi(3*u + v) == 3*phi(u) + phi(v) True The identity endomorphism:: - sage: Id = M.identity_map() ; Id + sage: Id = End(M).one() ; Id Identity endomorphism of Rank-3 free module M over the Integer Ring sage: Id.parent() Set of Morphisms from Rank-3 free module M over the Integer Ring to @@ -168,11 +174,6 @@ class FiniteRankFreeModuleMorphism(Morphism): sage: Id.parent() is End(M) True - The identity endomorphism is actually the unit of End(M):: - - sage: Id is End(M).one() - True - The matrix of Id with respect to the basis e is of course the identity matrix:: @@ -187,7 +188,7 @@ class FiniteRankFreeModuleMorphism(Morphism): True """ - def __init__(self, parent, matrix_rep, bases=None, name=None, + def __init__(self, parent, matrix_rep, bases=None, name=None, latex_name=None, is_identity=False): r""" TESTS: @@ -238,11 +239,11 @@ def __init__(self, parent, matrix_rep, bases=None, name=None, if bases is None: def_basis1 = fmodule1.default_basis() if def_basis1 is None: - raise ValueError("the " + str(fmodule1) + " has no default " + + raise ValueError("the {} has no default ".format(fmodule1) + "basis") def_basis2 = fmodule2.default_basis() if def_basis2 is None: - raise ValueError("the " + str(fmodule2) + " has no default " + + raise ValueError("the {} has no default ".format(fmodule2) + "basis") bases = (def_basis1, def_basis2) else: @@ -250,11 +251,11 @@ def __init__(self, parent, matrix_rep, bases=None, name=None, if len(bases) != 2: raise TypeError("the argument bases must contain 2 bases") if bases[0] not in fmodule1.bases(): - raise TypeError(str(bases[0]) + " is not a basis on the " + \ - str(fmodule1)) + raise TypeError("{} is not a basis on the {}".format(bases[0], + fmodule1)) if bases[1] not in fmodule2.bases(): - raise TypeError(str(bases[1]) + " is not a basis on the " + \ - str(fmodule2)) + raise TypeError("{} is not a basis on the {}".format(bases[1], + fmodule2)) ring = parent.base_ring() n1 = fmodule1.rank() n2 = fmodule2.rank() @@ -313,7 +314,8 @@ def _latex_(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\Phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\Phi') sage: phi._latex_() '\\Phi' sage: latex(phi) # indirect doctest @@ -339,7 +341,7 @@ def _latex_(self): def __eq__(self, other): r""" - Comparison (equality) operator. + Comparison (equality) operator. INPUT: @@ -354,7 +356,8 @@ def __eq__(self, other): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: psi = M.hom(N, [[-1,2,0], [5,1,2]]) sage: phi.__eq__(psi) True @@ -372,7 +375,7 @@ def __eq__(self, other): Comparison of homomorphisms defined on different bases:: - sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: a = M.automorphism() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") sage: psi = M.hom(N, [[-2,0,-1], [-1,-2, 5]], bases=(ep,f)) sage: phi.__eq__(psi) @@ -388,11 +391,11 @@ def __eq__(self, other): sage: phi1 = M.hom(N1, [[-1,2,0], [5,1,2]]) sage: phi.matrix() == phi1.matrix() # same matrix in the default bases True - sage: phi.__eq__(phi1) + sage: phi.__eq__(phi1) False Comparison to zero:: - + sage: phi.__eq__(0) False sage: phi = M.hom(N, 0) @@ -417,12 +420,12 @@ def __eq__(self, other): bases = self._common_bases(other) if bases is None: raise ValueError("no common pair of bases has been found to " + - "compare " + str(self) + " and " + str(other)) + "compare {} and {}".format(self, other)) return bool( self.matrix(*bases) == other.matrix(*bases) ) def __ne__(self, other): r""" - Inequality operator. + Inequality operator. INPUT: @@ -438,7 +441,8 @@ def __ne__(self, other): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: psi = M.hom(N, [[-1,2,0], [5,1,2]]) sage: phi.__ne__(psi) False @@ -466,7 +470,8 @@ def __cmp__(self, other): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: psi = M.hom(N, [[-1,2,0], [5,1,2]]) sage: phi.__cmp__(psi) 0 @@ -487,7 +492,7 @@ def __cmp__(self, other): # # Required module methods - # + # def __nonzero__(self): r""" @@ -516,7 +521,7 @@ def __nonzero__(self): """ # Some matrix representation is picked at random: matrix_rep = self._matrices.values()[0] - return not matrix_rep.is_zero() + return not matrix_rep.is_zero() def _add_(self, other): r""" @@ -535,7 +540,8 @@ def _add_(self, other): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) sage: s = phi._add_(psi) ; s Generic morphism: @@ -551,9 +557,9 @@ def _add_(self, other): Addition of homomorphisms defined on different bases:: - sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: a = M.automorphism() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") - sage: b = N.automorphism_tensor() ; b[0,1], b[1,0] = -1, 1 + sage: b = N.automorphism() ; b[0,1], b[1,0] = -1, 1 sage: fp = f.new_basis(b, 'fp', latex_symbol="f'") sage: psi = M.hom(N, [[-2,0,-1], [-1,-2, 5]], bases=(ep,fp)) sage: s = phi._add_(psi) ; s @@ -572,14 +578,14 @@ def _add_(self, other): sage: phi._add_(Hom(M,N).zero()) == phi True - + """ # No need for consistency checks since self and other are guaranteed # to have the same parents bases = self._common_bases(other) if bases is None: raise ValueError("no common pair of bases has been found to " + - "add " + str(self) + " and " + str(other)) + "add {} and {}".format(self, other)) # Addition at the matrix level: resu_mat = self._matrices[bases] + other._matrices[bases] if self._name is not None and other._name is not None: @@ -611,7 +617,8 @@ def _sub_(self, other): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: psi = M.hom(N, [[1,1,0], [4,1,3]]) sage: s = phi._sub_(psi) ; s Generic morphism: @@ -625,11 +632,11 @@ def _sub_(self, other): sage: s == phi - psi # indirect doctest True - Addition of homomorphisms defined on different bases:: + Subtraction of homomorphisms defined on different bases:: - sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: a = M.automorphism() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") - sage: b = N.automorphism_tensor() ; b[0,1], b[1,0] = -1, 1 + sage: b = N.automorphism() ; b[0,1], b[1,0] = -1, 1 sage: fp = f.new_basis(b, 'fp', latex_symbol="f'") sage: psi = M.hom(N, [[-2,0,-1], [-1,-2, 5]], bases=(ep,fp)) sage: s = phi._sub_(psi) ; s @@ -659,8 +666,7 @@ def _sub_(self, other): bases = self._common_bases(other) if bases is None: raise ValueError("no common pair of bases has been found to " + - "substract " + str(other) + " from " + - str(self)) + "subtract {} from {}".format(other, self)) # Subtraction at the matrix level: resu_mat = self._matrices[bases] - other._matrices[bases] if self._name is not None and other._name is not None: @@ -680,7 +686,7 @@ def _rmul_(self, scalar): INPUT: - - ``scalar`` -- element of the ring over which the parent of ``self`` + - ``scalar`` -- element of the ring over which the parent of ``self`` is a module. OUPUT: @@ -693,7 +699,8 @@ def _rmul_(self, scalar): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: s = phi._rmul_(7) ; s Generic morphism: From: Rank-3 free module M over the Integer Ring @@ -711,9 +718,9 @@ def _rmul_(self, scalar): return resu - # + # # Other module methods - # + # def __pos__(self): r""" @@ -728,7 +735,8 @@ def __pos__(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: s = phi.__pos__() ; s Generic morphism: From: Rank-3 free module M over the Integer Ring @@ -741,12 +749,12 @@ def __pos__(self): False """ - resu = self.__class__(self.parent(), 0, is_identity=self._is_identity) + resu = self.__class__(self.parent(), 0, is_identity=self._is_identity) # 0 = provisory value for bases, mat in self._matrices.iteritems(): resu._matrices[bases] = +mat if self._name is not None: - resu._name = '+' + self._name + resu._name = '+' + self._name if self._latex_name is not None: resu._latex_name = '+' + self._latex_name return resu @@ -764,7 +772,8 @@ def __neg__(self): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: N = FiniteRankFreeModule(ZZ, 2, name='N') sage: e = M.basis('e') ; f = N.basis('f') - sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', latex_name=r'\phi') + sage: phi = M.hom(N, [[-1,2,0], [5,1,2]], name='phi', + ....: latex_name=r'\phi') sage: s = phi.__neg__() ; s Generic morphism: From: Rank-3 free module M over the Integer Ring @@ -782,7 +791,7 @@ def __neg__(self): for bases, mat in self._matrices.iteritems(): resu._matrices[bases] = -mat if self._name is not None: - resu._name = '-' + self._name + resu._name = '-' + self._name if self._latex_name is not None: resu._latex_name = '-' + self._latex_name return resu @@ -814,38 +823,38 @@ def _call_(self, element): sage: v = M([1,2,3], basis=e, name='v') sage: w = phi(v) ; w Element phi(v) of the Rank-2 free module N over the Integer Ring - sage: w.view() + sage: w.display() phi(v) = 3 f_0 + 13 f_1 Tests:: sage: for i in range(2): ....: print w[i] == sum( phi.matrix()[i,j]*v[j] for j in range(3) ), - ....: + ....: True True sage: phi.matrix(e,f) [-1 2 0] [ 5 1 2] - sage: phi(e[0]).view() + sage: phi(e[0]).display() phi(e_0) = -f_0 + 5 f_1 - sage: phi(e[1]).view() + sage: phi(e[1]).display() phi(e_1) = 2 f_0 + f_1 - sage: phi(e[2]).view() + sage: phi(e[2]).display() phi(e_2) = 2 f_1 Image of an element that is not defined on the default basis:: - sage: a = M.automorphism_tensor() + sage: a = M.automorphism() sage: a[0,2], a[1,0], a[2,1] = 1, -1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") sage: v = M([1,2,3], basis=ep, name='v') sage: w = phi(v) ; w Element phi(v) of the Rank-2 free module N over the Integer Ring - sage: w.view() + sage: w.display() phi(v) = -5 f_0 + 10 f_1 sage: for i in range(2): ....: print w[i] == sum( phi.matrix(ep,f)[i,j]*v[ep,j] for j in range(3) ), - ....: + ....: True True Check of homomorphism properties:: @@ -869,8 +878,8 @@ def _call_(self, element): except ValueError: continue else: - raise ValueError("no common basis found to evaluate the image " + - "of " + str(element) + " by " + str(self)) + raise ValueError("no common basis found to evaluate the image " + + "of {} by {}".format(element,self)) # Components of the result obtained by matrix multiplication mat = self.matrix(basis_dom, basis_codom) vcomp = element._components[basis_dom] @@ -891,9 +900,9 @@ def _call_(self, element): else: resu_latex_name = None # Creation of the result - return codom(tresu, basis=basis_codom, name=resu_name, + return codom(tresu, basis=basis_codom, name=resu_name, latex_name=resu_latex_name) - + def is_injective(self): r""" Determine whether ``self`` is injective. @@ -919,7 +928,7 @@ def is_injective(self): Indeed, phi has a non trivial kernel:: - sage: phi(4*e[0] + 2*e[1] - 11*e[2]).view() + sage: phi(4*e[0] + 2*e[1] - 11*e[2]).display() 0 An injective homomorphism:: @@ -934,9 +943,9 @@ def is_injective(self): Of course, the identity endomorphism is injective:: - sage: M.identity_map().is_injective() + sage: End(M).one().is_injective() True - sage: N.identity_map().is_injective() + sage: End(N).one().is_injective() True """ @@ -964,20 +973,21 @@ def is_surjective(self): sage: phi.is_surjective() Traceback (most recent call last): ... - NotImplementedError: FiniteRankFreeModuleMorphism.is_surjective() has not been implemented yet + NotImplementedError: FiniteRankFreeModuleMorphism.is_surjective() + has not been implemented yet - except for the identity map (!):: + except for the identity endomorphisme (!):: - sage: M.identity_map().is_surjective() + sage: End(M).one().is_surjective() True - sage: N.identity_map().is_surjective() + sage: End(N).one().is_surjective() True """ if self._is_identity: return True raise NotImplementedError( - "FiniteRankFreeModuleMorphism.is_surjective() " + + "FiniteRankFreeModuleMorphism.is_surjective() " + "has not been implemented yet") # # Morphism methods @@ -998,7 +1008,7 @@ def is_identity(self): False sage: End(M).zero().is_identity() False - sage: a = M.automorphism_tensor() ; a[0,1], a[1,0] = 1, -1 + sage: a = M.automorphism() ; a[0,1], a[1,0] = 1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") sage: phi = M.endomorphism([[1,0], [0,1]], basis=ep) sage: phi.is_identity() @@ -1028,9 +1038,9 @@ def is_identity(self): # The identity must be an endomorphism: fmodule = self.domain() if fmodule != self.codomain(): - return False - # Some basis in which ``self`` has a representation is picked at - # random and the test is performed on the images of the basis + return False + # Some basis in which ``self`` has a representation is picked at + # random and the test is performed on the images of the basis # elements: basis = self._matrices.keys()[0][0] for i in fmodule.irange(): @@ -1048,7 +1058,7 @@ def matrix(self, basis1=None, basis2=None): Return the matrix of ``self`` w.r.t to a pair of bases. If the matrix is not known already, it is computed from the matrix in - another pair of bases by means of the change-of-bases formula. + another pair of bases by means of the change-of-basis formula. INPUT: @@ -1063,7 +1073,7 @@ def matrix(self, basis1=None, basis2=None): - the matrix representing representing the homomorphism ``self`` w.r.t to bases ``basis1`` and ``basis2``; more precisely, the columns of - this matrix are formed by the components w.r.t. ``basis2`` of + this matrix are formed by the components w.r.t. ``basis2`` of the images of the elements of ``basis1``. EXAMPLES: @@ -1086,39 +1096,30 @@ def matrix(self, basis1=None, basis2=None): Matrix in bases different from those in which the homomorphism has been defined:: - sage: a = M.automorphism_tensor() - sage: a[0,2], a[1,0], a[2,1] = 1, -1, -1 - sage: a[:] - [ 0 0 1] - [-1 0 0] - [ 0 -1 0] + sage: a = M.automorphism(matrix=[[-1,0,0],[0,1,2],[0,1,3]], basis=e) sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") - sage: b = N.automorphism_tensor() - sage: b[0,1], b[1,0] = -1, 1 - sage: b[:] - [ 0 -1] - [ 1 0] + sage: b = N.automorphism(matrix=[[3,5],[4,7]], basis=f) sage: fp = f.new_basis(b, 'fp', latex_symbol="f'") sage: phi.matrix(ep, fp) - [-1 -2 5] - [ 2 0 1] + [ 32 -1 -12] + [-19 1 8] - Check of the change-of-bases formula:: + Check of the change-of-basis formula:: - sage: phi.matrix(ep, fp) == matrix(b.inverse()[:]) * phi.matrix(e,f) * matrix(a[:]) + sage: phi.matrix(ep, fp) == (b^(-1)).matrix(f) * phi.matrix(e,f) * a.matrix(e) True Single change of basis:: sage: phi.matrix(ep, f) - [-2 0 -1] - [-1 -2 5] - sage: phi.matrix(ep,f) == phi.matrix(e,f) * matrix(a[:]) + [ 1 2 4] + [-5 3 8] + sage: phi.matrix(ep,f) == phi.matrix(e,f) * a.matrix(e) True sage: phi.matrix(e, fp) - [ 5 1 2] - [ 1 -2 0] - sage: phi.matrix(e, fp) == matrix(b.inverse()[:]) * phi.matrix(e,f) + [-32 9 -10] + [ 19 -5 6] + sage: phi.matrix(e, fp) == (b^(-1)).matrix(f) * phi.matrix(e,f) True Matrix of an endomorphism:: @@ -1133,9 +1134,9 @@ def matrix(self, basis1=None, basis2=None): [4 5 6] [7 8 9] sage: phi.matrix() # matrix w.r.t to the module's default basis - [ 9 -7 -8] - [-3 1 2] - [-6 4 5] + [ 1 -3 1] + [-18 39 -18] + [-25 54 -25] """ from sage.matrix.constructor import matrix @@ -1158,7 +1159,7 @@ def matrix(self, basis1=None, basis2=None): if self._is_identity: # The identity endomorphism # ------------------------- - if basis1 == basis2: + if basis1 == basis2: # the matrix is the identity matrix: ring = fmodule1.base_ring() zero = ring.zero() @@ -1172,8 +1173,8 @@ def matrix(self, basis1=None, basis2=None): else: # the matrix is the change-of-basis matrix: change = fmodule1.change_of_basis(basis1, basis2) - mat = [[change[[i,j]] for j in fmodule1.irange()] - for i in fmodule1.irange()] + mat = [[change[[i,j]] for j in fmodule1.irange()] + for i in fmodule1.irange()] self._matrices[(basis1, basis2)] = matrix(mat) else: # Generic homomorphism @@ -1186,10 +1187,10 @@ def matrix(self, basis1=None, basis2=None): nb2 = b2 break else: - raise ValueError("no start basis could be found for " + - "applying the change-of-bases formula") + raise ValueError("no start basis could be found for " + + "applying the change-of-basis formula") change2 = fmodule2._basis_changes[(basis2, nb2)] - mat2 = matrix( [[change2[[i,j]] for j in fmodule2.irange()] + mat2 = matrix( [[change2[[i,j]] for j in fmodule2.irange()] for i in fmodule2.irange()] ) self._matrices[(basis1, basis2)] = \ mat2 * self._matrices[(basis1,nb2)] @@ -1199,8 +1200,8 @@ def matrix(self, basis1=None, basis2=None): nb1 = b1 break else: - raise ValueError("no start basis could be found for " + - "applying the change-of-bases formula") + raise ValueError("no start basis could be found for " + + "applying the change-of-basis formula") change1 = fmodule1._basis_changes[(nb1, basis1)] mat1 = matrix( [[change1[[i,j]] for j in fmodule1.irange()] for i in fmodule1.irange()] ) @@ -1213,16 +1214,16 @@ def matrix(self, basis1=None, basis2=None): nb1, nb2 = b1, b2 break else: - raise ValueError("no start basis could be found for " + - "applying the change-of-bases formula") + raise ValueError("no start basis could be found for " + + "applying the change-of-basis formula") change1 = fmodule1._basis_changes[(nb1, basis1)] change2 = fmodule2._basis_changes[(basis2, nb2)] mat1 = matrix( [[change1[[i,j]] for j in fmodule1.irange()] for i in fmodule1.irange()] ) - mat2 = matrix( [[change2[[i,j]] for j in fmodule2.irange()] + mat2 = matrix( [[change2[[i,j]] for j in fmodule2.irange()] for i in fmodule2.irange()] ) self._matrices[(basis1, basis2)] = \ - mat2 * self._matrices[(nb1,nb2)] * mat1 + mat2 * self._matrices[(nb1,nb2)] * mat1 return self._matrices[(basis1, basis2)] def _common_bases(self, other): @@ -1249,7 +1250,7 @@ def _common_bases(self, other): sage: phi._common_bases(psi) # matrices of phi and psi both defined on (e,f) (Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, Basis (f_0,f_1) on the Rank-2 free module N over the Integer Ring) - sage: a = M.automorphism_tensor() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 + sage: a = M.automorphism() ; a[0,2], a[1,0], a[2,1] = 1, -1, -1 sage: ep = e.new_basis(a, 'ep', latex_symbol="e'") sage: psi = M.hom(N, [[1,1,0], [4,1,3]], bases=(ep,f)) sage: phi._common_bases(psi) # matrix of psi w.r.t. (e,f) computed diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index 02a7fcccd38..d5484779a34 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -1,11 +1,9 @@ r""" Tensors on free modules -The class :class:`FreeModuleTensor` implements tensors over a free module `M`, -i.e. elements of the free module `T^{(k,l)}(M)` of tensors of type `(k,l)` -acting as multilinear forms on `M`. - -A *tensor of type* `(k,l)` is a multilinear map: +The class :class:`FreeModuleTensor` implements tensors on a free module `M` +of finite rank over a commutative ring. A *tensor of type* `(k,l)` on `M` +is a multilinear map: .. MATH:: @@ -15,37 +13,49 @@ where `R` is the commutative ring over which the free module `M` is defined and `M^* = \mathrm{Hom}_R(M,R)` is the dual of `M`. The integer `k + l` is -called the *tensor rank*. +called the *tensor rank*. The set `T^{(k,l)}(M)` of tensors of type `(k,l)` +on `M` is a free module of finite rank over `R`, described by the +class :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. Various derived classes of :class:`FreeModuleTensor` are devoted to specific tensors: * :class:`FiniteRankFreeModuleElement` for elements of `M`, considered as - type-(1,0) tensors thanks to the canonical identification `M^{**}=M`, which - holds since `M` is a free module of finite rank + type-(1,0) tensors thanks to the canonical identification `M^{**}=M` (which + holds since `M` is a free module of finite rank); * :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm` for - fully antisymmetric type-`(0, l)` tensors (alternating forms) + fully antisymmetric type-`(0, l)` tensors (alternating forms); - * :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleLinForm` for - type-(0, 1) tensors (linear forms) +* :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism` + for type-(1,1) tensors representing invertible endomorphisms. -* :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleEndomorphismTensor` - for type-(1,1) tensors +Each of these classes is a Sage *element* class, the corresponding *parent* +classes being: - * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleAutomorphismTensor` - for type-(1,1) tensors representing invertible endomorphisms +* for :class:`FreeModuleTensor`: + :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule` +* for :class:`FiniteRankFreeModuleElement`: + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` +* for :class:`~sage.tensor.modules.free_module_alt_form.FreeModuleAltForm`: + :class:`~sage.tensor.modules.ext_pow_free_module.ExtPowerFreeModule` +* for + :class:`~sage.tensor.modules.free_module_automorphism.FreeModuleAutomorphism`: + :class:`~sage.tensor.modules.free_module_linear_group.FreeModuleLinearGroup` - * :class:`~sage.tensor.modules.free_module_tensor_spec.FreeModuleIdentityTensor` - for the type-(1,1) tensor representing the free module identity map -:class:`FreeModuleTensor` is a Sage *element* class, the corresponding *parent* -class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. +AUTHORS: +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version -AUTHORS: +REFERENCES: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Chap. 21 of R. Godement: *Algebra*, Hermann (Paris) / Houghton Mifflin + (Boston) (1968) +- Chap. 12 of J. M. Lee: *Introduction to Smooth Manifolds*, 2nd ed., Springer + (New York) (2013) (only when the free module is a vector space) +- Chap. 2 of B. O'Neill: *Semi-Riemannian Geometry*, Academic Press (San Diego) + (1983) EXAMPLES: @@ -53,7 +63,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t - Endomorphism tensor t on the Rank-3 free module M over the Integer Ring + Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring sage: t.parent() Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring @@ -74,7 +84,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. [-3 0 0] [ 0 0 0] [ 0 0 0] - sage: t.view(e) # displays the expansion of t on the basis e_i*e^j of T^(1,1)(M) + sage: t.display(e) # displays the expansion of t on the basis e_i*e^j of T^(1,1)(M) t = -3 e_0*e^0 The commands ``t.set_comp(e)`` and ``t.comp(e)`` can be abridged by providing @@ -137,7 +147,7 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. sage: t.set_comp(e)[0,0] = -3 ; t.set_comp(e)[1,2] = 2 sage: t.comp(e)._comp # random output order (dictionary) {(0, 0): -3, (1, 2): 2} - sage: t.view(e) + sage: t.display(e) t = -3 e_0*e^0 + 2 e_1*e^2 Further tests of the comparison operator:: @@ -167,8 +177,8 @@ class being :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -186,10 +196,14 @@ class FreeModuleTensor(ModuleElement): r""" Tensor over a free module of finite rank over a commutative ring. + This is a Sage *element* class, the corresponding *parent* class being + :class:`~sage.tensor.modules.tensor_free_module.TensorFreeModule`. + INPUT: - - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an - instance of :class:`FiniteRankFreeModule`) + - ``fmodule`` -- free module `M` of finite rank over a commutative ring + `R`, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the contravariant rank and ``l`` the covariant rank - ``name`` -- (default: ``None``) name given to the tensor @@ -206,6 +220,9 @@ class FreeModuleTensor(ModuleElement): - ``antisym`` -- (default: ``None``) antisymmetry or list of antisymmetries among the arguments, with the same convention as for ``sym`` + - ``parent`` -- (default: ``None``) some specific parent (e.g. exterior + power for alternating forms); if ``None``, ``fmodule.tensor_module(k,l)`` + is used EXAMPLES: @@ -213,7 +230,7 @@ class FreeModuleTensor(ModuleElement): sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: t = M.tensor((1,1), name='t') ; t - Endomorphism tensor t on the Rank-3 free module M over the Integer Ring + Type-(1,1) tensor t on the Rank-3 free module M over the Integer Ring Tensors are *Element* objects whose parents are tensor free modules:: @@ -225,7 +242,7 @@ class FreeModuleTensor(ModuleElement): """ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, - sym=None, antisym=None): + sym=None, antisym=None, parent=None): r""" TESTS:: @@ -248,7 +265,9 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, sage: TestSuite(t1).run() """ - ModuleElement.__init__(self, fmodule.tensor_module(*tensor_type)) + if parent is None: + parent = fmodule.tensor_module(*tensor_type) + ModuleElement.__init__(self, parent) self._fmodule = fmodule self._tensor_type = tuple(tensor_type) self._tensor_rank = self._tensor_type[0] + self._tensor_type[1] @@ -271,7 +290,7 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None, for i in isym: if i<0 or i>self._tensor_rank-1: raise IndexError("invalid position: " + str(i) + - " not in [0," + str(self._tensor_rank-1) + "]") + " not in [0," + str(self._tensor_rank-1) + "]") self._sym.append(tuple(isym)) self._antisym = [] if antisym is not None and antisym != []: @@ -321,14 +340,14 @@ def __nonzero__(self): sage: t == 0 True sage: t[e,1,0,2] = 4 # setting a non-zero component in basis e - sage: t.view() + sage: t.display() 4 e_1*e_0*e^2 sage: t.__nonzero__() True sage: t == 0 False sage: t[e,1,0,2] = 0 - sage: t.view() + sage: t.display() 0 sage: t.__nonzero__() False @@ -339,7 +358,7 @@ def __nonzero__(self): basis = self.pick_a_basis() return not self._components[basis].is_zero() - ####### End of required methods for ModuleElement (beside arithmetic) ####### + ##### End of required methods for ModuleElement (beside arithmetic) ##### def _repr_(self): r""" @@ -362,7 +381,7 @@ def _repr_(self): self._tensor_type[0], self._tensor_type[1]) if self._name is not None: description += " " + self._name - description += " on the " + str(self._fmodule) + description += " on the {}".format(self._fmodule) return description def _latex_(self): @@ -486,7 +505,7 @@ def base_module(self): def symmetries(self): r""" - Print the list of symmetries and antisymmetries. + Print the list of symmetries and antisymmetries of ``self``. EXAMPLES: @@ -510,23 +529,26 @@ def symmetries(self): if len(self._sym) == 0: s = "no symmetry; " elif len(self._sym) == 1: - s = "symmetry: " + str(self._sym[0]) + "; " + s = "symmetry: {}; ".format(self._sym[0]) else: - s = "symmetries: " + str(self._sym) + "; " + s = "symmetries: {}; ".format(self._sym) if len(self._antisym) == 0: a = "no antisymmetry" elif len(self._antisym) == 1: - a = "antisymmetry: " + str(self._antisym[0]) + a = "antisymmetry: {}".format(self._antisym[0]) else: - a = "antisymmetries: " + str(self._antisym) - print s, a + a = "antisymmetries: {}".format(self._antisym) + print(s+a) #### End of simple accessors ##### - def view(self, basis=None, format_spec=None): + def display(self, basis=None, format_spec=None): r""" - Display the tensor in terms of its expansion onto a given basis. + Display ``self`` in terms of its expansion w.r.t. a given module basis. + The expansion is actually performed onto tensor products of elements + of the given basis and of elements of its dual basis (see examples + below). The output is either text-formatted (console mode) or LaTeX-formatted (notebook mode). @@ -543,60 +565,75 @@ def view(self, basis=None, format_spec=None): Display of a module element (type-`(1,0)` tensor):: sage: M = FiniteRankFreeModule(QQ, 2, name='M', start_index=1) - sage: e = M.basis('e') + sage: e = M.basis('e') ; e + Basis (e_1,e_2) on the 2-dimensional vector space M over the + Rational Field sage: v = M([1/3,-2], name='v') - sage: v.view() + sage: v.display(e) v = 1/3 e_1 - 2 e_2 - sage: latex(v.view()) # display in the notebook + sage: v.display() # a shortcut since e is M's default basis + v = 1/3 e_1 - 2 e_2 + sage: latex(v.display()) # display in the notebook v = \frac{1}{3} e_1 -2 e_2 + A shortcut is ``disp()``:: + + sage: v.disp() + v = 1/3 e_1 - 2 e_2 + Display of a linear form (type-`(0,1)` tensor):: - sage: de = e.dual_basis() + sage: de = e.dual_basis() ; de + Dual basis (e^1,e^2) on the 2-dimensional vector space M over the + Rational Field sage: w = - 3/4 * de[1] + de[2] ; w - Linear form on the Rank-2 free module M over the Rational Field + Linear form on the 2-dimensional vector space M over the Rational + Field sage: w.set_name('w', latex_name='\omega') - sage: w.view() + sage: w.display() w = -3/4 e^1 + e^2 - sage: latex(w.view()) # display in the notebook + sage: latex(w.display()) # display in the notebook \omega = -\frac{3}{4} e^1 +e^2 Display of a type-`(1,1)` tensor:: sage: t = v*w ; t # the type-(1,1) is formed as the tensor product of v by w - Endomorphism tensor v*w on the Rank-2 free module M over the Rational Field - sage: t.view() + Type-(1,1) tensor v*w on the 2-dimensional vector space M over the + Rational Field + sage: t.display() v*w = -1/4 e_1*e^1 + 1/3 e_1*e^2 + 3/2 e_2*e^1 - 2 e_2*e^2 - sage: latex(t.view()) # display in the notebook - v\otimes \omega = -\frac{1}{4} e_1\otimes e^1 + \frac{1}{3} e_1\otimes e^2 + \frac{3}{2} e_2\otimes e^1 -2 e_2\otimes e^2 + sage: latex(t.display()) # display in the notebook + v\otimes \omega = -\frac{1}{4} e_1\otimes e^1 + + \frac{1}{3} e_1\otimes e^2 + \frac{3}{2} e_2\otimes e^1 + -2 e_2\otimes e^2 Display in a basis which is not the default one:: - sage: a = M.automorphism_tensor() - sage: a[:] = [[1,2],[3,4]] + sage: a = M.automorphism(matrix=[[1,2],[3,4]], basis=e) sage: f = e.new_basis(a, 'f') - sage: v.view(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a + sage: v.display(f) # the components w.r.t basis f are first computed via the change-of-basis formula defined by a v = -8/3 f_1 + 3/2 f_2 - sage: w.view(f) + sage: w.display(f) w = 9/4 f^1 + 5/2 f^2 - sage: t.view(f) + sage: t.display(f) v*w = -6 f_1*f^1 - 20/3 f_1*f^2 + 27/8 f_2*f^1 + 15/4 f_2*f^2 The output format can be set via the argument ``output_formatter`` passed at the module construction:: - sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1, output_formatter=Rational.numerical_approx) + sage: N = FiniteRankFreeModule(QQ, 2, name='N', start_index=1, + ....: output_formatter=Rational.numerical_approx) sage: e = N.basis('e') sage: v = N([1/3,-2], name='v') - sage: v.view() # default format (53 bits of precision) + sage: v.display() # default format (53 bits of precision) v = 0.333333333333333 e_1 - 2.00000000000000 e_2 - sage: latex(v.view()) + sage: latex(v.display()) v = 0.333333333333333 e_1 -2.00000000000000 e_2 The output format is then controled by the argument ``format_spec`` of - the method :meth:`view`:: + the method :meth:`display`:: - sage: v.view(format_spec=10) # 10 bits of precision + sage: v.display(format_spec=10) # 10 bits of precision v = 0.33 e_1 - 2.0 e_2 """ @@ -623,7 +660,7 @@ def view(self, basis=None, format_spec=None): bases_latex.append(latex(cobasis[ind[k]])) basis_term_txt = "*".join(bases_txt) basis_term_latex = r"\otimes ".join(bases_latex) - coef_txt = repr(coef) + coef_txt = repr(coef) if coef_txt == "1": terms_txt.append(basis_term_txt) terms_latex.append(basis_term_latex) @@ -640,8 +677,8 @@ def view(self, basis=None, format_spec=None): if is_atomic(coef_latex): terms_latex.append(coef_latex + basis_term_latex) else: - terms_latex.append(r"\left(" + coef_latex + r"\right)" + - basis_term_latex) + terms_latex.append(r"\left(" + coef_latex + + r"\right)" + basis_term_latex) if terms_txt == []: expansion_txt = "0" else: @@ -671,6 +708,32 @@ def view(self, basis=None, format_spec=None): result.latex = latex(self) + " = " + expansion_latex return result + disp = display + + + def view(self, basis=None, format_spec=None): + r""" + Deprecated method. + + Use method :meth:`display` instead. + + EXAMPLE:: + + sage: M = FiniteRankFreeModule(ZZ, 2, 'M') + sage: e = M.basis('e') + sage: v = M([2,-3], basis=e, name='v') + sage: v.view(e) + doctest:...: DeprecationWarning: Use function display() instead. + See http://trac.sagemath.org/15916 for details. + v = 2 e_0 - 3 e_1 + sage: v.display(e) + v = 2 e_0 - 3 e_1 + + """ + from sage.misc.superseded import deprecation + deprecation(15916, 'Use function display() instead.') + return self.display(basis=basis, format_spec=format_spec) + def set_name(self, name=None, latex_name=None): r""" Set (or change) the text name and LaTeX name of ``self``. @@ -724,12 +787,17 @@ def _new_instance(self): def _new_comp(self, basis): r""" - Create some components in the given basis. + Create some (uninitialized) components of ``self`` w.r.t a given + module basis. This method, to be called by :meth:`comp`, must be redefined by derived classes to adapt the output to the relevant subclass of :class:`~sage.tensor.modules.comp.Components`. + INPUT: + + - ``basis`` -- basis of the free module on which ``self`` is defined + OUTPUT: - an instance of :class:`~sage.tensor.modules.comp.Components` @@ -772,10 +840,10 @@ def _new_comp(self, basis): def components(self, basis=None, from_basis=None): r""" - Return the components in a given basis. + Return the components of ``self`` w.r.t to a given module basis. - If the components are not known already, they are computed by the tensor - change-of-basis formula from components in another basis. + If the components are not known already, they are computed by the + tensor change-of-basis formula from components in another basis. INPUT: @@ -785,7 +853,8 @@ def components(self, basis=None, from_basis=None): - ``from_basis`` -- (default: ``None``) basis from which the required components are computed, via the tensor change-of-basis formula, if they are not known already in the basis ``basis``; - if none, a basis is picked in ``self._components`` + if none, a basis from which both the components and a change-of-basis + to ``basis`` are known is selected. OUTPUT: @@ -827,7 +896,7 @@ class :class:`~sage.tensor.modules.comp.Components` Components computed via a change-of-basis formula:: - sage: a = M.automorphism_tensor() + sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: f = e.new_basis(a, 'f') sage: t.comp(f) @@ -848,15 +917,15 @@ class :class:`~sage.tensor.modules.comp.Components` if from_basis is None: for known_basis in self._components: if (known_basis, basis) in self._fmodule._basis_changes \ - and (basis, known_basis) in self._fmodule._basis_changes: + and (basis, known_basis) in self._fmodule._basis_changes: from_basis = known_basis break if from_basis is None: raise ValueError("no basis could be found for computing " + - "the components in the " + str(basis)) + "the components in the {}".format(basis)) elif from_basis not in self._components: - raise ValueError("the tensor components are not known in the " + - "basis "+ str(from_basis)) + raise ValueError("the tensor components are not known in " + + "the {}".format(from_basis)) (n_con, n_cov) = self._tensor_type if n_cov > 0: if (from_basis, basis) not in fmodule._basis_changes: @@ -869,7 +938,7 @@ class :class:`~sage.tensor.modules.comp.Components` if n_con > 0: if (basis, from_basis) not in fmodule._basis_changes: raise ValueError("the change-of-basis matrix from the " + - "{} to the {}".format(from_basis, basis) + + "{} to the {}".format(basis, from_basis) + " has not been set") ppinv = \ fmodule._basis_changes[(basis, from_basis)].comp(from_basis) @@ -898,7 +967,8 @@ class :class:`~sage.tensor.modules.comp.Components` def set_comp(self, basis=None): r""" - Return the components in a given basis for assignment. + Return the components of ``self`` w.r.t. a given module basis for + assignment. The components with respect to other bases are deleted, in order to avoid any inconsistency. To keep them, use the method :meth:`add_comp` @@ -913,8 +983,8 @@ def set_comp(self, basis=None): OUTPUT: - components in the given basis, as an instance of the - class :class:`~sage.tensor.modules.comp.Components`; if such components did not exist - previously, they are created. + class :class:`~sage.tensor.modules.comp.Components`; if such + components did not exist previously, they are created. EXAMPLES: @@ -924,10 +994,10 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t.set_comp()[0,1] = -3 - sage: t.view() + sage: t.display() t = -3 e_0*e^1 sage: t.set_comp()[1,2] = 2 - sage: t.view() + sage: t.display() t = -3 e_0*e^1 + 2 e_1*e^2 sage: t.set_comp(e) 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the @@ -939,16 +1009,16 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not sage: t.set_comp(f)[0,1] = 4 sage: t._components.keys() # the components w.r.t. basis e have been deleted [Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] - sage: t.view(f) + sage: t.display(f) t = 4 f_0*f^1 The components w.r.t. basis e can be deduced from those w.r.t. basis f, once a relation between the two bases has been set:: - sage: a = M.automorphism_tensor() + sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: M.set_change_of_basis(e, f, a) - sage: t.view(e) + sage: t.display(e) t = -4 e_1*e^2 sage: t._components.keys() # random output (dictionary keys) [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, @@ -959,8 +1029,8 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not basis = self._fmodule._def_basis if basis not in self._components: if basis not in self._fmodule._known_bases: - raise ValueError("the " + str(basis) + " has not been " + - "defined on the " + str(self._fmodule)) + raise ValueError("the {} has not been ".format(basis) + + "defined on the {}".format(self._fmodule)) self._components[basis] = self._new_comp(basis) self._del_derived() # deletes the derived quantities self.del_other_comp(basis) @@ -968,10 +1038,10 @@ class :class:`~sage.tensor.modules.comp.Components`; if such components did not def add_comp(self, basis=None): r""" - Return the components in a given basis for assignment, keeping the - components in other bases. + Return the components of ``self`` w.r.t. a given module basis for + assignment, keeping the components w.r.t. other bases. - To delete the components in other bases, use the method + To delete the components w.r.t. other bases, use the method :meth:`set_comp` instead. INPUT: @@ -1000,10 +1070,10 @@ class :class:`~sage.tensor.modules.comp.Components`; sage: e = M.basis('e') sage: t = M.tensor((1,1), name='t') sage: t.add_comp()[0,1] = -3 - sage: t.view() + sage: t.display() t = -3 e_0*e^1 sage: t.add_comp()[1,2] = 2 - sage: t.view() + sage: t.display() t = -3 e_0*e^1 + 2 e_1*e^2 sage: t.add_comp(e) 2-indices components w.r.t. Basis (e_0,e_1,e_2) on the @@ -1019,17 +1089,17 @@ class :class:`~sage.tensor.modules.comp.Components`; sage: t._components.keys() # # random output (dictionary keys) [Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring, Basis (f_0,f_1,f_2) on the Rank-3 free module M over the Integer Ring] - sage: t.view(f) + sage: t.display(f) t = 4 f_0*f^1 - sage: t.view(e) + sage: t.display(e) t = -3 e_0*e^1 + 2 e_1*e^2 """ if basis is None: basis = self._fmodule._def_basis if basis not in self._components: if basis not in self._fmodule._known_bases: - raise ValueError("the " + str(basis) + " has not been " + - "defined on the " + str(self._fmodule)) + raise ValueError("the {} has not been ".format(basis) + + "defined on the {}".format(self._fmodule)) self._components[basis] = self._new_comp(basis) self._del_derived() # deletes the derived quantities return self._components[basis] @@ -1073,8 +1143,8 @@ def del_other_comp(self, basis=None): """ if basis is None: basis = self._fmodule._def_basis if basis not in self._components: - raise ValueError("the components w.r.t. the " + - str(basis) + " have not been defined") + raise ValueError("the components w.r.t. the {}".format(basis) + + " have not been defined") to_be_deleted = [] for other_basis in self._components: if other_basis != basis: @@ -1161,13 +1231,13 @@ def __setitem__(self, args, value): sage: t = M.tensor((0,2), name='t') sage: e = M.basis('e') sage: t.__setitem__((e,0,1), 5) - sage: t.view() + sage: t.display() t = 5 e^0*e^1 sage: t.__setitem__((0,1), 5) # equivalent to above since e is the default basis - sage: t.view() + sage: t.display() t = 5 e^0*e^1 sage: t[0,1] = 5 # end-user usage - sage: t.view() + sage: t.display() t = 5 e^0*e^1 sage: t.__setitem__(slice(None), [[1,-2,3], [-4,5,-6], [7,-8,9]]) sage: t[:] @@ -1278,7 +1348,7 @@ def common_basis(self, other): Linking bases ``e`` and ``f`` changes the result:: - sage: a = M.automorphism_tensor() + sage: a = M.automorphism() sage: a[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: M.set_change_of_basis(e, f, a) sage: u.common_basis(v) @@ -1504,7 +1574,7 @@ def __pos__(self): sage: t[0,1] = 7 sage: p = t.__pos__() ; p Type-(2,0) tensor +t on the Rank-3 free module M over the Integer Ring - sage: p.view() + sage: p.display() +t = 7 e_0*e_1 sage: p == t True @@ -1535,11 +1605,11 @@ def __neg__(self): sage: t = M.tensor((2,0), name='t') sage: e = M.basis('e') sage: t[0,1], t[1,2] = 7, -4 - sage: t.view() + sage: t.display() t = 7 e_0*e_1 - 4 e_1*e_2 sage: a = t.__neg__() ; a Type-(2,0) tensor -t on the Rank-3 free module M over the Integer Ring - sage: a.view() + sage: a.display() -t = -7 e_0*e_1 + 4 e_1*e_2 sage: a == -t True @@ -1791,14 +1861,15 @@ def __mul__(self, other): k2, l2 = other._tensor_type if l1 != 0: comp_result = comp_prov.swap_adjacent_indices(k1, - self._tensor_rank, - self._tensor_rank+k2) + self._tensor_rank, + self._tensor_rank+k2) else: comp_result = comp_prov # no reordering is necessary - result = self._fmodule.tensor_from_comp((k1+k2, l1+l2), comp_result) + result = self._fmodule.tensor_from_comp((k1+k2, l1+l2), + comp_result) result._name = format_mul_txt(self._name, '*', other._name) - result._latex_name = format_mul_latex(self._latex_name, r'\otimes ', - other._latex_name) + result._latex_name = format_mul_latex(self._latex_name, + r'\otimes ', other._latex_name) return result # multiplication by a scalar: @@ -1819,7 +1890,8 @@ def __div__(self, other): sage: a = M.tensor((2,0), name='a') sage: a[:] = [[4,0], [-2,5]] sage: s = a.__div__(4) ; s - Type-(2,0) tensor on the Rank-2 free module M over the Rational Field + Type-(2,0) tensor on the 2-dimensional vector space M over the + Rational Field sage: s[:] [ 1 0] [-1/2 5/4] @@ -1845,13 +1917,15 @@ def __call__(self, *args): - ``*args`` -- list of `k` linear forms and `l` module elements with ``self`` being a tensor of type `(k, l)` - EXAMPLES:: + EXAMPLES: + + Action of a type-(2,1) tensor:: sage: M = FiniteRankFreeModule(ZZ, 2, name='M') sage: e = M.basis('e') sage: t = M.tensor((2,1), name='t', antisym=(0,1)) sage: t[0,1,0], t[0,1,1] = 3, 2 - sage: t.view() + sage: t.display() t = 3 e_0*e_1*e^0 + 2 e_0*e_1*e^1 - 3 e_1*e_0*e^0 - 2 e_1*e_0*e^1 sage: a = M.linear_form() sage: a[:] = 1, 2 @@ -1864,24 +1938,40 @@ def __call__(self, *args): True sage: t(a,b,v) == t.contract(v).contract(b).contract(a) True + + Action of a linear form on a vector:: + sage: a.__call__(v) 0 - sage: v.__call__(a) - 0 + sage: a.__call__(v) == a(v) + True + sage: a(v) == a.contract(v) + True sage: b.__call__(v) -7 + sage: b.__call__(v) == b(v) + True + sage: b(v) == b.contract(v) + True + + Action of a vector on a linear form:: + + sage: v.__call__(a) + 0 sage: v.__call__(b) -7 """ - from free_module_alt_form import FreeModuleLinForm # Consistency checks: p = len(args) if p != self._tensor_rank: raise TypeError(str(self._tensor_rank) + " arguments must be provided") for i in range(self._tensor_type[0]): - if not isinstance(args[i], FreeModuleLinForm): + if not isinstance(args[i], FreeModuleTensor): + raise TypeError("the argument no. " + str(i+1) + + " must be a linear form") + if args[i]._tensor_type != (0,1): raise TypeError("the argument no. " + str(i+1) + " must be a linear form") for i in range(self._tensor_type[0],p): @@ -1889,6 +1979,32 @@ def __call__(self, *args): raise TypeError("the argument no. " + str(i+1) + " must be a module element") fmodule = self._fmodule + # + # Specific case of a linear form acting on a vector (for efficiency): + # + if self._tensor_type == (0,1): + vector = args[0] + basis = self.common_basis(vector) + if basis is None: + raise ValueError("no common basis for the components") + omega = self._components[basis] + vv = vector._components[basis] + resu = 0 + for i in fmodule.irange(): + resu += omega[[i]]*vv[[i]] + # Name and LaTeX symbol of the output: + if hasattr(resu, '_name'): + if self._name is not None and vector._name is not None: + resu._name = self._name + "(" + vector._name + ")" + if hasattr(resu, '_latex_name'): + if self._latex_name is not None and \ + vector._latex_name is not None: + resu._latex_name = self._latex_name + r"\left(" + \ + vector._latex_name + r"\right)" + return resu + # + # Generic case + # # Search for a common basis basis = None # First try with the module's default basis @@ -1993,7 +2109,7 @@ def trace(self, pos1=0, pos2=1): sage: e = M.basis('e') ; e Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a = M.tensor((1,1), name='a') ; a - Endomorphism tensor a on the Rank-3 free module M over the Integer Ring + Type-(1,1) tensor a on the Rank-3 free module M over the Integer Ring sage: a[:] = [[1,2,3], [4,5,6], [7,8,9]] sage: a.trace() 15 @@ -2065,7 +2181,8 @@ def trace(self, pos1=0, pos2=1): [-26 -4 6] [-31 -2 9] [-36 0 12] - sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) for j in M.irange()] for i in M.irange()] ) # check + sage: s[:] == matrix( [[sum(t[k,i,k,j] for k in M.irange()) + ....: for j in M.irange()] for i in M.irange()] ) # check True Use of index notation instead of :meth:`trace`:: @@ -2108,7 +2225,8 @@ def trace(self, pos1=0, pos2=1): if self._tensor_rank == 2: # result is a scalar return resu_comp else: - return self._fmodule.tensor_from_comp((k_con-1, l_cov-1), resu_comp) + return self._fmodule.tensor_from_comp((k_con-1, l_cov-1), + resu_comp) def contract(self, *args): r""" @@ -2186,11 +2304,11 @@ def contract(self, *args): Contraction of a tensor of type `(1,1)` with a tensor of type `(1,0)`:: - sage: a = M.endomorphism_tensor() # tensor of type (1,1) + sage: a = M.tensor((1,1)) sage: a[:] = [[-1,2,3],[4,-5,6],[7,8,9]] sage: s = a.contract(b) ; s Element of the Rank-3 free module M over the Integer Ring - sage: s.view() + sage: s.display() 2 e_0 - 29 e_1 + 36 e_2 Since the index positions have not been specified, the contraction @@ -2229,12 +2347,6 @@ def contract(self, *args): ... TypeError: contraction on two contravariant indices not permitted - In the present case, performing the contraction is identical to - applying the endomorphism to the module element:: - - sage: a.contract(b) == a(b) - True - Contraction of a tensor of type `(2,1)` with a tensor of type `(0,2)`:: sage: a = a*b ; a @@ -2273,7 +2385,7 @@ def contract(self, *args): contraction to take place, reflecting the fact that the contraction is basis-independent:: - sage: A = M.automorphism_tensor() + sage: A = M.automorphism() sage: A[:] = [[0,0,1], [1,0,0], [0,-1,0]] sage: h = e.new_basis(A, 'h') sage: b.comp(h)[:] # forces the computation of b's components w.r.t. basis h @@ -2299,7 +2411,7 @@ def contract(self, *args): sage: b = M([1,-1,2])*b ; b # a tensor of type (1,2) Type-(1,2) tensor on the Rank-3 free module M over the Integer Ring sage: s = a.contract(1,2,b,1,0) ; s # the double contraction - Endomorphism tensor on the Rank-3 free module M over the Integer Ring + Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring sage: s[:] [ -36 30 15] [-252 210 105] @@ -2403,7 +2515,8 @@ def symmetrize(self, *pos, **kwargs): sage: t = M.tensor((2,0)) sage: t[:] = [[2,1,-3],[0,-4,5],[-1,4,2]] sage: s = t.symmetrize() ; s - Type-(2,0) tensor on the Rank-3 free module M over the Rational Field + Type-(2,0) tensor on the 3-dimensional vector space M over the + Rational Field sage: t[:], s[:] ( [ 2 1 -3] [ 2 1/2 -2] @@ -2421,7 +2534,8 @@ def symmetrize(self, *pos, **kwargs): suffices to pass the indices as a string inside square brackets:: sage: t['(ij)'] - Type-(2,0) tensor on the Rank-3 free module M over the Rational Field + Type-(2,0) tensor on the 3-dimensional vector space M over the + Rational Field sage: t['(ij)'].symmetries() symmetry: (0, 1); no antisymmetry sage: t['(ij)'] == t.symmetrize() @@ -2441,9 +2555,12 @@ def symmetrize(self, *pos, **kwargs): Symmetrization of a tensor of type `(0,3)` on the first two arguments:: sage: t = M.tensor((0,3)) - sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], + ....: [[10,-11,12], [13,14,-15], [16,17,18]], + ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.symmetrize(0,1) ; s # (0,1) = the first two arguments - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: s[:] @@ -2471,7 +2588,8 @@ def symmetrize(self, *pos, **kwargs): last arguments:: sage: s = t.symmetrize(0,2) ; s # (0,2) = first and last arguments - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() symmetry: (0, 2); no antisymmetry sage: s[:] @@ -2487,7 +2605,8 @@ def symmetrize(self, *pos, **kwargs): Symmetrization of a tensor of type `(0,3)` on the last two arguments:: sage: s = t.symmetrize(1,2) ; s # (1,2) = the last two arguments - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() symmetry: (1, 2); no antisymmetry sage: s[:] @@ -2512,7 +2631,8 @@ def symmetrize(self, *pos, **kwargs): Full symmetrization of a tensor of type `(0,3)`:: sage: s = t.symmetrize() ; s - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() symmetry: (0, 1, 2); no antisymmetry sage: s[:] @@ -2535,7 +2655,9 @@ def symmetrize(self, *pos, **kwargs): Symmetrization can be performed only on arguments on the same type:: sage: t = M.tensor((1,2)) - sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], + ....: [[10,-11,12], [13,14,-15], [16,17,18]], + ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.symmetrize(0,1) Traceback (most recent call last): ... @@ -2625,7 +2747,8 @@ def antisymmetrize(self, *pos, **kwargs): sage: t = M.tensor((2,0)) sage: t[:] = [[1,-2,3], [4,5,6], [7,8,-9]] sage: s = t.antisymmetrize() ; s - Type-(2,0) tensor on the Rank-3 free module M over the Rational Field + Type-(2,0) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) sage: t[:], s[:] @@ -2646,9 +2769,12 @@ def antisymmetrize(self, *pos, **kwargs): arguments:: sage: t = M.tensor((0,3)) - sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], + ....: [[10,-11,12], [13,14,-15], [16,17,18]], + ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.antisymmetrize(0,1) ; s # (0,1) = the first two arguments - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1) sage: s[:] @@ -2669,7 +2795,8 @@ def antisymmetrize(self, *pos, **kwargs): inside square brackets:: sage: s1 = t['_[ij]k'] ; s1 - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s1.symmetries() no symmetry; antisymmetry: (0, 1) sage: s1 == s @@ -2690,7 +2817,8 @@ def antisymmetrize(self, *pos, **kwargs): arguments:: sage: s = t.antisymmetrize(0,2) ; s # (0,2) = first and last arguments - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 2) sage: s[:] @@ -2711,7 +2839,8 @@ def antisymmetrize(self, *pos, **kwargs): arguments:: sage: s = t.antisymmetrize(1,2) ; s # (1,2) = the last two arguments - Type-(0,3) tensor on the Rank-3 free module M over the Rational Field + Type-(0,3) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() no symmetry; antisymmetry: (1, 2) sage: s[:] @@ -2735,15 +2864,16 @@ def antisymmetrize(self, *pos, **kwargs): Full antisymmetrization of a tensor of type (0,3):: sage: s = t.antisymmetrize() ; s - Alternating form of degree 3 on the - Rank-3 free module M over the Rational Field + Alternating form of degree 3 on the 3-dimensional vector space M + over the Rational Field sage: s.symmetries() no symmetry; antisymmetry: (0, 1, 2) sage: s[:] [[[0, 0, 0], [0, 0, 2/3], [0, -2/3, 0]], [[0, 0, -2/3], [0, 0, 0], [2/3, 0, 0]], [[0, 2/3, 0], [-2/3, 0, 0], [0, 0, 0]]] - sage: all(s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k]+t[k,i,j]-t[k,j,i]) # Check: + sage: all(s[i,j,k] == 1/6*(t[i,j,k]-t[i,k,j]+t[j,k,i]-t[j,i,k] + ....: +t[k,i,j]-t[k,j,i]) ....: for i in range(3) for j in range(3) for k in range(3)) True sage: s.antisymmetrize() == s # another test @@ -2772,7 +2902,9 @@ def antisymmetrize(self, *pos, **kwargs): Antisymmetrization can be performed only on arguments on the same type:: sage: t = M.tensor((1,2)) - sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], [[10,-11,12], [13,14,-15], [16,17,18]], [[19,-20,-21], [-22,23,24], [25,26,-27]]] + sage: t[:] = [[[1,2,3], [-4,5,6], [7,8,-9]], + ....: [[10,-11,12], [13,14,-15], [16,17,18]], + ....: [[19,-20,-21], [-22,23,24], [25,26,-27]]] sage: s = t.antisymmetrize(0,1) Traceback (most recent call last): ... @@ -2842,6 +2974,9 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): r""" Element of a free module of finite rank over a commutative ring. + This is a Sage *element* class, the corresponding *parent* class being + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule`. + The class :class:`FiniteRankFreeModuleElement` inherits from :class:`FreeModuleTensor` because the elements of a free module `M` of finite rank over a commutative ring `R` are identified with tensors of @@ -2860,8 +2995,9 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): INPUT: - - ``fmodule`` -- free module `M` over a commutative ring `R` (must be an - instance of :class:`FiniteRankFreeModule`) + - ``fmodule`` -- free module `M` of finite rank over a commutative ring + `R`, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` - ``name`` -- (default: ``None``) name given to the element - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the element; if none is provided, the LaTeX symbol is set to ``name`` @@ -2879,7 +3015,7 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): sage: v = M([2,0,-1], basis=e, name='v') ; v Element v of the Rank-3 free module M over the Integer Ring - sage: v.view() # expansion on the default basis (e) + sage: v.display() # expansion on the default basis (e) v = 2 e_0 - e_2 sage: v.parent() is M True @@ -2890,7 +3026,7 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): sage: v2 = M.tensor((1,0), name='v') sage: v2[0], v2[2] = 2, -1 ; v2 Element v of the Rank-3 free module M over the Integer Ring - sage: v2.view() + sage: v2.display() v = 2 e_0 - e_2 sage: v2 == v True @@ -2901,7 +3037,7 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): sage: v3 = 2*e[0] - e[2] sage: v3.set_name('v') ; v3 # in this case, the name has to be set separately Element v of the Rank-3 free module M over the Integer Ring - sage: v3.view() + sage: v3.display() v = 2 e_0 - e_2 sage: v3 == v True @@ -2929,15 +3065,15 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring sage: a = M([0,1,3], name='a') ; a Element a of the Rank-3 free module M over the Integer Ring - sage: a.view() + sage: a.display() a = e_1 + 3 e_2 sage: b = M([2,-2,1], name='b') ; b Element b of the Rank-3 free module M over the Integer Ring - sage: b.view() + sage: b.display() b = 2 e_0 - 2 e_1 + e_2 sage: s = a + b ; s Element a+b of the Rank-3 free module M over the Integer Ring - sage: s.view() + sage: s.display() a+b = 2 e_0 - e_1 + 4 e_2 sage: all(s[i] == a[i] + b[i] for i in M.irange()) True @@ -2946,7 +3082,7 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): sage: s = a - b ; s Element a-b of the Rank-3 free module M over the Integer Ring - sage: s.view() + sage: s.display() a-b = -2 e_0 + 3 e_1 + 2 e_2 sage: all(s[i] == a[i] - b[i] for i in M.irange()) True @@ -2955,9 +3091,9 @@ class FiniteRankFreeModuleElement(FreeModuleTensor): sage: s = 2*a ; s Element of the Rank-3 free module M over the Integer Ring - sage: s.view() + sage: s.display() 2 e_1 + 6 e_2 - sage: a.view() + sage: a.display() a = e_1 + 3 e_2 Tensor product:: @@ -3017,15 +3153,23 @@ def _repr_(self): description = "Element " if self._name is not None: description += self._name + " " - description += "of the " + str(self._fmodule) + description += "of the {}".format(self._fmodule) return description def _new_comp(self, basis): r""" - Create some components in the given basis. + Create some (uninitialized) components of ``self`` in a given basis. This method, which is already implemented in - :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency + :meth:`FreeModuleTensor._new_comp`, is redefined here for efficiency. + + INPUT: + + - ``basis`` -- basis of the free module on which ``self`` is defined + + OUTPUT: + + - an instance of :class:`~sage.tensor.modules.comp.Components` EXAMPLE:: @@ -3060,4 +3204,3 @@ def _new_instance(self): """ return self.__class__(self._fmodule) - diff --git a/src/sage/tensor/modules/free_module_tensor_spec.py b/src/sage/tensor/modules/free_module_tensor_spec.py deleted file mode 100644 index 73ff1d66034..00000000000 --- a/src/sage/tensor/modules/free_module_tensor_spec.py +++ /dev/null @@ -1,728 +0,0 @@ -""" -Type-(1,1) tensors on free modules - -Three derived classes of -:class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor` are devoted -to type-(1,1) tensors: - -* :class:`FreeModuleEndomorphismTensor` for endomorphisms viewed as type-(1,1) - tensors - - * :class:`FreeModuleAutomorphismTensor` for invertible endomorphisms viewed - as type-(1,1) tensors - - * :class:`FreeModuleIdentityTensor` for the identity map viewed as a - type-(1,1) tensor - - -AUTHORS: - -- Eric Gourgoulhon, Michal Bejger (2014): initial version - -.. TODO:: - - Suppress :class:`FreeModuleEndomorphismTensor` ? (since the - coercion of type-(1,1) tensors to free module endomorphisms is implemented - now) This would leave only :class:`FreeModuleAutomorphismTensor` and - :class:`FreeModuleIdentityTensor`. - -""" -#****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger -# -# Distributed under the terms of the GNU General Public License (GPL) -# as published by the Free Software Foundation; either version 2 of -# the License, or (at your option) any later version. -# http://www.gnu.org/licenses/ -#****************************************************************************** - -from sage.tensor.modules.free_module_tensor import FreeModuleTensor - -class FreeModuleEndomorphismTensor(FreeModuleTensor): - r""" - Endomorphism (considered as a type-`(1,1)` tensor) on a free module. - - INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: ``None``) name given to the endomorphism - - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the - endomorphism; if none is provided, the LaTeX symbol is set to ``name`` - - EXAMPLES: - - Endomorphism tensor on a rank-3 module:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: t = M.endomorphism_tensor('T') ; t - Endomorphism tensor T on the Rank-3 free module M over the Integer Ring - sage: t.parent() - Free module of type-(1,1) tensors on the - Rank-3 free module M over the Integer Ring - sage: t.tensor_type() - (1, 1) - sage: t.tensor_rank() - 2 - - The method - :meth:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule.tensor` - with the argument ``(1,1)`` can be used as well to create such a tensor:: - - sage: t = M.tensor((1,1), name='T') ; t - Endomorphism tensor T on the Rank-3 free module M over the Integer Ring - - Components of the endomorphism with respect to a given basis:: - - sage: e = M.basis('e') ; e - Basis (e_0,e_1,e_2) on the Rank-3 free module M over the Integer Ring - sage: t[:] = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] - sage: t[:] - [1 2 3] - [4 5 6] - [7 8 9] - sage: t.view() - T = e_0*e^0 + 2 e_0*e^1 + 3 e_0*e^2 + 4 e_1*e^0 + 5 e_1*e^1 - + 6 e_1*e^2 + 7 e_2*e^0 + 8 e_2*e^1 + 9 e_2*e^2 - - The matrix of components w.r.t. to a given basis:: - - sage: m = matrix(t.components(e)) ; m - [1 2 3] - [4 5 6] - [7 8 9] - sage: m.parent() - Full MatrixSpace of 3 by 3 dense matrices over Integer Ring - - The endomorphism acting on a module element:: - - sage: v = M([1,2,3], basis=e, name='v') ; v - Element v of the Rank-3 free module M over the Integer Ring - sage: w = t(v) ; w - Element T(v) of the Rank-3 free module M over the Integer Ring - sage: w[:] - [14, 32, 50] - sage: for i in M.irange(): # Check: - ....: print sum( t[i,j]*v[j] for j in M.irange() ), - 14 32 50 - - """ - def __init__(self, fmodule, name=None, latex_name=None): - r""" - TESTS:: - - sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleEndomorphismTensor - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: E = FreeModuleEndomorphismTensor(M, name='a') - sage: E[e,0,1] = -3 - sage: TestSuite(E).run(skip="_test_category") # see below - - In the above test suite, _test_category fails because E is not an - instance of E.parent().category().element_class. - - """ - FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, - latex_name=latex_name) - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: M.endomorphism_tensor() - Endomorphism tensor on the Rank-3 free module M over the Integer Ring - sage: M.endomorphism_tensor(name='a') - Endomorphism tensor a on the Rank-3 free module M over the Integer Ring - - """ - description = "Endomorphism tensor " - if self._name is not None: - description += self._name + " " - description += "on the " + str(self._fmodule) - return description - - def _new_instance(self): - r""" - Create an instance of the same class as ``self``. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.endomorphism_tensor(name='a') - sage: a._new_instance() - Endomorphism tensor on the Rank-3 free module M over the Integer Ring - - """ - return self.__class__(self._fmodule) - - def __call__(self, *arg): - r""" - Redefinition of :meth:`FreeModuleTensor.__call__` to allow for a single - argument (module element). - - EXAMPLES: - - Call with a single argument --> return a module element:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: a = M.endomorphism_tensor(name='a') - sage: e = M.basis('e') - sage: a[0,1], a[1,1], a[2,1] = 2, 4, -5 - sage: v = M([2,1,4], name='v') - sage: s = a.__call__(v) ; s - Element a(v) of the Rank-3 free module M over the Integer Ring - sage: s.view() - a(v) = 2 e_0 + 4 e_1 - 5 e_2 - sage: s == a(v) - True - sage: s == a.contract(v) - True - - Call with two arguments (:class:`FreeModuleTensor` behaviour) - --> return a scalar:: - - sage: b = M.linear_form(name='b') - sage: b[:] = 7, 0, 2 - sage: a.__call__(b,v) - 4 - sage: a(b,v) == a.__call__(b,v) - True - sage: a(b,v) == s(b) - True - - """ - from free_module_tensor import FiniteRankFreeModuleElement - if len(arg) > 1: - # the endomorphism acting as a type-(1,1) tensor on a pair - # (linear form, module element), returning a scalar: - return FreeModuleTensor.__call__(self, *arg) - # the endomorphism acting as such, on a module element, returning a - # module element: - vector = arg[0] - if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("the argument must be an element of a free module") - basis = self.common_basis(vector) - t = self._components[basis] - v = vector._components[basis] - fmodule = self._fmodule - result = vector._new_instance() - for i in fmodule.irange(): - res = 0 - for j in fmodule.irange(): - res += t[[i,j]]*v[[j]] - result.set_comp(basis)[i] = res - # Name of the output: - result._name = None - if self._name is not None and vector._name is not None: - result._name = self._name + "(" + vector._name + ")" - # LaTeX symbol for the output: - result._latex_name = None - if self._latex_name is not None and vector._latex_name is not None: - result._latex_name = self._latex_name + r"\left(" + \ - vector._latex_name + r"\right)" - return result - -#****************************************************************************** - -class FreeModuleAutomorphismTensor(FreeModuleEndomorphismTensor): - r""" - Automorphism (considered as a type-`(1,1)` tensor) on a free module. - - INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: ``None``) name given to the automorphism - - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the - automorphism; if none is provided, the LaTeX symbol is set to ``name`` - - EXAMPLES: - - Automorphism tensor on a rank-2 free module (vector space) on `\QQ`:: - - sage: M = FiniteRankFreeModule(QQ, 2, name='M') - sage: a = M.automorphism_tensor('A') ; a - Automorphism tensor A on the Rank-2 free module M over the Rational Field - - Automorphisms are tensors of type `(1,1)`:: - - sage: a.parent() - Free module of type-(1,1) tensors on the - Rank-2 free module M over the Rational Field - sage: a.tensor_type() - (1, 1) - sage: a.tensor_rank() - 2 - - Setting the components in a basis:: - - sage: e = M.basis('e') ; e - Basis (e_0,e_1) on the Rank-2 free module M over the Rational Field - sage: a[:] = [[1, 2], [-1, 3]] - sage: a[:] - [ 1 2] - [-1 3] - sage: a.view(basis=e) - A = e_0*e^0 + 2 e_0*e^1 - e_1*e^0 + 3 e_1*e^1 - - The inverse automorphism is obtained via the method :meth:`inverse`:: - - sage: b = a.inverse() ; b - Automorphism tensor A^(-1) on the Rank-2 free module M over the Rational Field - sage: b.view(basis=e) - A^(-1) = 3/5 e_0*e^0 - 2/5 e_0*e^1 + 1/5 e_1*e^0 + 1/5 e_1*e^1 - sage: b[:] - [ 3/5 -2/5] - [ 1/5 1/5] - sage: a[:] * b[:] # check that b is indeed the inverse of a - [1 0] - [0 1] - - """ - def __init__(self, fmodule, name=None, latex_name=None): - r""" - TESTS:: - - sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleAutomorphismTensor - sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: e = M.basis('e') - sage: a = FreeModuleAutomorphismTensor(M, name='a') - sage: a[e,:] = [[1,0,1],[0,2,0],[0,0,-3]] - sage: TestSuite(a).run(skip="_test_category") # see below - - In the above test suite, _test_category fails because a is not an - instance of a.parent().category().element_class. - - """ - FreeModuleEndomorphismTensor.__init__(self, fmodule, name=name, - latex_name=latex_name) - self._inverse = None # inverse automorphism not set yet - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: M.automorphism_tensor() - Automorphism tensor on the Rank-3 free module M over the Rational Field - sage: M.automorphism_tensor(name='a') - Automorphism tensor a on the Rank-3 free module M over the Rational Field - - """ - description = "Automorphism tensor " - if self._name is not None: - description += self._name + " " - description += "on the " + str(self._fmodule) - return description - - def _del_derived(self): - r""" - Delete the derived quantities. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: a = M.automorphism_tensor(name='a') - sage: e = M.basis('e') - sage: a[e,:] = [[1,0,-1], [0,3,0], [0,0,2]] - sage: b = a.inverse() - sage: a._inverse - Automorphism tensor a^(-1) on the Rank-3 free module M over the Rational Field - sage: a._del_derived() - sage: a._inverse # has been reset to None - - """ - # First delete the derived quantities pertaining to the mother class: - FreeModuleEndomorphismTensor._del_derived(self) - # Then deletes the inverse automorphism: - self._inverse = None - - def inverse(self): - r""" - Return the inverse automorphism. - - OUTPUT: - - - instance of :class:`FreeModuleAutomorphismTensor` representing the - automorphism that is the inverse of ``self``. - - EXAMPLES: - - Inverse of an automorphism on a rank-3 free module:: - - sage: M = FiniteRankFreeModule(QQ, 3, name='M') - sage: a = M.automorphism_tensor('A') - sage: e = M.basis('e') - sage: a[:] = [[1,0,-1], [0,3,0], [0,0,2]] - sage: b = a.inverse() ; b - Automorphism tensor A^(-1) on the Rank-3 free module M over the Rational Field - sage: b[:] - [ 1 0 1/2] - [ 0 1/3 0] - [ 0 0 1/2] - - We may check that ``b`` is the inverse of ``a`` by performing the - matrix product of the components in the basis ``e``:: - - sage: a[:] * b[:] - [1 0 0] - [0 1 0] - [0 0 1] - - Another check is of course:: - - sage: b.inverse() == a - True - - """ - from sage.matrix.constructor import matrix - from comp import Components - if self._inverse is None: - if self._name is None: - inv_name = None - else: - inv_name = self._name + '^(-1)' - if self._latex_name is None: - inv_latex_name = None - else: - inv_latex_name = self._latex_name + r'^{-1}' - fmodule = self._fmodule - si = fmodule._sindex - nsi = fmodule._rank + si - self._inverse = self.__class__(fmodule, inv_name, inv_latex_name) - for basis in self._components: - try: - mat_self = matrix( - [[self.comp(basis)[[i, j]] - for j in range(si, nsi)] for i in range(si, nsi)]) - except (KeyError, ValueError): - continue - mat_inv = mat_self.inverse() - cinv = Components(fmodule._ring, basis, 2, start_index=si, - output_formatter=fmodule._output_formatter) - for i in range(si, nsi): - for j in range(si, nsi): - cinv[i, j] = mat_inv[i-si,j-si] - self._inverse._components[basis] = cinv - return self._inverse - - -#****************************************************************************** - -class FreeModuleIdentityTensor(FreeModuleAutomorphismTensor): - r""" - Identity map (considered as a type-(1,1) tensor) on a free module. - - INPUT: - - - ``fmodule`` -- free module `M` over a commutative ring `R` - (must be an instance of :class:`FiniteRankFreeModule`) - - ``name`` -- (default: 'Id') name given to the identity tensor. - - ``latex_name`` -- (default: ``None``) LaTeX symbol to denote the identity - tensor; if none is provided, the LaTeX symbol is set to ``name`` - - EXAMPLES: - - Identity tensor on a rank-3 free module:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.identity_tensor() ; a - Identity tensor on the Rank-3 free module M over the Integer Ring - - The LaTeX symbol is set by default to `\mathrm{Id}`, but can be changed:: - - sage: latex(a) - \mathrm{Id} - sage: a = M.identity_tensor(latex_name=r'\mathrm{1}') - sage: latex(a) - \mathrm{1} - - The identity is a tensor of type `(1,1)` on the free module:: - - sage: a.parent() - Free module of type-(1,1) tensors on the - Rank-3 free module M over the Integer Ring - sage: a.tensor_type() - (1, 1) - sage: a.tensor_rank() - 2 - - Its components are Kronecker deltas in any basis:: - - sage: a[:] - [1 0 0] - [0 1 0] - [0 0 1] - sage: a.comp() # components in the module's default basis (e) - Kronecker delta of size 3x3 - sage: a.view() - Id = e_0*e^0 + e_1*e^1 + e_2*e^2 - sage: f = M.basis('f') - sage: a.comp(basis=f) - Kronecker delta of size 3x3 - sage: a.comp(f)[:] - [1 0 0] - [0 1 0] - [0 0 1] - - The components can be read, but cannot be set:: - - sage: a[1,1] - 1 - sage: a[1,1] = 2 - Traceback (most recent call last): - ... - TypeError: the components of the identity map cannot be changed - - The identity tensor acting on a module element:: - - sage: v = M([2,-3,1], basis=e, name='v') - sage: v.view() - v = 2 e_0 - 3 e_1 + e_2 - sage: u = a(v) ; u - Element v of the Rank-3 free module M over the Integer Ring - sage: u is v - True - - The identity tensor acting as a type-`(1,1)` tensor on a pair (linear form, - module element):: - - sage: w = M.tensor((0,1), name='w') ; w - Linear form w on the Rank-3 free module M over the Integer Ring - sage: w[:] = [0, 3, 2] - sage: s = a(w,v) ; s - -7 - sage: s == w(v) - True - - The identity tensor is its own inverse:: - - sage: a.inverse() == a - True - sage: a.inverse() is a - True - - """ - def __init__(self, fmodule, name='Id', latex_name=None): - r""" - TESTS:: - - sage: from sage.tensor.modules.free_module_tensor_spec import FreeModuleIdentityTensor - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: Id = FreeModuleIdentityTensor(M) - sage: TestSuite(Id).run(skip="_test_category") # see below - - In the above test suite, _test_category fails because Id is not an - instance of Id.parent().category().element_class. - - """ - if latex_name is None and name == 'Id': - latex_name = r'\mathrm{Id}' - FreeModuleAutomorphismTensor.__init__(self, fmodule, name=name, - latex_name=latex_name) - self._inverse = self # the identity is its own inverse - self.comp() # Initializing the components in the module's default basis - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: M.identity_tensor() - Identity tensor on the Rank-3 free module M over the Integer Ring - - """ - description = "Identity tensor " - if self._name != 'Id': - description += self._name + " " - description += "on the " + str(self._fmodule) - return description - - def _del_derived(self): - r""" - Delete the derived quantities. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: id = M.identity_tensor() - sage: id._del_derived() - - """ - # FreeModuleAutomorphismTensor._del_derived is bypassed: - FreeModuleEndomorphismTensor._del_derived(self) - - def _new_comp(self, basis): - r""" - Create some components in the given basis. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: id = M.identity_tensor() - sage: id._new_comp(e) - Kronecker delta of size 3x3 - sage: type(id._new_comp(e)) - - - """ - from comp import KroneckerDelta - fmodule = self._fmodule # the base free module - return KroneckerDelta(fmodule._ring, basis, start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) - - def components(self, basis=None, from_basis=None): - r""" - Return the components in a given basis as a Kronecker delta. - - INPUT: - - - ``basis`` -- (default: ``None``) module basis in which the components - are required; if none is provided, the components are assumed to - refer to the module's default basis - - ``from_basis`` -- (default: ``None``) unused (present just for - ensuring compatibility with ``FreeModuleTensor.comp`` calling list) - - OUTPUT: - - - components in the basis ``basis``, as an instance of the - class :class:`~sage.tensor.modules.comp.KroneckerDelta` - - EXAMPLES: - - Components of the identity map on a rank-3 free module:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.identity_tensor() - sage: a.components(basis=e) - Kronecker delta of size 3x3 - - For the module's default basis, the argument ``basis`` can be omitted:: - - sage: a.components() is a.components(basis=e) - True - sage: a.components()[:] - [1 0 0] - [0 1 0] - [0 0 1] - - A shortcut is ``a.comp()``:: - - sage: a.comp() is a.components() - True - - """ - if basis is None: - basis = self._fmodule._def_basis - if basis not in self._components: - self._components[basis] = self._new_comp(basis) - return self._components[basis] - - comp = components - - def set_comp(self, basis=None): - r""" - Redefinition of the generic tensor method - :meth:`FreeModuleTensor.set_comp`: should not be called. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.identity_tensor() - sage: a.set_comp(e) - Traceback (most recent call last): - ... - TypeError: the components of the identity map cannot be changed - - """ - raise TypeError("the components of the identity map cannot be changed") - - def add_comp(self, basis=None): - r""" - Redefinition of the generic tensor method - :meth:`FreeModuleTensor.add_comp`: should not be called. - - EXAMPLE:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: a = M.identity_tensor() - sage: a.add_comp(e) - Traceback (most recent call last): - ... - TypeError: the components of the identity map cannot be changed - - """ - raise TypeError("the components of the identity map cannot be changed") - - def __call__(self, *arg): - r""" - Redefinition of :meth:`FreeModuleEndomorphismTensor.__call__`. - - EXAMPLES: - - Call with a single argument --> return a module element:: - - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: e = M.basis('e') - sage: id = M.identity_tensor() - sage: v = M([-1,4,3]) - sage: s = id.__call__(v) ; s - Element of the Rank-3 free module M over the Integer Ring - sage: s == v - True - sage: s == id(v) - True - sage: s == id.contract(v) - True - - Call with two arguments (:class:`FreeModuleTensor` behaviour) --> - return a scalar:: - - sage: b = M.linear_form(name='b') - sage: b[:] = 7, 0, 2 - sage: id.__call__(b,v) - -1 - sage: id(b,v) == id.__call__(b,v) - True - sage: id(b,v) == b(v) - True - - """ - from free_module_tensor import FiniteRankFreeModuleElement - from free_module_alt_form import FreeModuleLinForm - if len(arg) == 1: - # the identity map acting as such, on a module element: - vector = arg[0] - if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("the argument must be a module element") - return vector - #!# should it be return vector.copy() instead ? - elif len(arg) == 2: - # the identity map acting as a type-(1,1) tensor on a pair - # (1-form, vector), returning a scalar: - linform = arg[0] - if not isinstance(linform, FreeModuleLinForm): - raise TypeError("the first argument must be a linear form") - vector = arg[1] - if not isinstance(vector, FiniteRankFreeModuleElement): - raise TypeError("the second argument must be a module element") - return linform(vector) - else: - raise TypeError("wrong number of arguments") - diff --git a/src/sage/tensor/modules/tensor_free_module.py b/src/sage/tensor/modules/tensor_free_module.py index 8a776864b46..afd3fd71882 100644 --- a/src/sage/tensor/modules/tensor_free_module.py +++ b/src/sage/tensor/modules/tensor_free_module.py @@ -1,24 +1,18 @@ r""" -Tensor free modules +Tensor products of free modules -The class :class:`TensorFreeModule` implements the tensor products of the type +The class :class:`TensorFreeModule` implements tensor products of the type .. MATH:: T^{(k,l)}(M) = \underbrace{M\otimes\cdots\otimes M}_{k\ \; \mbox{times}} - \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}} + \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}}, where `M` is a free module of finite rank over a commutative ring `R` and `M^*=\mathrm{Hom}_R(M,R)` is the dual of `M`. -`T^{(k,l)}(M)` can be canonically identified with the set of tensors of -type `(k,l)` acting as multilinear forms on `M`. -Note that `T^{(1,0)}(M) = M`. +Note that `T^{(1,0)}(M) = M` and `T^{(0,1)}(M) = M^*`. -`T^{(k,l)}(M)` is itself a free module over `R`, of rank `n^{k+l}`, `n` -being the rank of `M`. Accordingly the class :class:`TensorFreeModule` -inherits from the class :class:`FiniteRankFreeModule` - -Thanks to the canonical isomorphism `M^{**} \simeq M` (which holds because `M` +Thanks to the canonical isomorphism `M^{**} \simeq M` (which holds since `M` is a free module of finite rank), `T^{(k,l)}(M)` can be identified with the set of tensors of type `(k,l)` defined as multilinear maps @@ -32,6 +26,11 @@ *element* class is :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`. +`T^{(k,l)}(M)` is itself a free module over `R`, of rank `n^{k+l}`, `n` +being the rank of `M`. Accordingly the class :class:`TensorFreeModule` +inherits from the class +:class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule`. + .. TODO:: implement more general tensor products, i.e. tensor product of the type @@ -40,12 +39,20 @@ AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version + +REFERENCES: + +- K. Conrad: *Tensor products*, + `http://www.math.uconn.edu/~kconrad/blurbs/ `_ +- Chap. 21 (Exer. 4) of R. Godement: *Algebra*, Hermann (Paris) / Houghton + Mifflin (Boston) (1968) +- Chap. 16 of S. Lang: *Algebra*, 3rd ed., Springer (New York) (2002) """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -54,9 +61,11 @@ #****************************************************************************** from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule -from sage.tensor.modules.free_module_tensor import (FreeModuleTensor, - FiniteRankFreeModuleElement) -from sage.tensor.modules.free_module_morphism import FiniteRankFreeModuleMorphism +from sage.tensor.modules.free_module_tensor import FreeModuleTensor +from sage.tensor.modules.free_module_alt_form import FreeModuleAltForm +from sage.tensor.modules.free_module_morphism import \ + FiniteRankFreeModuleMorphism +from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism class TensorFreeModule(FiniteRankFreeModule): r""" @@ -70,12 +79,16 @@ class TensorFreeModule(FiniteRankFreeModule): \otimes \underbrace{M^*\otimes\cdots\otimes M^*}_{l\ \; \mbox{times}} As recalled above, `T^{(k,l)}(M)` can be canonically identified with the - set of tensors of type `(k,l)` acting as multilinear forms on `M`. + set of tensors of type `(k,l)` on `M`. + + This is a Sage *parent* class, whose *element* class is + :class:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor`. INPUT: - - ``fmodule`` -- free module `M` of finite rank (must be an instance of - :class:`FiniteRankFreeModule`) + - ``fmodule`` -- free module `M` of finite rank over a commutative ring + `R`, as an instance of + :class:`~sage.tensor.modules.finite_rank_free_module.FiniteRankFreeModule` - ``tensor_type`` -- pair ``(k, l)`` with ``k`` being the contravariant rank and ``l`` the covariant rank - ``name`` -- (default: ``None``) string; name given to the tensor module @@ -84,7 +97,7 @@ class TensorFreeModule(FiniteRankFreeModule): EXAMPLES: - Set of tensors of type `(1,2)` on a free module of rank 3 over `\ZZ`:: + Set of tensors of type `(1,2)` on a free `\ZZ`-module of rank 3:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') @@ -100,6 +113,8 @@ class TensorFreeModule(FiniteRankFreeModule): sage: T = M.tensor_module(1,2) ; T Free module of type-(1,2) tensors on the Rank-3 free module M over the Integer Ring + sage: latex(T) + T^{(1, 2)}\left(M\right) The module ``M`` itself is considered as the set of tensors of type `(1,0)`:: @@ -153,7 +168,7 @@ class TensorFreeModule(FiniteRankFreeModule): [[[0, 1, 2], [-1, 0, 1], [-2, -1, 0]], [[1, 2, 3], [0, 1, 2], [-1, 0, 1]], [[2, 3, 4], [1, 2, 3], [0, 1, 2]]] - sage: t.view(e) + sage: t.display(e) t = e_0*e^0*e^1 + 2 e_0*e^0*e^2 - e_0*e^1*e^0 + e_0*e^1*e^2 - 2 e_0*e^2*e^0 - e_0*e^2*e^1 + e_1*e^0*e^0 + 2 e_1*e^0*e^1 + 3 e_1*e^0*e^2 + e_1*e^1*e^1 + 2 e_1*e^1*e^2 - e_1*e^2*e^0 @@ -167,7 +182,7 @@ class TensorFreeModule(FiniteRankFreeModule): sage: t = T([], name='t') sage: t.set_comp(e)[0,1,1] = -3 sage: t.set_comp(e)[2,0,1] = 4 - sage: t.view(e) + sage: t.display(e) t = -3 e_0*e^1*e^1 + 4 e_2*e^0*e^1 See the documentation of @@ -179,7 +194,7 @@ class TensorFreeModule(FiniteRankFreeModule): sage: t = T([], name='t', sym=(1,2)) sage: t.set_comp(e)[0,1,1] = -3 sage: t.set_comp(e)[2,0,1] = 4 - sage: t.view(e) # notice that t^2_{10} has be set equal to t^2_{01} by symmetry + sage: t.display(e) # notice that t^2_{10} has be set equal to t^2_{01} by symmetry t = -3 e_0*e^1*e^1 + 4 e_2*e^0*e^1 + 4 e_2*e^1*e^0 The tensor modules over a given module `M` are unique:: @@ -187,6 +202,76 @@ class TensorFreeModule(FiniteRankFreeModule): sage: T is M.tensor_module(1,2) True + There is a coercion map from `\Lambda^p(M^*)`, the set of alternating + forms of degree `p`, to `T^{(0,p)}(M)`:: + + sage: L2 = M.dual_exterior_power(2) ; L2 + 2nd exterior power of the dual of the Rank-3 free module M over the + Integer Ring + sage: T02 = M.tensor_module(0,2) ; T02 + Free module of type-(0,2) tensors on the Rank-3 free module M over the + Integer Ring + sage: T02.has_coerce_map_from(L2) + True + + Of course, for `p\geq 2`, there is no coercion in the reverse direction, + since not every tensor of type (0,p) is alternating:: + + sage: L2.has_coerce_map_from(T02) + False + + The coercion map `\Lambda^2(M^*)\rightarrow T^{(0,2)}(M)` in action:: + + sage: a = M.alternating_form(2, name='a') ; a + Alternating form a of degree 2 on the Rank-3 free module M over the + Integer Ring + sage: a[0,1], a[1,2] = 4, -3 + sage: a.display(e) + a = 4 e^0/\e^1 - 3 e^1/\e^2 + sage: a.parent() is L2 + True + sage: ta = T02(a) ; ta + Type-(0,2) tensor a on the Rank-3 free module M over the Integer Ring + sage: ta.display(e) + a = 4 e^0*e^1 - 4 e^1*e^0 - 3 e^1*e^2 + 3 e^2*e^1 + sage: ta.symmetries() # the antisymmetry is of course preserved + no symmetry; antisymmetry: (0, 1) + + For the degree `p=1`, there is a coercion in both directions:: + + sage: L1 = M.dual_exterior_power(1) ; L1 + Dual of the Rank-3 free module M over the Integer Ring + sage: T01 = M.tensor_module(0,1) ; T01 + Free module of type-(0,1) tensors on the Rank-3 free module M over the + Integer Ring + sage: T01.has_coerce_map_from(L1) + True + sage: L1.has_coerce_map_from(T01) + True + + The coercion map `\Lambda^1(M^*)\rightarrow T^{(0,1)}(M)` in action:: + + sage: a = M.linear_form('a') + sage: a[:] = -2, 4, 1 ; a.display(e) + a = -2 e^0 + 4 e^1 + e^2 + sage: a.parent() is L1 + True + sage: ta = T01(a) ; ta + Type-(0,1) tensor a on the Rank-3 free module M over the Integer Ring + sage: ta.display(e) + a = -2 e^0 + 4 e^1 + e^2 + + The coercion map `T^{(0,1)}(M) \rightarrow \Lambda^1(M^*)` in action:: + + sage: ta.parent() is T01 + True + sage: lta = L1(ta) ; lta + Linear form a on the Rank-3 free module M over the Integer Ring + sage: lta.display(e) + a = -2 e^0 + 4 e^1 + e^2 + sage: lta == a + True + There is a canonical identification between tensors of type (1,1) and endomorphisms of module `M`. Accordingly, coercion maps have been implemented between `T^{(1,1)}(M)` and `\mathrm{End}(M)` (the module of @@ -194,7 +279,7 @@ class TensorFreeModule(FiniteRankFreeModule): :class:`~sage.tensor.modules.free_module_homset.FreeModuleHomset`):: sage: T11 = M.tensor_module(1,1) ; T11 - Free module of type-(1,1) tensors on the Rank-3 free module M over the + Free module of type-(1,1) tensors on the Rank-3 free module M over the Integer Ring sage: End(M) Set of Morphisms from Rank-3 free module M over the Integer Ring to @@ -226,13 +311,13 @@ class TensorFreeModule(FiniteRankFreeModule): [ 0 -2 0] [ 0 0 -3] sage: s = t + phi ; s # phi is coerced to a type-(1,1) tensor prior to the addition - Endomorphism tensor on the Rank-3 free module M over the Integer Ring + Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring sage: s[:] [ 0 1 1] [ 1 -1 1] [ 1 1 -2] - The reverse coercion map in action:: + The coercion map `T^{(1,1)}(M) \rightarrow \mathrm{End}(M)` in action:: sage: phi1 = End(M)(tphi) ; phi1 Generic endomorphism of Rank-3 free module M over the Integer Ring @@ -245,6 +330,35 @@ class TensorFreeModule(FiniteRankFreeModule): [ 1 -1 1] [ 1 1 -2] + There is a coercion `\mathrm{GL}(M)\rightarrow T^{(1,1)}(M)`, i.e. from + automorphisms of `M` to type-(1,1) tensors on `M`:: + + sage: GL = M.general_linear_group() ; GL + General linear group of the Rank-3 free module M over the Integer Ring + sage: T11.has_coerce_map_from(GL) + True + + The coercion map `\mathrm{GL}(M)\rightarrow T^{(1,1)}(M)` in action:: + + sage: a = GL.an_element() ; a + Automorphism of the Rank-3 free module M over the Integer Ring + sage: a.matrix(e) + [ 1 0 0] + [ 0 -1 0] + [ 0 0 1] + sage: ta = T11(a) ; ta + Type-(1,1) tensor on the Rank-3 free module M over the Integer Ring + sage: ta.display(e) + e_0*e^0 - e_1*e^1 + e_2*e^2 + sage: a.display(e) + e_0*e^0 - e_1*e^1 + e_2*e^2 + + Of course, there is no coercion in the reverse direction, since not + every type-(1,1) tensor is invertible:: + + sage: GL.has_coerce_map_from(T11) + False + """ Element = FreeModuleTensor @@ -264,19 +378,26 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): rank = pow(fmodule._rank, tensor_type[0] + tensor_type[1]) self._zero_element = 0 # provisory (to avoid infinite recursion in what # follows) - if tensor_type == (0,1): # case of the dual + if self._tensor_type == (0,1): # case of the dual if name is None and fmodule._name is not None: name = fmodule._name + '*' if latex_name is None and fmodule._latex_name is not None: latex_name = fmodule._latex_name + r'^*' + else: + if name is None and fmodule._name is not None: + name = 'T^' + str(self._tensor_type) + '(' + fmodule._name + \ + ')' + if latex_name is None and fmodule._latex_name is not None: + latex_name = r'T^{' + str(self._tensor_type) + r'}\left(' + \ + fmodule._latex_name + r'\right)' FiniteRankFreeModule.__init__(self, fmodule._ring, rank, name=name, - latex_name=latex_name, - start_index=fmodule._sindex, - output_formatter=fmodule._output_formatter) + latex_name=latex_name, + start_index=fmodule._sindex, + output_formatter=fmodule._output_formatter) # Unique representation: if self._tensor_type in self._fmodule._tensor_modules: - raise ValueError("the module of tensors of type" + - str(self._tensor_type) + + raise ValueError("the module of tensors of type {}".format( + self._tensor_type) + " has already been created") else: self._fmodule._tensor_modules[self._tensor_type] = self @@ -288,7 +409,8 @@ def __init__(self, fmodule, tensor_type, name=None, latex_name=None): self._zero_element._new_comp(basis) # (since new components are initialized to zero) - #### Methods required for any Parent + #### Parent Methods + def _element_constructor_(self, comp=[], basis=None, name=None, latex_name=None, sym=None, antisym=None): r""" @@ -301,13 +423,15 @@ def _element_constructor_(self, comp=[], basis=None, name=None, sage: T._element_constructor_(0) is T.zero() True sage: e = M.basis('e') - sage: t = T._element_constructor_(comp=[[2,0],[1/2,-3]], basis=e, name='t') ; t - Type-(1,1) tensor t on the Rank-2 free module M over the Rational Field - sage: t.view() + sage: t = T._element_constructor_(comp=[[2,0],[1/2,-3]], basis=e, + ....: name='t') ; t + Type-(1,1) tensor t on the 2-dimensional vector space M over the + Rational Field + sage: t.display() t = 2 e_0*e^0 + 1/2 e_1*e^0 - 3 e_1*e^1 sage: t.parent() - Free module of type-(1,1) tensors on the - Rank-2 free module M over the Rational Field + Free module of type-(1,1) tensors on the 2-dimensional vector + space M over the Rational Field sage: t.parent() is T True @@ -315,7 +439,7 @@ def _element_constructor_(self, comp=[], basis=None, name=None, if comp == 0: return self._zero_element if isinstance(comp, FiniteRankFreeModuleMorphism): - # coercion of an endomorphism to a type-(1,1) tensor: + # coercion of an endomorphism to a type-(1,1) tensor: endo = comp # for readability if self._tensor_type == (1,1) and endo.is_endomorphism() and \ self._fmodule is endo.domain(): @@ -325,8 +449,36 @@ def _element_constructor_(self, comp=[], basis=None, name=None, for basis, mat in endo._matrices.iteritems(): resu.add_comp(basis[0])[:] = mat else: - raise TypeError("cannot coerce the " + str(endo) + - " to an element of " + str(self)) + raise TypeError("cannot coerce the {}".format(endo) + + " to an element of {}".format(self)) + elif isinstance(comp, FreeModuleAltForm): + # coercion of an alternating form to a type-(0,p) tensor: + form = comp # for readability + p = form.degree() + if self._tensor_type != (0,p) or \ + self._fmodule != form.base_module(): + raise TypeError("cannot coerce the {}".format(form) + + " to an element of {}".format(self)) + if p == 1: + asym = None + else: + asym = range(p) + resu = self.element_class(self._fmodule, (0,p), name=form._name, + latex_name=form._latex_name, + antisym=asym) + for basis, comp in form._components.iteritems(): + resu._components[basis] = comp.copy() + elif isinstance(comp, FreeModuleAutomorphism): + # coercion of an automorphism to a type-(1,1) tensor: + autom = comp # for readability + if self._tensor_type != (1,1) or \ + self._fmodule != autom.base_module(): + raise TypeError("cannot coerce the {}".format(autom) + + " to an element of {}".format(self)) + resu = self.element_class(self._fmodule, (1,1), name=autom._name, + latex_name=autom._latex_name) + for basis, comp in autom._components.iteritems(): + resu._components[basis] = comp.copy() else: # Standard construction: resu = self.element_class(self._fmodule, self._tensor_type, @@ -338,7 +490,7 @@ def _element_constructor_(self, comp=[], basis=None, name=None, def _an_element_(self): r""" - Construct some (unamed) tensor. + Construct some (unamed) element of ``self``. EXAMPLES:: @@ -346,12 +498,13 @@ def _an_element_(self): sage: T = M.tensor_module(1,1) sage: e = M.basis('e') sage: t = T._an_element_() ; t - Type-(1,1) tensor on the Rank-2 free module M over the Rational Field - sage: t.view() + Type-(1,1) tensor on the 2-dimensional vector space M over the + Rational Field + sage: t.display() 1/2 e_0*e^0 sage: t.parent() is T True - sage: M.tensor_module(2,3)._an_element_().view() + sage: M.tensor_module(2,3)._an_element_().display() 1/2 e_0*e_0*e^0*e^0*e^0 """ @@ -368,7 +521,7 @@ def _coerce_map_from_(self, other): EXAMPLES: - Sets of module endomorphisms coerces to type-(1,1) tensor modules:: + Sets of module endomorphisms coerce to type-(1,1) tensor modules:: sage: M = FiniteRankFreeModule(ZZ, 3, name='M') sage: e = M.basis('e') @@ -392,17 +545,40 @@ def _coerce_map_from_(self, other): sage: M.tensor_module(1,1)._coerce_map_from_(Hom(M,N)) False + Coercion from alternating forms:: + + sage: M.tensor_module(0,1)._coerce_map_from_(M.dual_exterior_power(1)) + True + sage: M.tensor_module(0,2)._coerce_map_from_(M.dual_exterior_power(2)) + True + sage: M.tensor_module(0,2)._coerce_map_from_(M.dual_exterior_power(3)) + False + sage: M.tensor_module(0,2)._coerce_map_from_(N.dual_exterior_power(2)) + False + """ from free_module_homset import FreeModuleHomset + from ext_pow_free_module import ExtPowerFreeModule + from free_module_linear_group import FreeModuleLinearGroup if isinstance(other, FreeModuleHomset): # Coercion of an endomorphism to a type-(1,1) tensor: if self._tensor_type == (1,1): - if other.is_endomorphism_set() and \ - self._fmodule is other.domain(): - return True + return other.is_endomorphism_set() and \ + self._fmodule is other.domain() + else: + return False + if isinstance(other, ExtPowerFreeModule): + # Coercion of an alternating form to a type-(0,p) tensor: + return self._tensor_type == (0, other.degree()) and \ + self._fmodule is other.base_module() + + if isinstance(other, FreeModuleLinearGroup): + # Coercion of an automorphism to a type-(1,1) tensor: + return self._tensor_type == (1,1) and \ + self._fmodule is other.base_module() return False - #### End of methods required for any Parent + #### End of parent methods def _repr_(self): r""" @@ -412,19 +588,15 @@ def _repr_(self): sage: M = FiniteRankFreeModule(QQ, 2, name='M') sage: M.tensor_module(1,1) - Free module of type-(1,1) tensors on the Rank-2 free module M - over the Rational Field + Free module of type-(1,1) tensors on the 2-dimensional vector space + M over the Rational Field sage: M.tensor_module(0,1) - Dual of the Rank-2 free module M over the Rational Field + Free module of type-(0,1) tensors on the 2-dimensional vector space + M over the Rational Field """ - if self._tensor_type == (0,1): - return "Dual of the " + str(self._fmodule) - description = "Free module " - if self._name is not None: - description += self._name + " " - description += "of type-({},{}) tensors on the {}".format( - self._tensor_type[0], self._tensor_type[1], self._fmodule) + description = "Free module of type-({},{}) tensors on the {}".format( + self._tensor_type[0], self._tensor_type[1], self._fmodule) return description def base_module(self): @@ -433,8 +605,8 @@ def base_module(self): OUTPUT: - - instance of :class:`FiniteRankFreeModule` representing the free module - on which the tensor module is defined. + - instance of :class:`FiniteRankFreeModule` representing the free + module on which the tensor module is defined. EXAMPLE: @@ -444,6 +616,8 @@ def base_module(self): sage: T = M.tensor_module(1,2) sage: T.base_module() Rank-3 free module M over the Integer Ring + sage: T.base_module() is M + True """ return self._fmodule diff --git a/src/sage/tensor/modules/tensor_with_indices.py b/src/sage/tensor/modules/tensor_with_indices.py index 325f53dcc20..2ca8e265ad1 100644 --- a/src/sage/tensor/modules/tensor_with_indices.py +++ b/src/sage/tensor/modules/tensor_with_indices.py @@ -3,12 +3,12 @@ AUTHORS: -- Eric Gourgoulhon, Michal Bejger (2014): initial version +- Eric Gourgoulhon, Michal Bejger (2014-2015): initial version """ #****************************************************************************** -# Copyright (C) 2014 Eric Gourgoulhon -# Copyright (C) 2014 Michal Bejger +# Copyright (C) 2015 Eric Gourgoulhon +# Copyright (C) 2015 Michal Bejger # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -43,7 +43,8 @@ class TensorWithIndices(SageObject): sage: b = M.tensor((0,2), name='b') sage: b[:] = [[-1,2,-3], [-4,5,6], [7,-8,9]] sage: t = a*b ; t.set_name('t') ; t - Type-(2,2) tensor t on the Rank-3 free module M over the Rational Field + Type-(2,2) tensor t on the 3-dimensional vector space M over the + Rational Field sage: from sage.tensor.modules.tensor_with_indices import TensorWithIndices sage: T = TensorWithIndices(t, '^ij_kl') ; T t^ij_kl @@ -78,7 +79,8 @@ class TensorWithIndices(SageObject): a symmetrization:: sage: s = t['^(ij)_kl'] ; s # the symmetrization on i,j is indicated by parentheses - Type-(2,2) tensor on the Rank-3 free module M over the Rational Field + Type-(2,2) tensor on the 3-dimensional vector space M over the + Rational Field sage: s.symmetries() symmetry: (0, 1); no antisymmetry sage: s == t.symmetrize(0,1) @@ -93,7 +95,8 @@ class TensorWithIndices(SageObject): Similarly, for an antisymmetrization:: sage: s = t['^ij_[kl]'] ; s # the symmetrization on k,l is indicated by square brackets - Type-(2,2) tensor on the Rank-3 free module M over the Rational Field + Type-(2,2) tensor on the 3-dimensional vector space M over the Rational + Field sage: s.symmetries() no symmetry; antisymmetry: (2, 3) sage: s == t.antisymmetrize(2,3) @@ -102,7 +105,8 @@ class TensorWithIndices(SageObject): Another example of an operation indicated by indices is a contraction:: sage: s = t['^ki_kj'] ; s # contraction on the repeated index k - Endomorphism tensor on the Rank-3 free module M over the Rational Field + Type-(1,1) tensor on the 3-dimensional vector space M over the Rational + Field sage: s == t.trace(0,2) True @@ -115,11 +119,13 @@ class TensorWithIndices(SageObject): the ``*`` operator:: sage: s = a['^ik'] * b['_kj'] ; s - Endomorphism tensor on the Rank-3 free module M over the Rational Field + Type-(1,1) tensor on the 3-dimensional vector space M over the Rational + Field sage: s == a.contract(1, b, 0) True sage: s = t['^.k_..'] * b['_.k'] ; s - Type-(1,3) tensor on the Rank-3 free module M over the Rational Field + Type-(1,3) tensor on the 3-dimensional vector space M over the Rational + Field sage: s == t.contract(1, b, 1) True sage: t['^{ik}_{jl}']*b['_{mk}'] == s # LaTeX notation @@ -250,8 +256,8 @@ def __init__(self, tensor, indices): # tensor cov = cov.replace('[','').replace(']','') if len(cov) != tensor._tensor_type[1]: - raise IndexError("number of covariant indices not compatible " + - "with the tensor type") + raise IndexError("number of covariant indices not " + + "compatible with the tensor type") self._cov = cov else: raise IndexError("too many '_' in the list of indices") @@ -397,7 +403,8 @@ def __mul__(self, other): sage: ai = TensorWithIndices(a, '^ij') sage: bi = TensorWithIndices(b, '_k') sage: s = ai.__mul__(bi) ; s # no repeated indices ==> tensor product - Type-(2,1) tensor a*b on the Rank-3 free module M over the Rational Field + Type-(2,1) tensor a*b on the 3-dimensional vector space M over the + Rational Field sage: s == a*b True sage: s[:] @@ -406,7 +413,7 @@ def __mul__(self, other): [[28, 14, 7], [-32, -16, -8], [36, 18, 9]]] sage: ai = TensorWithIndices(a, '^kj') sage: s = ai.__mul__(bi) ; s # repeated index k ==> contraction - Element of the Rank-3 free module M over the Rational Field + Element of the 3-dimensional vector space M over the Rational Field sage: s == a.contract(0, b) True sage: s[:] @@ -514,4 +521,3 @@ def __neg__(self): """ return TensorWithIndices(-self._tensor, self._con + '_' + self._cov) - From 3c0c50adb02c5046a9b26273498c1f56eee9385e Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 11 Feb 2015 23:37:09 -0800 Subject: [PATCH 055/129] Implemented faster versions of tuples. --- src/sage/combinat/combinat.py | 114 ++++++++++++++++++++++++---------- 1 file changed, 80 insertions(+), 34 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 7d71d5a3510..510e5f439ad 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2193,63 +2193,101 @@ def __iter__(self): ##################################################### #### combinatorial sets/lists -def tuples(S,k): +def tuples(S, k, algorithm='itertools'): """ Return a list of all `k`-tuples of elements of a given set ``S``. This function accepts the set ``S`` in the form of any iterable - (list, tuple or iterator), and returns a list of `k`-tuples - (themselves encoded as lists). If ``S`` contains duplicate entries, - then you should expect the method to return tuples multiple times! + (list, tuple or iterator), and returns a list of `k`-tuples. + If ``S`` contains duplicate entries, then you should expect the + method to return tuples multiple times! Recall that `k`-tuples are ordered (in the sense that two `k`-tuples differing in the order of their entries count as different) and can have repeated entries (even if ``S`` is a list with no repetition). + INPUT: + + - ``S`` -- the base set + - ``k`` -- the length of the tuples + - ``algorithm`` -- can be one of the following: + + * ``'itertools'`` - (default) use python's itertools + * ``'native'`` - use a native Sage implementation + + .. NOTE:: + + The ordering of the list of tuples differs for the algorithms. + EXAMPLES:: sage: S = [1,2] sage: tuples(S,3) - [[1, 1, 1], [2, 1, 1], [1, 2, 1], [2, 2, 1], [1, 1, 2], [2, 1, 2], [1, 2, 2], [2, 2, 2]] + [(1, 1, 1), (1, 1, 2), (1, 2, 1), (1, 2, 2), + (2, 1, 1), (2, 1, 2), (2, 2, 1), (2, 2, 2)] sage: mset = ["s","t","e","i","n"] - sage: tuples(mset,2) - [['s', 's'], ['t', 's'], ['e', 's'], ['i', 's'], ['n', 's'], ['s', 't'], ['t', 't'], - ['e', 't'], ['i', 't'], ['n', 't'], ['s', 'e'], ['t', 'e'], ['e', 'e'], ['i', 'e'], - ['n', 'e'], ['s', 'i'], ['t', 'i'], ['e', 'i'], ['i', 'i'], ['n', 'i'], ['s', 'n'], - ['t', 'n'], ['e', 'n'], ['i', 'n'], ['n', 'n']] - - The Set(...) comparisons are necessary because finite fields are - not enumerated in a standard order. + sage: tuples(mset, 2) + [('s', 's'), ('s', 't'), ('s', 'e'), ('s', 'i'), ('s', 'n'), + ('t', 's'), ('t', 't'), ('t', 'e'), ('t', 'i'), ('t', 'n'), + ('e', 's'), ('e', 't'), ('e', 'e'), ('e', 'i'), ('e', 'n'), + ('i', 's'), ('i', 't'), ('i', 'e'), ('i', 'i'), ('i', 'n'), + ('n', 's'), ('n', 't'), ('n', 'e'), ('n', 'i'), ('n', 'n')] :: sage: K. = GF(4, 'a') - sage: mset = [x for x in K if x!=0] - sage: tuples(mset,2) - [[a, a], [a + 1, a], [1, a], [a, a + 1], [a + 1, a + 1], [1, a + 1], [a, 1], [a + 1, 1], [1, 1]] + sage: mset = [x for x in K if x != 0] + sage: tuples(mset, 2) + [(a, a), (a, a + 1), (a, 1), (a + 1, a), (a + 1, a + 1), + (a + 1, 1), (1, a), (1, a + 1), (1, 1)] + + We check that the implementations agree (up to ordering):: + + sage: tuples(S, 3, 'native') + [(1, 1, 1), (2, 1, 1), (1, 2, 1), (2, 2, 1), + (1, 1, 2), (2, 1, 2), (1, 2, 2), (2, 2, 2)] + + Lastly we check on a multiset:: + + sage: S = [1,1,2] + sage: sorted(tuples(S, 3)) == sorted(tuples(S, 3, 'native')) + True AUTHORS: - Jon Hanke (2006-08) """ - import copy - if k<=0: - return [[]] - if k==1: - return [[x] for x in S] - ans = [] - for s in S: - for x in tuples(S,k-1): - y = copy.copy(x) - y.append(s) - ans.append(y) - return ans - -def number_of_tuples(S, k): + if algorithm == 'itertools': + import itertools + return list(itertools.product(S, repeat=k)) + if algorithm == 'native': + if k <= 0: + return [()] + if k == 1: + return [(x,) for x in S] + ans = [] + for s in S: + for x in tuples(S, k-1, 'native'): + y = list(x) + y.append(s) + ans.append(tuple(y)) + return ans + raise ValueError('invalid algorithm') + +def number_of_tuples(S, k, algorithm='naive'): """ Return the size of ``tuples(S,k)``. Wraps GAP's ``NrTuples``. + INPUT: + + - ``S`` -- the base set + - ``k`` -- the length of the tuples + - ``algorithm`` -- can be one of the following: + + * ``'naive'`` - (default) use the naive counting `|S|^k` + * ``'gap'`` - wraps GAP's ``NrTuples`` + EXAMPLES:: sage: S = [1,2,3,4,5] @@ -2259,8 +2297,14 @@ def number_of_tuples(S, k): sage: number_of_tuples(S,2) 25 """ - ans=gap.eval("NrTuples(%s,%s)"%(S,ZZ(k))) - return ZZ(ans) + if algorithm == 'naive': + return ZZ( len(set(S)) )**k # The set is there to avoid duplicates + if algorithm == 'gap': + k = ZZ(k) + from sage.libs.gap.libgap import libgap + S = libgap.eval(str(S)) + return libgap.NrTuples(S, k).sage() + raise ValueError('invalid algorithm') def unordered_tuples(S, k, algorithm='itertools'): """ @@ -2315,8 +2359,10 @@ def unordered_tuples(S, k, algorithm='itertools'): import itertools return list(itertools.combinations_with_replacement(sorted(set(S)), k)) if algorithm == 'gap': - ans = gap.eval("UnorderedTuples(%s,%s)"%(S,ZZ(k))) - return map(tuple, eval(ans)) + k = ZZ(k) + from sage.libs.gap.libgap import libgap + S = libgap.eval(str(S)) + return [tuple(x) for x in libgap.UnorderedTuples(S, k).sage()] raise ValueError('invalid algorithm') def number_of_unordered_tuples(S,k): From dcafd16c021e03d8b2fb3a52e57554ea932a7153 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Thu, 12 Feb 2015 04:02:01 -0800 Subject: [PATCH 056/129] Some fixes and simplifications. --- src/sage/categories/crystals.py | 26 +++++++++---------- .../categories/highest_weight_crystals.py | 1 - 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/src/sage/categories/crystals.py b/src/sage/categories/crystals.py index 17a8b00ac5f..e77b9fcc419 100644 --- a/src/sage/categories/crystals.py +++ b/src/sage/categories/crystals.py @@ -1190,40 +1190,38 @@ def to_lowest_weight(self, index_set = None): return [lw[0], [i] + lw[1]] return [self, []] - def all_paths(self,index_set=None,start=[]): + def all_paths_to_highest_weight(self, index_set=None): r""" Return all paths to the highest weight from ``self`` with respect to `index_set`. INPUT: - - ``self`` -- an element of a crystal - - - ``index_set`` -- a subset of the index set of ``self` + - ``index_set`` -- (optional) a subset of the index set of ``self`` EXAMPLES:: sage: B = crystals.infinity.Tableaux("A2") sage: b0 = B.highest_weight_vector() - sage: b = b0.f_string([1,2,1,2]) - sage: L = b.all_paths() + sage: b = b0.f_string([1, 2, 1, 2]) + sage: L = b.all_paths_to_highest_weight() sage: list(L) [[2, 1, 2, 1], [2, 2, 1, 1]] sage: Y = crystals.infinity.GeneralizedYoungWalls(3) sage: y0 = Y.highest_weight_vector() - sage: y = y0.f_string([0,1,2,3,2,1,0]) - sage: list(y.all_paths()) + sage: y = y0.f_string([0, 1, 2, 3, 2, 1, 0]) + sage: list(y.all_paths_to_highest_weight()) [[0, 1, 2, 3, 2, 1, 0], [0, 1, 3, 2, 2, 1, 0], [0, 3, 1, 2, 2, 1, 0], [0, 3, 2, 1, 1, 0, 2], [0, 3, 2, 1, 1, 2, 0]] - sage: B = crystals.Tableaux("A3",shape=[4,2,1]) + sage: B = crystals.Tableaux("A3", shape=[4,2,1]) sage: b0 = B.highest_weight_vector() - sage: b = b0.f_string([1,1,2,3]) - sage: list(b.all_paths()) + sage: b = b0.f_string([1, 1, 2, 3]) + sage: list(b.all_paths_to_highest_weight()) [[1, 3, 2, 1], [3, 1, 2, 1], [3, 2, 1, 1]] """ if index_set is None: @@ -1232,11 +1230,11 @@ def all_paths(self,index_set=None,start=[]): for i in index_set: next = self.e(i) if next is not None: - for x in next.all_paths(index_set,start+[i]): - yield x + for x in next.all_paths_to_highest_weight(index_set): + yield [i] + x hw = False if hw: - yield start + yield [] def subcrystal(self, index_set=None, max_depth=float("inf"), direction="both"): r""" diff --git a/src/sage/categories/highest_weight_crystals.py b/src/sage/categories/highest_weight_crystals.py index be53fe0042d..969e11bbcd5 100644 --- a/src/sage/categories/highest_weight_crystals.py +++ b/src/sage/categories/highest_weight_crystals.py @@ -395,7 +395,6 @@ class ElementMethods: pass - class TensorProducts(TensorProductsCategory): """ The category of highest weight crystals constructed by tensor From f9586a5756d46e9e70bec59d171de07d5b841353 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Thu, 19 Feb 2015 16:34:01 +0100 Subject: [PATCH 057/129] Further simplify values(), use @cached_method --- src/sage/modular/dirichlet.py | 66 ++++++++++++----------------------- 1 file changed, 23 insertions(+), 43 deletions(-) diff --git a/src/sage/modular/dirichlet.py b/src/sage/modular/dirichlet.py index dc00f191319..9f254a1989f 100644 --- a/src/sage/modular/dirichlet.py +++ b/src/sage/modular/dirichlet.py @@ -274,8 +274,7 @@ def __call__(self, m): .. warning:: A table of values of the character is made the first time - you call this. This table is currently constructed in a - somewhat stupid way, though it is still pretty fast. + you call this (unless `m` equals -1) EXAMPLES:: @@ -283,6 +282,8 @@ def __call__(self, m): sage: e = prod(G.gens(), G(1)) sage: e Dirichlet character modulo 60 of conductor 60 mapping 31 |--> -1, 41 |--> -1, 37 |--> zeta4 + sage: e(-1) + -1 sage: e(2) 0 sage: e(7) @@ -300,18 +301,11 @@ def __call__(self, m): sage: parent(e(31*37)) Cyclotomic Field of order 4 and degree 2 """ - m = int(m%self.__modulus) - try: - return self.__values[m] - except AttributeError: - pass - - val = self.__modulus - 1 - if m == val: - return self.__eval_at_minus_one() + m = int(m % self.__modulus) + if self.values.is_in_cache() or m != self.__modulus - 1: + return self.values()[m] else: - self.values() # compute all values - return self.__values[m] + return self.__eval_at_minus_one() def change_ring(self, R): """ @@ -1495,6 +1489,7 @@ def restrict(self, M): H = DirichletGroup(M, self.base_ring()) return H(self) + @cached_method def values(self): """ Return a list of the values of this character on each integer @@ -1543,60 +1538,45 @@ def values(self): sage: chi(1) 1 """ - try: - return self.__values - except AttributeError: - pass - - # Build cache of all values of the Dirichlet character. - # I'm going to do it this way, since in my app the modulus - # is *always* small and we want to evaluate the character - # a *lot*. G = self.parent() R = G.base_ring() mod = self.__modulus if mod == 1: - self.__values = [R.one()] - return self.__values + return [R.one()] elif mod == 2: - self.__values = [R.zero(), R.one()] - return self.__values + return [R.zero(), R.one()] result_list = [R.zero()] * mod gens = G.unit_gens() - ngens = len(gens) orders = G.integers_mod().unit_group().gens_orders() - A = rings.IntegerModRing(G.zeta_order()) R_values = G._zeta_powers - val_on_gen = [A(R_values.index(x)) for x in self.values_on_gens()] + val_on_gen = self.element() - exponents = [0] * ngens + exponents = [0] * len(orders) n = G.integers_mod().one() - value = A.zero() + value = val_on_gen.base_ring().zero() - final_index = ngens - 1 - stop = orders[-1] - while exponents[-1] < stop: + while True: # record character value on n result_list[n] = R_values[value] # iterate: # increase the exponent vector by 1, # increase n accordingly, and increase value i = 0 - exponents[0] += 1 - value += val_on_gen[0] - n *= gens[0] - while i < final_index and exponents[i] >= orders[i]: - exponents[i] = 0 - i += 1 - exponents[i] += 1 + while True: + try: + exponents[i] += 1 + except IndexError: # Done! + return result_list value += val_on_gen[i] n *= gens[i] + if exponents[i] < orders[i]: + break + exponents[i] = 0 + i += 1 - self.__values = result_list - return self.__values def values_on_gens(self): r""" From fd9074ffb0b8ddd94170bdb0ff652b0764dd9a81 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Thu, 15 Jan 2015 19:43:44 +0100 Subject: [PATCH 058/129] Auto-generate parts of gen.pyx and pari_instance.pyx --- build/pkgs/pari/spkg-install | 4 +- src/sage/.gitignore | 3 +- src/sage/libs/pari/gen.pxd | 10 +- src/sage/libs/pari/gen.pyx | 651 +---------------------- src/sage/libs/pari/pari_instance.pxd | 7 +- src/sage/libs/pari/pari_instance.pyx | 34 +- src/sage_setup/.gitignore | 1 + src/sage_setup/autogen/__init__.py | 3 + src/sage_setup/autogen/pari/__init__.py | 18 + src/sage_setup/autogen/pari/args.py | 291 ++++++++++ src/sage_setup/autogen/pari/generator.py | 227 ++++++++ src/sage_setup/autogen/pari/parser.py | 199 +++++++ src/sage_setup/autogen/pari/ret.py | 84 +++ src/sage_setup/find.py | 2 +- src/setup.py | 5 +- 15 files changed, 864 insertions(+), 675 deletions(-) create mode 100644 src/sage_setup/.gitignore create mode 100644 src/sage_setup/autogen/__init__.py create mode 100644 src/sage_setup/autogen/pari/__init__.py create mode 100644 src/sage_setup/autogen/pari/args.py create mode 100644 src/sage_setup/autogen/pari/generator.py create mode 100644 src/sage_setup/autogen/pari/parser.py create mode 100644 src/sage_setup/autogen/pari/ret.py diff --git a/build/pkgs/pari/spkg-install b/build/pkgs/pari/spkg-install index 7d32abb68c3..6a7b1ebc9d3 100755 --- a/build/pkgs/pari/spkg-install +++ b/build/pkgs/pari/spkg-install @@ -17,7 +17,9 @@ TOP=`pwd` ####################################################################### cd src for patch in ../patches/*.patch; do - patch -p1 <"$patch" + # Use --no-backup-if-mismatch to prevent .orig files (these confuse + # PARI's Perl script which creates pari.desc). + patch --no-backup-if-mismatch -p1 <"$patch" if [ $? -ne 0 ]; then echo >&2 "Error applying '$patch'" exit 1 diff --git a/src/sage/.gitignore b/src/sage/.gitignore index 0f87f214b0e..75766a3b76e 100644 --- a/src/sage/.gitignore +++ b/src/sage/.gitignore @@ -1,6 +1,7 @@ *.c *.cpp -/libs/pari/gen.h +/libs/pari/auto_gen.pxi +/libs/pari/auto_instance.pxi /modular/arithgroup/farey_symbol.h /rings/real_mpfi.h /symbolic/pynac.h diff --git a/src/sage/libs/pari/gen.pxd b/src/sage/libs/pari/gen.pxd index 4941f395279..72f4d17d2d3 100644 --- a/src/sage/libs/pari/gen.pxd +++ b/src/sage/libs/pari/gen.pxd @@ -1,12 +1,16 @@ include 'decl.pxi' -cimport sage.structure.element +from sage.structure.element cimport RingElement cimport cython -@cython.final -cdef class gen(sage.structure.element.RingElement): + +cdef class gen_auto(RingElement): cdef GEN g cdef pari_sp b cdef dict refers_to +@cython.final +cdef class gen(gen_auto): + pass + cpdef gen objtogen(s) diff --git a/src/sage/libs/pari/gen.pyx b/src/sage/libs/pari/gen.pyx index d3889c4cc65..75a4d896a1d 100644 --- a/src/sage/libs/pari/gen.pyx +++ b/src/sage/libs/pari/gen.pyx @@ -65,18 +65,18 @@ cdef extern from "misc.h": cdef extern from "mpz_pylong.h": cdef int mpz_set_pylong(mpz_t dst, src) except -1 -# Will be imported as needed -Integer = None +from pari_instance cimport PariInstance, prec_bits_to_words, pari_instance +cdef PariInstance P = pari_instance -import pari_instance -from pari_instance cimport PariInstance, prec_bits_to_words -cdef PariInstance P = pari_instance.pari +from sage.rings.integer cimport Integer +include 'auto_gen.pxi' + @cython.final -cdef class gen(sage.structure.element.RingElement): +cdef class gen(gen_auto): """ - Python extension class that models the PARI GEN type. + Cython extension class that models the PARI GEN type. """ def __init__(self): raise RuntimeError("PARI objects cannot be instantiated directly; use pari(x) to convert x to PARI") @@ -1307,12 +1307,7 @@ cdef class gen(sage.structure.element.RingElement): 9223372036854775807 # 64-bit sage: int(pari(RealField(63)(2^63+2))) 9223372036854775810L - """ - global Integer - if Integer is None: - import sage.rings.integer - Integer = sage.rings.integer.Integer return int(Integer(self)) def python_list_small(gen self): @@ -1430,10 +1425,6 @@ cdef class gen(sage.structure.element.RingElement): sage: long(pari("Mod(2, 7)")) 2L """ - global Integer - if Integer is None: - import sage.rings.integer - Integer = sage.rings.integer.Integer return long(Integer(self)) def __float__(gen self): @@ -1978,66 +1969,6 @@ cdef class gen(sage.structure.element.RingElement): ########################################### # 1: Standard monadic or dyadic OPERATORS ########################################### - def divrem(gen x, y, var=-1): - """ - divrem(x, y, v): Euclidean division of x by y giving as a - 2-dimensional column vector the quotient and the remainder, with - respect to v (to main variable if v is omitted). - """ - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(divrem(x.g, t0.g, P.get_var(var))) - - def lex(gen x, y): - """ - lex(x,y): Compare x and y lexicographically (1 if xy, 0 if x==y, -1 - if xy) - """ - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - r = lexcmp(x.g, t0.g) - pari_catch_sig_off() - return r - - def max(gen x, y): - """ - max(x,y): Return the maximum of x and y. - """ - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(gmax(x.g, t0.g)) - - def min(gen x, y): - """ - min(x,y): Return the minimum of x and y. - """ - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(gmin(x.g, t0.g)) - - def shift(gen x, long n): - """ - shift(x,n): shift x left n bits if n=0, right -n bits if n0. - """ - pari_catch_sig_on() - return P.new_gen(gshift(x.g, n)) - - def shiftmul(gen x, long n): - """ - shiftmul(x,n): Return the product of x by `2^n`. - """ - pari_catch_sig_on() - return P.new_gen(gmul2n(x.g, n)) - - def moebius(gen x): - """ - moebius(x): Moebius function of x. - """ - pari_catch_sig_on() - r = moebius(x.g) - pari_catch_sig_off() - return r - def sign(gen x): """ Return the sign of x, where x is of type integer, real or @@ -3467,12 +3398,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(gimag(x.g)) - def length(self): - """ - - """ - return glength(self.g) - def lift(gen x, v=-1): """ lift(x,v): Returns the lift of an element of Z/nZ to Z or R[x]/(P) @@ -3522,49 +3447,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(numbpart(x.g)) - def numerator(gen x): - """ - numerator(x): Returns the numerator of x. - - INPUT: - - - - ``x`` - gen - - - OUTPUT: gen - - EXAMPLES: - """ - pari_catch_sig_on() - return P.new_gen(numer(x.g)) - - - def numtoperm(gen k, long n): - """ - numtoperm(k, n): Return the permutation number k (mod n!) of n - letters, where n is an integer. - - INPUT: - - - - ``k`` - gen, integer - - - ``n`` - int - - - OUTPUT: - - - - ``gen`` - vector (permutation of 1,...,n) - - - EXAMPLES: - """ - pari_catch_sig_on() - return P.new_gen(numtoperm(n, k.g)) - - def padicprec(gen x, p): """ padicprec(x,p): Return the absolute p-adic precision of the object @@ -3628,31 +3510,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(gel(x.g, 2)) - def permtonum(gen x): - """ - permtonum(x): Return the ordinal (between 1 and n!) of permutation - vector x. ??? Huh ??? say more. what is a perm vector. 0 to n-1 or - 1-n. - - INPUT: - - - - ``x`` - gen (vector of integers) - - - OUTPUT: - - - - ``gen`` - integer - - - EXAMPLES: - """ - if typ(x.g) != t_VEC: - raise TypeError, "x (=%s) must be of type t_VEC, but is of type %s."%(x,x.type()) - pari_catch_sig_on() - return P.new_gen(permtonum(x.g)) - def precision(gen x, long n=-1): """ precision(x,n): Change the precision of x to be n, where n is a @@ -3675,47 +3532,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(precision0(x.g, n)) - def random(gen N): - r""" - ``random(N=2^31)``: Return a pseudo-random integer - between 0 and `N-1`. - - INPUT: - - - -``N`` - gen, integer - - - OUTPUT: - - - - ``gen`` - integer - - - EXAMPLES: - """ - if typ(N.g) != t_INT: - raise TypeError, "x (=%s) must be of type t_INT, but is of type %s."%(N,N.type()) - pari_catch_sig_on() - return P.new_gen(genrand(N.g)) - - def real(gen x): - """ - real(x): Return the real part of x. - - INPUT: - - - - ``x`` - gen - - - OUTPUT: gen - - EXAMPLES: - """ - pari_catch_sig_on() - return P.new_gen(greal(x.g)) - def round(gen x, estimate=False): """ round(x,estimate=False): If x is a real number, returns x rounded @@ -5380,26 +5196,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(binomial(x.g, k)) - def contfrac(gen x, b=0, long lmax=0): - """ - contfrac(x,b,lmax): continued fraction expansion of x (x rational, - real or rational function). b and lmax are both optional, where b - is the vector of numerators of the continued fraction, and lmax is - a bound for the number of terms in the continued fraction - expansion. - """ - cdef gen t0 = objtogen(b) - pari_catch_sig_on() - return P.new_gen(contfrac0(x.g, t0.g, lmax)) - - def contfracpnqn(gen x, b=0, long lmax=0): - """ - contfracpnqn(x): [p_n,p_n-1; q_n,q_n-1] corresponding to the - continued fraction x. - """ - pari_catch_sig_on() - return P.new_gen(pnqn(x.g)) - def ffgen(gen T, v=-1): r""" Return the generator `g=x \bmod T` of the finite field defined @@ -6985,49 +6781,10 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_off() return n - def bnfinit(self, long flag=0, tech=None, unsigned long precision=0): - cdef gen t0 - if tech is None: - pari_catch_sig_on() - return P.new_gen(bnfinit0(self.g, flag, NULL, prec_bits_to_words(precision))) - else: - t0 = objtogen(tech) - pari_catch_sig_on() - return P.new_gen(bnfinit0(self.g, flag, t0.g, prec_bits_to_words(precision))) - - def bnfisintnorm(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(bnfisintnorm(self.g, t0.g)) - - def bnfisnorm(self, x, long flag=0): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(bnfisnorm(self.g, t0.g, flag)) - - def bnfisprincipal(self, x, long flag=1): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(bnfisprincipal0(self.g, t0.g, flag)) - - def bnfnarrow(self): - pari_catch_sig_on() - return P.new_gen(buchnarrow(self.g)) - - def bnfsunit(self, S, unsigned long precision=0): - cdef gen t0 = objtogen(S) - pari_catch_sig_on() - return P.new_gen(bnfsunit(self.g, t0.g, prec_bits_to_words(precision))) - def bnfunit(self): pari_catch_sig_on() return P.new_gen(bnf_get_fu(self.g)) - def bnfisunit(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(bnfisunit(self.g, t0.g)) - def bnrclassno(self, I): r""" Return the order of the ray class group of self modulo ``I``. @@ -7050,23 +6807,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(bnrclassno(self.g, t0.g)) - def bnfissunit(self, sunit_data, x): - cdef gen t0 = objtogen(x) - cdef gen t1 = objtogen(sunit_data) - pari_catch_sig_on() - return P.new_gen(bnfissunit(self.g, t1.g, t0.g)) - - def dirzetak(self, n): - cdef gen t0 = objtogen(n) - pari_catch_sig_on() - return P.new_gen(dirzetak(self.g, t0.g)) - - def galoisapply(self, aut, x): - cdef gen t0 = objtogen(aut) - cdef gen t1 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(galoisapply(self.g, t0.g, t1.g)) - def galoisinit(self, den=None): """ Calculate the Galois group of ``self``. @@ -7332,29 +7072,6 @@ cdef class gen(sage.structure.element.RingElement): P.clear_stack() return v - def idealred(self, I, vdir=0): - cdef gen t0 = objtogen(I) - cdef gen t1 = objtogen(vdir) - pari_catch_sig_on() - return P.new_gen(idealred0(self.g, t0.g, t1.g if vdir else NULL)) - - def idealadd(self, x, y): - cdef gen t0 = objtogen(x) - cdef gen t1 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(idealadd(self.g, t0.g, t1.g)) - - def idealaddtoone(self, x, y): - cdef gen t0 = objtogen(x) - cdef gen t1 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(idealaddtoone0(self.g, t0.g, t1.g)) - - def idealappr(self, x, long flag=0): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(idealappr0(self.g, t0.g, flag)) - def idealchinese(self, x, y): """ Chinese Remainder Theorem over number fields. @@ -7414,28 +7131,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(idealcoprime(self.g, t0.g, t1.g)) - def idealdiv(self, x, y, long flag=0): - cdef gen t0 = objtogen(x) - cdef gen t1 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(idealdiv0(self.g, t0.g, t1.g, flag)) - - def idealfactor(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(idealfactor(self.g, t0.g)) - - def idealhnf(self, a, b=None): - cdef gen t0 = objtogen(a) - cdef gen t1 - if b is None: - pari_catch_sig_on() - return P.new_gen(idealhnf(self.g, t0.g)) - else: - t1 = objtogen(b) - pari_catch_sig_on() - return P.new_gen(idealhnf0(self.g, t0.g, t1.g)) - def idealintersection(self, x, y): cdef gen t0 = objtogen(x) cdef gen t1 = objtogen(y) @@ -7502,20 +7197,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(ideallog(self.g, t0.g, t1.g)) - def idealmul(self, x, y, long flag=0): - cdef gen t0 = objtogen(x) - cdef gen t1 = objtogen(y) - pari_catch_sig_on() - if flag == 0: - return P.new_gen(idealmul(self.g, t0.g, t1.g)) - else: - return P.new_gen(idealmulred(self.g, t0.g, t1.g)) - - def idealnorm(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(idealnorm(self.g, t0.g)) - def idealprimedec(nf, p): """ Prime ideal decomposition of the prime number `p` in the number @@ -7576,17 +7257,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(idealstar0(self.g, t0.g, flag)) - def idealtwoelt(self, x, a=None): - cdef gen t0 = objtogen(x) - cdef gen t1 - if a is None: - pari_catch_sig_on() - return P.new_gen(idealtwoelt0(self.g, t0.g, NULL)) - else: - t1 = objtogen(a) - pari_catch_sig_on() - return P.new_gen(idealtwoelt0(self.g, t0.g, t1.g)) - def idealval(self, x, p): cdef gen t0 = objtogen(x) cdef gen t1 = objtogen(p) @@ -7603,15 +7273,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_off() return v - def modreverse(self): - """ - modreverse(x): reverse polymod of the polymod x, if it exists. - - EXAMPLES: - """ - pari_catch_sig_on() - return P.new_gen(modreverse(self.g)) - def nfbasis(self, long flag=0, fa=None): """ Integral basis of the field `\QQ[a]`, where ``a`` is a root of @@ -7857,11 +7518,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(nfreduce(self.g, t0.g, t1.g)) - def nffactor(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(nffactor(self.g, t0.g)) - def nfgenerator(self): f = self[0] x = f.variable() @@ -7900,7 +7556,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_off() return r - def nfhnf(self,x): """ nfhnf(nf,x) : given a pseudo-matrix (A, I) or an integral pseudo-matrix (A,I,J), finds a @@ -7958,7 +7613,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(nfhnf(self.g, t0.g)) - def nfinit(self, long flag=0, unsigned long precision=0): """ nfinit(pol, {flag=0}): ``pol`` being a nonconstant irreducible @@ -8063,38 +7717,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(nfsubfields(self.g, d)) - def rnfcharpoly(self, T, a, v='x'): - cdef gen t0 = objtogen(T) - cdef gen t1 = objtogen(a) - cdef gen t2 = objtogen(v) - pari_catch_sig_on() - return P.new_gen(rnfcharpoly(self.g, t0.g, t1.g, gvar(t2.g))) - - def rnfdisc(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfdiscf(self.g, t0.g)) - - def rnfeltabstorel(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfeltabstorel(self.g, t0.g)) - - def rnfeltreltoabs(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfeltreltoabs(self.g, t0.g)) - - def rnfequation(self, poly, long flag=0): - cdef gen t0 = objtogen(poly) - pari_catch_sig_on() - return P.new_gen(rnfequation0(self.g, t0.g, flag)) - - def rnfidealabstorel(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfidealabstorel(self.g, t0.g)) - def rnfidealdown(self, x): r""" rnfidealdown(rnf,x): finds the intersection of the ideal x with the base field. @@ -8120,26 +7742,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(rnfidealdown(self.g, t0.g)) - def rnfidealhnf(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfidealhnf(self.g, t0.g)) - - def rnfidealnormrel(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfidealnormrel(self.g, t0.g)) - - def rnfidealreltoabs(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfidealreltoabs(self.g, t0.g)) - - def rnfidealtwoelt(self, x): - cdef gen t0 = objtogen(x) - pari_catch_sig_on() - return P.new_gen(rnfidealtwoelement(self.g, t0.g)) - def rnfinit(self, poly): """ EXAMPLES: We construct a relative number field. @@ -8156,13 +7758,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(rnfinit(self.g, t0.g)) - def rnfisfree(self, poly): - cdef gen t0 = objtogen(poly) - pari_catch_sig_on() - r = rnfisfree(self.g, t0.g) - pari_catch_sig_off() - return r - def quadhilbert(self): r""" Returns a polynomial over `\QQ` whose roots generate the @@ -8211,10 +7806,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(content(self.g)) - def deriv(self, v=-1): - pari_catch_sig_on() - return P.new_gen(deriv(self.g, P.get_var(v))) - def eval(self, *args, **kwds): """ Evaluate ``self`` with the given arguments. @@ -8495,34 +8086,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(factorpadic(self.g, t0.g, r)) - def factormod(self, p, long flag=0): - """ - x.factormod(p,flag=0): factorization mod p of the polynomial x - using Berlekamp. flag is optional, and can be 0: default or 1: - simple factormod, same except that only the degrees of the - irreducible factors are given. - """ - cdef gen t0 = objtogen(p) - pari_catch_sig_on() - return P.new_gen(factormod0(self.g, t0.g, flag)) - - def intformal(self, y=-1): - """ - x.intformal(y): formal integration of x with respect to the main - variable of y, or to the main variable of x if y is omitted - """ - pari_catch_sig_on() - return P.new_gen(integ(self.g, P.get_var(y))) - - def padicappr(self, a): - """ - x.padicappr(a): p-adic roots of the polynomial x congruent to a mod - p - """ - cdef gen t0 = objtogen(a) - pari_catch_sig_on() - return P.new_gen(padicappr(self.g, t0.g)) - def newtonpoly(self, p): """ x.newtonpoly(p): Newton polygon of polynomial x with respect to the @@ -8557,11 +8120,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(polcoeff0(self.g, n, P.get_var(var))) - def polcompositum(self, pol2, long flag=0): - cdef gen t0 = objtogen(pol2) - pari_catch_sig_on() - return P.new_gen(polcompositum0(self.g, t0.g, flag)) - def poldegree(self, var=-1): """ f.poldegree(var=x): Return the degree of this polynomial. @@ -8593,17 +8151,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(poldisc0(self.g, P.get_var(var))) - def poldiscreduced(self): - pari_catch_sig_on() - return P.new_gen(reduceddiscsmith(self.g)) - - def polgalois(self, unsigned long precision=0): - """ - f.polgalois(): Galois group of the polynomial f - """ - pari_catch_sig_on() - return P.new_gen(polgalois(self.g, prec_bits_to_words(precision))) - def nfgaloisconj(self, long flag=0, denom=None, unsigned long precision=0): r""" Edited from the pari documentation: @@ -8656,17 +8203,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(nfroots(self.g, t0.g)) - def polhensellift(self, y, p, long e): - """ - self.polhensellift(y, p, e): lift the factorization y of self - modulo p to a factorization modulo `p^e` using Hensel lift. - The factors in y must be pairwise relatively prime modulo p. - """ - cdef gen t0 = objtogen(y) - cdef gen t1 = objtogen(p) - pari_catch_sig_on() - return P.new_gen(polhensellift(self.g, t0.g, t1.g, e)) - def polisirreducible(self): """ f.polisirreducible(): Returns True if f is an irreducible @@ -8677,43 +8213,6 @@ cdef class gen(sage.structure.element.RingElement): P.clear_stack() return t != 0 - def pollead(self, v=-1): - """ - self.pollead(v): leading coefficient of polynomial or series self, - or self itself if self is a scalar. Error otherwise. With respect - to the main variable of self if v is omitted, with respect to the - variable v otherwise - """ - pari_catch_sig_on() - return P.new_gen(pollead(self.g, P.get_var(v))) - - def polrecip(self): - pari_catch_sig_on() - return P.new_gen(polrecip(self.g)) - - def polred(self, long flag=0, fa=None): - cdef gen t0 - if fa is None: - pari_catch_sig_on() - return P.new_gen(polred0(self.g, flag, NULL)) - else: - t0 = objtogen(fa) - pari_catch_sig_on() - return P.new_gen(polred0(self.g, flag, t0.g)) - - def polredabs(self, long flag=0): - pari_catch_sig_on() - return P.new_gen(polredabs0(self.g, flag)) - - def polredbest(self, long flag=0): - pari_catch_sig_on() - return P.new_gen(polredbest(self.g, flag)) - - def polresultant(self, y, var=-1, long flag=0): - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(polresultant0(self.g, t0.g, P.get_var(var), flag)) - def polroots(self, long flag=-1, unsigned long precision=0): """ Complex roots of the given polynomial using Schonhage's method, @@ -8725,16 +8224,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(cleanroots(self.g, prec_bits_to_words(precision))) - def polrootsmod(self, p, long flag=0): - cdef gen t0 = objtogen(p) - pari_catch_sig_on() - return P.new_gen(rootmod0(self.g, t0.g, flag)) - - def polrootspadic(self, p, r=20): - cdef gen t0 = objtogen(p) - pari_catch_sig_on() - return P.new_gen(rootpadic(self.g, t0.g, r)) - def polrootspadicfast(self, p, r=20): from sage.misc.superseded import deprecation deprecation(16997, 'polrootspadicfast is deprecated, use polrootspadic or the direct PARI call ZpX_roots instead') @@ -8742,38 +8231,12 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(rootpadic(self.g, t0.g, r)) - def polsturm(self, a, b): - cdef gen t0 = objtogen(a) - cdef gen t1 = objtogen(b) - pari_catch_sig_on() - n = sturmpart(self.g, t0.g, t1.g) - pari_catch_sig_off() - return n - def polsturm_full(self): pari_catch_sig_on() n = sturmpart(self.g, NULL, NULL) pari_catch_sig_off() return n - def polsylvestermatrix(self, g): - cdef gen t0 = objtogen(g) - pari_catch_sig_on() - return P.new_gen(sylvestermatrix(self.g, t0.g)) - - def polsym(self, long n): - pari_catch_sig_on() - return P.new_gen(polsym(self.g, n)) - - def serconvol(self, g): - cdef gen t0 = objtogen(g) - pari_catch_sig_on() - return P.new_gen(convol(self.g, t0.g)) - - def serlaplace(self): - pari_catch_sig_on() - return P.new_gen(laplace(self.g)) - def serreverse(self): """ serreverse(f): reversion of the power series f. @@ -8795,16 +8258,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(serreverse(self.g)) - def thueinit(self, long flag=0, unsigned long precision=0): - pari_catch_sig_on() - return P.new_gen(thueinit(self.g, flag, prec_bits_to_words(precision))) - - - def rnfisnorminit(self, polrel, long flag=2): - cdef gen t0 = objtogen(polrel) - pari_catch_sig_on() - return P.new_gen(rnfisnorminit(self.g, t0.g, flag)) - def rnfisnorm(self, T, long flag=0): cdef gen t0 = objtogen(T) pari_catch_sig_on() @@ -8899,34 +8352,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(adj(self.g)).Mat() - def qflll(self, long flag=0): - """ - qflll(x,flag=0): LLL reduction of the vectors forming the matrix x - (gives the unimodular transformation matrix). The columns of x must - be linearly independent, unless specified otherwise below. flag is - optional, and can be 0: default, 1: assumes x is integral, columns - may be dependent, 2: assumes x is integral, returns a partially - reduced basis, 4: assumes x is integral, returns [K,I] where K is - the integer kernel of x and I the LLL reduced image, 5: same as 4 - but x may have polynomial coefficients, 8: same as 0 but x may have - polynomial coefficients. - """ - pari_catch_sig_on() - return P.new_gen(qflll0(self.g,flag)).Mat() - - def qflllgram(self, long flag=0): - """ - qflllgram(x,flag=0): LLL reduction of the lattice whose gram matrix - is x (gives the unimodular transformation matrix). flag is optional - and can be 0: default,1: lllgramint algorithm for integer matrices, - 4: lllgramkerim giving the kernel and the LLL reduced image, 5: - lllgramkerimgen same when the matrix has polynomial coefficients, - 8: lllgramgen, same as qflllgram when the coefficients are - polynomials. - """ - pari_catch_sig_on() - return P.new_gen(qflllgram0(self.g,flag)).Mat() - def lllgram(self): return self.qflllgram(0) @@ -9524,19 +8949,6 @@ cdef class gen(sage.structure.element.RingElement): # misc (classify when I know where they go) ########################################### - def hilbert(x, y, p): - cdef gen t0 = objtogen(y) - cdef gen t1 = objtogen(p) - pari_catch_sig_on() - r = hilbert(x.g, t0.g, t1.g) - P.clear_stack() - return r - - def chinese(self, y): - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(chinese(self.g, t0.g)) - def order(self): pari_catch_sig_on() return P.new_gen(order(self.g)) @@ -9605,10 +9017,6 @@ cdef class gen(sage.structure.element.RingElement): def __abs__(self): return self.abs() - def norm(gen self): - pari_catch_sig_on() - return P.new_gen(gnorm(self.g)) - def nextprime(gen self, bint add_one=0): """ nextprime(x): smallest pseudoprime greater than or equal to `x`. @@ -9704,12 +9112,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(gsubst(self.g, P.get_var(var), t0.g)) - def substpol(self, y, z): - cdef gen t0 = objtogen(y) - cdef gen t1 = objtogen(z) - pari_catch_sig_on() - return P.new_gen(gsubstpol(self.g, t0.g, t1.g)) - def nf_subst(self, z): """ Given a PARI number field ``self``, return the same PARI @@ -9751,33 +9153,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(gsubst(self.g, gvar(self.g), t0.g)) - def taylor(self, v=-1): - pari_catch_sig_on() - return P.new_gen(tayl(self.g, P.get_var(v), precdl)) - - def thue(self, rhs, ne): - cdef gen t0 = objtogen(rhs) - cdef gen t1 = objtogen(ne) - pari_catch_sig_on() - return P.new_gen(thue(self.g, t0.g, t1.g)) - - def charpoly(self, var=-1, long flag=0): - """ - charpoly(A,v=x,flag=0): det(v\*Id-A) = characteristic polynomial of - A using the comatrix. flag is optional and may be set to 1 (use - Lagrange interpolation) or 2 (use Hessenberg form), 0 being the - default. - """ - pari_catch_sig_on() - return P.new_gen(charpoly0(self.g, P.get_var(var), flag)) - - def kronecker(gen self, y): - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - r = kronecker(self.g, t0.g) - P.clear_stack() - return r - def type(gen self): """ Return the PARI type of self as a string. @@ -9835,7 +9210,6 @@ cdef class gen(sage.structure.element.RingElement): else: raise TypeError, "Unknown PARI type: %s"%t - def polinterpolate(self, ya, x): """ self.polinterpolate(ya,x,e): polynomial interpolation at x @@ -9869,15 +9243,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(algdep(self.g, n)) - def concat(self, y): - cdef gen t0 = objtogen(y) - pari_catch_sig_on() - return P.new_gen(concat(self.g, t0.g)) - - def lindep(self, long flag=0): - pari_catch_sig_on() - return P.new_gen(lindep0(self.g, flag)) - def listinsert(self, obj, long n): cdef gen t0 = objtogen(obj) pari_catch_sig_on() @@ -9888,8 +9253,6 @@ cdef class gen(sage.structure.element.RingElement): pari_catch_sig_on() return P.new_gen(listput(self.g, t0.g, n)) - - def elleisnum(self, long k, long flag=0, unsigned long precision=0): """ om.elleisnum(k, flag=0): om=[om1,om2] being a 2-component vector diff --git a/src/sage/libs/pari/pari_instance.pxd b/src/sage/libs/pari/pari_instance.pxd index fdd20767ec4..707e23e6ed5 100644 --- a/src/sage/libs/pari/pari_instance.pxd +++ b/src/sage/libs/pari/pari_instance.pxd @@ -1,15 +1,18 @@ include 'decl.pxi' from sage.libs.flint.types cimport fmpz_mat_t -cimport sage.structure.parent_base +from sage.structure.parent_base cimport ParentWithBase cimport cython from sage.libs.pari.gen cimport gen cpdef long prec_bits_to_words(unsigned long prec_in_bits) +cdef class PariInstance_auto(ParentWithBase): + pass + @cython.final -cdef class PariInstance(sage.structure.parent_base.ParentWithBase): +cdef class PariInstance(PariInstance_auto): cdef long _real_precision cdef gen PARI_ZERO, PARI_ONE, PARI_TWO cdef inline gen new_gen(self, GEN x) diff --git a/src/sage/libs/pari/pari_instance.pyx b/src/sage/libs/pari/pari_instance.pyx index 9487adbb93d..8a9a59e072f 100644 --- a/src/sage/libs/pari/pari_instance.pyx +++ b/src/sage/libs/pari/pari_instance.pyx @@ -370,8 +370,10 @@ cdef void sage_flush(): sys.stdout.flush() +include 'auto_instance.pxi' + @cython.final -cdef class PariInstance(sage.structure.parent_base.ParentWithBase): +cdef class PariInstance(PariInstance_auto): def __init__(self, long size=1000000, unsigned long maxprime=500000): """ Initialize the PARI system. @@ -977,16 +979,18 @@ cdef class PariInstance(sage.structure.parent_base.ParentWithBase): return x - cdef long get_var(self, v): """ - Converts a Python string into a PARI variable reference number. Or - if v = -1, returns -1. + Convert a Python string into a PARI variable number. """ - if v != -1: - s = str(v) - return fetch_user_var(s) - return -1 + if v is None: + return -1 + if v == -1: + #from sage.misc.superseded import deprecation + #deprecation(XXXXX, 'passing -1 as PARI variable is deprecated, use None instead') + return -1 + cdef bytes s = bytes(v) + return fetch_user_var(s) ############################################################ # Initialization @@ -1260,15 +1264,6 @@ cdef class PariInstance(sage.structure.parent_base.ParentWithBase): self.init_primes(n+1) return self.prime_list(pari(n).primepi()) -## cdef long k -## k = (n+10)/math.log(n) -## p = 2 -## while p <= n: -## p = self.nth_prime(k) -## k = 2 -## v = self.prime_list(k) -## return v[:pari(n).primepi()] - def __nth_prime(self, long n): """ nth_prime(n): returns the n-th prime, where n is a C-int @@ -1288,7 +1283,6 @@ cdef class PariInstance(sage.structure.parent_base.ParentWithBase): self.init_primes(max(2*maxprime(), 20*n)) return self.nth_prime(n) - def euler(self, unsigned long precision=0): """ Return Euler's constant to the requested real precision (in bits). @@ -1429,10 +1423,6 @@ cdef class PariInstance(sage.structure.parent_base.ParentWithBase): return plist #return self.new_gen(polsubcyclo(n, d, self.get_var(v))) - def polzagier(self, long n, long m): - pari_catch_sig_on() - return self.new_gen(polzag(n, m)) - def setrand(self, seed): """ Sets PARI's current random number seed. diff --git a/src/sage_setup/.gitignore b/src/sage_setup/.gitignore new file mode 100644 index 00000000000..1e18f206406 --- /dev/null +++ b/src/sage_setup/.gitignore @@ -0,0 +1 @@ +/autogen/pari/timestamp diff --git a/src/sage_setup/autogen/__init__.py b/src/sage_setup/autogen/__init__.py new file mode 100644 index 00000000000..05624234ff9 --- /dev/null +++ b/src/sage_setup/autogen/__init__.py @@ -0,0 +1,3 @@ +def autogen_all(): + import pari + pari.rebuild() diff --git a/src/sage_setup/autogen/pari/__init__.py b/src/sage_setup/autogen/pari/__init__.py new file mode 100644 index 00000000000..dc5d4caed60 --- /dev/null +++ b/src/sage_setup/autogen/pari/__init__.py @@ -0,0 +1,18 @@ +from sage_setup.autogen.pari.generator import PariFunctionGenerator +import os + +def rebuild(): + stamp = os.path.join(os.path.dirname(__file__), 'timestamp') + desc = os.path.join(os.environ['SAGE_LOCAL'], 'share', 'pari', 'pari.desc') + try: + if os.stat(stamp).st_mtime >= os.stat(desc).st_mtime: + # No need to rebuild + return + except OSError: + pass + + print("Generating PARI functions.") + G = PariFunctionGenerator() + G() + + open(stamp, 'w').close() diff --git a/src/sage_setup/autogen/pari/args.py b/src/sage_setup/autogen/pari/args.py new file mode 100644 index 00000000000..b3b795f9055 --- /dev/null +++ b/src/sage_setup/autogen/pari/args.py @@ -0,0 +1,291 @@ +""" +Arguments for PARI calls +""" + +#***************************************************************************** +# Copyright (C) 2015 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +# Some replacements for reserved words +replacements = {'char': 'character'} + +class PariArgument(object): + """ + This class represents one argument in a PARI call. + """ + def __init__(self, namesiter, default, index): + """ + Create a new argument for a PARI call. + + INPUT: + + - ``namesiter`` -- iterator over all names of the arguments. + Usually, the next name from this iterator is used as argument + name. + + - ``default`` -- default value for this argument (``None`` + means that the argument is not optional). + + - ``index`` -- (integer >= 0). Index of this argument in the + list of arguments. Index 0 means a ``"self"`` argument which + is treated specially. For a function which is not a method, + start counting at 1. + """ + self.index = index + self.name = self.get_argument_name(namesiter) + if self.index == 0: # "self" argument can never have a default + self.default = None + elif default is None: + self.default = self.always_default() + elif default == "": + self.default = self.default_default() + else: + self.default = default + + def __repr__(self): + s = self._typerepr() + " " + self.name + if self.default is not None: + s += "=" + self.default + return s + + def _typerepr(self): + """ + Return a string representing the type of this argument. + """ + return "(generic)" + + def always_default(self): + """ + If this returns not ``None``, it is a value which is always + the default for this argument, which is then automatically + optional. + """ + return None + + def default_default(self): + """ + The default value for an optional argument if no other default + was specified in the prototype. + """ + return "NULL" + + def get_argument_name(self, namesiter): + """ + Return the name for this argument, given ``namesiter`` which is + an iterator over the argument names given by the help string. + """ + try: + n = namesiter.next() + try: + return replacements[n] + except KeyError: + return n + except StopIteration: + # No more names available, use something default. + # This is used in listcreate() for example which has a + # deprecated argument which is not listed in the help. + return "_arg%s" % self.index + + def prototype_code(self): + """ + Return code to appear in the prototype of the Cython wrapper. + """ + raise NotImplementedError + + def convert_code(self): + """ + Return code to appear in the function body to convert this + argument to something that PARI understand. This code can also + contain extra checks. + """ + return "" + + def call_code(self): + """ + Return code to put this argument in a PARI function call. + """ + return self.name + + +class PariArgumentObject(PariArgument): + """ + Class for arguments which are passed as generic Python ``object``. + """ + def __init__(self, *args, **kwds): + super(PariArgumentObject, self).__init__(*args, **kwds) + self.tmpname = "_" + self.name + + def prototype_code(self): + """ + Return code to appear in the prototype of the Cython wrapper. + """ + s = self.name + if self.default is not None: + # Default corresponds to None, actual default value should + # be handled in convert_code() + s += "=None" + return s + +class PariArgumentClass(PariArgument): + """ + Class for arguments which are passed as a specific C/Cython class. + + The C/Cython type is given by ``self.ctype()``. + """ + def ctype(self): + raise NotImplementedError + + def prototype_code(self): + """ + Return code to appear in the prototype of the Cython wrapper. + """ + s = self.ctype() + " " + self.name + if self.default is not None: + s += "=" + self.default + return s + + +class PariInstanceArgument(PariArgumentObject): + """ + ``self`` argument for ``PariInstance`` object. + """ + def __init__(self): + PariArgument.__init__(self, iter(["self"]), None, 0) + def convert_code(self): + return " cdef PariInstance pari_instance = self\n" + def _typerepr(self): + return "PariInstance" + + +class PariArgumentGEN(PariArgumentObject): + def _typerepr(self): + return "GEN" + def convert_code(self): + if self.index == 0: + # "self" is always of type gen, we skip the conversion + s = " cdef GEN {tmp} = {name}.g\n" + elif self.default is None: + s = " {name} = objtogen({name})\n" + s += " cdef GEN {tmp} = ({name}).g\n" + elif self.default == "NULL": + s = " cdef GEN {tmp} = {default}\n" + s += " if {name} is not None:\n" + s += " {name} = objtogen({name})\n" + s += " {tmp} = ({name}).g\n" + elif self.default == "0": + s = " cdef GEN {tmp}\n" + s += " if {name} is None:\n" + s += " {tmp} = gen_0\n" + s += " else:\n" + s += " {name} = objtogen({name})\n" + s += " {tmp} = ({name}).g\n" + else: + raise ValueError("default value %r for GEN argument %r is not supported" % (self.default, self.name)) + return s.format(name=self.name, tmp=self.tmpname, default=self.default) + def call_code(self): + return self.tmpname + +class PariArgumentString(PariArgumentObject): + def _typerepr(self): + return "str" + def convert_code(self): + if self.default is None: + s = " {name} = str({name})\n" + s += " cdef char* {tmp} = {name}\n" + else: + s = " cdef char* {tmp}\n" + s += " if {name} is None:\n" + s += " {tmp} = {default}\n" + s += " else:\n" + s += " {name} = bytes({name})\n" + s += " {tmp} = {name}\n" + return s.format(name=self.name, tmp=self.tmpname, default=self.default) + def call_code(self): + return self.tmpname + +class PariArgumentVariable(PariArgumentObject): + def _typerepr(self): + return "var" + def default_default(self): + return "-1" + def convert_code(self): + if self.default is None: + s = " cdef long {tmp} = pari_instance.get_var({name})\n" + else: + s = " cdef long {tmp} = {default}\n" + s += " if {name} is not None:\n" + s += " {tmp} = pari_instance.get_var({name})\n" + return s.format(name=self.name, tmp=self.tmpname, default=self.default) + def call_code(self): + return self.tmpname + +class PariArgumentLong(PariArgumentClass): + def _typerepr(self): + return "long" + def ctype(self): + return "long" + def default_default(self): + return "0" + +class PariArgumentULong(PariArgumentClass): + def _typerepr(self): + return "unsigned long" + def ctype(self): + return "unsigned long" + def default_default(self): + return "0" + +class PariArgumentPrec(PariArgumentClass): + def _typerepr(self): + return "prec" + def ctype(self): + return "long" + def always_default(self): + return "0" + def get_argument_name(self, namesiter): + return "precision" + def convert_code(self): + s = " {name} = prec_bits_to_words({name})\n" + return s.format(name=self.name) + +class PariArgumentSeriesPrec(PariArgumentClass): + def _typerepr(self): + return "serprec" + def ctype(self): + return "long" + def default_default(self): + return "-1" + def get_argument_name(self, namesiter): + return "serprec" + def convert_code(self): + s = " if {name} < 0:\n" + s += " {name} = pari_instance.get_series_precision()\n" + return s.format(name=self.name) + + +pari_arg_types = { + 'G': PariArgumentGEN, + 'r': PariArgumentString, + 's': PariArgumentString, + 'L': PariArgumentLong, + 'U': PariArgumentULong, + 'n': PariArgumentVariable, + 'p': PariArgumentPrec, + 'P': PariArgumentSeriesPrec, + + # Codes which are known but not actually supported for Sage + '&': None, + 'V': None, + 'W': None, + 'I': None, + 'E': None, + 'J': None, + 'C': None, + '*': None, + '=': None} diff --git a/src/sage_setup/autogen/pari/generator.py b/src/sage_setup/autogen/pari/generator.py new file mode 100644 index 00000000000..8a4f622ab49 --- /dev/null +++ b/src/sage_setup/autogen/pari/generator.py @@ -0,0 +1,227 @@ +""" +Auto-generate methods for PARI functions. +""" + +#***************************************************************************** +# Copyright (C) 2015 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +from __future__ import print_function +import os, re + +from sage_setup.autogen.pari.args import (PariArgumentGEN, + PariInstanceArgument) +from sage_setup.autogen.pari.parser import (sage_src_pari, + read_pari_desc, read_decl, parse_prototype) + + +gen_banner = '''# This file is auto-generated by {} + +cdef class gen_auto(RingElement): + """ + Part of the :class:`gen` class containing auto-generated functions. + + This class is not meant to be used directly, use the derived class + :class:`gen` instead. + """ +'''.format(__file__) + +instance_banner = '''# This file is auto-generated by {} + +cdef class PariInstance_auto(ParentWithBase): + """ + Part of the :class:`PariInstance` class containing auto-generated functions. + + You must never use this class directly (in fact, Sage may crash if + you do), use the derived class :class:`PariInstance` instead. + """ +'''.format(__file__) + + +function_re = re.compile(r"^[A-Za-z][A-Za-z0-9_]*$") +function_blacklist = {"O"} # O(p^e) needs special parser support + +class PariFunctionGenerator(object): + """ + Class to auto-generate ``auto_gen.pxi`` and ``auto_instance.pxi``. + + The PARI file ``pari.desc`` is read and all suitable PARI functions + are written as methods of either :class:`gen` or + :class:`PariInstance`. + """ + def __init__(self): + self.declared = read_decl() + self.gen_filename = os.path.join(sage_src_pari(), 'auto_gen.pxi') + self.instance_filename = os.path.join(sage_src_pari(), 'auto_instance.pxi') + + def can_handle_function(self, function, cname, **kwds): + """ + Can we actually handle this function in Sage? + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.generator import PariFunctionGenerator + sage: G = PariFunctionGenerator() + sage: G.can_handle_function("bnfinit", "bnfinit0", **{"class":"basic"}) + True + sage: G.can_handle_function("_bnfinit", "bnfinit0", **{"class":"basic"}) + False + sage: G.can_handle_function("bnfinit", "BNFINIT0", **{"class":"basic"}) + False + sage: G.can_handle_function("bnfinit", "bnfinit0", **{"class":"hard"}) + False + """ + if function in function_blacklist: + # Blacklist specific troublesome functions + return False + if not function_re.match(function): + # Not a legal function name, like "!_" + return False + if cname not in self.declared: + # PARI function not in Sage's decl.pxi or declinl.pxi + return False + cls = kwds.get("class", "unknown") + sec = kwds.get("section", "unknown") + if cls not in ("basic", "highlevel"): + # Different class: probably something technical or + # gp2c-specific + return False + if sec == "programming/control": + # Skip if, return, break, ... + return False + return True + + def handle_pari_function(self, function, cname="", prototype="", help="", doc="", **kwds): + r""" + Handle one PARI function: decide whether or not to add the + function to Sage, in which file (as method of :class:`gen` or + of :class:`PariInstance`?) and call :meth:`write_method` to + actually write the code. + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.parser import read_pari_desc + sage: from sage_setup.autogen.pari.generator import PariFunctionGenerator + sage: G = PariFunctionGenerator() + sage: G.gen_file = sys.stdout + sage: G.instance_file = sys.stdout + sage: G.handle_pari_function("bnfinit", + ....: cname="bnfinit0", prototype="GD0,L,DGp", + ....: help=r"bnfinit(P,{flag=0},{tech=[]}): compute...", + ....: doc=r"Doc: initializes a \var{bnf} structure", + ....: **{"class":"basic", "section":"number_fields"}) + def bnfinit(P, long flag=0, tech=None, long precision=0): + cdef GEN _P = P.g + cdef GEN _tech = NULL + if tech is not None: + tech = objtogen(tech) + _tech = (tech).g + precision = prec_bits_to_words(precision) + pari_catch_sig_on() + cdef GEN _ret = bnfinit0(_P, flag, _tech, precision) + return pari_instance.new_gen(_ret) + + sage: G.handle_pari_function("ellmodulareqn", + ....: cname="ellmodulareqn", prototype="LDnDn", + ....: help=r"ellmodulareqn(N,{x},{y}): return...", + ....: doc=r"return a vector [\kbd{eqn},$t$] where \kbd{eqn} is...", + ....: **{"class":"basic", "section":"elliptic_curves"}) + def ellmodulareqn(self, long N, x=None, y=None): + cdef PariInstance pari_instance = self + cdef long _x = -1 + if x is not None: + _x = pari_instance.get_var(x) + cdef long _y = -1 + if y is not None: + _y = pari_instance.get_var(y) + pari_catch_sig_on() + cdef GEN _ret = ellmodulareqn(N, _x, _y) + return pari_instance.new_gen(_ret) + + sage: G.handle_pari_function("setrand", + ....: cname="setrand", prototype="vG", + ....: help=r"setrand(n): reset the seed...", + ....: doc=r"reseeds the random number generator...", + ....: **{"class":"basic", "section":"programming/specific"}) + def setrand(n): + cdef GEN _n = n.g + pari_catch_sig_on() + setrand(_n) + pari_instance.clear_stack() + + """ + if not self.can_handle_function(function, cname, **kwds): + return + + try: + args, ret = parse_prototype(prototype, help) + except NotImplementedError: + return # Skip unsupported prototype codes + + # Is the first argument a GEN? + if len(args) > 0 and isinstance(args[0], PariArgumentGEN): + # If yes, write a method of the gen class. + cargs = args + f = self.gen_file + else: + # If no, write a method of the PariInstance class. + # Parse again with an extra "self" argument. + args, ret = parse_prototype(prototype, help, [PariInstanceArgument()]) + cargs = args[1:] + f = self.instance_file + + self.write_method(function, cname, args, ret, cargs, f) + + def write_method(self, function, cname, args, ret, cargs, file): + """ + Write Cython code with a method to call one PARI function. + + INPUT: + + - ``function`` -- name for the method + + - ``cname`` -- name of the PARI C library call + + - ``args``, ``ret`` -- output from ``parse_prototype``, + including the initial args like ``self`` + + - ``cargs`` -- like ``args`` but excluding the initial args + + - ``file`` -- a file object where the code should be written to + """ + protoargs = ", ".join(a.prototype_code() for a in args) + callargs = ", ".join(a.call_code() for a in cargs) + + s = " def {function}({protoargs}):\n" + for a in args: + s += a.convert_code() + s += " pari_catch_sig_on()\n" + s += ret.assign_code("{cname}({callargs})") + s += ret.return_code() + + s = s.format(function=function, protoargs=protoargs, cname=cname, callargs=callargs) + print (s, file=file) + + def __call__(self): + """ + Top-level function to generate the auto-generated files. + """ + D = read_pari_desc() + D = sorted(D.values(), key=lambda d: d['function']) + + self.gen_file = open(self.gen_filename, 'w') + self.gen_file.write(gen_banner) + self.instance_file = open(self.instance_filename, 'w') + self.instance_file.write(instance_banner) + + for v in D: + self.handle_pari_function(**v) + + self.gen_file.close() + self.instance_file.close() diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py new file mode 100644 index 00000000000..28cf3269de6 --- /dev/null +++ b/src/sage_setup/autogen/pari/parser.py @@ -0,0 +1,199 @@ +""" +Read and parse the file pari.desc +""" + +#***************************************************************************** +# Copyright (C) 2015 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +import os, re + +from sage_setup.autogen.pari.args import pari_arg_types +from sage_setup.autogen.pari.ret import pari_ret_types + + +def sage_src_pari(): + """ + Return the directory in the Sage source tree where the interface to + the PARI library is and where the auto-generated files should be + stored. + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.parser import sage_src_pari + sage: sage_src_pari() + '.../src/sage/libs/pari' + """ + SAGE_SRC = os.environ['SAGE_SRC'] + return os.path.join(SAGE_SRC, 'sage', 'libs', 'pari') + + +paren_re = re.compile(r"[(](.*)[)]") +argname_re = re.compile(r"[ {]*([A-Za-z0-9_]+)") + +def read_pari_desc(): + """ + Read and parse the file ``pari.desc``. + + The output is a dictionary where the keys are GP function names + and the corresponding values are dictionaries containing the + ``(key, value)`` pairs from ``pari.desc``. + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.parser import read_pari_desc + sage: D = read_pari_desc() + sage: D["cos"] + {'class': 'basic', + 'cname': 'gcos', + 'doc': 'cosine of $x$.', + 'function': 'cos', + 'help': 'cos(x): cosine of x.', + 'prototype': 'Gp', + 'section': 'transcendental'} + """ + SAGE_LOCAL = os.environ["SAGE_LOCAL"] + with open(os.path.join(SAGE_LOCAL, "share", "pari", "pari.desc")) as f: + lines = f.readlines() + + n = 0 + N = len(lines) + + functions = {} + while n < N: + fun = {} + while True: + L = lines[n]; n += 1 + if L == "\n": + break + # As long as the next lines start with a space, append them + while lines[n].startswith(" "): + L += (lines[n])[1:]; n += 1 + key, value = L.split(":", 1) + # Change key to an allowed identifier name + key = key.lower().replace("-", "") + fun[key] = value.strip() + + name = fun["function"] + functions[name] = fun + + return functions + + +decl_re = re.compile(" ([A-Za-z][A-Za-z0-9_]*)[(]") + +def read_decl(): + """ + Read the files ``decl.pxi`` and ``declinl.pxi`` and return a set + of all declared PARI library functions. + + We do a simple regexp search, so there might be false positives. + The main use is to skip undeclared functions. + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.parser import read_decl + sage: read_decl() + {'ABC_to_bnr', ..., 'zx_to_ZX'} + """ + decl = open(os.path.join(sage_src_pari(), "decl.pxi")).read() + decl += open(os.path.join(sage_src_pari(), "declinl.pxi")).read() + + D = set() + for m in decl_re.finditer(decl): + D.add(m.groups()[0]) + return D + + +def parse_prototype(proto, help, initial_args=[]): + """ + Parse arguments and return type of a PARI function. + + INPUT: + + - ``proto`` -- a PARI prototype like ``"GD0,L,DGDGDG"`` + + - ``help`` -- a PARI help string like + ``"qfbred(x,{flag=0},{d},{isd},{sd})"`` + + - ``initial_args`` -- other arguments to this function which come + before the PARI arguments, for example a ``self`` argument. + + OUTPUT: a tuple ``(args, ret)`` where + + - ``args`` is a list consisting of ``initial_args`` followed by + :class:`PariArgument` instances with all arguments of this + function. + + - ``ret`` is a :class:`PariReturn` instance with the return type of + this function. + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.parser import parse_prototype + sage: proto = 'GD0,L,DGDGDG' + sage: help = 'qfbred(x,{flag=0},{d},{isd},{sd})' + sage: parse_prototype(proto, help) + ([GEN x, long flag=0, GEN d=NULL, GEN isd=NULL, GEN sd=NULL], GEN) + sage: parse_prototype("lp", "foo()", ["TEST"]) + (['TEST', prec precision=0], long) + """ + # Use the help string just for the argument names. + # "names" should be an iterator over the argument names. + m = paren_re.search(help) + if m is None: + names = iter([]) + else: + s = m.groups()[0] + matches = [argname_re.match(x) for x in s.split(",")] + names = (m.groups()[0] for m in matches if m is not None) + + # First, handle the return type + try: + c = proto[0] + t = pari_ret_types[c] + n = 1 # index in proto + except (IndexError, KeyError): + t = pari_ret_types[""] + n = 0 # index in proto + ret = t() + + # Go over the prototype characters and build up the arguments + args = list(initial_args) + default = None + while n < len(proto): + c = proto[n]; n += 1 + + # Parse default value + if c == "D": + default = "" + if proto[n] not in pari_arg_types: + while True: + c = proto[n]; n += 1 + if c == ",": + break + default += c + c = proto[n]; n += 1 + else: + default = None + + try: + t = pari_arg_types[c] + if t is None: + raise NotImplementedError('unsupported prototype character %r' % c) + except KeyError: + if c == ",": + continue # Just skip additional commas + else: + raise ValueError('unknown prototype character %r' % c) + + arg = t(names, default, index=len(args)) + args.append(arg) + + return (args, ret) diff --git a/src/sage_setup/autogen/pari/ret.py b/src/sage_setup/autogen/pari/ret.py new file mode 100644 index 00000000000..6555765bedb --- /dev/null +++ b/src/sage_setup/autogen/pari/ret.py @@ -0,0 +1,84 @@ +""" +Return types for PARI calls +""" + +#***************************************************************************** +# Copyright (C) 2015 Jeroen Demeyer +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +class PariReturn(object): + """ + This class represents the return value of a PARI call. + """ + def __init__(self): + self.name = "_ret" + + def __repr__(self): + return self.ctype() + + def ctype(self): + """ + Return the C type of the result of the PARI call. + """ + raise NotImplementedError + + def assign_code(self, value): + """ + Return code to assign the result of the PARI call in ``value`` + to the variable named ``self.name``. + """ + s = " cdef {ctype} {name} = {value}\n" + return s.format(ctype=self.ctype(), name=self.name, value=value) + + def return_code(self): + """ + Return code to return from the Cython wrapper. + """ + s = " pari_instance.clear_stack()\n" + s += " return {name}\n" + return s.format(name=self.name) + + +class PariReturnGEN(PariReturn): + def ctype(self): + return "GEN" + def return_code(self): + s = " return pari_instance.new_gen({name})\n" + return s.format(name=self.name) + +class PariReturnInt(PariReturn): + def ctype(self): + return "int" + +class PariReturnLong(PariReturn): + def ctype(self): + return "long" + +class PariReturnULong(PariReturn): + def ctype(self): + return "unsigned long" + +class PariReturnVoid(PariReturn): + def ctype(self): + return "void" + def assign_code(self, value): + return " {value}\n".format(value=value) + def return_code(self): + s = " pari_instance.clear_stack()\n" + return s + + +pari_ret_types = { + '': PariReturnGEN, + 'm': PariReturnGEN, + 'i': PariReturnInt, + 'l': PariReturnLong, + 'u': PariReturnULong, + 'v': PariReturnVoid, + } diff --git a/src/sage_setup/find.py b/src/sage_setup/find.py index 56c4d6f76ad..d208364601b 100644 --- a/src/sage_setup/find.py +++ b/src/sage_setup/find.py @@ -48,7 +48,7 @@ def find_python_sources(src_dir, modules=('sage',)): 1 loops, best of 1: 18.8 ms per loop sage: find_python_sources(SAGE_SRC, modules=['sage_setup']) - (['sage_setup'], [...'sage_setup.find'...]) + (['sage_setup', ...], [...'sage_setup.find'...]) """ PYMOD_EXT = os.path.extsep + 'py' INIT_FILE = '__init__' + PYMOD_EXT diff --git a/src/setup.py b/src/setup.py index f55d9e74df9..703c024ea8c 100644 --- a/src/setup.py +++ b/src/setup.py @@ -68,10 +68,13 @@ extra_compile_args.append('-fno-tree-dominator-opts') # Generate interpreters - sage.ext.gen_interpreters.rebuild(os.path.join(SAGE_SRC, 'sage', 'ext', 'interpreters')) ext_modules = ext_modules + sage.ext.gen_interpreters.modules +# Generate auto-generated files +from sage_setup.autogen import autogen_all +autogen_all() + ######################################################### ### Testing related stuff From 82a67fc5d375bc5a5a3cd182aa60940f48e79414 Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Thu, 26 Feb 2015 21:05:56 +0100 Subject: [PATCH 059/129] trac #17631 (reviewer commit 1): add a force argument --- src/sage_setup/autogen/__init__.py | 11 +++++++++-- src/sage_setup/autogen/pari/__init__.py | 4 ++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/sage_setup/autogen/__init__.py b/src/sage_setup/autogen/__init__.py index 05624234ff9..ea3e8e35c4d 100644 --- a/src/sage_setup/autogen/__init__.py +++ b/src/sage_setup/autogen/__init__.py @@ -1,3 +1,10 @@ -def autogen_all(): +def autogen_all(force=False): + """ + Regenerate the automatically generated files of the Sage library. + + INPUT: + + - ``force`` -- whether we force rebuilding the files (default is ``False``) + """ import pari - pari.rebuild() + pari.rebuild(force=force) diff --git a/src/sage_setup/autogen/pari/__init__.py b/src/sage_setup/autogen/pari/__init__.py index dc5d4caed60..4b4ba3f7cd8 100644 --- a/src/sage_setup/autogen/pari/__init__.py +++ b/src/sage_setup/autogen/pari/__init__.py @@ -1,11 +1,11 @@ from sage_setup.autogen.pari.generator import PariFunctionGenerator import os -def rebuild(): +def rebuild(force=False): stamp = os.path.join(os.path.dirname(__file__), 'timestamp') desc = os.path.join(os.environ['SAGE_LOCAL'], 'share', 'pari', 'pari.desc') try: - if os.stat(stamp).st_mtime >= os.stat(desc).st_mtime: + if not force and os.stat(stamp).st_mtime >= os.stat(desc).st_mtime: # No need to rebuild return except OSError: From 8da123ad8c935f26573712f34784a5b4ecebce0a Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Thu, 26 Feb 2015 21:17:53 +0100 Subject: [PATCH 060/129] trac #17631 (reviewer commit 2): pari_src function --- src/sage_setup/autogen/pari/__init__.py | 3 ++- src/sage_setup/autogen/pari/parser.py | 15 +++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/sage_setup/autogen/pari/__init__.py b/src/sage_setup/autogen/pari/__init__.py index 4b4ba3f7cd8..0b3c1615005 100644 --- a/src/sage_setup/autogen/pari/__init__.py +++ b/src/sage_setup/autogen/pari/__init__.py @@ -1,9 +1,10 @@ from sage_setup.autogen.pari.generator import PariFunctionGenerator +from sage_setup.autogen.pari.parser import pari_src import os def rebuild(force=False): stamp = os.path.join(os.path.dirname(__file__), 'timestamp') - desc = os.path.join(os.environ['SAGE_LOCAL'], 'share', 'pari', 'pari.desc') + desc = os.path.join(pari_src(), 'pari.desc') try: if not force and os.stat(stamp).st_mtime >= os.stat(desc).st_mtime: # No need to rebuild diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py index 28cf3269de6..e2cda44cb0e 100644 --- a/src/sage_setup/autogen/pari/parser.py +++ b/src/sage_setup/autogen/pari/parser.py @@ -33,6 +33,18 @@ def sage_src_pari(): SAGE_SRC = os.environ['SAGE_SRC'] return os.path.join(SAGE_SRC, 'sage', 'libs', 'pari') +def pari_src(): + r""" + Return the directory of the pari source code. + + EXAMPLES:: + + sage: from sage_setup.autogen.pari.parser import pari_src + sage: pari_src() + '.../local/share/pari' + """ + SAGE_LOCAL = os.environ["SAGE_LOCAL"] + return os.path.join(SAGE_LOCAL, "share", "pari") paren_re = re.compile(r"[(](.*)[)]") argname_re = re.compile(r"[ {]*([A-Za-z0-9_]+)") @@ -58,8 +70,7 @@ def read_pari_desc(): 'prototype': 'Gp', 'section': 'transcendental'} """ - SAGE_LOCAL = os.environ["SAGE_LOCAL"] - with open(os.path.join(SAGE_LOCAL, "share", "pari", "pari.desc")) as f: + with open(os.path.join(pari_src(), 'pari.desc')) as f: lines = f.readlines() n = 0 From b1b29ebb9e9b54e929cdd04dabe3693541123f0c Mon Sep 17 00:00:00 2001 From: Vincent Delecroix <20100.delecroix@gmail.com> Date: Thu, 26 Feb 2015 21:20:04 +0100 Subject: [PATCH 061/129] trac #17631 (reviewer commit 3): simpler read_decl --- src/sage_setup/autogen/pari/parser.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py index e2cda44cb0e..7ab17e9dc79 100644 --- a/src/sage_setup/autogen/pari/parser.py +++ b/src/sage_setup/autogen/pari/parser.py @@ -113,13 +113,12 @@ def read_decl(): sage: read_decl() {'ABC_to_bnr', ..., 'zx_to_ZX'} """ - decl = open(os.path.join(sage_src_pari(), "decl.pxi")).read() - decl += open(os.path.join(sage_src_pari(), "declinl.pxi")).read() - - D = set() - for m in decl_re.finditer(decl): - D.add(m.groups()[0]) - return D + s = set() + with open(os.path.join(sage_src_pari(), "decl.pxi")) as f: + s.update(decl_re.findall(f.read())) + with open(os.path.join(sage_src_pari(), "declinl.pxi")) as f: + s.update(decl_re.findall(f.read())) + return s def parse_prototype(proto, help, initial_args=[]): From 98022fd94d8483a4b7f2b19af598bc8c1d3eb1dc Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Fri, 27 Feb 2015 10:00:39 +0100 Subject: [PATCH 062/129] Rename pari_src() to pari_share() --- src/sage_setup/autogen/pari/__init__.py | 4 ++-- src/sage_setup/autogen/pari/parser.py | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage_setup/autogen/pari/__init__.py b/src/sage_setup/autogen/pari/__init__.py index 0b3c1615005..781d09d5208 100644 --- a/src/sage_setup/autogen/pari/__init__.py +++ b/src/sage_setup/autogen/pari/__init__.py @@ -1,10 +1,10 @@ from sage_setup.autogen.pari.generator import PariFunctionGenerator -from sage_setup.autogen.pari.parser import pari_src +from sage_setup.autogen.pari.parser import pari_share import os def rebuild(force=False): stamp = os.path.join(os.path.dirname(__file__), 'timestamp') - desc = os.path.join(pari_src(), 'pari.desc') + desc = os.path.join(pari_share(), 'pari.desc') try: if not force and os.stat(stamp).st_mtime >= os.stat(desc).st_mtime: # No need to rebuild diff --git a/src/sage_setup/autogen/pari/parser.py b/src/sage_setup/autogen/pari/parser.py index 7ab17e9dc79..24c7c8de253 100644 --- a/src/sage_setup/autogen/pari/parser.py +++ b/src/sage_setup/autogen/pari/parser.py @@ -33,14 +33,14 @@ def sage_src_pari(): SAGE_SRC = os.environ['SAGE_SRC'] return os.path.join(SAGE_SRC, 'sage', 'libs', 'pari') -def pari_src(): +def pari_share(): r""" - Return the directory of the pari source code. + Return the directory where the PARI data files are stored. EXAMPLES:: - sage: from sage_setup.autogen.pari.parser import pari_src - sage: pari_src() + sage: from sage_setup.autogen.pari.parser import pari_share + sage: pari_share() '.../local/share/pari' """ SAGE_LOCAL = os.environ["SAGE_LOCAL"] @@ -70,7 +70,7 @@ def read_pari_desc(): 'prototype': 'Gp', 'section': 'transcendental'} """ - with open(os.path.join(pari_src(), 'pari.desc')) as f: + with open(os.path.join(pari_share(), 'pari.desc')) as f: lines = f.readlines() n = 0 From 7d1b5f8ca56180ca2d7044453707c619ef17b51a Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Sat, 7 Mar 2015 10:45:27 +0100 Subject: [PATCH 063/129] Upgrade PARI to latest master --- build/pkgs/pari/checksums.ini | 6 +++--- build/pkgs/pari/package-version.txt | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/pkgs/pari/checksums.ini b/build/pkgs/pari/checksums.ini index e1254b553cd..30e411f46d1 100644 --- a/build/pkgs/pari/checksums.ini +++ b/build/pkgs/pari/checksums.ini @@ -1,4 +1,4 @@ tarball=pari-VERSION.tar.gz -sha1=c11c4452abfaf1a8f1498e4d0ae83995d2cc1f6e -md5=7bb2efb14829832627bc81735bc50db5 -cksum=2193237587 +sha1=7617f60af26582d4a77e5af413b26fde32bd4f1a +md5=2ae5684a10d557016fc1b6ad10b8bf80 +cksum=4177496658 diff --git a/build/pkgs/pari/package-version.txt b/build/pkgs/pari/package-version.txt index 3a769906b9d..4846e2756d7 100644 --- a/build/pkgs/pari/package-version.txt +++ b/build/pkgs/pari/package-version.txt @@ -1 +1 @@ -2.8-1315-g031c9d6 +2.8-1369-g0e48e9b From b7a9203340a9ffbba497bb57fdbce39c542d5d71 Mon Sep 17 00:00:00 2001 From: Sergios Lenis Date: Sat, 7 Mar 2015 15:12:19 +0200 Subject: [PATCH 064/129] Correction of dominating_set for DiGraph --- src/sage/graphs/generic_graph.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index ef9cbc7e562..d163d59b4b9 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7989,9 +7989,14 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve # For any vertex v, one of its neighbors or v itself is in # the minimum dominating set - for v in g.vertices(): - p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) - + # for v in g.vertices(): + # p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) + if isinstance(g,sage.graphs.digraph.DiGraph): + for v in g.vertices(): + p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors_in(v)]),min=1) + else: + for v in g.vertices(): + p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) if independent: # no two adjacent vertices are in the set From fe80386f9fcc211d80ae24dad472da5ec5cf1229 Mon Sep 17 00:00:00 2001 From: Sergios Lenis Date: Sat, 7 Mar 2015 15:21:17 +0200 Subject: [PATCH 065/129] Correction of dominating_set for DiGraph --- src/sage/graphs/generic_graph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index d163d59b4b9..3354b5338eb 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7989,8 +7989,8 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve # For any vertex v, one of its neighbors or v itself is in # the minimum dominating set - # for v in g.vertices(): - # p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) + # If Digraph then it's the neighbors_in else neighbors + if isinstance(g,sage.graphs.digraph.DiGraph): for v in g.vertices(): p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors_in(v)]),min=1) From 3ff2fcfc5d6ebf92b553c5222383c2b139df64e6 Mon Sep 17 00:00:00 2001 From: Sergios Lenis Date: Sun, 8 Mar 2015 03:12:35 +0200 Subject: [PATCH 066/129] Add doctest for the ticket --- src/sage/graphs/generic_graph.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 3354b5338eb..2b6ac4b462f 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7978,6 +7978,18 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve sage: G = graphs.PetersenGraph() sage: G.dominating_set(total=True,value_only=True) 4 + + For a directed graph of 3 nodes 1->2->3 has cardinality 2 but + for the undirected graph 1-2-3 1:: + sage: g=DiGraph() + sage: g.add_edges([(1,2),(2,3)]) + sage: len(g.dominating_set()) + 2 + sage: gg=Graph() + sage: gg.add_edges([(1,2),(2,3)]) + sage: len(gg.dominating_set()) + 1 + """ self._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total) From 88e75e80779149429b11e2196ba78e4682edfc54 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Wed, 11 Mar 2015 13:16:05 +0100 Subject: [PATCH 067/129] Small change to docstring --- src/sage/matrix/matrix2.pyx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 45d4182fe52..4cb166c47fe 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -6407,7 +6407,7 @@ cdef class Matrix(matrix1.Matrix): A 3-tuple `(W,N,d)` consisting of: - 1. `W` - a matrix over `k(x)` giving a weak the Popov form of self + 1. `W` - a matrix over `k(x)` giving a row reduced form of `self` 2. `N` - a matrix over `k[x]` representing row operations used to transform `self` to `W` 3. `d` - degree of respective columns of W; the degree of a column is @@ -6524,10 +6524,6 @@ cdef class Matrix(matrix1.Matrix): for row operations; however, references such as [H] transpose and use column operations. - - There are multiple weak Popov forms of a matrix, so one may want to - extend this code to produce a Popov form (see section 1.2 of [V]). The - latter is canonical, but more work to produce. - REFERENCES: .. [H] F. Hess, "Computing Riemann-Roch spaces in algebraic function From c19cc1182669d5534f1371dd42c5ddbcdf7875d7 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Wed, 11 Mar 2015 15:35:37 +0100 Subject: [PATCH 068/129] Changed references in docstring --- src/sage/matrix/matrix2.pyx | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/sage/matrix/matrix2.pyx b/src/sage/matrix/matrix2.pyx index 4cb166c47fe..de42649c0b9 100644 --- a/src/sage/matrix/matrix2.pyx +++ b/src/sage/matrix/matrix2.pyx @@ -6530,12 +6530,8 @@ cdef class Matrix(matrix1.Matrix): fields and related topics," J. Symbolic Comput. 33 (2002), no. 4, 425--445. - .. [MS] T. Mulders, A. Storjohann, "On lattice reduction for polynomial - matrices," J. Symbolic Comput. 35 (2003), no. 4, 377--401 + .. [K] T. Kaliath, "Linear Systems", Prentice-Hall, 1980, 383--386. - .. [V] G. Villard, "Computing Popov and Hermite forms of polynomial - matrices", ISSAC '96: Proceedings of the 1996 international symposium - on Symbolic and algebraic computation, 250--258. """ import sage.matrix.matrix_misc From c33efac6a6846ce26d671cfeccdb621dc2c43784 Mon Sep 17 00:00:00 2001 From: Sergios Lenis Date: Thu, 12 Mar 2015 10:53:22 +0200 Subject: [PATCH 069/129] Change isinstance with is_directed and fix the doctest --- src/sage/graphs/generic_graph.py | 38 ++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 2b6ac4b462f..6a9f65dce95 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7979,17 +7979,16 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve sage: G.dominating_set(total=True,value_only=True) 4 - For a directed graph of 3 nodes 1->2->3 has cardinality 2 but - for the undirected graph 1-2-3 1:: - sage: g=DiGraph() - sage: g.add_edges([(1,2),(2,3)]) - sage: len(g.dominating_set()) + The dominating set is calculated for both the directed and undirected graphs(modification introduced in :trac:`17905`):: + + sage: g=digraphs.Path(3) + sage: g.dominating_set(value_only=True) 2 - sage: gg=Graph() - sage: gg.add_edges([(1,2),(2,3)]) - sage: len(gg.dominating_set()) + sage: g=graphs.PathGraph(3) + sage: g.dominating_set(value_only=True) 1 + """ self._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total) @@ -8000,15 +7999,22 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve b=p.new_variable(binary=True) # For any vertex v, one of its neighbors or v itself is in - # the minimum dominating set - # If Digraph then it's the neighbors_in else neighbors - - if isinstance(g,sage.graphs.digraph.DiGraph): - for v in g.vertices(): - p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors_in(v)]),min=1) + # the minimum dominating set. If g is directed, we use the + # in neighbors of v instead. + + if g.is_directed(): + neighbors_iter=g.neighbor_in_iterator else: - for v in g.vertices(): - p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) + neighbors_iter=g.neighbor_iterator + for v in g.vertices(): + p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in neighbors_iter(v)]),min=1) + + # if isinstance(g,sage.graphs.digraph.DiGraph): + # for v in g.vertices(): + # p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors_in(v)]),min=1) + # else: + # for v in g.vertices(): + # p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) if independent: # no two adjacent vertices are in the set From 7d0c749b1eb9d1b208f53c9b3a99ecaec16ee1bb Mon Sep 17 00:00:00 2001 From: Sergios Lenis Date: Thu, 12 Mar 2015 11:01:52 +0200 Subject: [PATCH 070/129] Change isinstance with is_directed and fix the doctest --- src/sage/graphs/generic_graph.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 6a9f65dce95..003baa070ca 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -8009,13 +8009,6 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve for v in g.vertices(): p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in neighbors_iter(v)]),min=1) - # if isinstance(g,sage.graphs.digraph.DiGraph): - # for v in g.vertices(): - # p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors_in(v)]),min=1) - # else: - # for v in g.vertices(): - # p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in g.neighbors(v)]),min=1) - if independent: # no two adjacent vertices are in the set for (u,v) in g.edges(labels=None): From 5e2de2d29888559935c58a4b48db89e5c3fcba6f Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Thu, 12 Mar 2015 10:21:34 +0100 Subject: [PATCH 071/129] trac #17905: Small simplification --- src/sage/graphs/generic_graph.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 003baa070ca..74f5516278b 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7979,7 +7979,8 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve sage: G.dominating_set(total=True,value_only=True) 4 - The dominating set is calculated for both the directed and undirected graphs(modification introduced in :trac:`17905`):: + The dominating set is calculated for both the directed and undirected + graphs(modification introduced in :trac:`17905`):: sage: g=digraphs.Path(3) sage: g.dominating_set(value_only=True) @@ -7988,9 +7989,7 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve sage: g.dominating_set(value_only=True) 1 - """ - self._scream_if_not_simple(allow_multiple_edges=True, allow_loops=not total) from sage.numerical.mip import MixedIntegerLinearProgram @@ -8002,10 +8001,8 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve # the minimum dominating set. If g is directed, we use the # in neighbors of v instead. - if g.is_directed(): - neighbors_iter=g.neighbor_in_iterator - else: - neighbors_iter=g.neighbor_iterator + neighbors_iter=g.neighbor_in_iterator if g.is_directed() else g.neighbor_iterator + for v in g.vertices(): p.add_constraint(int(not total)*b[v]+p.sum([b[u] for u in neighbors_iter(v)]),min=1) From 0ca9d108e87269365c8659755356d7ae1ca73b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Thu, 12 Mar 2015 11:41:15 +0100 Subject: [PATCH 072/129] trac #17938 common refinement of fans --- src/sage/geometry/fan.py | 59 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 811b16b0698..7e276afc0c3 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -1588,6 +1588,65 @@ def cartesian_product(self, other, lattice=None): except AttributeError: # The result is either incomplete or unknown. return RationalPolyhedralFan(new_cones, rc.rays(), rc.lattice()) + def common_refinement(self, other): + """ + Compute the common refinement of ``self`` and ``other``. + + This is currently only implemented when one of the fans is complete. + + OUTPUT: + + - a fan + + EXAMPLES: + + Refining a fan with itself gives itself:: + + sage: F0 = Fan2d([(1,0),(0,1),(-1,0),(0,-1)]) + sage: F0.common_refinement(F0) == F0 + True + + A more complex example:: + + sage: F1 = Fan([[0],[1]],[(1,),(-1,)]) + sage: F2 = Fan2d([(1,0),(1,1),(0,1),(-1,0),(0,-1)]) + sage: F3 = F2.cartesian_product(F1) + sage: F4 = F1.cartesian_product(F2) + sage: FF = F3.common_refinement(F4); FF + Rational polyhedral fan in 3-d lattice N+N + sage: FF.ngenerating_cones() + 13 + + TESTS: + + This does not work when both fans are incomplete:: + + sage: F5 = Fan([[0,1],[1,2],[2,3]],[(1,0),(0,1),(-1,0),(0,-1)]) + sage: F5.common_refinement(F5) + Traceback (most recent call last): + ... + ValueError: only implemented for complete fans + + Both fans must live in the same lattice:: + + sage: F0.common_refinement(F1) + Traceback (most recent call last): + ... + ValueError: the fans are not in the same lattice + """ + from sage.categories.homset import End + from sage.geometry.fan_morphism import FanMorphism + latt = self.lattice() + if other.lattice() != latt: + raise ValueError('the fans are not in the same lattice') + if not(self.is_complete()): + if other.is_complete(): + self, other = other, self + else: + raise ValueError('only implemented for complete fans') + id = FanMorphism(End(latt).identity(), self, other, subdivide=True) + return id.domain_fan() + def _latex_(self): r""" Return a LaTeX representation of ``self``. From c7b5c2461f16bc76ff8de8e0738d3d391c13c2aa Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Thu, 12 Mar 2015 12:45:56 +0100 Subject: [PATCH 073/129] trac #17905: Typo --- src/sage/graphs/generic_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/graphs/generic_graph.py b/src/sage/graphs/generic_graph.py index 74f5516278b..63158f74ff5 100644 --- a/src/sage/graphs/generic_graph.py +++ b/src/sage/graphs/generic_graph.py @@ -7980,7 +7980,7 @@ def dominating_set(self, independent=False, total=False, value_only=False, solve 4 The dominating set is calculated for both the directed and undirected - graphs(modification introduced in :trac:`17905`):: + graphs (modification introduced in :trac:`17905`):: sage: g=digraphs.Path(3) sage: g.dominating_set(value_only=True) From d3a0ed67bb4725020baf72c1a40c59cfadb742f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Fri, 13 Mar 2015 22:58:03 +1300 Subject: [PATCH 074/129] Use IPython's own call to find ipython's configuration folder rather than importing IPYTHONDIR directly with os.environ --- src/sage/repl/interpreter.py | 3 ++- src/sage/repl/ipython_kernel/install.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/sage/repl/interpreter.py b/src/sage/repl/interpreter.py index 5c0b98025e3..f86818a4c6b 100644 --- a/src/sage/repl/interpreter.py +++ b/src/sage/repl/interpreter.py @@ -755,7 +755,8 @@ def load_config_file(self, *args, **kwds): sage: from sage.misc.temporary_file import tmp_dir sage: from sage.repl.interpreter import SageTerminalApp sage: d = tmp_dir() - sage: IPYTHONDIR = os.environ['IPYTHONDIR'] + sage: from IPython.utils.path import get_ipython_dir + sage: IPYTHONDIR = get_ipython_dir() sage: os.environ['IPYTHONDIR'] = d sage: SageTerminalApp().load_config_file() sage: os.environ['IPYTHONDIR'] = IPYTHONDIR diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 60afba207d6..3ec03cf6dfb 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -12,7 +12,7 @@ from IPython.kernel.kernelspec import ( get_kernel_spec, install_kernel_spec, NoSuchKernel) - +from IPython.utils.path import get_ipython_dir from sage.env import ( SAGE_ROOT, SAGE_DOC, SAGE_LOCAL, SAGE_EXTCODE, @@ -35,7 +35,7 @@ def __init__(self): 'Sage 6.6.beta2' """ self._display_name = 'Sage {0}'.format(SAGE_VERSION) - self._ipython_dir = os.environ['IPYTHONDIR'] + self._ipython_dir = get_ipython_dir() self._mkdirs() def _mkdirs(self): @@ -115,7 +115,7 @@ def use_local_mathjax(self): sage: from sage.repl.ipython_kernel.install import SageKernelSpec sage: spec = SageKernelSpec() sage: spec.use_local_mathjax() - sage: ipython_dir = os.environ['IPYTHONDIR'] + sage: ipython_dir = get_ipython_dir() sage: mathjax = os.path.join(ipython_dir, 'nbextensions', 'mathjax') sage: os.path.exists(mathjax) True From 278c94a66f826eb9dfbf5399441fcfda6f883641 Mon Sep 17 00:00:00 2001 From: Eric Gourgoulhon Date: Fri, 13 Mar 2015 11:05:57 +0100 Subject: [PATCH 075/129] Minor changes to coincide with version 0.7 of SageManifolds src/sage/tensor/modules/comp.py: - zero_element() --> zero() src/sage/tensor/modules/format_utilities.py: - improve documentation class FormattedExpansion: - suppress the attribute "tensor" (it was actually useless) - rename attribute "txt" to "_txt" - rename attribute "latex" to "_latex" class FiniteRankFreeModule: - method identity_map(): add arguments name and latex_name class FreeModuleHomset: - add method one() class FreeModuleTensor: - suppress methods __radd__() and __rsub__() : they were introduced to write 0 + t and 0 - t, where t is a tensor, before the coercion of 0 to a tensor was implemented. src/doc/en/reference/tensor_free_modules/index.rst: Added html target ".. _tensors-on-free-modules" --- .../reference/tensor_free_modules/index.rst | 2 + src/sage/tensor/modules/comp.py | 18 ++--- .../tensor/modules/finite_rank_free_module.py | 41 +++++++++- src/sage/tensor/modules/format_utilities.py | 59 ++++++++------ .../tensor/modules/free_module_alt_form.py | 14 ++-- .../modules/free_module_automorphism.py | 7 +- src/sage/tensor/modules/free_module_homset.py | 68 +++++++++++++++++ .../modules/free_module_linear_group.py | 1 - src/sage/tensor/modules/free_module_tensor.py | 76 ++----------------- 9 files changed, 170 insertions(+), 116 deletions(-) diff --git a/src/doc/en/reference/tensor_free_modules/index.rst b/src/doc/en/reference/tensor_free_modules/index.rst index c5bdcf06abe..c8f0b58b988 100644 --- a/src/doc/en/reference/tensor_free_modules/index.rst +++ b/src/doc/en/reference/tensor_free_modules/index.rst @@ -1,3 +1,5 @@ +.. _tensors-on-free-modules: + Tensors on free modules of finite rank ====================================== diff --git a/src/sage/tensor/modules/comp.py b/src/sage/tensor/modules/comp.py index 8ed735ef9d0..ef44f8a7145 100644 --- a/src/sage/tensor/modules/comp.py +++ b/src/sage/tensor/modules/comp.py @@ -737,11 +737,11 @@ def __getitem__(self, args): return self._output_formatter(self._comp[ind], format_type) else: # if the value is not stored in self._comp, it is zero: if no_format: - return self._ring.zero_element() + return self._ring.zero() elif format_type is None: - return self._output_formatter(self._ring.zero_element()) + return self._output_formatter(self._ring.zero()) else: - return self._output_formatter(self._ring.zero_element(), + return self._output_formatter(self._ring.zero(), format_type) def _get_list(self, ind_slice, no_format=True, format_type=None): @@ -2696,11 +2696,11 @@ def __getitem__(self, args): sign, ind = self._ordered_indices(indices) if (sign == 0) or (ind not in self._comp): # the value is zero: if no_format: - return self._ring.zero_element() + return self._ring.zero() elif format_type is None: - return self._output_formatter(self._ring.zero_element()) + return self._output_formatter(self._ring.zero()) else: - return self._output_formatter(self._ring.zero_element(), + return self._output_formatter(self._ring.zero(), format_type) else: # non zero value if no_format: @@ -4164,11 +4164,11 @@ def __getitem__(self, args): # the value is zero if no_format: - return self._ring.zero_element() + return self._ring.zero() elif format_type is None: - return self._output_formatter(self._ring.zero_element()) + return self._output_formatter(self._ring.zero()) else: - return self._output_formatter(self._ring.zero_element(), + return self._output_formatter(self._ring.zero(), format_type) def __setitem__(self, args, value): diff --git a/src/sage/tensor/modules/finite_rank_free_module.py b/src/sage/tensor/modules/finite_rank_free_module.py index ad1898459b7..c3735a04a9f 100644 --- a/src/sage/tensor/modules/finite_rank_free_module.py +++ b/src/sage/tensor/modules/finite_rank_free_module.py @@ -782,7 +782,7 @@ def __init__(self, ring, rank, name=None, latex_name=None, start_index=0, if not hasattr(self, '_zero_element'): self._zero_element = self._element_constructor_(name='zero', latex_name='0') - # Identity endomorphism: + # Identity automorphism: self._identity_map = None # to be set by self.identity_map() # General linear group: self._general_linear_group = None # to be set by @@ -2380,10 +2380,18 @@ def endomorphism(self, matrix_rep, basis=None, name=None, latex_name=None): return End(self)(matrix_rep, bases=(basis,basis), name=name, latex_name=latex_name) - def identity_map(self): + def identity_map(self, name='Id', latex_name=None): r""" Return the identity map of the free module ``self``. + INPUT: + + - ``name`` -- (string; default: 'Id') name given to the identity + identity map + - ``latex_name`` -- (string; default: ``None``) LaTeX symbol to denote + the identity map; if none is provided, the LaTeX symbol is set to + '\mathrm{Id}' if ``name`` is 'Id' and to ``name`` otherwise + OUTPUT: - the identity map of ``self`` as an instance of @@ -2399,13 +2407,23 @@ def identity_map(self): Identity map of the Rank-3 free module M over the Integer Ring sage: Id.parent() General linear group of the Rank-3 free module M over the Integer Ring - sage: latex(Id) - \mathrm{Id} sage: Id.matrix(e) [1 0 0] [0 1 0] [0 0 1] + The default LaTeX symbol:: + + sage: latex(Id) + \mathrm{Id} + + It can be changed by means of the method + :meth:`~sage.tensor.modules.free_module_tensor.FreeModuleTensor.set_name`:: + + sage: Id.set_name(latex_name=r'\mathrm{1}_M') + sage: latex(Id) + \mathrm{1}_M + The identity map is actually the identity element of GL(M):: sage: Id is M.general_linear_group().one() @@ -2422,7 +2440,22 @@ def identity_map(self): [0 1 0] [0 0 1] + Example with a LaTeX symbol different from the default one and set + at the creation of the object:: + + sage: N = FiniteRankFreeModule(ZZ, 3, name='N') + sage: f = N.basis('f') + sage: Id = N.identity_map(name='Id_N', latex_name=r'\mathrm{Id}_N') + sage: Id + Identity map of the Rank-3 free module N over the Integer Ring + sage: latex(Id) + \mathrm{Id}_N + """ if self._identity_map is None: self._identity_map = self.general_linear_group().one() + if name != 'Id': + if latex_name is None: + latex_name = name + self._identity_map.set_name(name=name, latex_name=latex_name) return self._identity_map diff --git a/src/sage/tensor/modules/format_utilities.py b/src/sage/tensor/modules/format_utilities.py index 597761ee9ae..b634a0636f5 100644 --- a/src/sage/tensor/modules/format_utilities.py +++ b/src/sage/tensor/modules/format_utilities.py @@ -26,8 +26,11 @@ def is_atomic(expression): r""" Helper function to check whether some LaTeX expression is atomic. - Adapted from function :meth:`DifferentialFormFormatter._is_atomic` - written by Joris Vankerschaver (2010) + Adapted from method + :meth:`~sage.tensor.differential_form_element.DifferentialFormFormatter._is_atomic` + of class + :class:`~sage.tensor.differential_form_element.DifferentialFormFormatter` + written by Joris Vankerschaver (2010). INPUT: @@ -68,8 +71,11 @@ def is_atomic_wedge_txt(expression): Helper function to check whether some text-formatted expression is atomic in terms of wedge products. - Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written - by Joris Vankerschaver (2010) + Adapted from method + :meth:`~sage.tensor.differential_form_element.DifferentialFormFormatter._is_atomic` + of class + :class:`~sage.tensor.differential_form_element.DifferentialFormFormatter` + written by Joris Vankerschaver (2010). INPUT: @@ -114,8 +120,11 @@ def is_atomic_wedge_latex(expression): Helper function to check whether LaTeX-formatted expression is atomic in terms of wedge products. - Adapted from function :meth:`DifferentialFormFormatter._is_atomic` written - by Joris Vankerschaver (2010) + Adapted from method + :meth:`~sage.tensor.differential_form_element.DifferentialFormFormatter._is_atomic` + of class + :class:`~sage.tensor.differential_form_element.DifferentialFormFormatter` + written by Joris Vankerschaver (2010). INPUT: @@ -276,24 +285,30 @@ class FormattedExpansion(SageObject): EXAMPLES:: sage: from sage.tensor.modules.format_utilities import FormattedExpansion - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: v = M.an_element() - sage: f = FormattedExpansion(v, 'v', r'\tilde v') + sage: f = FormattedExpansion('v', r'\tilde v') sage: f v + sage: latex(f) + \tilde v + sage: f = FormattedExpansion('x/2', r'\frac{x}{2}') + sage: f + x/2 + sage: latex(f) + \frac{x}{2} + """ - def __init__(self, tensor, txt=None, latex=None): + def __init__(self, txt=None, latex=None): r""" TESTS:: sage: from sage.tensor.modules.format_utilities import FormattedExpansion - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: v = M.an_element() - sage: f = FormattedExpansion(v, 'v', r'\tilde v') + sage: f = FormattedExpansion('v', r'\tilde v') + sage: f + v + """ - self.tensor = tensor - self.txt = txt - self.latex = latex + self._txt = txt + self._latex = latex def _repr_(self): r""" @@ -302,14 +317,12 @@ def _repr_(self): EXAMPLES:: sage: from sage.tensor.modules.format_utilities import FormattedExpansion - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: v = M.an_element() - sage: f = FormattedExpansion(v, 'v', r'\tilde v') + sage: f = FormattedExpansion('v', r'\tilde v') sage: f._repr_() 'v' """ - return self.txt + return self._txt def _latex_(self): r""" @@ -318,11 +331,9 @@ def _latex_(self): EXAMPLE:: sage: from sage.tensor.modules.format_utilities import FormattedExpansion - sage: M = FiniteRankFreeModule(ZZ, 3, name='M') - sage: v = M.an_element() - sage: f = FormattedExpansion(v, 'v', r'\tilde v') + sage: f = FormattedExpansion('v', r'\tilde v') sage: f._latex_() '\\tilde v' """ - return self.latex + return self._latex diff --git a/src/sage/tensor/modules/free_module_alt_form.py b/src/sage/tensor/modules/free_module_alt_form.py index f68697cd06b..12b602bc0a6 100644 --- a/src/sage/tensor/modules/free_module_alt_form.py +++ b/src/sage/tensor/modules/free_module_alt_form.py @@ -482,7 +482,8 @@ def display(self, basis=None, format_spec=None): """ from sage.misc.latex import latex - from format_utilities import is_atomic, FormattedExpansion + from sage.tensor.modules.format_utilities import is_atomic, \ + FormattedExpansion if basis is None: basis = self._fmodule._def_basis cobasis = basis.dual_basis() @@ -537,16 +538,15 @@ def display(self, basis=None, format_spec=None): expansion_latex += term else: expansion_latex += "+" + term - result = FormattedExpansion(self) if self._name is None: - result.txt = expansion_txt + resu_txt = expansion_txt else: - result.txt = self._name + " = " + expansion_txt + resu_txt = self._name + " = " + expansion_txt if self._latex_name is None: - result.latex = expansion_latex + resu_latex = expansion_latex else: - result.latex = latex(self) + " = " + expansion_latex - return result + resu_latex = latex(self) + " = " + expansion_latex + return FormattedExpansion(resu_txt, resu_latex) disp = display diff --git a/src/sage/tensor/modules/free_module_automorphism.py b/src/sage/tensor/modules/free_module_automorphism.py index e7ccb8c519a..ca2ae787d10 100644 --- a/src/sage/tensor/modules/free_module_automorphism.py +++ b/src/sage/tensor/modules/free_module_automorphism.py @@ -289,8 +289,11 @@ def __init__(self, fmodule, name=None, latex_name=None, is_identity=False): if is_identity: if name is None: name = 'Id' - if latex_name is None and name == 'Id': - latex_name = r'\mathrm{Id}' + if latex_name is None: + if name == 'Id': + latex_name = r'\mathrm{Id}' + else: + latex_name = name FreeModuleTensor.__init__(self, fmodule, (1,1), name=name, latex_name=latex_name, parent=fmodule.general_linear_group()) diff --git a/src/sage/tensor/modules/free_module_homset.py b/src/sage/tensor/modules/free_module_homset.py index 889056e9658..d9b45200761 100644 --- a/src/sage/tensor/modules/free_module_homset.py +++ b/src/sage/tensor/modules/free_module_homset.py @@ -232,6 +232,8 @@ def __init__(self, fmodule1, fmodule2, name=None, latex_name=None): fmodule2._latex_name + r"\right)" else: self._latex_name = latex_name + self._one = None # to be set by self.one() if self is an endomorphism + # set (fmodule1 = fmodule2) def _latex_(self): r""" @@ -463,3 +465,69 @@ def _coerce_map_from_(self, other): return False #### End of methods required for any Parent + + + #### Monoid methods (case of an endomorphism set) #### + + def one(self): + r""" + Return the identity element of ``self`` considered as a monoid (case of + an endomorphism set). + + This applies only when the codomain of ``self`` is equal to its domain, + i.e. when ``self`` is of the type `\mathrm{Hom}(M,M)`. + + OUTPUT: + + - the identity element of `\mathrm{End}(M) = \mathrm{Hom}(M,M)`, as an + instance of + :class:`~sage.tensor.modules.free_module_morphism.FiniteRankFreeModuleMorphism` + + EXAMPLE: + + Identity element of the set of endomorphisms of a free module + over `\ZZ`:: + + sage: M = FiniteRankFreeModule(ZZ, 3, name='M') + sage: e = M.basis('e') + sage: H = End(M) + sage: H.one() + Identity endomorphism of Rank-3 free module M over the Integer Ring + sage: H.one().matrix(e) + [1 0 0] + [0 1 0] + [0 0 1] + sage: H.one().is_identity() + True + + NB: mathematically, ``H.one()`` coincides with the identity map of the + free module `M`. However the latter is considered here as an + element of `\mathrm{GL}(M)`, the general linear group of `M`. + Accordingly, one has to use the coercion map + `\mathrm{GL}(M) \rightarrow \mathrm{End}(M)` + to recover ``H.one()`` from ``M.identity_map()``:: + + sage: M.identity_map() + Identity map of the Rank-3 free module M over the Integer Ring + sage: M.identity_map().parent() + General linear group of the Rank-3 free module M over the Integer Ring + sage: H.one().parent() + Set of Morphisms from Rank-3 free module M over the Integer Ring to Rank-3 free module M over the Integer Ring in Category of modules over Integer Ring + sage: H.one() == H(M.identity_map()) + True + + Conversely, one can recover ``M.identity_map()`` from ``H.one()`` by + means of a conversion `\mathrm{End}(M)\rightarrow \mathrm{GL}(M)`:: + + sage: GL = M.general_linear_group() + sage: M.identity_map() == GL(H.one()) + True + + """ + if self._one is None: + if self.codomain() != self.domain(): + raise TypeError("the {} is not a monoid".format(self)) + self._one = self.element_class(self, [], is_identity=True) + return self._one + + #### End of monoid methods #### diff --git a/src/sage/tensor/modules/free_module_linear_group.py b/src/sage/tensor/modules/free_module_linear_group.py index 49297d09bfc..b4ebc96348d 100644 --- a/src/sage/tensor/modules/free_module_linear_group.py +++ b/src/sage/tensor/modules/free_module_linear_group.py @@ -35,7 +35,6 @@ from sage.tensor.modules.finite_rank_free_module import FiniteRankFreeModule from sage.tensor.modules.free_module_automorphism import FreeModuleAutomorphism - class FreeModuleLinearGroup(UniqueRepresentation, Parent): r""" General linear group of a free module of finite rank over a commutative diff --git a/src/sage/tensor/modules/free_module_tensor.py b/src/sage/tensor/modules/free_module_tensor.py index d5484779a34..96ca6775366 100644 --- a/src/sage/tensor/modules/free_module_tensor.py +++ b/src/sage/tensor/modules/free_module_tensor.py @@ -638,7 +638,8 @@ def display(self, basis=None, format_spec=None): """ from sage.misc.latex import latex - from format_utilities import is_atomic, FormattedExpansion + from sage.tensor.modules.format_utilities import is_atomic, \ + FormattedExpansion if basis is None: basis = self._fmodule._def_basis cobasis = basis.dual_basis() @@ -697,16 +698,15 @@ def display(self, basis=None, format_spec=None): expansion_latex += term else: expansion_latex += "+" + term - result = FormattedExpansion(self) if self._name is None: - result.txt = expansion_txt + resu_txt = expansion_txt else: - result.txt = self._name + " = " + expansion_txt + resu_txt = self._name + " = " + expansion_txt if self._latex_name is None: - result.latex = expansion_latex + resu_latex = expansion_latex else: - result.latex = latex(self) + " = " + expansion_latex - return result + resu_latex = latex(self) + " = " + expansion_latex + return FormattedExpansion(resu_txt, resu_latex) disp = display @@ -1767,68 +1767,6 @@ def _rmul_(self, other): ######### End of ModuleElement arithmetic operators ######## - def __radd__(self, other): - r""" - Addition on the left with ``other``. - - This allows to write "0 + t", where "t" is a tensor - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 2, name='M') - sage: e = M.basis('e') - sage: a = M.tensor((2,0), name='a') - sage: a[:] = [[4,0], [-2,5]] - sage: b = M.tensor((2,0), name='b') - sage: b[:] = [[0,1], [2,3]] - sage: s = a.__radd__(b) ; s - Type-(2,0) tensor a+b on the Rank-2 free module M over the Integer Ring - sage: s[:] - [4 1] - [0 8] - sage: s == a+b - True - sage: s = a.__radd__(0) ; s - Type-(2,0) tensor +a on the Rank-2 free module M over the Integer Ring - sage: s == a - True - sage: 0 + a == a - True - - """ - return self.__add__(other) - - def __rsub__(self, other): - r""" - Subtraction from ``other``. - - This allows to write ``0 - t``, where ``t`` is a tensor. - - EXAMPLES:: - - sage: M = FiniteRankFreeModule(ZZ, 2, name='M') - sage: e = M.basis('e') - sage: a = M.tensor((2,0), name='a') - sage: a[:] = [[4,0], [-2,5]] - sage: b = M.tensor((2,0), name='b') - sage: b[:] = [[0,1], [2,3]] - sage: s = a.__rsub__(b) ; s - Type-(2,0) tensor -a+b on the Rank-2 free module M over the Integer Ring - sage: s[:] - [-4 1] - [ 4 -2] - sage: s == b - a - True - sage: s = a.__rsub__(0) ; s - Type-(2,0) tensor +-a on the Rank-2 free module M over the Integer Ring - sage: s == -a - True - sage: 0 - a == -a - True - - """ - return (-self).__add__(other) - def __mul__(self, other): r""" Tensor product. From 64c8ab704827519379236fbe19dcc657445c8f31 Mon Sep 17 00:00:00 2001 From: Darij Grinberg Date: Fri, 13 Mar 2015 17:37:29 +0100 Subject: [PATCH 076/129] review changes and add analogous change to number_of_unordered_tuples --- src/sage/combinat/combinat.py | 141 +++++++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 29 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 6c9576bc518..af3d2d182ce 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -548,13 +548,13 @@ def fibonacci(n, algorithm="pari"): else: raise ValueError("no algorithm {}".format(algorithm)) -def lucas_number1(n,P,Q): +def lucas_number1(n, P, Q): """ Return the `n`-th Lucas number "of the first kind" (this is not standard terminology). The Lucas sequence `L^{(1)}_n` is - defined by the initial conditions `L^{(1)}_1=0`, - `L^{(1)}_2=1` and the recurrence relation - `L^{(1)}_{n+2} = P*L^{(1)}_{n+1} - Q*L^{(1)}_n`. + defined by the initial conditions `L^{(1)}_1 = 0`, + `L^{(1)}_2 = 1` and the recurrence relation + `L^{(1)}_{n+2} = P \cdot L^{(1)}_{n+1} - Q \cdot L^{(1)}_n`. Wraps GAP's ``Lucas(...)[1]``. @@ -612,13 +612,13 @@ def lucas_number1(n,P,Q): from sage.libs.gap.libgap import libgap return libgap.Lucas(P, Q, n)[0].sage() -def lucas_number2(n,P,Q): +def lucas_number2(n, P, Q): r""" Return the `n`-th Lucas number "of the second kind" (this is not standard terminology). The Lucas sequence `L^{(2)}_n` is - defined by the initial conditions `L^{(2)}_1=2`, - `L^{(2)}_2=P` and the recurrence relation - `L^{(2)}_{n+2} = P*L^{(2)}_{n+1} - Q*L^{(2)}_n`. + defined by the initial conditions `L^{(2)}_1 = 2`, + `L^{(2)}_2 = P` and the recurrence relation + `L^{(2)}_{n+2} = P \cdot L^{(2)}_{n+1} - Q \cdot L^{(2)}_n`. Wraps GAP's Lucas(...)[2]. @@ -934,7 +934,6 @@ def __eq__(self, other): sage: c == d False """ - if isinstance(other, CombinatorialObject): return self._list.__eq__(other._list) else: @@ -951,7 +950,6 @@ def __lt__(self, other): sage: c < [2,3,4] True """ - if isinstance(other, CombinatorialObject): return self._list.__lt__(other._list) else: @@ -2265,22 +2263,42 @@ def tuples(S, k, algorithm='itertools'): import itertools return list(itertools.product(S, repeat=k)) if algorithm == 'native': - if k <= 0: - return [()] - if k == 1: - return [(x,) for x in S] - ans = [] - for s in S: - for x in tuples(S, k-1, 'native'): - y = list(x) - y.append(s) - ans.append(tuple(y)) - return ans + return _tuples_native(S, k) raise ValueError('invalid algorithm') +def _tuples_native(S, k): + """ + Return a list of all `k`-tuples of elements of a given set ``S``. + + This is a helper method used in :meth:`tuples`. It returns the + same as ``tuples(S, k, algorithm="native")``. + + EXAMPLES:: + + sage: S = [1,2,2] + sage: from sage.combinat.combinat import _tuples_native + sage: _tuples_native(S,2) + [(1, 1), (2, 1), (2, 1), (1, 2), (2, 2), (2, 2), + (1, 2), (2, 2), (2, 2)] + """ + if k <= 0: + return [()] + if k == 1: + return [(x,) for x in S] + ans = [] + for s in S: + for x in _tuples_native(S, k-1): + y = list(x) + y.append(s) + ans.append(tuple(y)) + return ans + def number_of_tuples(S, k, algorithm='naive'): """ - Return the size of ``tuples(S,k)``. Wraps GAP's ``NrTuples``. + Return the size of ``tuples(S, k)`` when `S` is a set. More + generally, return the size of ``tuples(set(S), k)``. (So, + unlike :meth:`tuples`, this method removes redundant entries from + `S`.) INPUT: @@ -2291,14 +2309,34 @@ def number_of_tuples(S, k, algorithm='naive'): * ``'naive'`` - (default) use the naive counting `|S|^k` * ``'gap'`` - wraps GAP's ``NrTuples`` + .. WARNING:: + + When using ``algorithm='gap'``, ``S`` must be a list of objects + that have string representations that can be interpreted by the GAP + interpreter. If ``S`` consists of at all complicated Sage + objects, this function might *not* do what you expect. + + .. TODO:: + + Is ``algorithm='gap'`` of any use? I can't imagine it beating the + native interpretation... + EXAMPLES:: sage: S = [1,2,3,4,5] sage: number_of_tuples(S,2) 25 + sage: number_of_tuples(S,2, algorithm="gap") + 25 sage: S = [1,1,2,3,4,5] sage: number_of_tuples(S,2) 25 + sage: number_of_tuples(S,2, algorithm="gap") + 25 + sage: number_of_tuples(S,0) + 1 + sage: number_of_tuples(S,0, algorithm="gap") + 1 """ if algorithm == 'naive': return ZZ( len(set(S)) )**k # The set is there to avoid duplicates @@ -2311,12 +2349,19 @@ def number_of_tuples(S, k, algorithm='naive'): def unordered_tuples(S, k, algorithm='itertools'): """ - Return the set of all unordered tuples of length ``k`` of the set ``S``. + Return a list of all unordered tuples of length ``k`` of the set ``S``. An unordered tuple of length `k` of set `S` is a unordered selection with repetitions of `S` and is represented by a sorted list of length `k` containing elements from `S`. + Unlike :meth:`tuples`, the result of this method does not depend on + how often an element appears in `S`; only the *set* `S` is being + used. For example, ``unordered_tuples([1, 1, 1], 2)`` will return + ``[(1, 1)]``. If you want it to return + ``[(1, 1), (1, 1), (1, 1)]``, use Python's + ``itertools.combinations_with_replacement`` instead. + INPUT: - ``S`` -- the base set @@ -2357,6 +2402,8 @@ def unordered_tuples(S, k, algorithm='itertools'): sage: S = [1,1,2] sage: unordered_tuples(S, 3) == unordered_tuples(S, 3, 'gap') True + sage: unordered_tuples(S, 3) + [(1, 1, 1), (1, 1, 2), (1, 2, 2), (2, 2, 2)] """ if algorithm == 'itertools': import itertools @@ -2368,20 +2415,56 @@ def unordered_tuples(S, k, algorithm='itertools'): return [tuple(x) for x in libgap.UnorderedTuples(S, k).sage()] raise ValueError('invalid algorithm') -def number_of_unordered_tuples(S,k): +def number_of_unordered_tuples(S, k, algorithm='naive'): """ - Return the size of ``unordered_tuples(S,k)``. Wraps GAP's - ``NrUnorderedTuples``. + Return the size of ``unordered_tuples(S, k)`` when `S` is a set. + + INPUT: + + - ``S`` -- the base set + - ``k`` -- the length of the tuples + - ``algorithm`` -- can be one of the following: + + * ``'naive'`` - (default) use the naive counting `\binom{|S|+k-1}{k}` + * ``'gap'`` - wraps GAP's ``NrUnorderedTuples`` + + .. WARNING:: + + When using ``algorithm='gap'``, ``S`` must be a list of objects + that have string representations that can be interpreted by the GAP + interpreter. If ``S`` consists of at all complicated Sage + objects, this function might *not* do what you expect. + + .. TODO:: + + Is ``algorithm='gap'`` of any use? I can't imagine it beating the + native interpretation... EXAMPLES:: sage: S = [1,2,3,4,5] sage: number_of_unordered_tuples(S,2) 15 + sage: number_of_unordered_tuples(S,2, algorithm="gap") + 15 + sage: S = [1,1,2,3,4,5] + sage: number_of_unordered_tuples(S,2) + 15 + sage: number_of_unordered_tuples(S,2, algorithm="gap") + 15 + sage: number_of_unordered_tuples(S,0) + 1 + sage: number_of_unordered_tuples(S,0, algorithm="gap") + 1 """ - from sage.libs.gap.libgap import libgap - S = libgap.eval(str(S)) - return libgap.NrUnorderedTuples(S, k).sage() + if algorithm == 'naive': + return ZZ( len(set(S)) + k - 1 ).binomial(k) # The set is there to avoid duplicates + if algorithm == 'gap': + k = ZZ(k) + from sage.libs.gap.libgap import libgap + S = libgap.eval(str(S)) + return libgap.NrUnorderedTuples(S, k).sage() + raise ValueError('invalid algorithm') def unshuffle_iterator(a, one=1): r""" From 79cb89296fce3f90b9e59cab797e7c7de57443fc Mon Sep 17 00:00:00 2001 From: Darij Grinberg Date: Fri, 13 Mar 2015 17:40:16 +0100 Subject: [PATCH 077/129] let's not scare people with symbolics --- src/sage/combinat/combinat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index af3d2d182ce..2feb8c2ac60 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2671,7 +2671,7 @@ def bell_polynomial(n, k): OUTPUT: - - polynomial expression (SymbolicArithmetic) + - a polynomial in `n-k+1` variables over `\QQ` EXAMPLES:: From cb3f90b92685b1d08d7200a380c4479c260d5044 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Tue, 10 Mar 2015 08:28:08 +0100 Subject: [PATCH 078/129] Move interrupt setup to src/sage/ext/interrupt.pyx --- src/c_lib/include/interrupt.h | 7 ++--- src/sage/all.py | 7 ++--- src/sage/doctest/control.py | 4 +-- src/sage/ext/interrupt.pxd | 28 +++++++++++++++++++ src/sage/ext/interrupt.pxi | 28 +------------------ src/sage/ext/{c_lib.pyx => interrupt.pyx} | 33 ++++++++++++----------- src/sage/matrix/benchmark.py | 2 +- src/sage/misc/cython.py | 5 ++-- src/sage/misc/sageinspect.py | 2 +- src/sage/parallel/use_fork.py | 2 +- 10 files changed, 57 insertions(+), 61 deletions(-) create mode 100644 src/sage/ext/interrupt.pxd rename src/sage/ext/{c_lib.pyx => interrupt.pyx} (87%) diff --git a/src/c_lib/include/interrupt.h b/src/c_lib/include/interrupt.h index d04cbe1e935..edacc0aa97a 100644 --- a/src/c_lib/include/interrupt.h +++ b/src/c_lib/include/interrupt.h @@ -18,8 +18,8 @@ redirects stdin from /dev/null, to cause interactive sessions to exit. These are critical because they cannot be ignored. If they happen outside of sig_on(), we can only exit Sage with the dreaded "unhandled SIG..." message. Inside of sig_on(), they can be handled -and raise various exceptions (see sage/ext/c_lib.pyx). SIGQUIT will -never be handled and always causes Sage to exit. +and raise various exceptions (see sage/ext/interrupt.pyx). SIGQUIT +will never be handled and always causes Sage to exit. AUTHORS: @@ -108,9 +108,6 @@ void sage_signal_handler(int sig); /* * Setup the signal handlers. It is safe to call this more than once. - * - * We do not handle SIGALRM since there is code to deal with - * alarms in sage/misc/misc.py */ void setup_sage_signal_handler(void); diff --git a/src/sage/all.py b/src/sage/all.py index ca886cb9e08..d2494e0e262 100644 --- a/src/sage/all.py +++ b/src/sage/all.py @@ -75,14 +75,11 @@ ################################################################### -import sage.ext.c_lib -sage.ext.c_lib._init_csage() -sig_on_count = sage.ext.c_lib._sig_on_reset +# This import also setups the interrupt handler +from sage.ext.interrupt import AlarmInterrupt, SignalError, sig_on_reset as sig_on_count from time import sleep -from sage.ext.c_lib import AlarmInterrupt, SignalError - import sage.misc.lazy_import from sage.misc.all import * # takes a while from sage.repl.all import * diff --git a/src/sage/doctest/control.py b/src/sage/doctest/control.py index ed1dd5c8136..2fd11b3b3c9 100644 --- a/src/sage/doctest/control.py +++ b/src/sage/doctest/control.py @@ -23,7 +23,7 @@ import sage.misc.flatten from sage.structure.sage_object import SageObject from sage.env import DOT_SAGE, SAGE_LIB, SAGE_SRC -from sage.ext.c_lib import AlarmInterrupt, _init_csage +from sage.ext.interrupt import AlarmInterrupt, init_interrupts from sources import FileDocTestSource, DictAsObject from forker import DocTestDispatcher @@ -911,7 +911,7 @@ def run_val_gdb(self, testing=False): return # Setup Sage signal handler - _init_csage() + init_interrupts() import signal, subprocess p = subprocess.Popen(cmd, shell=True) diff --git a/src/sage/ext/interrupt.pxd b/src/sage/ext/interrupt.pxd new file mode 100644 index 00000000000..2f5465f86bc --- /dev/null +++ b/src/sage/ext/interrupt.pxd @@ -0,0 +1,28 @@ +# +# See c_lib/include/interrupt.h +# +cdef extern from 'interrupt.h': + int sig_on() nogil except 0 + int sig_str(char*) nogil except 0 + int sig_check() nogil except 0 + int sig_on_no_except() nogil + int sig_str_no_except(char*) nogil + int sig_check_no_except() nogil + void sig_off() nogil + void sig_retry() nogil # Does not return + void sig_error() nogil # Does not return + void sig_block() nogil + void sig_unblock() nogil + void setup_sage_signal_handler() nogil + void set_sage_signal_handler_message(char* s) nogil + void cython_check_exception() nogil except * + + ctypedef struct sage_signals_t: + int sig_on_count + int interrupt_received + int inside_signal_handler + int block_sigint + char* s + int (*raise_exception)(int sig, const char* msg) except 0 + + sage_signals_t _signals diff --git a/src/sage/ext/interrupt.pxi b/src/sage/ext/interrupt.pxi index 8997e777741..3e9d3829d79 100644 --- a/src/sage/ext/interrupt.pxi +++ b/src/sage/ext/interrupt.pxi @@ -1,27 +1 @@ -# -# See c_lib/include/interrupt.h -# -cdef extern from 'interrupt.h': - int sig_on() nogil except 0 - int sig_str(char*) nogil except 0 - int sig_check() nogil except 0 - int sig_on_no_except() nogil - int sig_str_no_except(char*) nogil - int sig_check_no_except() nogil - void sig_off() nogil - void sig_retry() nogil # Does not return - void sig_error() nogil # Does not return - void sig_block() nogil - void sig_unblock() nogil - void set_sage_signal_handler_message(char* s) nogil - void cython_check_exception() nogil except * - - ctypedef struct sage_signals_t: - int sig_on_count - int interrupt_received - int inside_signal_handler - int block_sigint - char* s - int (*raise_exception)(int sig, const char* msg) except 0 - - sage_signals_t _signals +from sage.ext.interrupt cimport * diff --git a/src/sage/ext/c_lib.pyx b/src/sage/ext/interrupt.pyx similarity index 87% rename from src/sage/ext/c_lib.pyx rename to src/sage/ext/interrupt.pyx index d01a50d0246..e9ec8f7b4f4 100644 --- a/src/sage/ext/c_lib.pyx +++ b/src/sage/ext/interrupt.pyx @@ -1,9 +1,7 @@ r""" -Interface between Python and c_lib. - -This allows Python code to access a few parts of c_lib. This is not -needed for Cython code, since such code can access c_lib directly. +Cython interface to the interrupt handling code. +See ``src/sage/tests/interrupt.pyx`` for extensive tests. AUTHORS: @@ -19,10 +17,10 @@ AUTHORS: # http://www.gnu.org/licenses/ #***************************************************************************** -include 'sage/ext/stdsage.pxi' include 'sage/ext/interrupt.pxi' -include 'sage/ext/cdefs.pxi' include 'sage/ext/signals.pxi' +from libc.stdio cimport freopen, stdin + class AlarmInterrupt(KeyboardInterrupt): """ @@ -34,7 +32,7 @@ class AlarmInterrupt(KeyboardInterrupt): Traceback (most recent call last): ... AlarmInterrupt - sage: from sage.ext.c_lib import do_raise_exception + sage: from sage.ext.interrupt import do_raise_exception sage: import signal sage: do_raise_exception(signal.SIGALRM) Traceback (most recent call last): @@ -50,7 +48,7 @@ class SignalError(BaseException): EXAMPLES:: - sage: from sage.ext.c_lib import do_raise_exception + sage: from sage.ext.interrupt import do_raise_exception sage: import signal sage: do_raise_exception(signal.SIGSEGV) Traceback (most recent call last): @@ -104,7 +102,7 @@ def do_raise_exception(sig, msg=None): EXAMPLES:: - sage: from sage.ext.c_lib import do_raise_exception + sage: from sage.ext.interrupt import do_raise_exception sage: import signal sage: do_raise_exception(signal.SIGFPE) Traceback (most recent call last): @@ -127,12 +125,12 @@ def do_raise_exception(sig, msg=None): sig_raise_exception(sig, m) -def _init_csage(): +def init_interrupts(): """ - Call init_csage() and enable interrupts. + Initialize the Sage interrupt framework. - This is normally done exactly once during Sage startup from - sage/all.py + This is normally done exactly once during Sage startup when + importing this module. """ # Set the Python-level interrupt handler. When a SIGINT occurs, # this will not be called directly. Instead, a SIGINT is caught by @@ -145,18 +143,18 @@ def _init_csage(): import signal signal.signal(signal.SIGINT, sage_python_check_interrupt) - init_csage() + setup_sage_signal_handler() _signals.raise_exception = sig_raise_exception -def _sig_on_reset(): +def sig_on_reset(): """ Return the current value of ``_signals.sig_on_count`` and set its value to zero. This is used by the doctesting framework. EXAMPLES:: - sage: from sage.ext.c_lib import _sig_on_reset as sig_on_reset + sage: from sage.ext.interrupt import sig_on_reset sage: cython('sig_on()'); sig_on_reset() 1 sage: sig_on_reset() @@ -174,3 +172,6 @@ def sage_python_check_interrupt(sig, frame): libcsage (c_lib). """ sig_check() + + +init_interrupts() diff --git a/src/sage/matrix/benchmark.py b/src/sage/matrix/benchmark.py index 636c5904118..93eb2d2f1d5 100644 --- a/src/sage/matrix/benchmark.py +++ b/src/sage/matrix/benchmark.py @@ -19,7 +19,7 @@ from constructor import random_matrix, Matrix from sage.rings.all import ZZ, QQ, GF from sage.misc.misc import alarm, cancel_alarm, cputime -from sage.ext.c_lib import AlarmInterrupt +from sage.ext.interrupt import AlarmInterrupt from sage.interfaces.all import magma diff --git a/src/sage/misc/cython.py b/src/sage/misc/cython.py index b996a64dcf1..d50124f9459 100644 --- a/src/sage/misc/cython.py +++ b/src/sage/misc/cython.py @@ -196,7 +196,7 @@ def pyx_preparse(s): sage: from sage.misc.cython import pyx_preparse sage: pyx_preparse("") - ('\ninclude "interrupt.pxi" # ctrl-c interrupt block support\ninclude "stdsage.pxi" # ctrl-c interrupt block support\n\ninclude "cdefs.pxi"\n', + ('\ninclude "interrupt.pxi" # ctrl-c interrupt block support\ninclude "stdsage.pxi"\n\ninclude "cdefs.pxi"\n', ['mpfr', 'gmp', 'gmpxx', @@ -270,8 +270,7 @@ def pyx_preparse(s): v, s = parse_keywords('cinclude', s) inc = [environ_parse(x.replace('"','').replace("'","")) for x in v] + include_dirs s = """\ninclude "cdefs.pxi"\n""" + s - if lang != "c++": # has issues with init_csage() - s = """\ninclude "interrupt.pxi" # ctrl-c interrupt block support\ninclude "stdsage.pxi" # ctrl-c interrupt block support\n""" + s + s = """\ninclude "interrupt.pxi" # ctrl-c interrupt block support\ninclude "stdsage.pxi"\n""" + s args, s = parse_keywords('cargs', s) args = ['-w','-O2'] + args diff --git a/src/sage/misc/sageinspect.py b/src/sage/misc/sageinspect.py index aef66173c07..d33057b06cb 100644 --- a/src/sage/misc/sageinspect.py +++ b/src/sage/misc/sageinspect.py @@ -193,7 +193,7 @@ def _extract_embedded_position(docstring): sage: print open(_extract_embedded_position(inspect.getdoc(test_funct))[1]).read() include "interrupt.pxi" # ctrl-c interrupt block support - include "stdsage.pxi" # ctrl-c interrupt block support + include "stdsage.pxi" include "cdefs.pxi" cpdef test_funct(x,y): return diff --git a/src/sage/parallel/use_fork.py b/src/sage/parallel/use_fork.py index 18d72775b7e..ef459ffcc34 100644 --- a/src/sage/parallel/use_fork.py +++ b/src/sage/parallel/use_fork.py @@ -11,7 +11,7 @@ # http://www.gnu.org/licenses/ ################################################################################ -from sage.ext.c_lib import AlarmInterrupt +from sage.ext.interrupt import AlarmInterrupt from sage.misc.misc import alarm, cancel_alarm class p_iter_fork: From 1d41ca2248be87c73a8cda9d21317bfa62dfb1bd Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Fri, 13 Mar 2015 18:18:16 +0100 Subject: [PATCH 079/129] Add interrupt.pyx to the reference manual Also add back pselect.pyx, which was removed by mistake in 4d2df6a35f. --- src/c_lib/src/interrupt.c | 4 ++-- src/doc/en/reference/libs/index.rst | 3 +++ src/sage/ext/interrupt.pyx | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/c_lib/src/interrupt.c b/src/c_lib/src/interrupt.c index 459b2a02942..802c8038e2c 100644 --- a/src/c_lib/src/interrupt.c +++ b/src/c_lib/src/interrupt.c @@ -132,8 +132,8 @@ void sage_interrupt_handler(int sig) else { /* Set the Python interrupt indicator, which will cause the - * Python-level interrupt handler in sage/ext/c_lib.pyx to be - * called. */ + * Python-level interrupt handler in sage/ext/interrupt.pyx to + * be called. */ PyErr_SetInterrupt(); } diff --git a/src/doc/en/reference/libs/index.rst b/src/doc/en/reference/libs/index.rst index 90a489c13a6..b0faf6b0203 100644 --- a/src/doc/en/reference/libs/index.rst +++ b/src/doc/en/reference/libs/index.rst @@ -67,6 +67,9 @@ to be aware of the modules described in this chapter. sage/gsl/gsl_array + sage/ext/interrupt + sage/ext/pselect + .. Cannot be imported independently of mpmath: sage/libs/mpmath/ext_main sage/libs/mpmath/ext_impl sage/libs/mpmath/ext_libmp .. Modules depending on optional packages: sage/libs/coxeter3/coxeter sage/libs/coxeter3/coxeter_group sage/libs/fes diff --git a/src/sage/ext/interrupt.pyx b/src/sage/ext/interrupt.pyx index e9ec8f7b4f4..96498223ef6 100644 --- a/src/sage/ext/interrupt.pyx +++ b/src/sage/ext/interrupt.pyx @@ -1,5 +1,5 @@ r""" -Cython interface to the interrupt handling code. +Cython interface to the interrupt handling code See ``src/sage/tests/interrupt.pyx`` for extensive tests. From bedbab187f3399e4231650c2d0947d8513c290b0 Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Fri, 13 Mar 2015 20:36:12 +0100 Subject: [PATCH 080/129] trac #17950: make modular_decomposition an optional spkg --- build/pkgs/latte_int/checksums.ini | 8 +- build/pkgs/modular_decomposition/SPKG.txt | 29 + .../pkgs/modular_decomposition/checksums.ini | 4 + .../modular_decomposition/package-version.txt | 1 + build/pkgs/modular_decomposition/spkg-install | 17 + src/doc/en/reference/graphs/index.rst | 1 - src/module_list.py | 7 +- src/sage/graphs/all.py | 2 +- src/sage/graphs/graph.py | 19 +- .../modular_decomposition.pxd | 6 +- .../modular_decomposition.pyx | 8 +- .../graphs/modular_decomposition/__init__.py | 4 - .../graphs/modular_decomposition/src/dm.c | 1286 ----------------- .../modular_decomposition/src/dm_english.h | 117 -- .../graphs/modular_decomposition/src/random.c | 149 -- 15 files changed, 78 insertions(+), 1580 deletions(-) create mode 100644 build/pkgs/modular_decomposition/SPKG.txt create mode 100644 build/pkgs/modular_decomposition/checksums.ini create mode 100644 build/pkgs/modular_decomposition/package-version.txt create mode 100755 build/pkgs/modular_decomposition/spkg-install rename src/sage/graphs/{modular_decomposition => }/modular_decomposition.pxd (94%) rename src/sage/graphs/{modular_decomposition => }/modular_decomposition.pyx (91%) delete mode 100644 src/sage/graphs/modular_decomposition/__init__.py delete mode 100644 src/sage/graphs/modular_decomposition/src/dm.c delete mode 100644 src/sage/graphs/modular_decomposition/src/dm_english.h delete mode 100644 src/sage/graphs/modular_decomposition/src/random.c diff --git a/build/pkgs/latte_int/checksums.ini b/build/pkgs/latte_int/checksums.ini index 72e4b5d312a..0ac787821e1 100644 --- a/build/pkgs/latte_int/checksums.ini +++ b/build/pkgs/latte_int/checksums.ini @@ -1,4 +1,4 @@ -tarball=latte_int-VERSION.tar.gz -sha1=165c9173b13f4bc9cb825ef63c06d6fb20dc41b5 -md5=57b151f7bb49fe5154a5697b70d359f9 -cksum=1072837189 +tarball=latte_int-VERSION.tar.bz2 +sha1=1a1b475d69697c45218b133df9b0519894ebce28 +md5=3a862e6a5d1b2e10b0bee342fc0f27a9 +cksum=3434585848 diff --git a/build/pkgs/modular_decomposition/SPKG.txt b/build/pkgs/modular_decomposition/SPKG.txt new file mode 100644 index 00000000000..277eeded0c5 --- /dev/null +++ b/build/pkgs/modular_decomposition/SPKG.txt @@ -0,0 +1,29 @@ += modular decomposition = + +== Description == + +This is an implementation of a modular decomposition algorithm. + +http://www.liafa.jussieu.fr/~fm/ (in french) + +== License == + +GPL + +== SPKG Maintainers == + +Nathann Cohen (nathann.cohen@gmail.com) + +== Upstream Contact == + +Fabien de Montgolfier + +http://www.liafa.jussieu.fr/~fm/ + +== Dependencies == + +None + +== Patches == + +None diff --git a/build/pkgs/modular_decomposition/checksums.ini b/build/pkgs/modular_decomposition/checksums.ini new file mode 100644 index 00000000000..6d94d635b5b --- /dev/null +++ b/build/pkgs/modular_decomposition/checksums.ini @@ -0,0 +1,4 @@ +tarball=modular_decomposition-VERSION.tar.bz2 +sha1=80617ba03f5b7d3db7e2c290f5785674f50bee37 +md5=88d6c7629c29926eaa6497212873dfe9 +cksum=1472531391 diff --git a/build/pkgs/modular_decomposition/package-version.txt b/build/pkgs/modular_decomposition/package-version.txt new file mode 100644 index 00000000000..ea0dd137c06 --- /dev/null +++ b/build/pkgs/modular_decomposition/package-version.txt @@ -0,0 +1 @@ +20150313 diff --git a/build/pkgs/modular_decomposition/spkg-install b/build/pkgs/modular_decomposition/spkg-install new file mode 100755 index 00000000000..e87a40528fd --- /dev/null +++ b/build/pkgs/modular_decomposition/spkg-install @@ -0,0 +1,17 @@ +#!/usr/bin/env bash + +if [ "$SAGE_LOCAL" = "" ]; then + echo "SAGE_LOCAL undefined ... exiting"; + echo "Maybe run 'sage -sh'?" + exit 1 +fi + +cd "src" + +gcc -o "$SAGE_LOCAL/lib/libmodulardecomposition.so" dm.c random.c -fPIC --shared && +mv dm_english.h "$SAGE_LOCAL/include/modular_decomposition.h" + +if [ $? -ne 0 ]; then + echo "An error occurred whilst building modular_decomposition" + exit 1 +fi diff --git a/src/doc/en/reference/graphs/index.rst b/src/doc/en/reference/graphs/index.rst index 055b93bdfca..65ef36fce4c 100644 --- a/src/doc/en/reference/graphs/index.rst +++ b/src/doc/en/reference/graphs/index.rst @@ -78,7 +78,6 @@ Libraries of algorithms sage/graphs/graph_decompositions/rankwidth sage/graphs/graph_decompositions/bandwidth sage/graphs/graph_decompositions/graph_products - sage/graphs/modular_decomposition/modular_decomposition sage/graphs/convexity_properties sage/graphs/weakly_chordal sage/graphs/distances_all_pairs diff --git a/src/module_list.py b/src/module_list.py index 4deedc717a0..3eba269f246 100755 --- a/src/module_list.py +++ b/src/module_list.py @@ -404,10 +404,9 @@ def uname_specific(name, value, alternative): Extension('sage.graphs.base.static_sparse_backend', sources = ['sage/graphs/base/static_sparse_backend.pyx']), - Extension('sage.graphs.modular_decomposition.modular_decomposition', - sources = ['sage/graphs/modular_decomposition/modular_decomposition.pyx', - 'sage/graphs/modular_decomposition/src/dm.c'], - depends = ['sage/graphs/modular_decomposition/src/dm_english.h']), + Extension('sage.graphs.modular_decomposition', + sources = ['sage/graphs/modular_decomposition.pyx'], + libraries = ['modulardecomposition']), Extension('sage.graphs.weakly_chordal', sources = ['sage/graphs/weakly_chordal.pyx']), diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 57fd01e8175..33d05892b28 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -17,7 +17,7 @@ import sage.graphs.digraph lazy_import("sage.graphs", "graph_coloring") import sage.graphs.graph_decompositions -import sage.graphs.modular_decomposition.modular_decomposition +import sage.graphs.modular_decomposition import sage.graphs.comparability from sage.graphs.cliquer import * from graph_database import graph_db_info diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index d4c07f0bca1..9ac22e12347 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -6355,6 +6355,11 @@ def modular_decomposition(self): r""" Returns the modular decomposition of the current graph. + .. NOTE:: + + In order to use this method you must install the + ``modular_decomposition`` optional package. + Crash course on modular decomposition: A module `M` of a graph `G` is a proper subset of its vertices @@ -6419,12 +6424,12 @@ def modular_decomposition(self): The Bull Graph is prime:: - sage: graphs.BullGraph().modular_decomposition() + sage: graphs.BullGraph().modular_decomposition() # optional -- modular_decomposition ('Prime', [3, 4, 0, 1, 2]) The Petersen Graph too:: - sage: graphs.PetersenGraph().modular_decomposition() + sage: graphs.PetersenGraph().modular_decomposition() # optional -- modular_decomposition ('Prime', [2, 6, 3, 9, 7, 8, 0, 1, 5, 4]) This a clique on 5 vertices with 2 pendant edges, though, has a more @@ -6433,7 +6438,7 @@ def modular_decomposition(self): sage: g = graphs.CompleteGraph(5) sage: g.add_edge(0,5) sage: g.add_edge(0,6) - sage: g.modular_decomposition() + sage: g.modular_decomposition() # optional -- modular_decomposition ('Serie', [0, ('Parallel', [5, ('Serie', [1, 4, 3, 2]), 6])]) ALGORITHM: @@ -6469,12 +6474,16 @@ def modular_decomposition(self): vol 4, number 1, pages 41--59, 2010 http://www.lirmm.fr/~paul/md-survey.pdf """ + try: + from sage.graphs.modular_decomposition import modular_decomposition + except ImportError: + raise RuntimeError("In order to use this method you must " + "install the modular_decomposition package") + self._scream_if_not_simple() from sage.misc.stopgap import stopgap stopgap("Graph.modular_decomposition is known to return wrong results",13744) - from sage.graphs.modular_decomposition.modular_decomposition import modular_decomposition - D = modular_decomposition(self) id_label = dict(enumerate(self.vertices())) diff --git a/src/sage/graphs/modular_decomposition/modular_decomposition.pxd b/src/sage/graphs/modular_decomposition.pxd similarity index 94% rename from src/sage/graphs/modular_decomposition/modular_decomposition.pxd rename to src/sage/graphs/modular_decomposition.pxd index 6000265dedf..ac6186e8469 100644 --- a/src/sage/graphs/modular_decomposition/modular_decomposition.pxd +++ b/src/sage/graphs/modular_decomposition.pxd @@ -2,9 +2,7 @@ include "sage/ext/interrupt.pxi" include 'sage/ext/cdefs.pxi' include 'sage/ext/stdsage.pxi' - - -cdef extern from "src/dm_english.h": +cdef extern from "modular_decomposition.h": ctypedef struct noeud: pass @@ -40,6 +38,4 @@ cdef extern from "src/dm_english.h": int n c_adj ** G -cdef extern from "src/dm_english.h": - c_noeud *decomposition_modulaire(c_graphe G) diff --git a/src/sage/graphs/modular_decomposition/modular_decomposition.pyx b/src/sage/graphs/modular_decomposition.pyx similarity index 91% rename from src/sage/graphs/modular_decomposition/modular_decomposition.pyx rename to src/sage/graphs/modular_decomposition.pyx index 855be3867de..6782784d483 100644 --- a/src/sage/graphs/modular_decomposition/modular_decomposition.pyx +++ b/src/sage/graphs/modular_decomposition.pyx @@ -69,13 +69,13 @@ cpdef modular_decomposition(g): The Bull Graph is prime:: - sage: from sage.graphs.modular_decomposition.modular_decomposition import modular_decomposition - sage: modular_decomposition(graphs.BullGraph()) + sage: from sage.graphs.modular_decomposition import modular_decomposition # optional -- modular_decomposition + sage: modular_decomposition(graphs.BullGraph()) # optional -- modular_decomposition ('Prime', [3, 4, 0, 1, 2]) The Petersen Graph too:: - sage: modular_decomposition(graphs.PetersenGraph()) + sage: modular_decomposition(graphs.PetersenGraph()) # optional -- modular_decomposition ('Prime', [2, 6, 3, 9, 7, 8, 0, 1, 5, 4]) This a clique on 5 vertices with 2 pendant edges, though, has a more @@ -84,7 +84,7 @@ cpdef modular_decomposition(g): sage: g = graphs.CompleteGraph(5) sage: g.add_edge(0,5) sage: g.add_edge(0,6) - sage: modular_decomposition(g) + sage: modular_decomposition(g) # optional -- modular_decomposition ('Serie', [0, ('Parallel', [5, ('Serie', [1, 4, 3, 2]), 6])]) diff --git a/src/sage/graphs/modular_decomposition/__init__.py b/src/sage/graphs/modular_decomposition/__init__.py deleted file mode 100644 index ff67b47096b..00000000000 --- a/src/sage/graphs/modular_decomposition/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# This file is not empty ! - -import sage.graphs.modular_decomposition.modular_decomposition - diff --git a/src/sage/graphs/modular_decomposition/src/dm.c b/src/sage/graphs/modular_decomposition/src/dm.c deleted file mode 100644 index 4eca712c9a6..00000000000 --- a/src/sage/graphs/modular_decomposition/src/dm.c +++ /dev/null @@ -1,1286 +0,0 @@ -/****************************************************** - -Copyright 2004, 2010 Fabien de Montgolfier -fm@liafa.jussieu.fr - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -**********************************************************/ - -/******************************************************** - - DECOMPOSITION MODULAIRE DE GRAPHES NON-ORIENTES - -Cet algorithme construit l'arbre de decomposition modulaire -d'un graphe donne sous forme d'une matrice d'adjacence. -Il s'effectue en temps O(m log n) temps et $O(m)$ espace. -Il est la concatenation de deux algorithmes distincts. -Le premier realise une permutation factorisante des sommets du graphe -(pour cette notion, cf these de Christian Capelle) -grace a une technique d'affinage de partitions (cf Habib, Paul & Viennot) -Le second construit l'arbre de decomposition modulaire a partir de cette -permutation, cf mon memoire de DEA -Montpellier, decembre 2000 -********************************************************/ - -#include "dm_english.h" -#include -#include - -#define DEBUG 0 /* si 0 aucune sortie graphique!! */ - -/* dm.h definit les constantes FEUILLE, MODULE, etc... -ainsi que les structures noeud et fils. Les autres -structures n'ont pas a etre connues par les programmes -exterieurs et sont donc definies ici. */ - - -/* un sommet du graphe -(utilise dans la premiere partie seulement, ainsi que ce qui suit)*/ -typedef struct Sommet { - int place;/* numero du sommet dans la NOUVELLE numerotation */ - int nom; /* numero du sommet dans l'ANCIENNE numerotation */ - /* On a donc sigma(nom)=place */ - struct Sadj *adj; - struct SClasse *classe; -} sommet; - -/* liste d'adjacence d'un sommet, DOUBLEMENT chainee*/ -typedef struct Sadj { - struct Sommet *pointe; - struct Sadj *suiv; - struct Sadj *prec; -} sadj; - -/* classe de la partition courante, - organisees en liste chainnee selon l'ordre de la partition */ -typedef struct SClasse { - int debut; - int fin; - struct Sommet *firstpivot; - int inpivot; /*indice de la classe dans le tableau pivot */ - int inmodule; /* (resp module); -1 si non present */ - int whereXa; /* lie le couple X/Xa: vaut - 0 si X n'est actuellement lie a aucun Xa - -1 si Xa est a gauche - +1 si Xa est a droite */ - struct SClasse *suiv; /* forment une liste chainee */ - struct SClasse *prec; /*...doublement */ -} sclasse; - -/* plein de parametres statiques que algo1() donne a Raffine() */ -typedef struct Info { - sclasse **pivot; - int *ipivot; - sclasse **module; - int *imodule; - int *numclasse; - int *n; -} info; - -/* clef a deux entrees utilisee pour le tri lineaire - represente l'arrete ij */ -typedef struct Clef2{ - int i; //sommet pointeur - int nom; // nom du sommet pointe - int place; //place du sommet pointe -} clef2; - -/************************************************************* -utilitaires -*************************************************************/ -void *fabmalloc(size_t s) -/* malloc sans erreur */ -{ - void *p; - p=malloc(s); - if(p==NULL) - { - perror("Erreur de malloc!\n"); - exit(1); - } - return p; -} - -int min(int a, int b) -{ - return (a b) ? a : b; -} -/************************************************************** -Premiere passe de l'algorithme: il s'agit de trouver une -permutation factorisante du graphe. Nous utilisons les -techniques de raffinement de partition. Tout cela est -explique dans l'article de Habib, Viennot & Paul, dont je ne -fais ici que transcrire le travail. -****************************************************************/ - -void printS(sommet ** S, int n) -{ - /* imprimme S selon S et selon les classes */ - int i; - sclasse *s; - - for (s = S[0]->classe; s != NULL; s = s->suiv) { - printf("[ "); - for (i = s->debut; i <= s->fin; i++) - printf("%i ", 1 + S[i]->nom); - printf("] "); - } - printf("\n"); -} - -sclasse *nouvclasse(sclasse * un, sclasse * deux) -{ - /* cree une nouvelle classe et l'insere entre un et deux; - on suppose que si un et deux sont pas NULL alors - FORCEMENT un=deux->suiv */ - - sclasse *nouv; - nouv = (sclasse *) fabmalloc(sizeof(sclasse)); - nouv->whereXa = 0; - nouv->inpivot = -1; - nouv->inmodule = -1; - nouv->firstpivot = NULL; - nouv->prec = un; - if (un != NULL) /* accroche pas en tete de chaine */ - un->suiv = nouv; - nouv->suiv = deux; - if (deux != NULL) /* pas en queue de chaine */ - deux->prec = nouv; - - /* debut et fin ne sont PAS initialises! */ - return nouv; -} - -void permute(sommet ** S, int a, int b) -{ - /* transpose les sommets a et b dans S */ - /* ne touche pas aux classes! */ - sommet *tmp; - S[a]->place = b; - S[b]->place = a; - tmp = S[a]; - S[a] = S[b]; - S[b] = tmp; -} - -void Raffiner(sommet ** S, sommet * p, sommet * centre, info * I) -{ - /* melange raffiner, pivotset, insertright et addpivot */ - sadj *a; /* parcours l'adjacence du pivot */ - sommet *x; /* sommet quiva changer de classe */ - sclasse *X, *Xa; /* x in X; Xa nouv classe de x */ - sclasse *Z; - sclasse **pivot; - sclasse **module; - int *ipivot, *imodule, *numclasse, n; - - if (DEBUG) - printf("Raffinage avec le pivot %i\n", 1 + p->nom); - pivot = I->pivot; - module = I->module; - ipivot = I->ipivot; - imodule = I->imodule; - numclasse = I->numclasse; - n = *(I->n); - - for (a = p->adj; a != NULL; a = a->suiv) { - x = a->pointe; - X = x->classe; - if (X == p->classe) - continue; /* on raffine pas la classe du pivot! */ - - if (X->whereXa == 0) { - /* c'est la premiere fois qu'on trouve un x - appartenant a X lors de cet appel a raffiner */ - - if ((centre->place < x->place && x->place < p->place) - || (p->place < x->place && x->place < centre->place)) { - /* insere a gauche */ - Xa = nouvclasse(X->prec, X); - (*numclasse)++; - permute(S, x->place, X->debut); - X->debut++; - X->whereXa = -1; - Xa->whereXa = 1; /* besoin dans le second tour */ - } - else { /* insere a droite */ - - Xa = nouvclasse(X, X->suiv); - (*numclasse)++; - permute(S, x->place, X->fin); - X->fin--; - X->whereXa = 1; - Xa->whereXa = -1; - } - x->classe = Xa; - Xa->debut = x->place; - Xa->fin = x->place; - } - else { - if (X->whereXa == -1) { - Xa = X->prec; - permute(S, x->place, X->debut); - X->debut++; - Xa->fin++; - } - else { - Xa = X->suiv; - permute(S, x->place, X->fin); - X->fin--; - Xa->debut--; - } - x->classe = Xa; - } - } - - for (a = p->adj; a != NULL; a = a->suiv) - /* deuxieme couche! Maintenant on va faire les addpivot, - et remettre les whereXa a 0 - Noter qu'on lit les Xa et plus les X */ - { - x = a->pointe; - Xa = x->classe; - if (Xa->whereXa == 0) - continue; /* deja remis a zero! */ - if (Xa->whereXa == -1) - X = Xa->prec; - else - X = Xa->suiv; - - if (X->debut > X->fin) { - /*on a trop enleve! X est vide - -> on va le supprimer mechamment */ - - (*numclasse)--; - if (Xa->whereXa == 1) { /*deconnecte */ - Xa->suiv = X->suiv; - if (Xa->suiv != NULL) - Xa->suiv->prec = Xa; - } - else { - Xa->prec = X->prec; - if (Xa->prec != NULL) - Xa->prec->suiv = Xa; - } - Xa->inpivot = X->inpivot; - if (X->inpivot != -1) /* ecrase X dans pivot */ - pivot[X->inpivot] = Xa; - Xa->inmodule = X->inmodule; - if (X->inmodule != -1) /* ecrase X dans pivot */ - module[X->inmodule] = Xa; - - Xa->whereXa = 0; - continue; - } - - /* Maintenant on fait addpivot(X,Xa) - noter que X et Xa sont non vides */ - - if (X->inpivot == -1) { - if ((X->inmodule != -1) - && (X->fin - X->debut < Xa->fin - Xa->debut)) { - /* remplace X par Xa dans module */ - module[X->inmodule] = Xa; - Xa->inmodule = X->inmodule; - X->inmodule = -1; - if (DEBUG) - printf("Dans module %i-%i ecrase %i-%i\n", - 1 + S[Xa->debut]->nom, 1 + S[Xa->fin]->nom, - 1 + S[X->debut]->nom, 1 + S[X->fin]->nom); - } - else { - if (X->inmodule == -1) { - if (X->fin - X->debut < Xa->fin - Xa->debut) - Z = Xa; - else - Z = X; - /* ajoute Z (=max(X,Xa)) a module */ - module[(*imodule)] = Z; - Z->inmodule = (*imodule); - (*imodule)++; - if (DEBUG) - printf("module empile:%i-%i\n", - 1 + S[Z->debut]->nom, 1 + S[Z->fin]->nom); - } - } - } - - if (X->inpivot != -1) - Z = Xa; - else if (X->fin - X->debut < Xa->fin - Xa->debut) - Z = X; - else - Z = Xa; - /* on empile Z dans pivot */ - pivot[(*ipivot)] = Z; - Z->inpivot = (*ipivot); - (*ipivot)++; - if (DEBUG) - printf("pivot empile: %i-%i\n", 1 + S[Z->debut]->nom, - 1 + S[Z->fin]->nom); - X->whereXa = 0; - Xa->whereXa = 0; - } - if (DEBUG) { - printS(S, n); - printf("\n"); - } -} - -sommet **algo1(graphe G) - /* Entree: un graphe G - Sortie: une permutation factorisante de G, - donnee sous la forme d'un tableau de structures Sommet ordonnees selon sigma. - d'apres le travail de Habib/Paul/Viennot */ -{ - int n; // nombre de sommets de G - - sclasse **pivot; /*pile des pivots */ - int ipivot = 0; /*indice sur la precedante */ - - sclasse **module; /*idem, modules */ - int imodule = 0; - - sclasse *singclasse; - /*invariant: toute classe avant singclasse dans la chaine */ - /*a un seul element */ - int numclasse; /* quand vaut n, on a fini!! */ - - sclasse *C1; /*premiere classe, tete de la chaine */ - sclasse *Y; /*classe qui raffine */ - sclasse *X; /*classe raffinee */ - sclasse *Xa, *Xc; /* morceaux de X */ - sommet *x; /* x in X */ - sommet *y; /* y in Y */ - sommet *centre; /* le centre du raffinage actuel */ - - sommet **S; /*la permutation factorisante ! */ - - int i, j; /*divers indices */ - sommet *scourant; /* pour l'init */ - sadj *nextadj; /*sommet adjacent suivant */ - adj *nextadj2; /* idem mais de type adj */ - info Inf; /* diverses info a passer a raffiner */ - - /* debut des initialisations */ - n=G.n; - /*initialisation des tableaux */ - module = (sclasse **) fabmalloc(n * sizeof(sclasse *)); - pivot = (sclasse **) fabmalloc(n * sizeof(sclasse *)); - S = (sommet **) fabmalloc(n * sizeof(sommet *)); - /* on va initialiser la permutation factorisante, - ainsi que chaque structure sommet */ - C1 = nouvclasse(NULL, NULL); - numclasse = 1; - singclasse = C1; - C1->debut = 0; - C1->fin = n - 1; - for (i = 0; i < n; i++) { - /* initialisation des sommets */ - /* notre bebe est le sommet i dans M */ - scourant = (sommet *) fabmalloc(sizeof(struct Sommet)); - scourant->nom = i; - scourant->place = i; /* a ce point S=identite */ - scourant->adj = NULL; /* pas encore d'adjacence */ - scourant->classe = C1; - S[i] = scourant; - } - for (i = 0; i < n; i++) - { - nextadj2 = G.G[i]; - while(nextadj2 != NULL) - { - j=nextadj2->s; //numero du sommet pointe - if((j<0)||(j>=n)) - { - perror("Graphe invalide (numero de sommet erronne)!\n"); - exit(1); - } - nextadj = (sadj *) fabmalloc(sizeof(struct Sadj)); - //un nouveau sadj - nextadj->pointe = S[j]; - nextadj->suiv = S[i]->adj; //tete de liste - if(nextadj->suiv!=NULL) - nextadj->suiv->prec=nextadj; - nextadj->prec=NULL; - S[i]->adj = nextadj; /*et le tour est joue */ - nextadj2=nextadj2->suiv; - } - } - /* NB: module et pivot sont vides */ - Inf.pivot = pivot; - Inf.ipivot = &ipivot; - Inf.module = module; - Inf.imodule = &imodule; - Inf.numclasse = &numclasse; - Inf.n = &n; - /* init terminnee */ - - while (1) { - while (ipivot > 0 || imodule > 0) { - while (ipivot > 0) { - /*cette boucle raffine selon tous les sommets - de la premiere classe dans pivot */ - - Y = pivot[ipivot - 1]; - ipivot--; - Y->inpivot = -1; - - for (i = Y->debut; i <= Y->fin; i++) - Raffiner(S, S[i], centre, &Inf); - - /* une optimisation de la fin de l'algo */ - if (numclasse == n) - return (S); - } - /*maintenant pivot est vide, mais peut-etre pas module */ - if (imodule > 0) { - /* relance par un sommet (pas au pif...) */ - /* de chaque module qui le represente */ - Y = module[imodule - 1]; - imodule--; - Y->inmodule = -1; - y = S[Y->debut]; /* le firstpivot sera toujours... */ - Y->firstpivot = y; /* le premier!! */ - if (DEBUG) - printf("module-pivot %i-%i: sommet %i\n", - 1 + S[Y->debut]->nom, 1 + S[Y->fin]->nom, - 1 + y->nom); - Raffiner(S, y, centre, &Inf); - } - } - /* a ce point, pivot et module sont vides... - pas de pb! On va faire initpartition HERE */ - if (DEBUG) - printf("\nInit Partition\n"); - /**** ajoute ici pour debbugger, mais moche!! */ - singclasse = S[0]->classe; - while ((singclasse != NULL) && - (singclasse->debut == singclasse->fin)) - { - singclasse = singclasse->suiv; - } - /* singclasse est la premiere classe - non singlette, sauf si: */ - if (singclasse == NULL) - /* on a n classes singlettes? ben c'est gagne! */ - { - return (S); - } - if (singclasse == NULL && numclasse < n) { - perror("c'est pas normal! Ca termine trop vite!\n"); - exit(1); - } - - X = singclasse; - x = X->firstpivot; - if (x == NULL) - x = S[X->debut]; - else /* remet firstpivot a NULL!! */ - X->firstpivot = NULL; - - if (DEBUG) - printf("Relance dans le module %i-%i avec le sommet %i\n", - 1 + S[X->debut]->nom, 1 + S[X->fin]->nom, 1 + x->nom); - - centre = x; /*important! */ - /* astuce: on place {x} en tete de X - ensuite, on raffine S selon x -> seule X est coupee - il y a alors {x} X Xa - -> on met {x} en queue de X et c'est bon! - ainsi on a bien nonvoisins-x-voisons */ - Xc = nouvclasse(X->prec, X); - numclasse++; - x->classe = Xc; - permute(S, x->place, X->debut); - X->debut++; - Xc->debut = x->place; - Xc->fin = x->place; - Raffiner(S, x, x, &Inf); - /* X existe-il encore? */ - if (X->debut > X->fin) - continue; - /* echange de x et {x}. Init: -{x}-X- */ - Xc->suiv = X->suiv; - if (X->suiv != NULL) - X->suiv->prec = Xc; - X->prec = Xc->prec; - if (Xc->prec != NULL) - Xc->prec->suiv = X; - X->suiv = Xc; - Xc->prec = X; - permute(S, x->place, X->fin); - Xc->debut = x->place; - Xc->fin = x->place; - X->debut--; - X->fin--; - //antibug? - singclasse=X; - /* now -X-{x}- */ - if (DEBUG) - printS(S, n); - } -} - -/*************************************************************** -Etape intermediaire: trier toutes les listes d'adjacence -selon S. ce sont les listes de type sadj qui sont concernees -***************************************************************/ -int Calculm(graphe G) -/* compte le nombre d'arretes du graphe */ -{ - int i,r; adj *a; - r=0; - for(i=0;isuiv; - r++; - } - } - if(r%2!=0) - { - perror("Erreur: nombre impaire d'arrete, graphe non-oriente??\n"); - exit(1); - } - return r/2; // G symetrique! -} - -void TrierTous(sommet **S, int n, int m) -/* trie chaque liste d'adjacence de S*/ -{ - //n sommets, m arretes - int i; // numero du sommet courant - sadj *a,*atmp;// parcours sa liste d'adjacence - clef2 *c; // enregistrement a trier - int *tab1; clef2 **tab2; //tableaux du tri par seaux - tab1=(int *)fabmalloc(n*sizeof(int)); - tab2=(clef2 **)fabmalloc(m * 2 * sizeof(clef2 *)); - for(i=0; iadj; - while(a!=NULL) - { - tab1[i]++; - a=a->suiv; - } - } - //deuxieme passe: frequences cumulees a rebours - // (car les listes d'adjacences se construisent a l'envers - //tab1[n-1]--; // a cause des indices de tableau qui commence a zero - //for(i=n-1;i>0;i--) - // tab1[i-1]+=tab1[i]; - - //deuxieme passe: frequences cumulees - for(i=1;iadj; - while(a!=NULL) - { - /* cree un nouveau record */ - c=(clef2 *)fabmalloc(sizeof(struct Clef2)); - c->i=i; - c->nom=a->pointe->nom; - c->place=a->pointe->place; - /* le place bien dans tab2 */ - tab1[c->place]--; - tab2[tab1[c->place]]=c; - /*et on continue */ - a=a->suiv; - } - } - - //quatrieme passe: detruit les vielles listes d'adjacence - for(i=0; iadj; - while(a!=NULL) - { - atmp=a->suiv; - free(a); - a=atmp; - } - S[i]->adj=NULL; - } - - //derniere passe: reconstruit les listes d'adjacence - for(i=0;i<2*m;i++) - { - c=tab2[i]; - a=(sadj *)fabmalloc(sizeof(struct Sadj)); - a->pointe=S[c->i]; - a->suiv=S[c->place]->adj; //insere en tete - if(a->suiv!=NULL) - a->suiv->prec=a; - a->prec=NULL; - S[c->place]->adj=a; - //nettoie - free(c); - } - free(tab1); - free(tab2); -} - - -/*************************************************************** - Maintenant, la deuxieme partie de l'aglorithme - On va, etant donne la matrice M construite a l'etape precedante, - etablir l'arbre de decomposition modulaire. - Tous les details sont dans mon memoire de DEA -****************************************************************/ -noeud *nouvnoeud(int type, noeud * pere, int sommet, int n) -{ - /* cree un nouveau noeud. Noter que l'on est oblige de passer n - comme parametre car les bords et separateurs droits doivent - etre initilises avec des valeurs >n */ - noeud *nn; - static int compteur = 0; - /*pour donner un ID unique aux noeuds. juste pour debug */ - - nn = (noeud *) fabmalloc(sizeof(noeud)); - nn->type = type; - nn->pere = pere; - /* nn->fpere ne peut etre deja mis a jour... */ - nn->sommet = sommet; - nn->ps = n + 2; - nn->ds = -2; - /*ces valeurs pour distinguer "non calcule" (-2) */ - /* de "abscence de separateur" (-1). De plus, on fera des min et des */ - /* max sur les bords */ - nn->bg = n + 2; - nn->bd = -2; /* idem */ - - nn->fils = NULL; - nn->lastfils = NULL; - nn->id = compteur; - compteur++; - return nn; -} - -void ajoutfils(noeud * pere, noeud * nfils) -{ - fils *nf; - /* noter que c'est un ajout en queue! */ - nf = (fils *) fabmalloc(sizeof(fils)); - nf->pointe = nfils; - nf->suiv = NULL; - if (pere->fils == NULL) - pere->fils = nf; /* on cree le premier fils */ - else - pere->lastfils->suiv = nf; /* on ajoute nf a la chaine */ - pere->lastfils = nf; - nfils->pere = pere; /* normalement: redondant,mais... */ - nfils->fpere = nf; -} - -void fusionne(noeud * pere, noeud * artefact) -{ - /*fusionne un artefact a son pere. - utilise le champ fpere qui permet de savoir ou se greffer - une structure fils sera detruite dans l'operation: artefact->fils */ - fils *greffe; - fils *f; - /* met a jour la liste des peres */ - f = artefact->fils; - while (f != NULL) { - f->pointe->pere = pere; /*avant c'etait ancien... */ - /* f->pointe->fpere est inchange */ - f = f->suiv; - } - /* greffe la liste */ - greffe = artefact->fpere; - artefact->lastfils->suiv = greffe->suiv; - greffe->pointe = artefact->fils->pointe; - greffe->suiv = artefact->fils->suiv; - artefact->fils->pointe->fpere = greffe; /*artefact->fils a disparu */ - if (pere->lastfils == greffe) - pere->lastfils = artefact->lastfils; -} - -void -extraire(noeud * ancien, noeud * nouveau, fils * premier, fils * dernier) -{ - /* extrait la liste [premier...dernier] des fils de l'ancien noeud, - et en fait la liste des fils du nouveau noeud */ - fils *nf; /* il faut une structure fils de plus */ - fils *f; /*indice de mise a jour */ - nf = (fils *) fabmalloc(sizeof(fils)); - nf->pointe = premier->pointe; - nf->suiv = premier->suiv; - premier->pointe->fpere = nf; - nouveau->pere = ancien; - nouveau->fils = nf; - nouveau->lastfils = dernier; - nouveau->bg = premier->pointe->bg; - nouveau->bd = dernier->pointe->bd; - nouveau->ps = premier->pointe->bg; /* nouveau est suppose etre un */ - nouveau->ds = dernier->pointe->bd; /* module, donc bords=separateurs! */ - if (ancien->lastfils == dernier) - ancien->lastfils = premier; - /* ecrase l'ancier premier */ - nouveau->fpere = premier; - premier->pointe = nouveau; - premier->suiv = dernier->suiv; - /* met a jour dernier */ - dernier->suiv = NULL; - /* met a jour la liste des peres */ - f = nf; - while (f != dernier->suiv) { - f->pointe->pere = nouveau; /*avant c'etait ancien... */ - f->pointe->fpere = premier; - f = f->suiv; - } -} - -void printnoeud(noeud * N, int level) -{ - /* imprime recursivement l'arbre par parcours en profondeur */ - fils *ffils; - noeud *nfils; - int i; - ffils = N->fils; - - for (i = 0; i < level - 1; i++) - printf(" |"); - if (N->pere == NULL) - printf(" "); - else - printf(" +-"); - switch (N->type) { - case UNKN: - printf("Noeud\n"); - break; - case MODULE: - printf("Module\n"); - break; - case ARTEFACT: - printf("Artefact\n"); - break; - case SERIE: - printf("Serie \n"); - break; - case PARALLELE: - printf("Parallele \n"); - break; - case PREMIER: - printf("Premier \n"); - break; - } - - do { - nfils = ffils->pointe; - if (nfils->type == FEUILLE) { - for (i = 0; i < level; i++) - printf(" |"); - printf(" +--"); - printf("%i\n", 1 + nfils->nom); - } - else { - printnoeud(nfils, level + 1); - } - ffils = ffils->suiv; - } - while (ffils != NULL); -} - -void printarbre(noeud * N) -{ - printnoeud(N, 0); -} - -noeud *algo2(graphe G, sommet **S) -{ -/* algorithme de decomposition modulaire, deuxieme passe -entree: le graphe G, et sa permutation factorisante S. -sortie: un pointeur sur un arbre de decomposition modulaire -*/ - /* debug: S n'est utilise que pour mettre vrainom a jour */ - int n; //nombre de sommets du graphe - int *ouvrantes; /* tableau du nombre de parentheses ouvrantes */ - /* ouvrante[i]=3 ssi i-1(((i */ - /* ouvrante [0]=3: (((0 */ - - int *fermantes; /* idem fermantes[i]=2 ssi i)))i+1 - fermante [n-1]=2 ssi n))) */ - int *ps; /* ps[i]=premier separateur de (i,i+1) */ - int *ds; - - int i, j; /*indices de paires ou de sommets */ - - sadj *a1, *a2; /* parcours de liste d'adjacence */ - - noeud *racine; /*racine du pseudocoardre */ - noeud *courant, *nouveau; /* noeud courant du pseudocoarbre */ - noeud **pileinterne; /* pile des modules pour les passes 3,5,5 */ - int indicepileinterne = 0; /*pointeur dans cette pile */ - int taillepileinterne; /* taille de la pile apres la 2eme passe */ - - int *adjii; /* adjii[i]=1 ssi S[i] et S[i+1] sont */ - /* adjacents */ - /*PROPHASE : initialisations */ - n=G.n; - ouvrantes = (int *) fabmalloc(n * sizeof(int)); - fermantes = (int *) fabmalloc(n * sizeof(int)); - ps = (int *) fabmalloc(n * sizeof(int)); - ds = (int *) fabmalloc(n * sizeof(int)); - pileinterne = (noeud **) fabmalloc((2 * n + 4) * sizeof(noeud *)); - adjii= (int *) fabmalloc(n*sizeof(int)); - /*pas plus de 2n+4 noeuds internes dans le pseudocoarbre */ - for (i = 0; i < n; i++) { - ouvrantes[i] = 0; - fermantes[i] = 0; - adjii[i]=0; - } - - /* remplit adjii qui dit si S[i] adjacent a S[i+1] */ - for(i=0; iadj; - while((a1!=NULL)&&(a1->pointe->place != i+1)) - a1=a1->suiv; - if( a1 == NULL) - adjii[i]=0; - else // a1->pointe->place==i+1, donc i adj i+1 - adjii[i]=1; - } - adjii[n-1]=0; //perfectionnisme - - /* PREMIERE PASSE - on va parentheser la permutation factorisante. - tout bonnement, on lit S et on cherche les separateurs; - apres quoi ouvrantes et fermantes sont ecrites - complexite: O(n^2) */ - - ouvrantes[0] = 1; - fermantes[n - 1] = 1; /* parentheses des bords */ - - for (i = 0; i < n - 1; i++) { - /*recherche de ps(i,i+1) */ - a1=S[i]->adj; - a2=S[i+1]->adj; - while((a1!=NULL) && (a2!=NULL) && (a1->pointe->placepointe->placepointe->place == a2->pointe->place)) - { - a1=a1->suiv; - a2=a2->suiv; - } - - //arbre de decision complique pour trouver le premier separateur! - if( ((a1==NULL) && (a2==NULL)) - ||((a1==NULL) &&(a2->pointe->place >= i)) - ||((a2==NULL) && (a1->pointe->place >= i)) - ||((a1!=NULL) && (a2!=NULL) && (a1->pointe->place >= i) && (a2->pointe->place >= i))) - //pas de separateur - ps[i]=i+1; - else - { - if((a1==NULL) || (a1->pointe->place >= i)) - ps[i]=a2->pointe->place; - else if((a2==NULL) || (a2->pointe->place >= i)) - ps[i]=a1->pointe->place; - else - { - if((a1->suiv!=NULL)&&(a1->suiv->pointe->place == a2->pointe->place)) - ps[i]=a1->pointe->place; - else if ((a2->suiv!=NULL)&&(a2->suiv->pointe->place == a1->pointe->place)) - ps[i]=a2->pointe->place; - else - ps[i]=min(a1->pointe->place , a2->pointe->place); - } - ouvrantes[ps[i]]++; /* marque la fracture gauche, if any */ - fermantes[i]++; - } - if (DEBUG) - printf("ps(%i,%i)=%i\n", i , i+1, ps[i]); - - /*recherche de ds(i,i+1) - plus penible encore!*/ - a1=S[i]->adj; - if(a1!=NULL) // se place en queue de liste. - while(a1->suiv!=NULL) - a1=a1->suiv; - a2=S[i+1]->adj; - if(a2!=NULL) - while(a2->suiv!=NULL) - a2=a2->suiv; - while((a1!=NULL) && (a2!=NULL) && (a1->pointe->place > i+1) && - (a2->pointe->place > i+1) && (a1->pointe->place == a2->pointe->place)) - { - a1=a1->prec; - a2=a2->prec; - } - if( ((a1==NULL) && (a2==NULL)) - ||((a1==NULL) && (a2->pointe->place <= i+1)) - ||((a2==NULL) && (a1->pointe->place <= i+1)) - ||((a1!=NULL) && (a2!=NULL) && (a1->pointe->place <= i+1) && (a2->pointe->place <= i+1))) - //pas de separateur - ds[i]=i+1; - else - { - if((a1==NULL) || (a1->pointe->place <= i+1)) - ds[i]=a2->pointe->place; - else if((a2==NULL) || (a2->pointe->place <= i+1)) - ds[i]=a1->pointe->place; - else - { - if((a1->prec!=NULL)&&(a1->prec->pointe->place == a2->pointe->place)) - ds[i]=a1->pointe->place; - else if((a2->prec!=NULL)&&(a2->prec->pointe->place == a1->pointe->place)) - ds[i]=a2->pointe->place; - else - ds[i]=max(a1->pointe->place , a2->pointe->place); - } - - - //ds[i] = j; - ouvrantes[i + 1]++; /* marque la fracture gauche, if any */ - fermantes[ds[i]]++; /* attention aux decalages d'indices */ - } - if (DEBUG) - printf("ds(%i,%i)=%i\n", i,i+1,ds[i]); - //S[i]->nom + 1, S[i + 1]->nom + 1, S[ds[i]]->nom + 1); - } - - /*DEUXIEME PASSE: construction du pseudocoarbre */ - - racine = nouvnoeud(UNKN, NULL, -1, n); - courant = racine; - for (i = 0; i < n; i++) { - /*1: on lit des parentheses ouvrantes: descentes */ - for (j = 0; j < ouvrantes[i]; j++) { - /*Descente vers un nouveau noeud */ - nouveau = nouvnoeud(UNKN, courant, -1, n); - ajoutfils(courant, nouveau); /*on l'ajoute... */ - courant = nouveau; /* et on descent */ - if (DEBUG) - printf("("); - } - /* 2: on lit le sommet: feuille */ - nouveau = nouvnoeud(FEUILLE, courant, i, n); - ajoutfils(courant, nouveau); /*on ajoute le bebe... */ - (*nouveau).ps = i; - (*nouveau).ds = i; - (*nouveau).bg = i; - (*nouveau).bd = i; - nouveau->nom = S[i]->nom; - /* et pourquoi pas i ? Je m'embrouille... */ - if (DEBUG) - printf(" %i ", S[i]->nom + 1); - - /*3: on lit des parentheses fermantes: remontees */ - for (j = 0; j < fermantes[i]; j++) { - /*ASTUCE: ici on va en profiter pour supprimer les - noeuds a un fils, afin d'economiser une passe */ - if (courant->fils == courant->lastfils) { /*just one son */ - courant->pere->lastfils->pointe = courant->fils->pointe; - courant->fils->pointe->pere = courant->pere; - courant->fils->pointe->fpere = courant->pere->lastfils; - /* explication: le dernier fils de courant.pere est - actuellement courant himself. Il est donc pointe par - courant.pere.lastfils.pointe. Il suffit de changer ce - pointeur pour qu'il pointe maintenant non plus sur courant, - mais sur l'unique fils de courant: courant.fils.pointe. - Ouf! */ - /* NB: courant est maintenant deconnecte de l'arbre. - on pourrait faire un free() mais bon... */ - } - else { - /*on empile ce noeud interne. - L'ordre est celui de la postvisite d'un DFS */ - pileinterne[indicepileinterne] = courant; - indicepileinterne++; - } - /* et dans tous les cas: on remonte! */ - courant = courant->pere; - if (DEBUG) - printf(")"); - } - } - if (DEBUG) - printf("\n"); - - /* on enleve un ptit defaut */ - racine = racine->fils->pointe; - racine->pere = NULL; - racine->fpere = NULL; - if (DEBUG) { - printf("Arbre apres la deuxieme passe:\n"); - printnoeud(racine, 0); - } - - /*TROISIEME PASSE */ - /* A ce stade, on a un pseudocoarbre de racine racine, - sans noeuds a un fils, et dont les etiquettes sont - FEUIILLE ou UNKN. Il y a une pile des noeuds UNKN, stockes - dans l'ordre de la postvisite d'un parcours en profondeur. - On va s'en servir pour faire remonter les bords et les - separateurs de bas en haut */ - - taillepileinterne = indicepileinterne; - for (indicepileinterne = 0; indicepileinterne < taillepileinterne; - indicepileinterne++) { - noeud *scanne; - fils *nextfils; - noeud *succourant; - /* scanne est le noeud (pere) que l'on regarde; - nextfils parcours sa liste de fils; - courant est le fils actuellement examine et - succourant=succ(courant) */ - noeud *debutnoeud; - fils *debutfils; - /*deux variables utilise pour la recherche de jumeaux: - debut du bloc max */ - - scanne = pileinterne[indicepileinterne]; /*he oui, la pile */ - nextfils = scanne->fils; /*on commence au premier fils */ - do { - /*la boucle chiante... cf mon memoire de DEA */ - courant = nextfils->pointe; - /* bords */ - scanne->bg = min(scanne->bg, courant->bg); - scanne->bd = max(scanne->bd, courant->bd); - /*separateurs */ - scanne->ps = min(scanne->ps, courant->ps); - if (scanne->fils->pointe != courant) - /*ce n'est pas le premier fils */ - scanne->ps = min(scanne->ps, ps[(courant->bg) - 1]); - scanne->ds = max(scanne->ds, courant->ds); - if (scanne->lastfils->pointe != courant) - /*ce n'est pas le dernier fils */ - scanne->ds = max(scanne->ds, ds[courant->bd]); - - nextfils = nextfils->suiv; - } - while (nextfils != NULL); - - - if (DEBUG) - printf("noeud %i-%i: ps=%i ds=%i", 1 + scanne->bg, - 1 + scanne->bd, 1 + scanne->ps, 1 + scanne->ds); - - /* maintenant le test tout simple pour savoir si on a un module: */ - if (((scanne->ps) == (scanne->bg)) - && ((scanne->ds) == (scanne->bd))) { - /*on a un module */ - scanne->type = MODULE; - if (DEBUG) - printf(" Module.\n"); - } - else { - scanne->type = ARTEFACT; - if (DEBUG) - printf(" artefact.\n"); - } - } - - if (DEBUG) { - printf("Arbre apres la troisieme passe:\n"); - printnoeud(racine, 0); - } - - /* QUATRIEME PASSE */ - /* technique:on se contente de fusionner les artefacts a leurs parents - ca se fait de bas en haut grace a pileinterne (toujours elle!) */ - - for (indicepileinterne = 0; indicepileinterne < taillepileinterne; - indicepileinterne++) { - noeud *scanne; - scanne = pileinterne[indicepileinterne]; /*he oui, la pile */ - if (scanne->type == ARTEFACT) { - /*attention! La pile peut contenir des noeuds deconnectes */ - fusionne(scanne->pere, scanne); - if (DEBUG) - printf("Artefact elimine: %i-%i\n", 1 + scanne->bg, - 1 + scanne->bd); - } - } - if (DEBUG) { - printf("Arbre apres la quatrieme passe:\n"); - printnoeud(racine, 0); - } - - /* CINQIEME ET DERNIERE PASSE */ - /* on va typer les noeuds et extraire les fusions. - comment on fait? Ben, la pile.... */ - for (indicepileinterne = 0; indicepileinterne < taillepileinterne; - indicepileinterne++) { - noeud *scanne; - fils *nextfils; - noeud *succourant; - /* scanne est le noeud (pere) que l'on regarde; - nextfils parcours sa liste de fils; - courant est le fils actuellement examine et - succourant=succ(courant) */ - noeud *debutnoeud; - fils *debutfils; - /*deux variables utilise pour la recherche de jumeaux: - debut du bloc max */ - - scanne = pileinterne[indicepileinterne]; - if (scanne->type != MODULE) - continue; /* je traite que les modules */ - - nextfils = scanne->fils; /*on commence au premier fils */ - while (1) { - courant = nextfils->pointe; - succourant = nextfils->suiv->pointe; - if (ps[courant->bd] >= courant->bg - && ds[courant->bd] <= succourant->bd) { - /*Des jumeaux!! ->serie ou parallele! */ - /* on va determiner le bloc max de jumeaux consecutifs */ - debutnoeud = courant; - debutfils = nextfils; - while (ps[courant->bd] >= courant->bg && - ds[courant->bd] <= succourant->bd && - nextfils->suiv != NULL) { - nextfils = nextfils->suiv; - courant = nextfils->pointe; - if (nextfils->suiv == NULL) - break; - succourant = nextfils->suiv->pointe; - } - /*maintenant on connait la taille du bloc: il va de - debutnoeud a courant inclus, - et dans la liste des fils de scanne, - il s'etend de debutfils a nextfils inclus. - On extrait cette liste pour en faire les fils d'un - nouveau noeud... sauf si pas la peine! */ - if (debutfils == scanne->fils - && nextfils == scanne->lastfils) { - /* le noeud scanne etait serie ou parallele */ - if ( adjii[debutnoeud->bd] !=0) - scanne->type = SERIE; - else - scanne->type = PARALLELE; - } - else { - if ( adjii[debutnoeud->bd]!=0) - /*seule cette ligne distingue G de G' !! */ - { - nouveau = nouvnoeud(SERIE, scanne, -1, n); - if (DEBUG) - printf("Module serie extrait: %i-%i\n", - 1 + debutnoeud->bg, 1 + courant->bd); - } - else { - nouveau = nouvnoeud(PARALLELE, scanne, -1, n); - if (DEBUG) - printf("Module parallele extrait: %i-%i\n", - 1 + debutnoeud->bg, 1 + courant->bd); - } - extraire(scanne, nouveau, debutfils, nextfils); - } - } - if (scanne->type == MODULE) - scanne->type = PREMIER; - if (nextfils->suiv == NULL || nextfils->suiv->suiv == NULL - || nextfils->suiv->suiv->suiv == NULL) - break; - nextfils = nextfils->suiv; - } - } - if (DEBUG) { - printf("Arbre final:\n"); - printnoeud(racine, 0); - } - return racine; -} - -void PrintG(graphe G) -/* affiche le graphe */ -{ - int i,r; adj *a; - r=0; - for(i=0;is); - a=a->suiv; - } - printf("\n"); - } -} -void PrintGS(sommet **S, int n) -/* affiche le graphe trie selon S (celui utilise par algo2)*/ -{ - int i; sadj *a; - for(i=0;iadj; - while(a!=NULL) - { - printf("%i ", a->pointe->place); - a=a->suiv; - } - printf("\n"); - } -} - -void PrintS2(sommet **S, int n) - /* affiche la permutation factorisante */ -{ - int i; - printf( "Place (nouvelle num) "); - for(i=0;iplace); - printf("\nNom (ancienne num) : "); - for(i=0;inom); - printf("\n"); -} - - -/* la fonction principale; qui fait pas grand'chose....*/ -noeud *decomposition_modulaire(graphe G) -{ - - sommet **S; /* la permutation factorisante */ - noeud *Racine; /* le futur arbre de decomposition */ - - setbuf(stdout,NULL); - - S = algo1(G); /* premiere partie: calcul - de la permutation factorisante */ - - TrierTous(S,G.n,Calculm(G));/* Trie les listes d'adjacence selon S - */ - if(DEBUG) - { - PrintGS(S,G.n); - PrintS2(S,G.n); - } - Racine = algo2(G, S); /* deuxieme partie: calcul de l'arbre */ - return Racine; -} diff --git a/src/sage/graphs/modular_decomposition/src/dm_english.h b/src/sage/graphs/modular_decomposition/src/dm_english.h deleted file mode 100644 index 1987fa0d70c..00000000000 --- a/src/sage/graphs/modular_decomposition/src/dm_english.h +++ /dev/null @@ -1,117 +0,0 @@ -/****************************************************** - -Copyright 2004, 2010 Fabien de Montgolfier -fm@liafa.jussieu.fr - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -**********************************************************/ - -/******************************************************** - -MODULAR DECOMPOSITION OF UNDIRECTED GRAPHS -by Fabien de Montgolfier - -The program dm.c offer a single function, -decomposition_modulaire(). - -The input is a graph, its output is its modular decomposition tree. - -Input graph is stored using adjacency lists, and trees using a pointers representation (see below) -********************************************************/ - -#include -#include - -/********************************** -Definition of graphs (for the input) -The graph has n vertices. Each vertex is numbered from 0 to n-1. -A graph is a structure (not an object-oriented program !). -If you declare "graphe Gr", Gr.n is the numer of vertices of Gr. -Gr.G is an array of size n. Gr.G[i] points the first element -of adjaceny list of vertex i (NULL if i is isolated) -An adjency list is an usual linked list (vertex;pointer to next). -Adjacency lists may be unsorted. - -WARNING : the input graph *MUST* be symmetric : if j belongs to adjacency list of i then i belongs to adjacency list of j. Graphs that do not respect this produce unpredictible and false results. -**********************************/ - - -/* structure for creating an adjacency list */ -typedef struct Adj { - int s; // number of the vertex - struct Adj *suiv; // adress of next pair in the list, NULL if last -} adj; - -typedef struct { - int n; //number of vertices of the graph - adj **G; // array of size n. G[i] points the first pair of the adjaceny list of vertex i - -} graphe; - -/******************************** -Output : definition of modular decomposition tree. -Each internal node is labelled SERIE (for series), PARALLELE (for parallel) or PREMIER (for prime) depending of the quotient's type. -Each leaf is labelled FEUILLE and also contains the vertex number of the leaf. -As the tree is an inclusion tree, the vertex-set corresponding to an internal node correspond to the vertices numbers of the leaves that descend from that tree. The function decomposition_modulaire() return a pointer to the root of the tree. - - - -/* define the type of nodes. UNKN,MODULE,ARTEFACT are for internal use*/ - -#define FEUILLE 0 // the node is a leaf -#define UNKN 1 -#define MODULE 2 -#define ARTEFACT 3 -#define SERIE 4 // series composition -#define PARALLELE 5 // parallel composition -#define PREMIER 6 // prime composition - -/* defines a node of the tree */ - -typedef struct Noeud { - int type; // is FEUILLE, SERIE, PARALLELE or PREMIER - struct Noeud *pere; // adress of parent node, NULL if root - struct Fils *fpere; // points the head of the linked list of sons (if type is not FEUILLE, else is NULL) - int ps; // internal use - int bg; // internal use - int ds; // internal use - int bd; // internal use - int sommet; // internal use - int nom; // if type=FEUILLE, number of the corresponding vertex of the graph - struct Fils *fils; // points the head of the linked list of sons - struct Fils *lastfils; // internal use (points the last item in the listed list of sons) - int id; // internal use (node unique ID) -} noeud; - -/* linked list that strore the sons of an internal node (in any order) */ - -typedef struct Fils { - struct Noeud *pointe; // adress of the node in the tree - struct Fils *suiv; // adress of the next pair in the list, NULL if last -} fils; - -/* prototype of the function. - Input is a graph, output the root of the modular decomposition tree */ - -noeud *decomposition_modulaire(graphe G); - - - - - - - - diff --git a/src/sage/graphs/modular_decomposition/src/random.c b/src/sage/graphs/modular_decomposition/src/random.c deleted file mode 100644 index 26951e28118..00000000000 --- a/src/sage/graphs/modular_decomposition/src/random.c +++ /dev/null @@ -1,149 +0,0 @@ -/****************************************************** - -Copyright 2004, 2010 Fabien de Montgolfier -fm@liafa.jussieu.fr - -This program is free software; you can redistribute it and/or -modify it under the terms of the GNU General Public License -as published by the Free Software Foundation; either version 2 -of the License, or (at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program; if not, write to the Free Software -Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - -**********************************************************/ - -#include -#include "dm_english.h" - -#define NIV 20 -#define VERBEUX 1 - -//extern noeud *decomposition_modulaire(int *,int); -extern void printarbre(noeud *); -/* ppm est la part par million d'arretes voulues dans le graphe */ - -void compte(noeud *N, int level, int *C) -{ - fils *F; - switch(N->type) - { - case SERIE: C[4*level]++; break; - case PARALLELE: C[4*level+1]++; break; - case PREMIER: C[4*level+2]++; break; - case FEUILLE: C[4*level+3]++; break; - } - if(N->type!=FEUILLE) - for(F=N->fils;F!=NULL;F=F->suiv) - compte(F->pointe, level+1, C); -} - -void test(int n, long int ppm, int *C) -{ - /*genere un graphe aleatoire, l'affiche, le decompose*/ - graphe G; - - int i,j; - adj *a; - noeud *R; - - - srandom((unsigned)time(NULL)); - - G.n=n; - G.G=(adj **)malloc(n*sizeof(adj *)); - - for(i=0;isuiv) - printf("%i ",1+a->s); - } - for(j=i+1;js=j; - a->suiv=G.G[i]; - G.G[i]=a; - // et reciproquement - a=(adj *)malloc(sizeof(adj)); - a->s=i; - a->suiv=G.G[j]; - G.G[j]=a; - if(VERBEUX) - printf("%i ",j+1); - } - } - if(VERBEUX) - printf("\n"); - } - - // appel de la fonction de decomposition - R = decomposition_modulaire(G); - - // affichage de l'arbre - if(VERBEUX) - printarbre(R); - - compte(R,0,C); - printf("Statistiques sur l'arbre de decomposition:\n"); - if(C[0]) - printf("La racine est Serie\n"); - else if(C[1]) - printf("La racine est Parrallele\n"); - else - printf("La racine est Premier \n"); - for(i=1 ; i Date: Fri, 13 Mar 2015 21:21:41 +0100 Subject: [PATCH 081/129] Don't use sig_str() message for AlarmInterrupt --- src/sage/ext/interrupt.pyx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/sage/ext/interrupt.pyx b/src/sage/ext/interrupt.pyx index 96498223ef6..20139a6884c 100644 --- a/src/sage/ext/interrupt.pyx +++ b/src/sage/ext/interrupt.pyx @@ -64,15 +64,13 @@ cdef int sig_raise_exception(int sig, const char* msg) except 0: """ if sig == SIGHUP or sig == SIGTERM: # Redirect stdin from /dev/null to close interactive sessions - freopen("/dev/null", "r", stdin); + freopen("/dev/null", "r", stdin) # This causes Python to exit raise SystemExit if sig == SIGINT: raise KeyboardInterrupt if sig == SIGALRM: - if msg == NULL: - msg = "" - raise AlarmInterrupt(msg) + raise AlarmInterrupt if sig == SIGILL: if msg == NULL: msg = "Illegal instruction" @@ -91,7 +89,7 @@ cdef int sig_raise_exception(int sig, const char* msg) except 0: raise SignalError(msg) if sig == SIGSEGV: if msg == NULL: - msg = "Segmentation fault"; + msg = "Segmentation fault" raise SignalError(msg) raise SystemError("unknown signal number %s"%sig) @@ -116,6 +114,17 @@ def do_raise_exception(sig, msg=None): Traceback (most recent call last): ... SystemError: unknown signal number 0 + + For interrupts, the message is ignored, see :trac:`17949`:: + + sage: do_raise_exception(signal.SIGINT, "ignored") + Traceback (most recent call last): + ... + KeyboardInterrupt + sage: do_raise_exception(signal.SIGALRM, "ignored") + Traceback (most recent call last): + ... + AlarmInterrupt """ cdef const char* m if msg is None: From d4f21852f01101c35e895319df17ee45a91df867 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Fri, 13 Mar 2015 21:54:25 +0100 Subject: [PATCH 082/129] Remove obsolete c_lib files --- src/c_lib/SConstruct | 8 +- src/c_lib/include/mpn_pylong.h | 49 ---- src/c_lib/include/mpz_longlong.h | 6 - src/c_lib/include/mpz_pylong.h | 26 --- src/c_lib/include/ntl_wrap.h | 1 - src/c_lib/include/stdsage.h | 154 ------------- src/c_lib/src/interrupt.c | 1 - src/c_lib/src/mpn_pylong.c | 209 ------------------ src/c_lib/src/mpz_longlong.c | 64 ------ src/c_lib/src/mpz_pylong.c | 81 ------- src/c_lib/src/stdsage.c | 38 ---- src/sage/combinat/matrices/dancing_links.pyx | 2 + src/{c_lib/include => sage/ext}/ccobject.h | 6 +- src/sage/ext/python_rich_object.pxi | 5 +- src/sage/ext/stdsage.pxd | 39 ++++ src/sage/ext/stdsage.pxi | 26 +-- src/sage/libs/ntl/decl.pxi | 2 + src/sage/libs/pari/decl.pxi | 4 +- src/sage/libs/pari/declinl.pxi | 2 +- .../include => sage/libs/pari}/parisage.h | 0 src/sage/libs/singular/singular-cdefs.pxi | 5 +- src/sage/matrix/matrix.pyx | 2 +- src/sage/rings/integer.pyx | 2 +- src/sage/rings/real_double.pyx | 2 +- src/sage/structure/element.pyx | 2 +- 25 files changed, 64 insertions(+), 672 deletions(-) delete mode 100644 src/c_lib/include/mpn_pylong.h delete mode 100644 src/c_lib/include/mpz_longlong.h delete mode 100644 src/c_lib/include/mpz_pylong.h delete mode 100644 src/c_lib/include/stdsage.h delete mode 100644 src/c_lib/src/mpn_pylong.c delete mode 100644 src/c_lib/src/mpz_longlong.c delete mode 100644 src/c_lib/src/mpz_pylong.c delete mode 100644 src/c_lib/src/stdsage.c rename src/{c_lib/include => sage/ext}/ccobject.h (97%) create mode 100644 src/sage/ext/stdsage.pxd rename src/{c_lib/include => sage/libs/pari}/parisage.h (100%) diff --git a/src/c_lib/SConstruct b/src/c_lib/SConstruct index 2420f39ae3d..5bd914ab3d1 100644 --- a/src/c_lib/SConstruct +++ b/src/c_lib/SConstruct @@ -128,13 +128,11 @@ env['PYV']=platform.python_version().rsplit('.', 1)[0] # whitespace without the syntax clutter of lists of strings. includes = ['$SAGE_LOCAL/include/', '$SAGE_LOCAL/include/python$PYV/', '$SAGE_LOCAL/include/NTL/', 'include'] -cFiles = Split( "interrupt.c mpn_pylong.c mpz_pylong.c") + \ - Split( "mpz_longlong.c stdsage.c" ) +cFiles = Split( "interrupt.c") cppFiles = Split( "ntl_wrap.cpp" ) srcFiles = cFiles + cppFiles -incFiles = Split( "ccobject.h" ) + \ - Split( "interrupt.h mpn_pylong.h mpz_longlong.h" ) + \ - Split( "mpz_pylong.h ntl_wrap.h parisage.h stdsage.h" ) +incFiles = Split( "interrupt.h" ) + \ + Split( "ntl_wrap.h" ) lib = env.SharedLibrary( "csage", [ "src/" + x for x in srcFiles ], LIBS=['ntl', 'pari', 'gmp', 'python$PYV'], diff --git a/src/c_lib/include/mpn_pylong.h b/src/c_lib/include/mpn_pylong.h deleted file mode 100644 index be5b929d705..00000000000 --- a/src/c_lib/include/mpn_pylong.h +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef MPN_PYLONG_H -#define MPN_PYLONG_H - -#include -#include - -/************************************************************/ - -/* Python internals for pylong */ - -#include -typedef int py_size_t; /* what python uses for ob_size */ - -/************************************************************/ - -/* mpn -> pylong conversion */ - -int mpn_pylong_size (mp_ptr up, mp_size_t un); - -/* Assume digits points to a chunk of size size - * where size >= mpn_pylong_size(up, un) - */ -void mpn_get_pylong (digit *digits, py_size_t size, mp_ptr up, mp_size_t un); - -/************************************************************/ - -/* pylong -> mpn conversion */ - -mp_size_t mpn_size_from_pylong (digit *digits, py_size_t size); - -/* Assume up points to a chunk of size un - * where un >= mpn_size_from_pylong(digits, size) - */ -void mpn_set_pylong (mp_ptr up, mp_size_t un, digit *digits, py_size_t size); - -/************************************************************/ - -/* Python hashing */ - -/* - * for an mpz, this number has to be multiplied by the sign - * also remember to catch -1 and map it to -2 ! - */ - -long mpn_pythonhash (mp_ptr up, mp_size_t un); - -/************************************************************/ - -#endif diff --git a/src/c_lib/include/mpz_longlong.h b/src/c_lib/include/mpz_longlong.h deleted file mode 100644 index ebee1a5c11c..00000000000 --- a/src/c_lib/include/mpz_longlong.h +++ /dev/null @@ -1,6 +0,0 @@ - -#include - -void mpz_set_longlong(mpz_ptr z, long long val); - -void mpz_set_ulonglong(mpz_ptr z, unsigned long long val); diff --git a/src/c_lib/include/mpz_pylong.h b/src/c_lib/include/mpz_pylong.h deleted file mode 100644 index 8523ccc2e15..00000000000 --- a/src/c_lib/include/mpz_pylong.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef MPZ_PYLONG_H -#define MPZ_PYLONG_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* mpz -> pylong conversion */ -PyObject * mpz_get_pylong(mpz_srcptr z); - -/* mpz -> pyint/pylong conversion */ -PyObject * mpz_get_pyintlong(mpz_srcptr z); - -/* pylong -> mpz conversion */ -int mpz_set_pylong(mpz_ptr z, PyObject * ll); - -/* mpz python hash */ -long mpz_pythonhash (mpz_srcptr z); - -#ifdef __cplusplus -} /* extern "C" */ -#endif -#endif diff --git a/src/c_lib/include/ntl_wrap.h b/src/c_lib/include/ntl_wrap.h index d1d6395420a..0d6c3e01f43 100644 --- a/src/c_lib/include/ntl_wrap.h +++ b/src/c_lib/include/ntl_wrap.h @@ -31,7 +31,6 @@ using namespace NTL; #endif #include "Python.h" -#include "ccobject.h" EXTERN void del_charstar(char*); diff --git a/src/c_lib/include/stdsage.h b/src/c_lib/include/stdsage.h deleted file mode 100644 index 4d40b55f192..00000000000 --- a/src/c_lib/include/stdsage.h +++ /dev/null @@ -1,154 +0,0 @@ -/****************************************************************************** - Copyright (C) 2006 William Stein - 2006 Martin Albrecht - - Distributed under the terms of the GNU General Public License (GPL) - as published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - http://www.gnu.org/licenses/ - -******************************************************************************/ - -/** - * @file stdsage.h - * - * @author William Stein - * @auhtor Martin Albrecht - * - * @brief General C (.h) code this is useful to include in any pyrex module. - * - * Put - @verbatim - - include 'relative/path/to/stdsage.pxi' - - @endverbatim - * - * at the top of your Pyrex file. - * - * These are mostly things that can't be done in Pyrex. - */ - -#ifndef STDSAGE_H -#define STDSAGE_H - -#include "Python.h" - -/* Building with this not commented out causes - serious problems on RHEL5 64-bit for Kiran Kedlaya... i.e., it doesn't work. */ -/* #include "ccobject.h" */ - -#ifdef __cplusplus -extern "C" { -#endif - - -/****************************************** - Some macros exported for Pyrex in cdefs.pxi - ****************************************/ - -/** Tests whether zzz_obj is of type zzz_type. The zzz_type must be a - * built-in or extension type. This is just a C++-compatible wrapper - * for PyObject_TypeCheck. - */ -#define PY_TYPE_CHECK(zzz_obj, zzz_type) \ - (PyObject_TypeCheck((PyObject*)(zzz_obj), (PyTypeObject*)(zzz_type))) - -/** Tests whether zzz_obj is exactly of type zzz_type. The zzz_type must be a - * built-in or extension type. - */ -#define PY_TYPE_CHECK_EXACT(zzz_obj, zzz_type) \ - ((PyTypeObject*)PY_TYPE(zzz_obj) == (PyTypeObject*)(zzz_type)) - -/** Returns the type field of a python object, cast to void*. The - * returned value should only be used as an opaque object e.g. for - * type comparisons. - */ -#define PY_TYPE(zzz_obj) ((void*)((zzz_obj)->ob_type)) - -/** Constructs a new object of type zzz_type by calling tp_new - * directly, with no arguments. - */ - -#define PY_NEW(zzz_type) \ - (((PyTypeObject*)(zzz_type))->tp_new((PyTypeObject*)(zzz_type), global_empty_tuple, NULL)) - - - /** Constructs a new object of type the same type as zzz_obj by calling tp_new - * directly, with no arguments. - */ - -#define PY_NEW_SAME_TYPE(zzz_obj) \ - PY_NEW(PY_TYPE(zzz_obj)) - -/** Resets the tp_new slot of zzz_type1 to point to the tp_new slot of - * zzz_type2. This is used in SAGE to speed up Pyrex's boilerplate - * object construction code by skipping irrelevant base class tp_new - * methods. - */ -#define PY_SET_TP_NEW(zzz_type1, zzz_type2) \ - (((PyTypeObject*)zzz_type1)->tp_new = ((PyTypeObject*)zzz_type2)->tp_new) - - -/** - * Tests whether the given object has a python dictionary. - */ -#define HAS_DICTIONARY(zzz_obj) \ - (((PyObject*)(zzz_obj))->ob_type->tp_dictoffset != NULL) - -/** - * Very very unsafe access to the list of pointers to PyObject*'s - * underlying a list / sequence. This does error checking of any kind - * -- make damn sure you hand it a list or sequence! - */ -#define FAST_SEQ_UNSAFE(zzz_obj) \ - PySequence_Fast_ITEMS(PySequence_Fast(zzz_obj, "expected sequence type")) - -/** Returns the type field of a python object, cast to void*. The - * returned value should only be used as an opaque object e.g. for - * type comparisons. - */ -#define PY_IS_NUMERIC(zzz_obj) \ - (PyInt_Check(zzz_obj) || PyBool_Check(zzz_obj) || PyLong_Check(zzz_obj) || \ - PyFloat_Check(zzz_obj) || PyComplex_Check(zzz_obj)) - - -/** This is exactly the same as isinstance (and does return a Python - * bool), but the second argument must be a C-extension type -- so it - * can't be a Python class or a list. If you just want an int return - * value, i.e., aren't going to pass this back to Python, just use - * PY_TYPE_CHECK. - */ -#define IS_INSTANCE(zzz_obj, zzz_type) \ - PyBool_FromLong(PY_TYPE_CHECK(zzz_obj, zzz_type)) - - -/** - * A global empty python tuple object. This is used to speed up some - * python API calls where we want to avoid constructing a tuple every - * time. - */ - -extern PyObject* global_empty_tuple; - - -/** - * Initialisation of signal handlers, global variables, etc. Called - * exactly once at Sage start-up. - */ -void init_csage(void); - - - -/** - * a handy macro to be placed at the top of a function definition - * below the variable declarations to ensure a function is called once - * at maximum. - */ -#define _CALLED_ONLY_ONCE static int ncalls = 0; if (ncalls>0) return; else ncalls++ - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/c_lib/src/interrupt.c b/src/c_lib/src/interrupt.c index 802c8038e2c..ab84beabe95 100644 --- a/src/c_lib/src/interrupt.c +++ b/src/c_lib/src/interrupt.c @@ -40,7 +40,6 @@ Interrupt and signal handling for Sage #ifdef __linux__ #include #endif -#include "stdsage.h" #include "interrupt.h" diff --git a/src/c_lib/src/mpn_pylong.c b/src/c_lib/src/mpn_pylong.c deleted file mode 100644 index 91e0064fcca..00000000000 --- a/src/c_lib/src/mpn_pylong.c +++ /dev/null @@ -1,209 +0,0 @@ -/* mpn <-> pylong conversion and "pythonhash" for mpn - * - * Author: Gonzalo Tornaría - * Date: March 2006 - * License: GPL v2 or later - * - * the code to change the base to 2^PyLong_SHIFT is based on the function - * mpn_get_str from GNU MP, but the new bugs are mine - * - * this is free software: if it breaks, you get to keep all the pieces - */ - -#include "mpn_pylong.h" - -/* This code assumes that PyLong_SHIFT < GMP_NUMB_BITS */ -#if PyLong_SHIFT >= GMP_NUMB_BITS -#error "Python limb larger than GMP limb !!!" -#endif - -/* Use these "portable" (I hope) sizebits functions - * We could implement this in terms of count_leading_zeros from GMP, - * but it is not exported ! - */ -static const -unsigned char -__sizebits_tab[128] = -{ - 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, - 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, - 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 -}; - -#if GMP_LIMB_BITS > 64 -#error "word size > 64 unsupported" -#endif - -static inline -unsigned long -mpn_sizebits(mp_ptr up, mp_size_t un) { - unsigned long cnt; - mp_limb_t x; - if (un==0) return 0; - cnt = (un - 1) * GMP_NUMB_BITS; - x = up[un - 1]; -#if GMP_LIMB_BITS > 32 - if ((x >> 32) != 0) { x >>= 32; cnt += 32; } -#endif -#if GMP_LIMB_BITS > 16 - if ((x >> 16) != 0) { x >>= 16; cnt += 16; } -#endif -#if GMP_LIMB_BITS > 8 - if ((x >> 8) != 0) { x >>= 8; cnt += 8; } -#endif - return cnt + ((x & 0x80) ? 8 : __sizebits_tab[x]); -} - -static inline -unsigned long -pylong_sizebits(digit *digits, py_size_t size) { - unsigned long cnt; - digit x; - if (size==0) return 0; - cnt = (size - 1) * PyLong_SHIFT; - x = digits[size - 1]; -#if PyLong_SHIFT > 32 - if ((x >> 32) != 0) { x >>= 32; cnt += 32; } -#endif -#if PyLong_SHIFT > 16 - if ((x >> 16) != 0) { x >>= 16; cnt += 16; } -#endif -#if PyLong_SHIFT > 8 - if ((x >> 8) != 0) { x >>= 8; cnt += 8; } -#endif - return cnt + ((x & 0x80) ? 8 : __sizebits_tab[x]); -} - - -/* mpn -> pylong conversion */ - -int -mpn_pylong_size (mp_ptr up, mp_size_t un) -{ - return (mpn_sizebits(up, un) + PyLong_SHIFT - 1) / PyLong_SHIFT; -} - -/* this is based from GMP code in mpn/get_str.c */ - -/* Assume digits points to a chunk of size size - * where size >= mpn_pylong_size(up, un) - */ -void -mpn_get_pylong (digit *digits, py_size_t size, mp_ptr up, mp_size_t un) -{ - mp_limb_t n1, n0; - mp_size_t i; - int bit_pos; - /* point past the allocated chunk */ - digit * s = digits + size; - - /* input length 0 is special ! */ - if (un == 0) { - while (size) digits[--size]=0; - return; - } - - i = un - 1; - n1 = up[i]; - bit_pos = size * PyLong_SHIFT - i * GMP_NUMB_BITS; - - for (;;) - { - bit_pos -= PyLong_SHIFT; - while (bit_pos >= 0) - { - *--s = (n1 >> bit_pos) & PyLong_MASK; - bit_pos -= PyLong_SHIFT; - } - if (i == 0) - break; - n0 = (n1 << -bit_pos) & PyLong_MASK; - n1 = up[--i]; - bit_pos += GMP_NUMB_BITS; - *--s = n0 | (n1 >> bit_pos); - } -} - -/* pylong -> mpn conversion */ - -mp_size_t -mpn_size_from_pylong (digit *digits, py_size_t size) -{ - return (pylong_sizebits(digits, size) + GMP_NUMB_BITS - 1) / GMP_NUMB_BITS; -} - -void -mpn_set_pylong (mp_ptr up, mp_size_t un, digit *digits, py_size_t size) -{ - mp_limb_t n1, d; - mp_size_t i; - int bit_pos; - /* point past the allocated chunk */ - digit * s = digits + size; - - /* input length 0 is special ! */ - if (size == 0) { - while (un) up[--un]=0; - return; - } - - i = un - 1; - n1 = 0; - bit_pos = size * PyLong_SHIFT - i * GMP_NUMB_BITS; - - for (;;) - { - bit_pos -= PyLong_SHIFT; - while (bit_pos >= 0) - { - d = (mp_limb_t) *--s; - n1 |= (d << bit_pos) & GMP_NUMB_MASK; - bit_pos -= PyLong_SHIFT; - } - if (i == 0) - break; - d = (mp_limb_t) *--s; - /* add some high bits of d; maybe none if bit_pos=-PyLong_SHIFT */ - up[i--] = n1 | (d & PyLong_MASK) >> -bit_pos; - bit_pos += GMP_NUMB_BITS; - n1 = (d << bit_pos) & GMP_NUMB_MASK; - } - up[0] = n1; -} - - -/************************************************************/ - -/* Hashing functions */ - -/* This is a bad hash... - * If we decide to give up pylong compatibility, we should research to - * find a decent (but fast) hash - * - * Some pointers to start: - * - * - * - */ -/* - * for an mpz, this number has to be multiplied by the sign - * also remember to catch -1 and map it to -2 ! - */ -long -mpn_pythonhash (mp_ptr up, mp_size_t un) -{ - /* Simply add all limbs */ - mp_limb_t h = 0; - mp_limb_t h0; - mp_size_t i; - for (i = 0; i < un; i++) - { - h0 = h; - h += up[i]; - /* Add 1 on overflow */ - if (h < h0) h++; - } - return h; -} - diff --git a/src/c_lib/src/mpz_longlong.c b/src/c_lib/src/mpz_longlong.c deleted file mode 100644 index 0932bf6f9ac..00000000000 --- a/src/c_lib/src/mpz_longlong.c +++ /dev/null @@ -1,64 +0,0 @@ -/* long long -> mpz conversion -* -* Author: Robert Bradshaw -* Date: November 2008 -* License: GPL v2 or later -* -* this is free software: if it breaks, you get to keep all the pieces -*/ - -#include "mpz_longlong.h" -#include - -void mpz_set_longlong(mpz_ptr z, long long val) { - if (sizeof(mp_limb_t) == sizeof(long long)) { - mpz_set_si(z, val); - } - else if (2*sizeof(mp_limb_t) == sizeof(long long)) { - mp_limb_t* limbs = (mp_limb_t*)&val; - int sign = (val > 0) - (val < 0); - val *= sign; - _mpz_realloc (z, 2); - const int endianness = 1; - if (((char*)&endianness)[0]) { - // little endian - z->_mp_d[0] = limbs[0]; - z->_mp_d[1] = limbs[1]; - } - else { - // big endian - z->_mp_d[0] = limbs[1]; - z->_mp_d[1] = limbs[0]; - } - z->_mp_size = sign * (2 - !z->_mp_d[1]); - } - else { - assert(sizeof(mp_limb_t) == sizeof(long long) || 2*sizeof(mp_limb_t) == sizeof(long long)); - } -} - - -void mpz_set_ulonglong(mpz_ptr z, unsigned long long val) { - if (sizeof(mp_limb_t) == sizeof(unsigned long long)) { - mpz_set_ui(z, (mp_limb_t)val); - } - else if (2*sizeof(mp_limb_t) == sizeof(unsigned long long)) { - mp_limb_t* limbs = (mp_limb_t*)&val; - _mpz_realloc (z, 2); - const int endianness = 1; - if (((char*)&endianness)[0]) { - // little endian - z->_mp_d[0] = limbs[0]; - z->_mp_d[1] = limbs[1]; - } - else { - // big endian - z->_mp_d[0] = limbs[1]; - z->_mp_d[1] = limbs[0]; - } - z->_mp_size = (!!z->_mp_d[1] + (z->_mp_d[1] || z->_mp_d[0])); // 0, 1, or 2 - } - else { - assert(sizeof(mp_limb_t) == sizeof(unsigned long long) || 2*sizeof(mp_limb_t) == sizeof(unsigned long long)); - } -} diff --git a/src/c_lib/src/mpz_pylong.c b/src/c_lib/src/mpz_pylong.c deleted file mode 100644 index 843b187d648..00000000000 --- a/src/c_lib/src/mpz_pylong.c +++ /dev/null @@ -1,81 +0,0 @@ -/* mpz <-> pylong conversion and "pythonhash" for mpz - * - * Author: Gonzalo Tornaría - * Date: March 2006 - * License: GPL v2 or later - * - * this is free software: if it breaks, you get to keep all the pieces - -AUTHORS: - -- David Harvey (2007-08-18): added mpz_get_pyintlong function - - */ - -#include "mpn_pylong.h" -#include "mpz_pylong.h" - -/* mpz python hash */ -long -mpz_pythonhash (mpz_srcptr z) -{ - long x = mpn_pythonhash(z->_mp_d, abs(z->_mp_size)); - if (z->_mp_size < 0) - x = -x; - if (x == -1) - x = -2; - return x; -} - -/* mpz -> pylong conversion */ -PyObject * -mpz_get_pylong(mpz_srcptr z) -{ - py_size_t size = mpn_pylong_size(z->_mp_d, abs(z->_mp_size)); - PyLongObject *l = PyObject_NEW_VAR(PyLongObject, &PyLong_Type, size); - - if (l != NULL) - { - mpn_get_pylong(l->ob_digit, size, z->_mp_d, abs(z->_mp_size)); - if (z->_mp_size < 0) - Py_SIZE(l) = -Py_SIZE(l); - } - - return (PyObject *) l; -} - -/* mpz -> pyint/pylong conversion; if the value fits in a python int, it -returns a python int (optimised for that pathway), otherwise returns -a python long */ -PyObject * -mpz_get_pyintlong(mpz_srcptr z) -{ - if (mpz_fits_slong_p(z)) - return PyInt_FromLong(mpz_get_si(z)); - - return mpz_get_pylong(z); -} - -/* pylong -> mpz conversion */ -int -mpz_set_pylong(mpz_ptr z, PyObject * ll) -{ - register PyLongObject * l = (PyLongObject *) ll; - mp_size_t size; - int i; - - if (l==NULL || !PyLong_Check(l)) { - PyErr_BadInternalCall(); - return -1; - } - - size = mpn_size_from_pylong(l->ob_digit, abs(Py_SIZE(l))); - - if (z->_mp_alloc < size) - _mpz_realloc (z, size); - - mpn_set_pylong(z->_mp_d, size, l->ob_digit, abs(Py_SIZE(l))); - z->_mp_size = Py_SIZE(l) < 0 ? -size : size; - - return size; -} - diff --git a/src/c_lib/src/stdsage.c b/src/c_lib/src/stdsage.c deleted file mode 100644 index 7a4b88ed7c9..00000000000 --- a/src/c_lib/src/stdsage.c +++ /dev/null @@ -1,38 +0,0 @@ -/** - * @file stdsage.c - * - * Some global C stuff that gets imported into pyrex modules. - * - */ - -/****************************************************************************** - * Copyright (C) 2006 William Stein - * 2006 David Harvey - * 2006 Martin Albrecht - * 2011 Jeroen Demeyer - * - * Distributed under the terms of the GNU General Public License (GPL) - * as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * http://www.gnu.org/licenses/ - ****************************************************************************/ - - -#include "stdsage.h" -#include "interrupt.h" - -PyObject* global_empty_tuple; - -void init_global_empty_tuple(void) { - _CALLED_ONLY_ONCE; - - global_empty_tuple = PyTuple_New(0); -} - - -/* This is called once during Sage startup. On some platforms like - * Cygwin, this is also called from init_csage_module(). */ -void init_csage() { - init_global_empty_tuple(); - setup_sage_signal_handler(); -} diff --git a/src/sage/combinat/matrices/dancing_links.pyx b/src/sage/combinat/matrices/dancing_links.pyx index df5936d0e2f..eaeafe2fb8c 100644 --- a/src/sage/combinat/matrices/dancing_links.pyx +++ b/src/sage/combinat/matrices/dancing_links.pyx @@ -41,9 +41,11 @@ cdef extern from "dancing_links_c.h": int search() void freemem() +cdef extern from "ccobject.h": dancing_links* dancing_links_construct "Construct"(void *mem) void dancing_links_destruct "Destruct"(dancing_links *mem) + from sage.rings.integer cimport Integer cdef class dancing_linksWrapper: diff --git a/src/c_lib/include/ccobject.h b/src/sage/ext/ccobject.h similarity index 97% rename from src/c_lib/include/ccobject.h rename to src/sage/ext/ccobject.h index 63a67d680e3..c44bf3ff861 100644 --- a/src/c_lib/include/ccobject.h +++ b/src/sage/ext/ccobject.h @@ -14,7 +14,7 @@ * @author Joel B. Mohler * * @brief These functions provide assistance for constructing and destructing - * C++ objects from pyrex. + * C++ objects from Cython. * */ @@ -108,6 +108,4 @@ PyObject* _to_PyString(const T *x) #endif -#endif // ndef __SAGE_CCOBJECT_H__ - - +#endif /* ifndef __SAGE_CCOBJECT_H__ */ diff --git a/src/sage/ext/python_rich_object.pxi b/src/sage/ext/python_rich_object.pxi index 0eed85a7087..bfd275dc9c6 100644 --- a/src/sage/ext/python_rich_object.pxi +++ b/src/sage/ext/python_rich_object.pxi @@ -1,7 +1,6 @@ from cpython.ref cimport PyObject, PyTypeObject cdef extern from "Python.h": - ctypedef void _typeobject ctypedef void (*freefunc)(void *) ctypedef void (*destructor)(PyObject *) ctypedef PyObject *(*getattrfunc)(PyObject *, char *) @@ -17,12 +16,14 @@ cdef extern from "Python.h": ctypedef PyObject *(*descrgetfunc) (PyObject *, PyObject *, PyObject *) ctypedef int (*descrsetfunc) (PyObject *, PyObject *, PyObject *) ctypedef int (*initproc)(PyObject *, PyObject *, PyObject *) - ctypedef PyObject *(*newfunc)(PyTypeObject *, PyObject *, PyObject *) + ctypedef object (*newfunc)(PyTypeObject *, PyObject *, PyObject *) ctypedef PyObject *(*allocfunc)(PyTypeObject *, Py_ssize_t) # We need a PyTypeObject with elements so we can # get and set tp_new, tp_dealloc, tp_flags, and tp_basicsize ctypedef struct RichPyTypeObject "PyTypeObject": + long tp_dictoffset + allocfunc tp_alloc newfunc tp_new freefunc tp_free diff --git a/src/sage/ext/stdsage.pxd b/src/sage/ext/stdsage.pxd new file mode 100644 index 00000000000..2a7ee094b3f --- /dev/null +++ b/src/sage/ext/stdsage.pxd @@ -0,0 +1,39 @@ +""" +Standard C helper code for Cython modules +""" +#***************************************************************************** +# Copyright (C) 2015 Jeroen Demeyer +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +#***************************************************************************** + +include 'python_rich_object.pxi' +from cpython.ref cimport Py_TYPE + + +cdef inline PY_NEW(type t): + """ + Return ``t.__new__(t)``. This works even for types like + :class:`Integer` where we change ``tp_new`` at runtime (Cython + optimizations assume that ``tp_new`` doesn't change). + """ + return (t).tp_new(t, NULL, NULL) + + +cdef inline void PY_SET_TP_NEW(type dst, type src): + """ + Manually set ``dst.__new__`` to ``src.__new__``. This is used to + speed up Cython's boilerplate object construction code by skipping + irrelevant base class ``tp_new`` methods. + """ + (dst).tp_new = (src).tp_new + + +cdef inline bint HAS_DICTIONARY(obj): + """ + Test whether the given object has a Python dictionary. + """ + return (Py_TYPE(obj)).tp_dictoffset != 0 diff --git a/src/sage/ext/stdsage.pxi b/src/sage/ext/stdsage.pxi index 5aa2a9d8ed8..b93074ca139 100644 --- a/src/sage/ext/stdsage.pxi +++ b/src/sage/ext/stdsage.pxi @@ -1,14 +1,8 @@ """ Standard C helper code for Cython modules - -Standard useful stuff for Sage Cython modules to include: -See stdsage.h for macros and stdsage.c for C functions. - -Each module currently gets its own copy of this, which is why -we call the initialization code below. """ #***************************************************************************** -# Copyright (C) 2005, 2006 William Stein +# Copyright (C) 2015 Jeroen Demeyer # # Distributed under the terms of the GNU General Public License (GPL) # as published by the Free Software Foundation; either version 2 of @@ -16,22 +10,6 @@ we call the initialization code below. # http://www.gnu.org/licenses/ #***************************************************************************** -cdef extern from "stdsage.h": - # Global tuple -- useful optimization - void init_global_empty_tuple() - object PY_NEW(object t) - object PY_NEW_SAME_TYPE(object t) - - void* PY_TYPE(object o) - bint PY_TYPE_CHECK(object o, object t) - bint PY_TYPE_CHECK_EXACT(object o, object t) - - object IS_INSTANCE(object o, object t) - void PY_SET_TP_NEW(object t1, object t2) - bint HAS_DICTIONARY(object o) - bint PY_IS_NUMERIC(object o) - - void init_csage() - +from sage.ext.stdsage cimport PY_NEW, HAS_DICTIONARY from sage.ext.memory cimport sage_free, sage_realloc, sage_malloc, sage_calloc from sage.ext.memory import init_memory_functions diff --git a/src/sage/libs/ntl/decl.pxi b/src/sage/libs/ntl/decl.pxi index bce10d5186b..782bc202890 100644 --- a/src/sage/libs/ntl/decl.pxi +++ b/src/sage/libs/ntl/decl.pxi @@ -2,6 +2,8 @@ include "sage/ext/cdefs.pxi" include "sage/ext/stdsage.pxi" include "sage/ext/python.pxi" +cdef extern from "ccobject.h": + pass cdef extern from "ntl_wrap.h": long NTL_OVFBND diff --git a/src/sage/libs/pari/decl.pxi b/src/sage/libs/pari/decl.pxi index 9b603ccf340..5caf1134e58 100644 --- a/src/sage/libs/pari/decl.pxi +++ b/src/sage/libs/pari/decl.pxi @@ -29,7 +29,7 @@ cdef extern from '': from sage.libs.flint.types cimport ulong -cdef extern from 'parisage.h': +cdef extern from "sage/libs/pari/parisage.h": char* PARIVERSION ctypedef long* GEN @@ -3931,7 +3931,7 @@ cdef extern from 'parisage.h': GEN uutoineg(ulong x, ulong y) long vali(GEN x) -cdef extern from 'parisage.h': +cdef extern from "sage/libs/pari/parisage.h": GEN set_gel(GEN x, long n, GEN z) # gel(x,n) = z GEN set_gmael(GEN x, long i, long j, GEN z) # gmael(x,i,j) = z GEN set_gcoeff(GEN x, long i, long j, GEN z) # gcoeff(x,i,j) = z diff --git a/src/sage/libs/pari/declinl.pxi b/src/sage/libs/pari/declinl.pxi index 5997218977c..1926a5e43e2 100644 --- a/src/sage/libs/pari/declinl.pxi +++ b/src/sage/libs/pari/declinl.pxi @@ -12,7 +12,7 @@ AUTHORS: """ -cdef extern from "parisage.h": +cdef extern from "sage/libs/pari/parisage.h": ################################################################### # # diff --git a/src/c_lib/include/parisage.h b/src/sage/libs/pari/parisage.h similarity index 100% rename from src/c_lib/include/parisage.h rename to src/sage/libs/pari/parisage.h diff --git a/src/sage/libs/singular/singular-cdefs.pxi b/src/sage/libs/singular/singular-cdefs.pxi index 5ab2708aba4..4914ffb6a9b 100644 --- a/src/sage/libs/singular/singular-cdefs.pxi +++ b/src/sage/libs/singular/singular-cdefs.pxi @@ -6,11 +6,14 @@ AUTHOR: Martin Albrecht NOTE: our ring, poly etc. types are not the SINGULAR ring, poly, etc. types. They are deferences. So a SINGULAR ring is a ring pointer here. - """ + include "sage/ext/stdsage.pxi" include "sage/ext/cdefs.pxi" +cdef extern from "ccobject.h": + pass + cdef extern from "stdlib.h": void delete "delete" (void *ptr) diff --git a/src/sage/matrix/matrix.pyx b/src/sage/matrix/matrix.pyx index 81fa6ca45d3..7c198a9bf0c 100644 --- a/src/sage/matrix/matrix.pyx +++ b/src/sage/matrix/matrix.pyx @@ -13,7 +13,7 @@ For design documentation see matrix/docs.py. # http://www.gnu.org/licenses/ ################################################################################ -include 'sage/ext/stdsage.pxi' +from sage.ext.stdsage cimport PY_SET_TP_NEW def is_Matrix(x): """ diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index bd09592ddd9..f7b4dbe819e 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -6644,7 +6644,7 @@ cdef hook_fast_tp_functions(): # Finally replace the functions called when an Integer needs # to be constructed/destructed. - hook_tp_functions(global_dummy_Integer, NULL, &fast_tp_new, NULL, &fast_tp_dealloc, False) + hook_tp_functions(global_dummy_Integer, NULL, (&fast_tp_new), NULL, &fast_tp_dealloc, False) cdef integer(x): if isinstance(x, Integer): diff --git a/src/sage/rings/real_double.pyx b/src/sage/rings/real_double.pyx index d4fdec4b3d2..cb69ac4c4aa 100644 --- a/src/sage/rings/real_double.pyx +++ b/src/sage/rings/real_double.pyx @@ -2745,7 +2745,7 @@ cdef void fast_tp_dealloc(PyObject* o): from sage.misc.allocator cimport hook_tp_functions -hook_tp_functions(global_dummy_element, NULL, &fast_tp_new, NULL, &fast_tp_dealloc, False) +hook_tp_functions(global_dummy_element, NULL, (&fast_tp_new), NULL, &fast_tp_dealloc, False) def time_alloc_list(n): diff --git a/src/sage/structure/element.pyx b/src/sage/structure/element.pyx index 48592801a53..112cead33a8 100644 --- a/src/sage/structure/element.pyx +++ b/src/sage/structure/element.pyx @@ -147,9 +147,9 @@ be accessible from the calling function after this operation. ################################################################## include "sage/ext/cdefs.pxi" -include "sage/ext/stdsage.pxi" include "sage/ext/python.pxi" include "coerce.pxi" +from sage.ext.stdsage cimport * from cpython.ref cimport PyObject From 10a88282babac28633778bab94f8aa2b5fdc3a28 Mon Sep 17 00:00:00 2001 From: Thierry Monteil Date: Fri, 13 Mar 2015 23:26:54 +0100 Subject: [PATCH 083/129] #17306 remove mathjax as a build dependency. --- build/deps | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build/deps b/build/deps index 97e0e8f14e3..4bdd42be682 100644 --- a/build/deps +++ b/build/deps @@ -309,7 +309,7 @@ $(INST)/$(LIBGAP): $(INST)/$(GAP) +$(PIPE) "$(SAGE_SPKG) $(LIBGAP) 2>&1" "tee -a $(SAGE_LOGS)/$(LIBGAP).log" $(INST)/$(IPYTHON): $(INST)/$(PYTHON) $(INST)/$(JINJA2) $(INST)/$(TORNADO) \ - $(INST)/$(PYZMQ) $(INST)/$(MATHJAX) + $(INST)/$(PYZMQ) +$(PIPE) "$(SAGE_SPKG) $(IPYTHON) 2>&1" "tee -a $(SAGE_LOGS)/$(IPYTHON).log" $(INST)/$(PEXPECT): $(INST)/$(PYTHON) @@ -447,8 +447,7 @@ $(INST)/$(ZNPOLY): $(INST)/$(MPIR) $(INST)/$(PYTHON) +$(PIPE) "$(SAGE_SPKG) $(ZNPOLY) 2>&1" "tee -a $(SAGE_LOGS)/$(ZNPOLY).log" $(INST)/$(SAGENB): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(PEXPECT) \ - $(INST)/$(JINJA2) $(INST)/$(SPHINX) $(INST)/$(DOCUTILS) \ - $(INST)/$(MATHJAX) + $(INST)/$(JINJA2) $(INST)/$(SPHINX) $(INST)/$(DOCUTILS) +$(PIPE) "$(SAGE_SPKG) $(SAGENB) 2>&1" "tee -a $(SAGE_LOGS)/$(SAGENB).log" $(INST)/$(SQLALCHEMY): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) From 191ac98e0a5e252e22d54f7a1251d6ebcdef7df6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois=20Bissey?= Date: Sat, 14 Mar 2015 13:47:18 +1300 Subject: [PATCH 084/129] Add missing import in doctest --- src/sage/repl/ipython_kernel/install.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sage/repl/ipython_kernel/install.py b/src/sage/repl/ipython_kernel/install.py index 3ec03cf6dfb..8c8ffdf416b 100644 --- a/src/sage/repl/ipython_kernel/install.py +++ b/src/sage/repl/ipython_kernel/install.py @@ -113,6 +113,7 @@ def use_local_mathjax(self): EXAMPLES:: sage: from sage.repl.ipython_kernel.install import SageKernelSpec + sage: from IPython.utils.path import get_ipython_dir sage: spec = SageKernelSpec() sage: spec.use_local_mathjax() sage: ipython_dir = get_ipython_dir() From ca38582eb8634f43ca311b7ff0b5d7577a17709d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Apitzsch?= Date: Sat, 14 Mar 2015 12:09:26 +0100 Subject: [PATCH 085/129] fix syntax error --- src/bin/sage-CSI | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bin/sage-CSI b/src/bin/sage-CSI index f07e0969dd0..658f7fe8cee 100755 --- a/src/bin/sage-CSI +++ b/src/bin/sage-CSI @@ -1,5 +1,7 @@ #!/usr/bin/env python +from __future__ import print_function + description = """ Attach the debugger to a Python process (given by its pid) and extract as much information about its internal state as possible @@ -12,8 +14,6 @@ description = """ # set) are automatically deleted, but with a negative value they are # never deleted. -from __future__ import print_function - import sys import os import subprocess From 13b1867998e2f1f172ffd9cb576e3ba4e8298735 Mon Sep 17 00:00:00 2001 From: Benjamin Hackl Date: Sat, 14 Mar 2015 14:26:52 +0100 Subject: [PATCH 086/129] fixed problem with series_precision and extended the doctest. --- src/sage/rings/laurent_series_ring.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/sage/rings/laurent_series_ring.py b/src/sage/rings/laurent_series_ring.py index d58e3e079f2..fa6ff9c97a3 100644 --- a/src/sage/rings/laurent_series_ring.py +++ b/src/sage/rings/laurent_series_ring.py @@ -84,18 +84,27 @@ def LaurentSeriesRing(base_ring, name=None, names=None, default_prec=None, spars TESTS: - Check if changing global series precision does it right:: + Check if changing global series precision does it right (and + that :trac:`17955` is fixed):: sage: set_series_precision(3) sage: R. = LaurentSeriesRing(ZZ) sage: 1/(1 - 2*x) 1 + 2*x + 4*x^2 + O(x^3) + sage: set_series_precision(5) + sage: R. = LaurentSeriesRing(ZZ) + sage: 1/(1 - 2*x) + 1 + 2*x + 4*x^2 + 8*x^3 + 16*x^4 + O(x^5) sage: set_series_precision(20) """ if not names is None: name = names if name is None: raise TypeError("You must specify the name of the indeterminate of the Laurent series ring.") + if default_prec is None: + from sage.misc.defaults import series_precision + default_prec = series_precision() + global laurent_series key = (base_ring, name, default_prec, sparse) if key in laurent_series: From 054df904098a3013b6b91fd3662cd00680a24511 Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Sat, 14 Mar 2015 17:33:27 +0100 Subject: [PATCH 087/129] trac #17950: tell modules_list.py that modular_decomposition is optional --- src/module_list.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/module_list.py b/src/module_list.py index 3eba269f246..f7951296fcc 100755 --- a/src/module_list.py +++ b/src/module_list.py @@ -404,10 +404,6 @@ def uname_specific(name, value, alternative): Extension('sage.graphs.base.static_sparse_backend', sources = ['sage/graphs/base/static_sparse_backend.pyx']), - Extension('sage.graphs.modular_decomposition', - sources = ['sage/graphs/modular_decomposition.pyx'], - libraries = ['modulardecomposition']), - Extension('sage.graphs.weakly_chordal', sources = ['sage/graphs/weakly_chordal.pyx']), @@ -2080,7 +2076,12 @@ def uname_specific(name, value, alternative): Extension("sage.graphs.mcqd", ["sage/graphs/mcqd.pyx"], language = "c++")) -# libraries = ["mcqd"])) + +if is_package_installed('modular_decomposition'): + ext_modules.append( + Extension('sage.graphs.modular_decomposition', + sources = ['sage/graphs/modular_decomposition.pyx'], + libraries = ['modulardecomposition'])) if is_package_installed('arb'): ext_modules.extend([ From 894556fe81b9031df978490c0faac643fad8991e Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Sat, 14 Mar 2015 18:21:38 +0100 Subject: [PATCH 088/129] trac #17950: Removing an import --- src/sage/graphs/all.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/sage/graphs/all.py b/src/sage/graphs/all.py index 33d05892b28..6a01362e303 100644 --- a/src/sage/graphs/all.py +++ b/src/sage/graphs/all.py @@ -17,7 +17,6 @@ import sage.graphs.digraph lazy_import("sage.graphs", "graph_coloring") import sage.graphs.graph_decompositions -import sage.graphs.modular_decomposition import sage.graphs.comparability from sage.graphs.cliquer import * from graph_database import graph_db_info From c19a921b7385396cd42a12774618418373887b82 Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Sat, 14 Mar 2015 18:30:58 +0100 Subject: [PATCH 089/129] trac #17950: Broken doctests --- src/sage/graphs/graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index 9ac22e12347..f2d0a5f332b 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -6502,14 +6502,14 @@ def is_prime(self): The Petersen Graph and the Bull Graph are both prime:: - sage: graphs.PetersenGraph().is_prime() + sage: graphs.PetersenGraph().is_prime() # optional - modular_decomposition True - sage: graphs.BullGraph().is_prime() + sage: graphs.BullGraph().is_prime() # optional - modular_decomposition True Though quite obviously, the disjoint union of them is not:: - sage: (graphs.PetersenGraph() + graphs.BullGraph()).is_prime() + sage: (graphs.PetersenGraph() + graphs.BullGraph()).is_prime() # optional - modular_decomposition False """ From 6de1caf39ead485f468cccf3d0969f0d6c98cd19 Mon Sep 17 00:00:00 2001 From: Julien Puydt Date: Sat, 14 Mar 2015 18:38:30 +0100 Subject: [PATCH 090/129] The other tests in the same file have an ellipsis for the version : do likewise! --- src/sage/interfaces/tachyon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/interfaces/tachyon.py b/src/sage/interfaces/tachyon.py index b7da4ae30e5..7a6458da689 100644 --- a/src/sage/interfaces/tachyon.py +++ b/src/sage/interfaces/tachyon.py @@ -128,7 +128,7 @@ def __call__(self, model, outfile='sage.png', verbose=1, extra_opts=''): sage: t(syntax_error, outfile=os.devnull) Traceback (most recent call last): ... - RuntimeError: Tachyon Parallel/Multiprocessor Ray Tracer Version 0.98.9 + RuntimeError: Tachyon Parallel/Multiprocessor Ray Tracer... ... Parser failed due to an input file syntax error. Aborting render. From 9770edfd83c1b8ed53842a43d9d0ea6da2b3faf9 Mon Sep 17 00:00:00 2001 From: Thierry Monteil Date: Sat, 14 Mar 2015 18:58:18 +0100 Subject: [PATCH 091/129] #17306 : move ipython dependencies on a single line. --- build/deps | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/build/deps b/build/deps index 4bdd42be682..7cee608dcb8 100644 --- a/build/deps +++ b/build/deps @@ -308,8 +308,7 @@ $(INST)/$(GAP): $(INST)/$(NCURSES) $(INST)/$(READLINE) $(INST)/$(MPIR) $(INST)/$(LIBGAP): $(INST)/$(GAP) +$(PIPE) "$(SAGE_SPKG) $(LIBGAP) 2>&1" "tee -a $(SAGE_LOGS)/$(LIBGAP).log" -$(INST)/$(IPYTHON): $(INST)/$(PYTHON) $(INST)/$(JINJA2) $(INST)/$(TORNADO) \ - $(INST)/$(PYZMQ) +$(INST)/$(IPYTHON): $(INST)/$(PYTHON) $(INST)/$(JINJA2) $(INST)/$(TORNADO) $(INST)/$(PYZMQ) +$(PIPE) "$(SAGE_SPKG) $(IPYTHON) 2>&1" "tee -a $(SAGE_LOGS)/$(IPYTHON).log" $(INST)/$(PEXPECT): $(INST)/$(PYTHON) From 0ecbf7ce7bd69bf23c215bb2c03852a29637cfcb Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Sat, 14 Mar 2015 19:15:58 +0100 Subject: [PATCH 092/129] trac #17950: add a link toward sage.misc.packages. --- src/sage/graphs/graph.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/sage/graphs/graph.py b/src/sage/graphs/graph.py index f2d0a5f332b..726e3414e79 100644 --- a/src/sage/graphs/graph.py +++ b/src/sage/graphs/graph.py @@ -6358,7 +6358,8 @@ def modular_decomposition(self): .. NOTE:: In order to use this method you must install the - ``modular_decomposition`` optional package. + ``modular_decomposition`` optional package. See + :mod:`sage.misc.package`. Crash course on modular decomposition: @@ -6494,9 +6495,16 @@ def modular_decomposition(self): def is_prime(self): r""" - Tests whether the current graph is prime. A graph is prime if - all its modules are trivial (i.e. empty, all of the graph or - singletons)-- see ``self.modular_decomposition?``. + Tests whether the current graph is prime. + + A graph is prime if all its modules are trivial (i.e. empty, all of the + graph or singletons) -- see :meth:`modular_decomposition`. + + .. NOTE:: + + In order to use this method you must install the + ``modular_decomposition`` optional package. See + :mod:`sage.misc.package`. EXAMPLE: From 697b1ed4e560ef15e1316b6f9957b900d1e99f5e Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Sun, 15 Mar 2015 00:38:20 -0700 Subject: [PATCH 093/129] Removed todo messages. --- src/sage/combinat/combinat.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 2feb8c2ac60..290e085cac3 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -2316,11 +2316,6 @@ def number_of_tuples(S, k, algorithm='naive'): interpreter. If ``S`` consists of at all complicated Sage objects, this function might *not* do what you expect. - .. TODO:: - - Is ``algorithm='gap'`` of any use? I can't imagine it beating the - native interpretation... - EXAMPLES:: sage: S = [1,2,3,4,5] @@ -2435,11 +2430,6 @@ def number_of_unordered_tuples(S, k, algorithm='naive'): interpreter. If ``S`` consists of at all complicated Sage objects, this function might *not* do what you expect. - .. TODO:: - - Is ``algorithm='gap'`` of any use? I can't imagine it beating the - native interpretation... - EXAMPLES:: sage: S = [1,2,3,4,5] From e72ab22fddbe84da5018eaf39f546a80d2133b2a Mon Sep 17 00:00:00 2001 From: Nathann Cohen Date: Sun, 15 Mar 2015 15:46:18 +0100 Subject: [PATCH 094/129] trac #17950: Use the old C code instead --- build/pkgs/modular_decomposition/checksums.ini | 6 +++--- build/pkgs/modular_decomposition/package-version.txt | 2 +- src/sage/graphs/modular_decomposition.pyx | 1 - 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/build/pkgs/modular_decomposition/checksums.ini b/build/pkgs/modular_decomposition/checksums.ini index 6d94d635b5b..2c97fe5389a 100644 --- a/build/pkgs/modular_decomposition/checksums.ini +++ b/build/pkgs/modular_decomposition/checksums.ini @@ -1,4 +1,4 @@ tarball=modular_decomposition-VERSION.tar.bz2 -sha1=80617ba03f5b7d3db7e2c290f5785674f50bee37 -md5=88d6c7629c29926eaa6497212873dfe9 -cksum=1472531391 +sha1=b0ce6d839d1cd2e93d806e70b13bc40bcdbaf9e9 +md5=9bc5245c5fab9df4f45c8e10c27cf3b8 +cksum=2034006428 diff --git a/build/pkgs/modular_decomposition/package-version.txt b/build/pkgs/modular_decomposition/package-version.txt index ea0dd137c06..b3089db1733 100644 --- a/build/pkgs/modular_decomposition/package-version.txt +++ b/build/pkgs/modular_decomposition/package-version.txt @@ -1 +1 @@ -20150313 +20100607 diff --git a/src/sage/graphs/modular_decomposition.pyx b/src/sage/graphs/modular_decomposition.pyx index 6782784d483..89d8c2b9718 100644 --- a/src/sage/graphs/modular_decomposition.pyx +++ b/src/sage/graphs/modular_decomposition.pyx @@ -136,7 +136,6 @@ cpdef modular_decomposition(g): a.suiv = G.G[j] G.G[j] = a - R = decomposition_modulaire(G) return build_dict_from_decomposition(R) From bb4e7dcb559766e5685b990833153acb790bc718 Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Sun, 15 Mar 2015 19:55:57 +0100 Subject: [PATCH 095/129] update to Jinja2-2.7.3 --- build/pkgs/jinja2/checksums.ini | 8 ++++---- build/pkgs/jinja2/package-version.txt | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build/pkgs/jinja2/checksums.ini b/build/pkgs/jinja2/checksums.ini index 4e74f2820bd..1f3635ea5e8 100644 --- a/build/pkgs/jinja2/checksums.ini +++ b/build/pkgs/jinja2/checksums.ini @@ -1,4 +1,4 @@ -tarball=jinja2-VERSION.tar.bz2 -sha1=e9e13b4f573effd35e03b1ecd978c684342d549c -md5=3ca3504c27a147d38f4d32214a3f3004 -cksum=1443664485 +tarball=jinja2-VERSION.tar.gz +sha1=25ab3881f0c1adfcf79053b58de829c5ae65d3ac +md5=b9dffd2f3b43d673802fe857c8445b1a +cksum=3563905877 diff --git a/build/pkgs/jinja2/package-version.txt b/build/pkgs/jinja2/package-version.txt index 0cadbc1e33b..2c9b4ef42ec 100644 --- a/build/pkgs/jinja2/package-version.txt +++ b/build/pkgs/jinja2/package-version.txt @@ -1 +1 @@ -2.5.5 +2.7.3 From 9acc6329f0453e71c760111a98af36b5b2e8c83d Mon Sep 17 00:00:00 2001 From: Andrey Novoseltsev Date: Sun, 15 Mar 2015 12:58:56 -0600 Subject: [PATCH 096/129] There is no problem subdividing incomplete fans. --- src/sage/geometry/fan.py | 45 ++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 24f1be7e016..54d9ea211f1 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -1616,13 +1616,16 @@ def __neg__(self): def common_refinement(self, other): """ - Compute the common refinement of ``self`` and ``other``. - - This is currently only implemented when one of the fans is complete. + Return the common refinement of this fan and ``other``. + + INPUT: + + - ``other`` -- a :class:`fan ` in the same + :meth:`lattice` and with the same support as this fan OUTPUT: - - a fan + - a :class:`fan ` EXAMPLES: @@ -1638,21 +1641,14 @@ def common_refinement(self, other): sage: F2 = Fan2d([(1,0),(1,1),(0,1),(-1,0),(0,-1)]) sage: F3 = F2.cartesian_product(F1) sage: F4 = F1.cartesian_product(F2) - sage: FF = F3.common_refinement(F4); FF - Rational polyhedral fan in 3-d lattice N+N + sage: FF = F3.common_refinement(F4) + sage: F3.ngenerating_cones() + 10 + sage: F4.ngenerating_cones() + 10 sage: FF.ngenerating_cones() 13 - TESTS: - - This does not work when both fans are incomplete:: - - sage: F5 = Fan([[0,1],[1,2],[2,3]],[(1,0),(0,1),(-1,0),(0,-1)]) - sage: F5.common_refinement(F5) - Traceback (most recent call last): - ... - ValueError: only implemented for complete fans - Both fans must live in the same lattice:: sage: F0.common_refinement(F1) @@ -1662,16 +1658,15 @@ def common_refinement(self, other): """ from sage.categories.homset import End from sage.geometry.fan_morphism import FanMorphism - latt = self.lattice() - if other.lattice() != latt: + N = self.lattice() + if other.lattice() is not N: raise ValueError('the fans are not in the same lattice') - if not(self.is_complete()): - if other.is_complete(): - self, other = other, self - else: - raise ValueError('only implemented for complete fans') - id = FanMorphism(End(latt).identity(), self, other, subdivide=True) - return id.domain_fan() + id = End(N).identity() + subdivision = FanMorphism(id, self, other, subdivide=True).domain_fan() + if not self.is_complete(): + # Construct the opposite morphism to ensure support equality + FanMorphism(id, other, self, subdivide=True) + return subdivision def _latex_(self): r""" From 8789fdce0cd66dd71243be5f6466b2e5951cc9ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 15 Mar 2015 20:26:28 +0100 Subject: [PATCH 097/129] trac #9439 correct wrong use of abs, plus some pep8 details --- .../hyperbolic_space/hyperbolic_coercion.py | 54 ++++++++++--------- .../hyperbolic_space/hyperbolic_geodesic.py | 38 +++++++------ .../hyperbolic_space/hyperbolic_interface.py | 2 +- .../hyperbolic_space/hyperbolic_isometry.py | 13 ++--- .../hyperbolic_space/hyperbolic_model.py | 38 +++++++------ .../hyperbolic_space/hyperbolic_point.py | 20 +++---- 6 files changed, 88 insertions(+), 77 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py index 1d068f99093..15b1d8ea289 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_coercion.py @@ -80,8 +80,8 @@ def _call_(self, x): """ C = self.codomain() if not C.is_bounded() and self.domain().is_bounded() and x.is_boundary(): - raise NotImplementedError("boundary points are not implemented for" - " the {0}".format(C.name())) + msg = u"boundary points are not implemented for the {}" + raise NotImplementedError(msg.format(C.name())) y = self.image_coordinates(x.coordinates()) if self.domain().is_bounded(): @@ -352,8 +352,9 @@ def image_isometry_matrix(self, x): [ 0 1 0] [ 0 0 -1] """ - return SL2R_to_SO21( matrix(2,[1,I,I,1]) * x * - matrix(2,[1,-I,-I,1])/Integer(2) ) + return SL2R_to_SO21(matrix(2, [1, I, I, 1]) * x * + matrix(2, [1, -I, -I, 1]) / Integer(2)) + class CoercionPDtoHM(HyperbolicModelCoercion): """ @@ -372,11 +373,10 @@ def image_coordinates(self, x): sage: phi.image_coordinates(0.5+0.5*I) (2.00000000000000, 2.00000000000000, 3.00000000000000) """ - return vector(( - 2*real(x)/(1 - real(x)**2 - imag(x)**2), - 2*imag(x)/(1 - real(x)**2 - imag(x)**2), - (real(x)**2 + imag(x)**2 + 1)/(1 - real(x)**2 - imag(x)**2) - )) + return vector((2*real(x)/(1 - real(x)**2 - imag(x)**2), + 2*imag(x)/(1 - real(x)**2 - imag(x)**2), + (real(x)**2 + imag(x)**2 + 1) / + (1 - real(x)**2 - imag(x)**2))) def image_isometry_matrix(self, x): """ @@ -393,13 +393,14 @@ def image_isometry_matrix(self, x): [ 0 1 0] [ 0 0 -1] """ - return SL2R_to_SO21( matrix(2,[1,I,I,1]) * x * - matrix(2,[1,-I,-I,1])/Integer(2) ) + return SL2R_to_SO21(matrix(2, [1, I, I, 1]) * x * + matrix(2, [1, -I, -I, 1]) / Integer(2)) ########### # From KM # ########### + class CoercionKMtoUHP(HyperbolicModelCoercion): """ Coercion from the KM to UHP model. @@ -421,9 +422,9 @@ def image_coordinates(self, x): """ if tuple(x) == (0, 1): return infinity - return ( -x[0]/(x[1] - 1) - + I*(-(sqrt(-x[0]**2 - x[1]**2 + 1) - x[0]**2 - x[1]**2 + 1) - / ((x[1] - 1)*sqrt(-x[0]**2 - x[1]**2 + 1) + x[1] - 1)) ) + return (-x[0]/(x[1] - 1) + + I*(-(sqrt(-x[0]**2 - x[1]**2 + 1) - x[0]**2 - x[1]**2 + 1) + / ((x[1] - 1)*sqrt(-x[0]**2 - x[1]**2 + 1) + x[1] - 1))) def image_isometry_matrix(self, x): """ @@ -459,8 +460,8 @@ def image_coordinates(self, x): sage: phi.image_coordinates((0, 0)) 0 """ - return ( x[0]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) - + I*x[1]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) ) + return (x[0]/(1 + (1 - x[0]**2 - x[1]**2).sqrt()) + + I*x[1]/(1 + (1 - x[0]**2 - x[1]**2).sqrt())) def image_isometry_matrix(self, x): """ @@ -650,7 +651,9 @@ def SL2R_to_SO21(A): sage: norm(A.transpose()*J*A - J) < 10**-4 True """ - a,b,c,d = (A/A.det().sqrt()).list() + a, b, c, d = (A/A.det().sqrt()).list() + + # Kill ~0 imaginary parts B = matrix(3, map(real, [a*d + b*c, a*c - b*d, a*c + b*d, a*b - c*d, Integer(1)/Integer(2)*a**2 - Integer(1)/Integer(2)*b**2 - @@ -661,8 +664,7 @@ def SL2R_to_SO21(A): Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 - Integer(1)/Integer(2)*d**2, Integer(1)/Integer(2)*a**2 + Integer(1)/Integer(2)*b**2 + Integer(1)/Integer(2)*c**2 + - Integer(1)/Integer(2)*d**2] - )) # Kill ~0 imaginary parts + Integer(1)/Integer(2)*d**2])) #B = B.apply_map(attrcall('real')) if A.det() > 0: @@ -704,20 +706,20 @@ def SO21_to_SL2R(M): (m_1,m_2,m_3,m_4,m_5,m_6,m_7,m_8,m_9) = M.list() d = sqrt(Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 - Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) - if M.det() > 0: #EPSILON? + if M.det() > 0: # EPSILON? det_sign = 1 - elif M.det() < 0: #EPSILON? + elif M.det() < 0: # EPSILON? det_sign = -1 - if d > 0: #EPSILON? + if d > 0: # EPSILON? c = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/d b = (-Integer(1)/Integer(2)*m_2 + Integer(1)/Integer(2)*m_3)/d - ad = det_sign*1 + b*c # ad - bc = pm 1 + ad = det_sign*1 + b*c # ad - bc = pm 1 a = ad/d - else: # d is 0, so we make c > 0 + else: # d is 0, so we make c > 0 c = sqrt(-Integer(1)/Integer(2)*m_5 - Integer(1)/Integer(2)*m_6 + - Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) + Integer(1)/Integer(2)*m_8 + Integer(1)/Integer(2)*m_9) d = (-Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/c - #d = 0, so ad - bc = -bc = pm 1. + # d = 0, so ad - bc = -bc = pm 1. b = - (det_sign*1)/c a = (Integer(1)/Integer(2)*m_4 + Integer(1)/Integer(2)*m_7)/b A = matrix(2, [a, b, c, d]) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index f79603cc324..d78ba295dca 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- r""" Hyperbolic Geodesics @@ -58,7 +59,8 @@ from sage.geometry.hyperbolic_space.hyperbolic_constants import EPSILON from sage.misc.lazy_import import lazy_import -lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', 'mobius_transform') +lazy_import('sage.geometry.hyperbolic_space.hyperbolic_isometry', + 'mobius_transform') class HyperbolicGeodesic(SageObject): @@ -783,7 +785,7 @@ def show(self, boundary=True, **options): sage: HyperbolicPlane().UHP().get_geodesic(0, 1).show() Graphics object consisting of 2 graphics primitives """ - opts = dict([('axes', False), ('aspect_ratio',1)]) + opts = {'axes': False, 'aspect_ratio': 1} opts.update(self.graphics_options()) opts.update(options) end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] @@ -813,7 +815,7 @@ def show(self, boundary=True, **options): theta2 = CC(end_2 - center).arg() if abs(theta1 - theta2) < EPSILON: theta2 += pi - [theta1, theta2] = sorted([theta1,theta2]) + [theta1, theta2] = sorted([theta1, theta2]) from sage.calculus.var import var from sage.plot.plot import parametric_plot x = var('x') @@ -825,7 +827,7 @@ def show(self, boundary=True, **options): # computations below compute the projection of the # geodesic to the real line, and then draw a little # to the left and right of the projection. - shadow_1, shadow_2 = [real(k) for k in [end_1,end_2]] + shadow_1, shadow_2 = [real(k) for k in [end_1, end_2]] midpoint = (shadow_1 + shadow_2)/2 length = abs(shadow_1 - shadow_2) bd_dict = {'bd_min': midpoint - length, 'bd_max': midpoint + @@ -996,7 +998,8 @@ def perpendicular_bisector(self): #UHP d = self._model._dist_points(start, self._end.coordinates()) / 2 S = self.complete()._to_std_geod(start) T1 = matrix([[exp(d/2), 0], [0, exp(-d/2)]]) - T2 = matrix([[cos(pi/4), -sin(pi/4)], [sin(pi/4), cos(pi/4)]]) + s2 = sqrt(2) * 0.5 + T2 = matrix([[s2, -s2], [s2, s2]]) isom_mtrx = S.inverse() * (T1 * T2) * S # We need to clean this matrix up. if (isom_mtrx - isom_mtrx.conjugate()).norm() < 5*EPSILON: # Imaginary part is small. isom_mtrx = (isom_mtrx + isom_mtrx.conjugate()) / 2 # Set it to its real part. @@ -1082,7 +1085,7 @@ def angle(self, other): #UHP if self.is_parallel(other): raise ValueError("geodesics do not intersect") # Make sure the segments are complete or intersect - if (not (self.is_complete() and other.is_complete()) \ + if (not(self.is_complete() and other.is_complete()) and not self.intersection(other)): print("Warning: Geodesic segments do not intersect. " "The angle between them is not defined.\n" @@ -1092,8 +1095,10 @@ def angle(self, other): #UHP if other._model is not self._model: other = other.to_model(self._model) - (p1, p2) = sorted([k.coordinates() for k in self.ideal_endpoints()], key=str) - (q1, q2) = sorted([k.coordinates() for k in other.ideal_endpoints()], key=str) + (p1, p2) = sorted([k.coordinates() + for k in self.ideal_endpoints()], key=str) + (q1, q2) = sorted([k.coordinates() + for k in other.ideal_endpoints()], key=str) # if the geodesics are equal, the angle between them is 0 if (abs(p1 - q1) < EPSILON and abs(p2 - q2) < EPSILON): @@ -1111,7 +1116,7 @@ def angle(self, other): #UHP if (b1 == infinity or b2 == infinity): # then since they intersect, they are equal return 0 - return real( arccos((b1+b2) / abs(b2-b1)) ) + return real(arccos((b1 + b2) / abs(b2 - b1))) ################## # Helper methods # @@ -1147,7 +1152,7 @@ def _to_std_geod(self, p): # outmat below will be returned after we normalize the determinant. outmat = B * HyperbolicGeodesicUHP._crossratio_matrix(s, p, e) outmat = outmat / outmat.det().sqrt() - if abs(outmat - outmat.conjugate()) < 10**-9: # Small imaginary part. + if (outmat - outmat.conjugate()).norm(1) < 10**-9: # Small imaginary part. outmat = (outmat + outmat.conjugate()) / 2 # Set it equal to its real part. return outmat @@ -1175,7 +1180,7 @@ def _crossratio_matrix(p0, p1, p2): #UHP sage: UHP = HyperbolicPlane().UHP() sage: (p1, p2, p3) = [UHP.random_point().coordinates() for k in range(3)] sage: A = HyperbolicGeodesicUHP._crossratio_matrix(p1, p2, p3) - sage: bool(abs(mobius_transform(A, p1) < 10**-9)) + sage: bool(abs(mobius_transform(A, p1)) < 10**-9) True sage: bool(abs(mobius_transform(A, p2) - 1) < 10**-9) True @@ -1199,7 +1204,7 @@ def _crossratio_matrix(p0, p1, p2): #UHP class HyperbolicGeodesicPD(HyperbolicGeodesic): r""" - A geodesic in the Poincare disk model. + A geodesic in the Poincaré disk model. INPUT: @@ -1224,13 +1229,13 @@ def show(self, boundary=True, **options): sage: HyperbolicPlane().PD().get_geodesic(0, 1).show() Graphics object consisting of 2 graphics primitives """ - opts = dict([('axes', False), ('aspect_ratio',1)]) + opts = dict([('axes', False), ('aspect_ratio', 1)]) opts.update(self.graphics_options()) opts.update(options) end_1, end_2 = [CC(k.coordinates()) for k in self.endpoints()] bd_1, bd_2 = [CC(k.coordinates()) for k in self.ideal_endpoints()] # Check to see if it's a line - if bool (real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))**2 < EPSILON: + if bool(real(bd_1)*imag(bd_2) - real(bd_2)*imag(bd_1))**2 < EPSILON: from sage.plot.line import line pic = line([(real(bd_1),imag(bd_1)),(real(bd_2),imag(bd_2))], **opts) @@ -1297,10 +1302,9 @@ def show(self, boundary=True, **options): Graphics object consisting of 2 graphics primitives """ from sage.plot.line import line - opts = dict ([('axes', False), ('aspect_ratio', 1)]) + opts = {'axes': False, 'aspect_ratio': 1} opts.update(self.graphics_options()) - end_1,end_2 = [k.coordinates() for k in self.endpoints()] - pic = line([end_1,end_2], **opts) + pic = line([k.coordinates() for k in self.endpoints()], **opts) if boundary: bd_pic = self._model.get_background_graphic() pic = bd_pic + pic diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py index fe44634b9d1..d8d3c411467 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_interface.py @@ -84,7 +84,7 @@ class HyperbolicPlane(Parent, UniqueRepresentation): Here are the models currently implemented: - ``UHP`` -- upper half plane - - ``PD`` -- Poincare disk + - ``PD`` -- Poincaré disk - ``KM`` -- Klein disk - ``HM`` -- hyperboloid model """ diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py index 7ec8d13a0c0..4840c055eaf 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_isometry.py @@ -191,7 +191,8 @@ def __eq__(self, other): m = self.matrix().ncols() A = A / sqrt(A.det(), m) # Normalized to have determinant 1 B = B / sqrt(B.det(), m) - test_matrix = bool( (A - B).norm() < EPSILON or (A + B).norm() < EPSILON ) + test_matrix = ((A - B).norm() < EPSILON + or (A + B).norm() < EPSILON) return self.domain() is other.domain() and test_matrix def __hash__(self): @@ -781,9 +782,9 @@ def fixed_point_set(self): #UHP ... ValueError: the identity transformation fixes the entire hyperbolic plane """ - d = sqrt(self._matrix.det()**2) + d = sqrt(self._matrix.det() ** 2) M = self._matrix / sqrt(d) - tau = M.trace()**2 + tau = M.trace() ** 2 M_cls = self.classification() if M_cls == 'identity': raise ValueError("the identity transformation fixes the entire " @@ -791,14 +792,14 @@ def fixed_point_set(self): #UHP pt = self.domain().get_point if M_cls == 'parabolic': - if abs(M[1,0]) < EPSILON: + if abs(M[1, 0]) < EPSILON: return [pt(infinity)] else: # boundary point - return [pt( (M[0,0] - M[1,1]) / (2*M[1,0]) )] + return [pt((M[0,0] - M[1,1]) / (2*M[1,0]))] elif M_cls == 'elliptic': d = sqrt(tau - 4) - return [pt( (M[0,0] - M[1,1] + sign(M[1,0])*d) / (2*M[1,0]) )] + return [pt((M[0,0] - M[1,1] + sign(M[1,0])*d) / (2*M[1,0]))] elif M_cls == 'hyperbolic': if M[1,0] != 0: #if the isometry doesn't fix infinity d = sqrt(tau - 4) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 2aee37d21af..5c5a298db49 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -147,7 +147,7 @@ def __init__(self, space, name, short_name, bounded, conformal, from sage.geometry.hyperbolic_space.hyperbolic_interface import HyperbolicModels Parent.__init__(self, category=HyperbolicModels(space)) - def _repr_(self): #Abstract + def _repr_(self): # Abstract """ Return a string representation of ``self``. @@ -156,7 +156,7 @@ def _repr_(self): #Abstract sage: HyperbolicPlane().UHP() Hyperbolic plane in the Upper Half Plane Model model """ - return "Hyperbolic plane in the {} model".format(self._name) + return u'Hyperbolic plane in the {} model'.format(self._name) def _element_constructor_(self, x, is_boundary=None, **graphics_options): #Abstract """ @@ -170,7 +170,7 @@ def _element_constructor_(self, x, is_boundary=None, **graphics_options): #Abstr """ return self.get_point(x, is_boundary, **graphics_options) - def name(self): #Abstract + def name(self): # Abstract """ Return the name of this model. @@ -884,7 +884,7 @@ def boundary_point_in_model(self, p): if isinstance(p, HyperbolicPoint): return p.is_boundary() im = abs(imag(CC(p)).n()) - return bool( (im < EPSILON) or (p == infinity) ) + return (im < EPSILON) or bool(p == infinity) def isometry_in_model(self, A): r""" @@ -1138,7 +1138,7 @@ def _mobius_sending(z, w): #UHP return B.inverse() * A ##################################################################### -## Poincare disk model +## Poincaré disk model class HyperbolicModelPD(HyperbolicModel): r""" @@ -1156,11 +1156,13 @@ def __init__(self, space): sage: PD = HyperbolicPlane().PD() sage: TestSuite(PD).run() """ + # name should really be 'Poincaré Disk Model', but utf8 is not + # accepted by repr HyperbolicModel.__init__(self, space, - name="Poincare Disk Model", # u"Poincaré Disk Model" - short_name="PD", - bounded=True, conformal=True, dimension=2, - isometry_group="PU(1, 1)", isometry_group_is_projective=True) + name=u'Poincare Disk Model', short_name="PD", + bounded=True, conformal=True, dimension=2, + isometry_group="PU(1, 1)", + isometry_group_is_projective=True) def _coerce_map_from_(self, X): """ @@ -1239,12 +1241,13 @@ def isometry_in_model(self, A): # beta = A[0][1] # Orientation preserving and reversing return (HyperbolicIsometryPD._orientation_preserving(A) - or HyperbolicIsometryPD._orientation_preserving(I*A)) + or HyperbolicIsometryPD._orientation_preserving(I * A)) def get_background_graphic(self, **bdry_options): r""" Return a graphic object that makes the model easier to visualize. - For the Poincare disk, the background object is the ideal boundary. + + For the Poincaré disk, the background object is the ideal boundary. EXAMPLES:: @@ -1471,11 +1474,12 @@ def get_background_graphic(self, **bdry_options): sage: H = HyperbolicPlane().HM().get_background_graphic() """ - hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1) - z_height = bdry_options.get('z_height', 7.0) - x_max = sqrt((z_height**2 - 1) / 2.0) from sage.plot.plot3d.all import plot3d from sage.all import var - (x,y) = var('x,y') - return plot3d((1 + x**2 + y**2).sqrt(), (x, -x_max, x_max), - (y,-x_max, x_max), opacity = hyperboloid_opacity, **bdry_options) + hyperboloid_opacity = bdry_options.get('hyperboloid_opacity', .1) + z_height = bdry_options.get('z_height', 7.0) + x_max = sqrt((z_height ** 2 - 1) / 2.0) + (x, y) = var('x,y') + return plot3d((1 + x ** 2 + y ** 2).sqrt(), + (x, -x_max, x_max), (y,-x_max, x_max), + opacity=hyperboloid_opacity, **bdry_options) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index ac331d380d8..4d65083e840 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -511,19 +511,19 @@ def show(self, boundary=True, **options): if p == infinity: raise NotImplementedError("can't draw the point infinity") - opts = dict([('axes', False), ('aspect_ratio',1)]) + opts = {'axes': False, 'aspect_ratio': 1} opts.update(self.graphics_options()) opts.update(options) from sage.plot.point import point from sage.misc.functional import numerical_approx - if self._bdry: # It is a boundary point + if self._bdry: # It is a boundary point p = numerical_approx(p) pic = point((p, 0), **opts) if boundary: - bd_pic = self._model.get_background_graphic(bd_min = p - 1, - bd_max = p + 1) + bd_pic = self._model.get_background_graphic(bd_min=p - 1, + bd_max=p + 1) pic = bd_pic + pic else: # It is an interior point if p in RR: @@ -590,23 +590,23 @@ def show(self, boundary=True, **options): p = self.coordinates() if p == infinity: raise NotImplementedError("can't draw the point infinity") - opts = dict([('axes', False), ('aspect_ratio', 1)]) + opts = {'axes': False, 'aspect_ratio': 1} opts.update(self.graphics_options()) opts.update(options) from sage.misc.functional import numerical_approx - p = numerical_approx(p + 0*I) + p = numerical_approx(p + 0 * I) from sage.plot.point import point if self._bdry: pic = point((p, 0), **opts) if boundary: - bd_pic = self.parent().get_background_graphic(bd_min = p - 1, - bd_max = p + 1) + bd_pic = self.parent().get_background_graphic(bd_min=p - 1, + bd_max=p + 1) pic = bd_pic + pic else: pic = point(p, **opts) if boundary: cent = real(p) - bd_pic = self.parent().get_background_graphic(bd_min = cent - 1, - bd_max = cent + 1) + bd_pic = self.parent().get_background_graphic(bd_min=cent - 1, + bd_max=cent + 1) pic = bd_pic + pic return pic From 82cf89e367b6f95cf44fdfb7b040974d4e247f94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Sun, 15 Mar 2015 21:38:21 +0100 Subject: [PATCH 098/129] trac #9439 fine tuning the doc --- .../hyperbolic_space/hyperbolic_geodesic.py | 6 +++--- .../geometry/hyperbolic_space/hyperbolic_model.py | 15 ++++++++------- .../geometry/hyperbolic_space/hyperbolic_point.py | 4 ++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py index d78ba295dca..709370e0bec 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_geodesic.py @@ -1006,9 +1006,9 @@ def perpendicular_bisector(self): #UHP H = self._model.get_isometry(isom_mtrx) return self._model.get_geodesic(H(self._start), H(self._end)) - def midpoint(self): #UHP + def midpoint(self): # UHP r""" - Return the (hyperbolic) midpoint of ``self`` if . + Return the (hyperbolic) midpoint of ``self`` if it exists. EXAMPLES:: @@ -1051,7 +1051,7 @@ def angle(self, other): #UHP INPUT: - -``other`` -- a hyperbolic geodesic in the UHP model + - ``other`` -- a hyperbolic geodesic in the UHP model OUTPUT: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py index 5c5a298db49..592b168171d 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_model.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_model.py @@ -46,7 +46,7 @@ False The isometry groups of UHP and PD are projective, while that of HM is -linear: +linear:: sage: U.is_isometry_group_projective() True @@ -55,7 +55,7 @@ The models are responsible for determining if the coordinates of points and the matrix of linear maps are appropriate for constructing points -and isometries in hyperbolic space: +and isometries in hyperbolic space:: sage: U.point_in_model(2 + I) True @@ -360,10 +360,11 @@ def get_point(self, coordinates, is_boundary=None, **graphics_options): r""" Return a point in ``self``. - Automatically determine the type of point to return given either - (1) the coordinates of a point in the interior or ideal boundary - of hyperbolic space or (2) a :class:`HyperbolicPoint` or - :class:`HyperbolicBdryPoint` object. + Automatically determine the type of point to return given either: + + #. the coordinates of a point in the interior or ideal boundary + of hyperbolic space, or + #. a :class:`~sage.geometry.hyperbolic_space.hyperbolic_point.HyperbolicPoint` object. INPUT: @@ -371,7 +372,7 @@ def get_point(self, coordinates, is_boundary=None, **graphics_options): OUTPUT: - - a :class:`HyperbolicPoint` + - a :class:`~sage.geometry.hyperbolic_space.hyperbolic_point.HyperbolicPoint` EXAMPLES: diff --git a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py index 4d65083e840..42ac2138159 100644 --- a/src/sage/geometry/hyperbolic_space/hyperbolic_point.py +++ b/src/sage/geometry/hyperbolic_space/hyperbolic_point.py @@ -443,7 +443,7 @@ def graphics_options(self): def symmetry_involution(self): r""" - Return the involutary isometry fixing the given point. + Return the involutory isometry fixing the given point. EXAMPLES:: @@ -556,7 +556,7 @@ class HyperbolicPointUHP(HyperbolicPoint): """ def symmetry_involution(self): r""" - Return the involutary isometry fixing the given point. + Return the involutory isometry fixing the given point. EXAMPLES:: From da9d6765d00393f8b76e496fdaae7f47307f943f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Mon, 16 Mar 2015 21:10:56 +0100 Subject: [PATCH 099/129] trac #17938 one more example --- src/sage/geometry/fan.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/sage/geometry/fan.py b/src/sage/geometry/fan.py index 54d9ea211f1..ca3f5e4d5ff 100644 --- a/src/sage/geometry/fan.py +++ b/src/sage/geometry/fan.py @@ -1617,9 +1617,9 @@ def __neg__(self): def common_refinement(self, other): """ Return the common refinement of this fan and ``other``. - + INPUT: - + - ``other`` -- a :class:`fan ` in the same :meth:`lattice` and with the same support as this fan @@ -1635,7 +1635,7 @@ def common_refinement(self, other): sage: F0.common_refinement(F0) == F0 True - A more complex example:: + A more complex example with complete fans:: sage: F1 = Fan([[0],[1]],[(1,),(-1,)]) sage: F2 = Fan2d([(1,0),(1,1),(0,1),(-1,0),(0,-1)]) @@ -1649,6 +1649,13 @@ def common_refinement(self, other): sage: FF.ngenerating_cones() 13 + An example with two non-complete fans with the same support:: + + sage: F5 = Fan2d([(1,0),(1,2),(0,1)]) + sage: F6 = Fan2d([(1,0),(2,1),(0,1)]) + sage: F5.common_refinement(F6).ngenerating_cones() + 3 + Both fans must live in the same lattice:: sage: F0.common_refinement(F1) From b880274653c46f0f7ee6baa46d6d00a0eec0496a Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Mon, 16 Mar 2015 21:23:07 +0100 Subject: [PATCH 100/129] Remove commented deprecation --- src/sage/libs/pari/pari_instance.pyx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/sage/libs/pari/pari_instance.pyx b/src/sage/libs/pari/pari_instance.pyx index ec47b43eceb..5f91c0123d1 100644 --- a/src/sage/libs/pari/pari_instance.pyx +++ b/src/sage/libs/pari/pari_instance.pyx @@ -986,8 +986,6 @@ cdef class PariInstance(PariInstance_auto): if v is None: return -1 if v == -1: - #from sage.misc.superseded import deprecation - #deprecation(XXXXX, 'passing -1 as PARI variable is deprecated, use None instead') return -1 cdef bytes s = bytes(v) return fetch_user_var(s) From f666b00883240553144a3d329ed6ec12ed42964f Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Mon, 16 Mar 2015 21:33:00 +0100 Subject: [PATCH 101/129] Jinja2 now depends on MarkupSafe --- COPYING.txt | 1 + build/deps | 6 +++++- build/install | 1 + build/pkgs/markupsafe/SPKG.txt | 19 +++++++++++++++++++ build/pkgs/markupsafe/checksums.ini | 4 ++++ build/pkgs/markupsafe/package-version.txt | 1 + build/pkgs/markupsafe/spkg-install | 15 +++++++++++++++ 7 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 build/pkgs/markupsafe/SPKG.txt create mode 100644 build/pkgs/markupsafe/checksums.ini create mode 100644 build/pkgs/markupsafe/package-version.txt create mode 100755 build/pkgs/markupsafe/spkg-install diff --git a/COPYING.txt b/COPYING.txt index 6996709bd6a..0bd95adb8c1 100644 --- a/COPYING.txt +++ b/COPYING.txt @@ -76,6 +76,7 @@ linbox LGPLv2.1+ lrcalc GPLv2+ m4ri GPLv2+ m4rie GPLv2+ +markupsafe Simplified BSD mathjax Apache License 2.0 matplotlib Matplotlib License (BSD compatible, see below) maxima See below diff --git a/build/deps b/build/deps index 6ec4b9ce142..1f51a99ac40 100644 --- a/build/deps +++ b/build/deps @@ -68,6 +68,7 @@ all-sage: \ $(INST)/$(LINBOX) \ $(INST)/$(M4RI) \ $(INST)/$(M4RIE) \ + $(INST)/$(MARKUPSAFE) \ $(INST)/$(MATHJAX) \ $(INST)/$(MATPLOTLIB) \ $(INST)/$(MAXIMA) \ @@ -467,7 +468,10 @@ $(INST)/$(SPHINX): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(DOCUTILS) \ $(INST)/$(JINJA2) $(INST)/$(PYGMENTS) +$(PIPE) "$(SAGE_SPKG) $(SPHINX) 2>&1" "tee -a $(SAGE_LOGS)/$(SPHINX).log" -$(INST)/$(JINJA2): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(DOCUTILS) +$(INST)/$(MARKUPSAFE): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) + +$(PIPE) "$(SAGE_SPKG) $(MARKUPSAFE) 2>&1" "tee -a $(SAGE_LOGS)/$(MARKUPSAFE).log" + +$(INST)/$(JINJA2): $(INST)/$(MARKUPSAFE) $(INST)/$(DOCUTILS) +$(PIPE) "$(SAGE_SPKG) $(JINJA2) 2>&1" "tee -a $(SAGE_LOGS)/$(JINJA2).log" $(INST)/$(PYGMENTS): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) diff --git a/build/install b/build/install index 84d4dfc4714..d9b1b77220b 100755 --- a/build/install +++ b/build/install @@ -343,6 +343,7 @@ LIBPNG=`newest_version libpng` LINBOX=`newest_version linbox` M4RI=`newest_version m4ri` M4RIE=`newest_version m4rie` +MARKUPSAFE=`newest_version markupsafe` MATHJAX=`newest_version mathjax` MATPLOTLIB=`newest_version matplotlib` MAXIMA=`newest_version maxima` diff --git a/build/pkgs/markupsafe/SPKG.txt b/build/pkgs/markupsafe/SPKG.txt new file mode 100644 index 00000000000..e0b5a22e19f --- /dev/null +++ b/build/pkgs/markupsafe/SPKG.txt @@ -0,0 +1,19 @@ += markupsafe = + +== Description == + +Implements a XML/HTML/XHTML Markup safe string for Python + +== License == + +Simplified BSD + +== Upstream Contact == + +Home page: http://github.com/mitsuhiko/markupsafe + +== Dependencies == + +Python, setuptools + + diff --git a/build/pkgs/markupsafe/checksums.ini b/build/pkgs/markupsafe/checksums.ini new file mode 100644 index 00000000000..d09e23b7503 --- /dev/null +++ b/build/pkgs/markupsafe/checksums.ini @@ -0,0 +1,4 @@ +tarball=markupsafe-VERSION.tar.gz +sha1=cd5c22acf6dd69046d6cb6a3920d84ea66bdf62a +md5=f5ab3deee4c37cd6a922fb81e730da6e +cksum=586757310 diff --git a/build/pkgs/markupsafe/package-version.txt b/build/pkgs/markupsafe/package-version.txt new file mode 100644 index 00000000000..39010d220e8 --- /dev/null +++ b/build/pkgs/markupsafe/package-version.txt @@ -0,0 +1 @@ +0.23 diff --git a/build/pkgs/markupsafe/spkg-install b/build/pkgs/markupsafe/spkg-install new file mode 100755 index 00000000000..f0ac21a8211 --- /dev/null +++ b/build/pkgs/markupsafe/spkg-install @@ -0,0 +1,15 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_LOCAL" ]; then + echo >&2 "SAGE_LOCAL undefined ... exiting" + echo >&2 "Maybe run 'sage --sh'?" + exit 1 +fi + +cd src + +python setup.py install +if [ $? -ne 0 ]; then + echo "Error installing markupsafe ... exiting" + exit 1 +fi From 351f0be7896c5b56f7224256344d08cfc0c91b36 Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Mon, 16 Mar 2015 21:47:47 +0100 Subject: [PATCH 102/129] Replace *, foo imports --- src/sage/libs/ntl/decl.pxi | 24 +++++++++---------- src/sage/libs/ntl/ntl_GF2X_linkage.pxi | 4 ++-- src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi | 4 ++-- src/sage/libs/ntl/ntl_ZZ_pX.pxd | 2 +- src/sage/libs/ntl/ntl_lzz_p.pxd | 2 +- src/sage/libs/ntl/ntl_lzz_pX.pxd | 4 ++-- .../polynomial_integer_dense_ntl.pyx | 2 +- .../polynomial/polynomial_modn_dense_ntl.pxd | 8 ++----- 8 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/sage/libs/ntl/decl.pxi b/src/sage/libs/ntl/decl.pxi index bce10d5186b..b18ccce5b4d 100644 --- a/src/sage/libs/ntl/decl.pxi +++ b/src/sage/libs/ntl/decl.pxi @@ -7,18 +7,18 @@ cdef extern from "ntl_wrap.h": long NTL_OVFBND bint NTL_OVERFLOW(long, long, long) -from sage.libs.ntl.ntl_ZZ_decl cimport *, ZZ_c -from sage.libs.ntl.ntl_lzz_pX_decl cimport *, zz_pX_c, zz_pX_Modulus_c -from sage.libs.ntl.ntl_ZZ_pX_decl cimport *, ZZ_pX_c, ZZ_pX_Modulus_c -from sage.libs.ntl.ntl_ZZ_pContext_decl cimport *, ZZ_pContext_c -from sage.libs.ntl.ntl_ZZ_p_decl cimport *, ZZ_p_c -from sage.libs.ntl.ntl_vec_ZZ_p_decl cimport *, vec_ZZ_p_c -from sage.libs.ntl.ntl_ZZX_decl cimport *, ZZX_c -from sage.libs.ntl.ntl_lzz_pContext_decl cimport *, zz_pContext_c -from sage.libs.ntl.ntl_ZZ_pEContext_decl cimport *, ZZ_pEContext_c -from sage.libs.ntl.ntl_ZZ_pE_decl cimport *, ZZ_pE_c -from sage.libs.ntl.ntl_vec_ZZ_pE_decl cimport *, vec_ZZ_pE_c -from sage.libs.ntl.ntl_ZZ_pEX_decl cimport *, ZZ_pEX_c +from sage.libs.ntl.ntl_ZZ_decl cimport * +from sage.libs.ntl.ntl_lzz_pX_decl cimport * +from sage.libs.ntl.ntl_ZZ_pX_decl cimport * +from sage.libs.ntl.ntl_ZZ_pContext_decl cimport * +from sage.libs.ntl.ntl_ZZ_p_decl cimport * +from sage.libs.ntl.ntl_vec_ZZ_p_decl cimport * +from sage.libs.ntl.ntl_ZZX_decl cimport * +from sage.libs.ntl.ntl_lzz_pContext_decl cimport * +from sage.libs.ntl.ntl_ZZ_pEContext_decl cimport * +from sage.libs.ntl.ntl_ZZ_pE_decl cimport * +from sage.libs.ntl.ntl_vec_ZZ_pE_decl cimport * +from sage.libs.ntl.ntl_ZZ_pEX_decl cimport * cdef extern from "ntl_wrap.h": #### mat_ZZ_c diff --git a/src/sage/libs/ntl/ntl_GF2X_linkage.pxi b/src/sage/libs/ntl/ntl_GF2X_linkage.pxi index f3f3c2036e8..478968048c3 100644 --- a/src/sage/libs/ntl/ntl_GF2X_linkage.pxi +++ b/src/sage/libs/ntl/ntl_GF2X_linkage.pxi @@ -14,8 +14,8 @@ AUTHOR: # http://www.gnu.org/licenses/ #***************************************************************************** -from sage.libs.ntl.ntl_GF2_decl cimport *, GF2_c -from sage.libs.ntl.ntl_GF2X_decl cimport *, GF2X_c, GF2XModulus_c +from sage.libs.ntl.ntl_GF2_decl cimport * +from sage.libs.ntl.ntl_GF2X_decl cimport * cdef GF2X_c *celement_new(long parent): diff --git a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi index 720b163d411..97a2446b4d7 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi +++ b/src/sage/libs/ntl/ntl_ZZ_pEX_linkage.pxi @@ -17,8 +17,8 @@ AUTHOR: include "sage/ext/cdefs.pxi" from sage.libs.ntl.ntl_ZZ_pEContext cimport ntl_ZZ_pEContext_class -from sage.libs.ntl.ntl_ZZ_pEContext_decl cimport *, ZZ_pEContext_c -from sage.libs.ntl.ntl_ZZ_pEX_decl cimport *, ZZ_pEX_c, ZZ_pEX_Modulus_c +from sage.libs.ntl.ntl_ZZ_pEContext_decl cimport * +from sage.libs.ntl.ntl_ZZ_pEX_decl cimport * from sage.libs.ntl.ntl_ZZ_pE_decl cimport ZZ_pE_from_str from sage.libs.ntl.ntl_ZZ_pE cimport ntl_ZZ_pE diff --git a/src/sage/libs/ntl/ntl_ZZ_pX.pxd b/src/sage/libs/ntl/ntl_ZZ_pX.pxd index dd30e4566d0..b96c20f0bfd 100644 --- a/src/sage/libs/ntl/ntl_ZZ_pX.pxd +++ b/src/sage/libs/ntl/ntl_ZZ_pX.pxd @@ -1,6 +1,6 @@ include "sage/ext/cdefs.pxi" -from ntl_ZZ_pX_decl cimport *, ZZ_pX_c, ZZ_pX_Modulus_c +from ntl_ZZ_pX_decl cimport * from sage.libs.ntl.ntl_ZZ_pContext cimport ntl_ZZ_pContext_class cdef class ntl_ZZ_pX: diff --git a/src/sage/libs/ntl/ntl_lzz_p.pxd b/src/sage/libs/ntl/ntl_lzz_p.pxd index ab09f378534..e99bef6cc5a 100644 --- a/src/sage/libs/ntl/ntl_lzz_p.pxd +++ b/src/sage/libs/ntl/ntl_lzz_p.pxd @@ -1,7 +1,7 @@ include "decl.pxi" from sage.libs.ntl.ntl_lzz_pContext cimport ntl_zz_pContext_class -from sage.libs.ntl.ntl_lzz_p_decl cimport *,zz_p_c +from sage.libs.ntl.ntl_lzz_p_decl cimport * #cdef extern from "ntl_wrap.h": # struct zz_p_c diff --git a/src/sage/libs/ntl/ntl_lzz_pX.pxd b/src/sage/libs/ntl/ntl_lzz_pX.pxd index 3a77b63acc8..bb69e2ed329 100644 --- a/src/sage/libs/ntl/ntl_lzz_pX.pxd +++ b/src/sage/libs/ntl/ntl_lzz_pX.pxd @@ -1,5 +1,5 @@ -from sage.libs.ntl.ntl_lzz_pX_decl cimport *, zz_pX_c, zz_pX_Modulus_c -from sage.libs.ntl.ntl_lzz_p_decl cimport *, zz_p_c +from sage.libs.ntl.ntl_lzz_pX_decl cimport * +from sage.libs.ntl.ntl_lzz_p_decl cimport * from sage.libs.ntl.ntl_lzz_pContext cimport ntl_zz_pContext_class diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx index 962f094d3ad..048fa74d306 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_ntl.pyx @@ -57,7 +57,7 @@ from sage.rings.fraction_field_element import FractionFieldElement from sage.rings.arith import lcm import sage.rings.polynomial.polynomial_ring -from sage.libs.ntl.ntl_ZZX_decl cimport *, vec_pair_ZZX_long_c +from sage.libs.ntl.ntl_ZZX_decl cimport * cdef class Polynomial_integer_dense_ntl(Polynomial): r""" diff --git a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pxd b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pxd index f6af12a389f..2686a6054b9 100644 --- a/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pxd +++ b/src/sage/rings/polynomial/polynomial_modn_dense_ntl.pxd @@ -11,13 +11,9 @@ from sage.libs.ntl.ntl_lzz_pContext cimport ntl_zz_pContext_class from sage.rings.integer cimport Integer -from sage.libs.ntl.ntl_ZZ_pX_decl cimport *, ZZ_pX_c, ZZ_pX_Modulus_c -from sage.libs.ntl.ntl_lzz_pX_decl cimport *, zz_pX_c, zz_pX_Modulus_c +from sage.libs.ntl.ntl_ZZ_pX_decl cimport * +from sage.libs.ntl.ntl_lzz_pX_decl cimport * -#include "sage/libs/ntl/decl.pxi" - -#cdef extern from "ntl_wrap.h": -# struct zz_pX cdef class Polynomial_dense_mod_n(Polynomial): cdef object __poly From caaebf42c100113f3a9ae25f06d44f2dd45adc0c Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Mon, 16 Mar 2015 22:38:11 +0100 Subject: [PATCH 103/129] Jinja depends on Sphinx instead of docutils --- build/deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/deps b/build/deps index 1f51a99ac40..5c77dfe4468 100644 --- a/build/deps +++ b/build/deps @@ -471,7 +471,7 @@ $(INST)/$(SPHINX): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) $(INST)/$(DOCUTILS) \ $(INST)/$(MARKUPSAFE): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) +$(PIPE) "$(SAGE_SPKG) $(MARKUPSAFE) 2>&1" "tee -a $(SAGE_LOGS)/$(MARKUPSAFE).log" -$(INST)/$(JINJA2): $(INST)/$(MARKUPSAFE) $(INST)/$(DOCUTILS) +$(INST)/$(JINJA2): $(INST)/$(MARKUPSAFE) $(INST)/$(SPHINX) +$(PIPE) "$(SAGE_SPKG) $(JINJA2) 2>&1" "tee -a $(SAGE_LOGS)/$(JINJA2).log" $(INST)/$(PYGMENTS): $(INST)/$(PYTHON) $(INST)/$(SETUPTOOLS) From 86768293bb94d077f37dc15aff84f0f332422b87 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Mon, 16 Mar 2015 15:18:37 -0700 Subject: [PATCH 104/129] Fixing pdf build. --- src/sage/combinat/combinat.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/combinat/combinat.py b/src/sage/combinat/combinat.py index 290e085cac3..53e4b56402c 100644 --- a/src/sage/combinat/combinat.py +++ b/src/sage/combinat/combinat.py @@ -549,7 +549,7 @@ def fibonacci(n, algorithm="pari"): raise ValueError("no algorithm {}".format(algorithm)) def lucas_number1(n, P, Q): - """ + r""" Return the `n`-th Lucas number "of the first kind" (this is not standard terminology). The Lucas sequence `L^{(1)}_n` is defined by the initial conditions `L^{(1)}_1 = 0`, @@ -2195,7 +2195,7 @@ def __iter__(self): #### combinatorial sets/lists def tuples(S, k, algorithm='itertools'): - """ + r""" Return a list of all `k`-tuples of elements of a given set ``S``. This function accepts the set ``S`` in the form of any iterable @@ -2343,7 +2343,7 @@ def number_of_tuples(S, k, algorithm='naive'): raise ValueError('invalid algorithm') def unordered_tuples(S, k, algorithm='itertools'): - """ + r""" Return a list of all unordered tuples of length ``k`` of the set ``S``. An unordered tuple of length `k` of set `S` is a unordered selection @@ -2411,7 +2411,7 @@ def unordered_tuples(S, k, algorithm='itertools'): raise ValueError('invalid algorithm') def number_of_unordered_tuples(S, k, algorithm='naive'): - """ + r""" Return the size of ``unordered_tuples(S, k)`` when `S` is a set. INPUT: From 80e9f806ff467e6d93be8c9c024788b214ffd2fe Mon Sep 17 00:00:00 2001 From: John Cremona Date: Tue, 17 Mar 2015 11:10:51 +0000 Subject: [PATCH 105/129] updated eclib to version 20150228 --- build/pkgs/eclib/checksums.ini | 6 +++--- build/pkgs/eclib/package-version.txt | 2 +- src/sage/libs/cremona/mat.pxd | 8 +++++--- src/sage/libs/cremona/mat.pyx | 6 +++--- 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/build/pkgs/eclib/checksums.ini b/build/pkgs/eclib/checksums.ini index 630c07f14e3..5fe0101d2e0 100644 --- a/build/pkgs/eclib/checksums.ini +++ b/build/pkgs/eclib/checksums.ini @@ -1,4 +1,4 @@ tarball=eclib-VERSION.tar.bz2 -sha1=b8a8b5232caf2332e650d8b6b8ec009979312974 -md5=da8b822c6bbef913bcef6d7491c8c999 -cksum=3800001876 +sha1=208567933690c8805eec313cfdedf7f9b9475ddb +md5=2ebc69ef8d99750e91178b8eb8f26c9d +cksum=3283693850 diff --git a/build/pkgs/eclib/package-version.txt b/build/pkgs/eclib/package-version.txt index cb7b2511ca6..d700bef6991 100644 --- a/build/pkgs/eclib/package-version.txt +++ b/build/pkgs/eclib/package-version.txt @@ -1 +1 @@ -20140921 +20150228 diff --git a/src/sage/libs/cremona/mat.pxd b/src/sage/libs/cremona/mat.pxd index 06f542dd522..f043f14ba43 100644 --- a/src/sage/libs/cremona/mat.pxd +++ b/src/sage/libs/cremona/mat.pxd @@ -13,11 +13,13 @@ cdef extern from "eclib/homspace.h": ctypedef struct mat "mat": scalar* get_entries() # TODO: possibly not int -- scalar sub(long,long) + # These became methods from eclib-20150228: + long nrows() + long ncols() + long rank() - long nrows(mat M) - long ncols(mat M) mat addscalar(mat M, scalar) - long rank(mat M) + # Constructors mat *new_mat "new mat" (mat m) diff --git a/src/sage/libs/cremona/mat.pyx b/src/sage/libs/cremona/mat.pyx index a67a84a5876..ea91dc0bb13 100644 --- a/src/sage/libs/cremona/mat.pyx +++ b/src/sage/libs/cremona/mat.pyx @@ -100,7 +100,7 @@ cdef class Matrix: cdef long i, j if self.M: i, j = ij - if 0 Date: Tue, 17 Mar 2015 15:54:11 +0100 Subject: [PATCH 106/129] Fix a merge conflict in the reference manual w.r.t. the latest develop version of Sage The conflict was due to the whole reorganization of the reference manual TOC introduced in Sage 6.6.beta4. --- src/doc/en/reference/index.rst | 140 +++++++++++++++++------------- src/doc/en/reference/todolist.rst | 20 ----- 2 files changed, 79 insertions(+), 81 deletions(-) delete mode 100644 src/doc/en/reference/todolist.rst diff --git a/src/doc/en/reference/index.rst b/src/doc/en/reference/index.rst index 9446c3128da..72e9b3bd8b7 100644 --- a/src/doc/en/reference/index.rst +++ b/src/doc/en/reference/index.rst @@ -1,68 +1,84 @@ +******** +Contents +******** + Welcome to Sage's Reference Manual! -================================================= -This is a thematic index of all of `Sage's `_ +This manual is a thematic index of all of `Sage's `_ features. It also contains many examples that illustrate their use, all of them systematically tested with each release. Enjoy Sage! -Table of Contents -================= +User Interface +============== -* :doc:`The Sage Command Line ` -* :doc:`The Sage Notebook ` +* :doc:`Command Line Interface (REPL) ` +* :doc:`Web Notebook ` -Calculus, Plotting ------------------- +Graphics +======== -* :doc:`Symbolic Calculus ` -* :doc:`Constants ` -* :doc:`Functions ` * :doc:`2D Graphics ` * :doc:`3D Graphics ` -Combinatorics, Discrete Mathematics ------------------------------------ - -* :doc:`Combinatorics ` -* :doc:`Graph Theory ` -* :doc:`Matroid Theory ` -* :doc:`Discrete Dynamics ` -* :doc:`Quivers ` +Mathematics +=========== -Structures, Coercion, Categories +Parents, Elements and Categories -------------------------------- -* :doc:`Basic Structures ` +* :doc:`Basic Infrastructure ` * :doc:`Coercion ` -* :doc:`Category Theory and Categories ` +* :doc:`Categories ` + +Standard Algebraic Structures +----------------------------- + +* :doc:`Monoids ` +* :doc:`Groups ` +* :doc:`Semirings ` +* :doc:`Rings ` +* :doc:`Algebras ` -Rings, Fields, Algebras ------------------------ +Standard Rings and Fields +------------------------- -* :doc:`General Rings, Ideals, and Morphisms ` -* :doc:`Standard Commutative Rings ` -* :doc:`Fixed and Arbitrary Precision Numerical Fields ` -* :doc:`Finite Rings ` +* :doc:`Integers, Rationals, etc. ` +* :doc:`Real and Complex Numbers ` +* :doc:`Finite Rings and Fields ` +* :doc:`Polynomials ` +* :doc:`Formal Power Series ` * :doc:`Algebraic Number Fields ` * :doc:`Function Fields ` -* :doc:`p-Adics ` -* :doc:`Polynomial Rings ` -* :doc:`Power Series Rings ` -* :doc:`Standard Semirings ` -* :doc:`Algebras ` +* :doc:`p-Adic Numbers ` * :doc:`Quaternion Algebras ` -Groups, Monoids, Matrices, Modules ----------------------------------- +Linear Algebra +-------------- -* :doc:`Groups ` -* :doc:`Monoids ` * :doc:`Matrices and Spaces of Matrices ` -* :doc:`Modules ` +* :doc:`Vectors and Modules ` * :doc:`Tensors on free modules of finite rank ` +Discrete Mathematics +-------------------- + +* :doc:`Combinatorics ` +* :doc:`Graph Theory ` +* :doc:`Quivers ` +* :doc:`Matroid Theory ` +* :doc:`Discrete Dynamics ` +* :doc:`Coding Theory ` +* :doc:`Game Theory ` + +Calculus +-------- + +* :doc:`Symbolic Calculus ` +* :doc:`Mathematical Constants ` +* :doc:`Elementary and Special Functions ` + Geometry and Topology --------------------- @@ -74,6 +90,7 @@ Geometry and Topology Number Theory, Algebraic Geometry --------------------------------- +* :doc:`Diophantine approximation ` * :doc:`Quadratic Forms ` * :doc:`L-Functions ` * :doc:`Schemes ` @@ -86,44 +103,45 @@ Number Theory, Algebraic Geometry * :doc:`Modular Abelian Varieties ` * :doc:`Miscellaneous Modular-Form-Related Modules ` -Miscellaneous Mathematics -------------------------- +Logic +----- -* :doc:`Games ` * :doc:`Symbolic Logic ` * :doc:`SAT solvers ` -* :doc:`Cryptography ` -* :doc:`Numerical Optimization ` + +Probability and Statistics +-------------------------- + * :doc:`Probability ` * :doc:`Statistics ` * :doc:`Quantitative Finance ` -* :doc:`Coding Theory ` -* :doc:`Game Theory ` -Doctesting, Interfaces, Databases, Miscellany ---------------------------------------------- +Miscellaneous +------------- -* :doc:`Doctesting ` -* :doc:`Development Scripts ` -* :doc:`Interpreter Interfaces ` -* :doc:`C/C++ Library Interfaces ` +* :doc:`Cryptography ` +* :doc:`Optimization ` * :doc:`Databases ` -* :doc:`Parallel Computing ` -* :doc:`Miscellaneous ` +* :doc:`Games ` -Other ------ +Programming +=========== -.. toctree:: - :maxdepth: 2 +* :doc:`Data Structures ` +* :doc:`Utilities ` +* :doc:`Test Framework ` +* :doc:`Parallel Computing ` - todolist +Interfaces +---------- -* :doc:`History and License ` +* :doc:`Interpreter Interfaces ` +* :doc:`C/C++ Library Interfaces ` -Indices and Tables ------------------- +General Information +=================== +* :doc:`History and License ` * :ref:`genindex` * :ref:`modindex` * :ref:`search` diff --git a/src/doc/en/reference/todolist.rst b/src/doc/en/reference/todolist.rst deleted file mode 100644 index 94b0d34ffc5..00000000000 --- a/src/doc/en/reference/todolist.rst +++ /dev/null @@ -1,20 +0,0 @@ -.. _ch:todolist: - -SAGE's To Do list -================= - -There is still some work to do :-) : - -.. warning:: - - This list is currently very incomplete as most doctests do not use the - ``.. todo::`` markup. - - .. todo:: - - Rewrite the hand-written TODOs by using the correct ``.. todo::`` - markup. - -The combined to do list is only available in the html version of the reference manual. - -.. todolist:: From 80770c5af1212d6801e94ed2406ed197cbf8cf40 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 16:16:59 +0100 Subject: [PATCH 107/129] Replaced gen_mat by generator_matrix --- src/sage/coding/code_constructions.py | 8 +- .../coding/codecan/autgroup_can_label.pyx | 30 ++-- src/sage/coding/codecan/codecan.pyx | 36 ++--- src/sage/coding/guava.py | 2 +- src/sage/coding/linear_code.py | 144 ++++++++++-------- 5 files changed, 115 insertions(+), 105 deletions(-) diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index e04591497fb..49baa59f1b7 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -646,7 +646,7 @@ def CyclicCodeFromGeneratingPolynomial(n,g,ignore=True): sage: g = x^3+x+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(7,g); C Linear code of length 7, dimension 4 over Finite Field of size 2 - sage: C.gen_mat() + sage: C.generator_matrix() [1 1 0 1 0 0 0] [0 1 1 0 1 0 0] [0 0 1 1 0 1 0] @@ -654,7 +654,7 @@ def CyclicCodeFromGeneratingPolynomial(n,g,ignore=True): sage: g = x+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(4,g); C Linear code of length 4, dimension 3 over Finite Field of size 2 - sage: C.gen_mat() + sage: C.generator_matrix() [1 1 0 0] [0 1 1 0] [0 0 1 1] @@ -720,7 +720,7 @@ def CyclicCodeFromCheckPolynomial(n,h,ignore=True): Linear code of length 4, dimension 1 over Finite Field of size 3 sage: C = codes.CyclicCodeFromCheckPolynomial(4,x^3 + x^2 + x + 1); C Linear code of length 4, dimension 3 over Finite Field of size 3 - sage: C.gen_mat() + sage: C.generator_matrix() [2 1 0 0] [0 2 1 0] [0 0 2 1] @@ -994,7 +994,7 @@ def HammingCode(r,F): H = MS(PFn).transpose() Cd = LinearCode(H) # Hamming code always has distance 3, so we provide the distance. - return LinearCode(Cd.dual_code().gen_mat(), d=3) + return LinearCode(Cd.dual_code().generator_matrix(), d=3) def LinearCodeFromCheckMatrix(H): diff --git a/src/sage/coding/codecan/autgroup_can_label.pyx b/src/sage/coding/codecan/autgroup_can_label.pyx index cc8410d72c0..c4630e78666 100644 --- a/src/sage/coding/codecan/autgroup_can_label.pyx +++ b/src/sage/coding/codecan/autgroup_can_label.pyx @@ -49,14 +49,14 @@ EXAMPLES:: sage: from sage.coding.codecan.autgroup_can_label import LinearCodeAutGroupCanLabel sage: C = codes.HammingCode(3, GF(3)).dual_code() sage: P = LinearCodeAutGroupCanLabel(C) - sage: P.get_canonical_form().gen_mat() + sage: P.get_canonical_form().generator_matrix() [1 0 0 0 0 1 1 1 1 1 1 1 1] [0 1 0 1 1 0 0 1 1 2 2 1 2] [0 0 1 1 2 1 2 1 2 1 2 0 0] - sage: LinearCode(P.get_transporter()*C.gen_mat()) == P.get_canonical_form() + sage: LinearCode(P.get_transporter()*C.generator_matrix()) == P.get_canonical_form() True sage: A = P.get_autom_gens() - sage: all( [ LinearCode(a*C.gen_mat()) == C for a in A]) + sage: all( [ LinearCode(a*C.generator_matrix()) == C for a in A]) True sage: P.get_autom_order() == GL(3, GF(3)).order() True @@ -65,7 +65,7 @@ If the dimension of the dual code is smaller, we will work on this code:: sage: C2 = codes.HammingCode(3, GF(3)) sage: P2 = LinearCodeAutGroupCanLabel(C2) - sage: P2.get_canonical_form().check_mat() == P.get_canonical_form().gen_mat() + sage: P2.get_canonical_form().check_mat() == P.get_canonical_form().generator_matrix() True There is a specialization of this algorithm to pass a coloring on the @@ -171,14 +171,14 @@ class LinearCodeAutGroupCanLabel: sage: from sage.coding.codecan.autgroup_can_label import LinearCodeAutGroupCanLabel sage: C = codes.HammingCode(3, GF(3)).dual_code() sage: P = LinearCodeAutGroupCanLabel(C) - sage: P.get_canonical_form().gen_mat() + sage: P.get_canonical_form().generator_matrix() [1 0 0 0 0 1 1 1 1 1 1 1 1] [0 1 0 1 1 0 0 1 1 2 2 1 2] [0 0 1 1 2 1 2 1 2 1 2 0 0] - sage: LinearCode(P.get_transporter()*C.gen_mat()) == P.get_canonical_form() + sage: LinearCode(P.get_transporter()*C.generator_matrix()) == P.get_canonical_form() True sage: a = P.get_autom_gens()[0] - sage: (a*C.gen_mat()).echelon_form() == C.gen_mat().echelon_form() + sage: (a*C.generator_matrix()).echelon_form() == C.generator_matrix().echelon_form() True sage: P.get_autom_order() == GL(3, GF(3)).order() True @@ -208,13 +208,13 @@ class LinearCodeAutGroupCanLabel: sage: from sage.coding.codecan.autgroup_can_label import LinearCodeAutGroupCanLabel sage: C = codes.HammingCode(3, GF(2)).dual_code() sage: P = LinearCodeAutGroupCanLabel(C) - sage: P.get_canonical_form().gen_mat() + sage: P.get_canonical_form().generator_matrix() [1 0 0 0 1 1 1] [0 1 0 1 0 1 1] [0 0 1 1 1 1 0] sage: P2 = LinearCodeAutGroupCanLabel(C, P=[[0,3,5],[1,2,4,6]], ....: algorithm_type="permutational") - sage: P2.get_canonical_form().gen_mat() + sage: P2.get_canonical_form().generator_matrix() [1 1 1 0 0 0 1] [0 1 0 1 1 0 1] [0 0 1 0 1 1 1] @@ -226,7 +226,7 @@ class LinearCodeAutGroupCanLabel: raise TypeError("%s is not a linear code"%C) self.C = C - mat = C.gen_mat() + mat = C.generator_matrix() F = mat.base_ring() S = SemimonomialTransformationGroup(F, mat.ncols()) @@ -534,7 +534,7 @@ class LinearCodeAutGroupCanLabel: sage: C = codes.HammingCode(3, GF(3)).dual_code() sage: CF1 = LinearCodeAutGroupCanLabel(C).get_canonical_form() sage: s = SemimonomialTransformationGroup(GF(3), C.length()).an_element() - sage: C2 = LinearCode(s*C.gen_mat()) + sage: C2 = LinearCode(s*C.generator_matrix()) sage: CF2 = LinearCodeAutGroupCanLabel(C2).get_canonical_form() sage: CF1 == CF2 True @@ -552,7 +552,7 @@ class LinearCodeAutGroupCanLabel: sage: P = LinearCodeAutGroupCanLabel(C) sage: g = P.get_transporter() sage: D = P.get_canonical_form() - sage: (g*C.gen_mat()).echelon_form() == D.gen_mat().echelon_form() + sage: (g*C.generator_matrix()).echelon_form() == D.generator_matrix().echelon_form() True """ return self._transporter @@ -566,7 +566,7 @@ class LinearCodeAutGroupCanLabel: sage: from sage.coding.codecan.autgroup_can_label import LinearCodeAutGroupCanLabel sage: C = codes.HammingCode(3, GF(2)).dual_code() sage: A = LinearCodeAutGroupCanLabel(C).get_autom_gens() - sage: Gamma = C.gen_mat().echelon_form() + sage: Gamma = C.generator_matrix().echelon_form() sage: all([(g*Gamma).echelon_form() == Gamma for g in A]) True """ @@ -601,12 +601,12 @@ class LinearCodeAutGroupCanLabel: sage: from sage.coding.codecan.autgroup_can_label import LinearCodeAutGroupCanLabel sage: C = codes.HammingCode(3, GF(4, 'a')).dual_code() sage: A = LinearCodeAutGroupCanLabel(C).get_PGammaL_gens() - sage: Gamma = C.gen_mat() + sage: Gamma = C.generator_matrix() sage: N = [ x.monic() for x in Gamma.columns() ] sage: all([ (g[0]*n.apply_map(g[1])).monic() in N for n in N for g in A]) True """ - Gamma = self.C.gen_mat() + Gamma = self.C.generator_matrix() res = [] for a in self._PGammaL_autom_gens: B = Gamma.solve_left(a * Gamma, check=True) diff --git a/src/sage/coding/codecan/codecan.pyx b/src/sage/coding/codecan/codecan.pyx index d793c775cb8..802e5cf8996 100644 --- a/src/sage/coding/codecan/codecan.pyx +++ b/src/sage/coding/codecan/codecan.pyx @@ -62,7 +62,7 @@ EXAMPLES: Get the canonical form of the Simplex code:: sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode - sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat() + sage: mat = codes.HammingCode(3, GF(3)).dual_code().generator_matrix() sage: P = PartitionRefinementLinearCode(mat.ncols(), mat) sage: cf = P.get_canonical_form(); cf [1 0 0 0 0 1 1 1 1 1 1 1 1] @@ -470,7 +470,7 @@ cdef class PartitionRefinementLinearCode(PartitionRefinement_generic): EXAMPLES:: sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode - sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat() + sage: mat = codes.HammingCode(3, GF(3)).dual_code().generator_matrix() sage: P = PartitionRefinementLinearCode(mat.ncols(), mat) sage: cf = P.get_canonical_form(); cf [1 0 0 0 0 1 1 1 1 1 1 1 1] @@ -490,39 +490,39 @@ cdef class PartitionRefinementLinearCode(PartitionRefinement_generic): sage: all( [(a*mat).echelon_form() == mat.echelon_form() for a in A]) True """ - def __cinit__(self, n, gen_mat, **kwds): + def __cinit__(self, n, generator_matrix, **kwds): r""" Initialization. See :meth:`__init__`. EXAMPLES:: sage: from sage.coding.codecan.codecan import PartitionRefinementLinearCode - sage: mat = codes.HammingCode(3, GF(3)).dual_code().gen_mat() + sage: mat = codes.HammingCode(3, GF(3)).dual_code().generator_matrix() sage: P = PartitionRefinementLinearCode(mat.ncols(), mat) """ - self._k = gen_mat.nrows() - self._q = len(gen_mat.base_ring()) + self._k = generator_matrix.nrows() + self._q = len(generator_matrix.base_ring()) self._nr_of_supp_refine_calls = 0 self._nr_of_point_refine_calls = 0 - self._matrix = copy(gen_mat) - self._root_matrix = gen_mat + self._matrix = copy(generator_matrix) + self._root_matrix = generator_matrix self._stored_states = dict() self._supp_refine_vals = _BestValStore(n) self._point_refine_vals = _BestValStore(n) # self._hyp_refine_vals will initialized after # we computed the set of codewords - def __init__(self, n, gen_mat, P=None, algorithm_type="semilinear"): + def __init__(self, n, generator_matrix, P=None, algorithm_type="semilinear"): r""" Initialization, we immediately start the algorithm (see :mod:``sage.coding.codecan.codecan``) to compute the canonical form and automorphism group of the linear code - generated by ``gen_mat``. + generated by ``generator_matrix``. INPUT: - ``n`` -- an integer - - ``gen_mat`` -- a `k \times n` matrix over `\GF{q}` of full row rank, + - ``generator_matrix`` -- a `k \times n` matrix over `\GF{q}` of full row rank, i.e. `k Date: Tue, 17 Mar 2015 16:21:33 +0100 Subject: [PATCH 108/129] Changed names of linear code parameters --- src/sage/coding/linear_code.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index cb422e10e76..359ce670c7d 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -826,11 +826,11 @@ def __init__(self, generator_matrix, d=None): facade_for = generator_matrix.row(0).parent() self.Element = type(generator_matrix.row(0)) # for when we make this a non-facade parent Parent.__init__(self, base=base_ring, facade=facade_for, category=cat) - self.__gens = generator_matrix.rows() + self._gens = generator_matrix.rows() self._generator_matrix = generator_matrix self._length = generator_matrix.ncols() - self.__dim = generator_matrix.rank() - self.__distance = d + self._dimension = generator_matrix.rank() + self._minimum_distance = d def _repr_(self): r""" @@ -860,7 +860,7 @@ def _an_element_(self): sage: C2.an_element() ((1, 0, 0, 0, 0, 1, 1), (1, 0, 0, 0, 0, 1, 1)) """ - return self.__gens[0] + return self._gens[0] def automorphism_group_gens(self, equivalence="semilinear"): r""" @@ -1069,7 +1069,7 @@ def basis(self): sage: C.basis() [(1, 0, 0, 0, 0, 1, 1), (0, 1, 0, 0, 1, 0, 1), (0, 0, 1, 0, 1, 1, 0), (0, 0, 0, 1, 1, 1, 1)] """ - return self.__gens + return self._gens # S. Pancratz, 19 Jan 2010: In the doctests below, I removed the example # ``C.binomial_moment(3)``, which was also marked as ``#long``. This way, @@ -1582,7 +1582,7 @@ def dimension(self): sage: C.dimension() 2 """ - return self.__dim + return self._dimension def direct_sum(self, other): """ @@ -1894,7 +1894,7 @@ def gens(self): sage: C.gens() [(1, 0, 0, 0, 0, 1, 1), (0, 1, 0, 0, 1, 0, 1), (0, 0, 1, 0, 1, 1, 0), (0, 0, 0, 1, 1, 1, 1)] """ - return self.__gens + return self._gens def genus(self): r""" @@ -2240,8 +2240,8 @@ def minimum_distance(self, algorithm=None): # If the minimum distance has already been computed or provided by # the user then simply return the stored value. # This is done only if algorithm is None. - if self.__distance is not None and algorithm is None: - return self.__distance + if self._minimum_distance is not None and algorithm is None: + return self._minimum_distance if algorithm not in (None, "gap", "guava"): raise ValueError("The algorithm argument must be one of None, " "'gap' or 'guava'; got '{0}'".format(algorithm)) @@ -2262,8 +2262,8 @@ def minimum_distance(self, algorithm=None): #print "Running Guava's MinimumWeight ...\n" return ZZ(d) Gstr = "%s*Z(%s)^0"%(gapG, q) - self.__distance = min_wt_vec_gap(Gstr,n,k,F).hamming_weight() - return self.__distance + self._minimum_distance = min_wt_vec_gap(Gstr,n,k,F).hamming_weight() + return self._minimum_distance def module_composition_factors(self, gp): r""" @@ -3058,7 +3058,7 @@ def zero(self): sage: C.sum((C.gens())) # indirect doctest (1, 1, 1, 1, 1, 1, 1) """ - v = 0*self.__gens[0] + v = 0*self._gens[0] v.set_immutable() return v From f57ba383f43a8d8400a45ee4d545ea984e8a92a0 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 16:24:02 +0100 Subject: [PATCH 109/129] Class parameters are now accessed by getter methods --- src/sage/coding/linear_code.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 359ce670c7d..390d1e5335b 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -860,7 +860,7 @@ def _an_element_(self): sage: C2.an_element() ((1, 0, 0, 0, 0, 1, 1), (1, 0, 0, 0, 0, 1, 1)) """ - return self._gens[0] + return self.gens()[0] def automorphism_group_gens(self, equivalence="semilinear"): r""" @@ -940,7 +940,7 @@ def ambient_space(self): sage: C.ambient_space() Vector space of dimension 7 over Finite Field of size 2 """ - return VectorSpace(self.base_ring(),self._length) + return VectorSpace(self.base_ring(),self.length()) def assmus_mattson_designs(self, t, mode=None): r""" @@ -1069,7 +1069,7 @@ def basis(self): sage: C.basis() [(1, 0, 0, 0, 0, 1, 1), (0, 1, 0, 0, 1, 0, 1), (0, 0, 1, 0, 1, 1, 0), (0, 0, 0, 1, 1, 1, 1)] """ - return self._gens + return self.gens() # S. Pancratz, 19 Jan 2010: In the doctests below, I removed the example # ``C.binomial_moment(3)``, which was also marked as ``#long``. This way, @@ -3058,7 +3058,7 @@ def zero(self): sage: C.sum((C.gens())) # indirect doctest (1, 1, 1, 1, 1, 1, 1) """ - v = 0*self._gens[0] + v = 0*self.gens()[0] v.set_immutable() return v From 7b7395eae5ca1897fa9edf445c5317b1ada00584 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 16:37:39 +0100 Subject: [PATCH 110/129] Replaced check_mat method by parity_check_matrix --- src/sage/coding/code_constructions.py | 6 ++--- .../coding/codecan/autgroup_can_label.pyx | 4 ++-- src/sage/coding/linear_code.py | 23 +++++++++++-------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/sage/coding/code_constructions.py b/src/sage/coding/code_constructions.py index 49baa59f1b7..941013ef9e9 100644 --- a/src/sage/coding/code_constructions.py +++ b/src/sage/coding/code_constructions.py @@ -1019,20 +1019,20 @@ def LinearCodeFromCheckMatrix(H): EXAMPLES:: sage: C = codes.HammingCode(3,GF(2)) - sage: H = C.check_mat(); H + sage: H = C.parity_check_matrix(); H [1 0 1 0 1 0 1] [0 1 1 0 0 1 1] [0 0 0 1 1 1 1] sage: codes.LinearCodeFromCheckMatrix(H) == C True sage: C = codes.HammingCode(2,GF(3)) - sage: H = C.check_mat(); H + sage: H = C.parity_check_matrix(); H [1 0 1 1] [0 1 1 2] sage: codes.LinearCodeFromCheckMatrix(H) == C True sage: C = codes.RandomLinearCode(10,5,GF(4,"a")) - sage: H = C.check_mat() + sage: H = C.parity_check_matrix() sage: codes.LinearCodeFromCheckMatrix(H) == C True """ diff --git a/src/sage/coding/codecan/autgroup_can_label.pyx b/src/sage/coding/codecan/autgroup_can_label.pyx index c4630e78666..abe95aa08c9 100644 --- a/src/sage/coding/codecan/autgroup_can_label.pyx +++ b/src/sage/coding/codecan/autgroup_can_label.pyx @@ -65,7 +65,7 @@ If the dimension of the dual code is smaller, we will work on this code:: sage: C2 = codes.HammingCode(3, GF(3)) sage: P2 = LinearCodeAutGroupCanLabel(C2) - sage: P2.get_canonical_form().check_mat() == P.get_canonical_form().generator_matrix() + sage: P2.get_canonical_form().parity_check_matrix() == P.get_canonical_form().generator_matrix() True There is a specialization of this algorithm to pass a coloring on the @@ -344,7 +344,7 @@ class LinearCodeAutGroupCanLabel: P=P_refined, algorithm_type=algorithm_type) can_transp = agcl.get_transporter() can_transp.invert_v() - can_col_set = agcl.get_canonical_form().check_mat().columns() + can_col_set = agcl.get_canonical_form().parity_check_matrix().columns() A = agcl.get_autom_gens() for a in A: a.invert_v() diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 390d1e5335b..0bb593c0e7e 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -66,8 +66,8 @@ bounds_minimum_distance which call tables in GUAVA (updated May 2006) created by Cen Tjhai instead of the online internet tables, -#. generator_matrix, generator_matrix_systematic, information_set, list, check_mat, decode, - dual_code, extended_code, shortened, punctured, genus, binomial_moment, +#. generator_matrix, generator_matrix_systematic, information_set, list, parity_check_matrix, + decode, dual_code, extended_code, shortened, punctured, genus, binomial_moment, and divisor methods for LinearCode, #. Boolean-valued functions such as "==", is_self_dual, is_self_orthogonal, @@ -1366,6 +1366,11 @@ def __cmp__(self, right): return cmp(self._generator_matrix, right._generator_matrix) def check_mat(self): + from sage.misc.superseded import deprecation + deprecation(17973, "check_mat method is now deprecated, please call parity_check_matrix instead") + return self.parity_check_matrix() + + def parity_check_matrix(self): r""" Returns the check matrix of ``self``. @@ -1381,11 +1386,11 @@ def check_mat(self): [0 1 0 0 1 0 1] [0 0 1 0 1 1 0] [0 0 0 1 1 1 1] - sage: C.check_mat() + sage: C.parity_check_matrix() [1 0 1 0 1 0 1] [0 1 1 0 0 1 1] [0 0 0 1 1 1 1] - sage: Cperp.check_mat() + sage: Cperp.parity_check_matrix() [1 0 0 0 0 1 1] [0 1 0 0 1 0 1] [0 0 1 0 1 1 0] @@ -1653,8 +1658,8 @@ def __eq__(self, right): return False sbasis = self.gens() rbasis = right.gens() - scheck = self.check_mat() - rcheck = right.check_mat() + scheck = self.parity_check_matrix() + rcheck = right.parity_check_matrix() for c in sbasis: if rcheck*c: return False @@ -1882,7 +1887,7 @@ def generator_matrix_systematic(self): [1 2 0] [0 0 1] """ - return self._generator_matrix.echelon_form() + return self.generator_matrix().echelon_form() def gens(self): r""" @@ -1942,7 +1947,7 @@ def information_set(self): sage: code.information_set() (0, 2) """ - return self._generator_matrix.transpose().pivot_rows() + return self.generator_matrix().transpose().pivot_rows() def is_permutation_automorphism(self,g): r""" @@ -1967,7 +1972,7 @@ def is_permutation_automorphism(self,g): 0 """ basis = self.generator_matrix().rows() - H = self.check_mat() + H = self.parity_check_matrix() V = H.column_space() HGm = H*g.matrix() # raise TypeError, (type(H), type(V), type(basis[0]), type(Gmc)) From 8813983b5f85ceeb0be6377d0248cc1eb19c1e6a Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 17:20:21 +0100 Subject: [PATCH 111/129] Replaced call to deprecation by the method deprecation_function_alias Minor change to the docstirng of parity_check_matrix. --- src/sage/coding/linear_code.py | 22 +++++++--------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/sage/coding/linear_code.py b/src/sage/coding/linear_code.py index 0bb593c0e7e..8c2af5f3472 100644 --- a/src/sage/coding/linear_code.py +++ b/src/sage/coding/linear_code.py @@ -236,6 +236,7 @@ from sage.misc.randstate import current_randstate from sage.misc.decorators import rename_keyword from sage.misc.cachefunc import cached_method +from sage.misc.superseded import deprecated_function_alias ZZ = IntegerRing() VectorSpace = fm.VectorSpace @@ -1365,14 +1366,9 @@ def __cmp__(self, right): return cmp(type(self), type(right)) return cmp(self._generator_matrix, right._generator_matrix) - def check_mat(self): - from sage.misc.superseded import deprecation - deprecation(17973, "check_mat method is now deprecated, please call parity_check_matrix instead") - return self.parity_check_matrix() - def parity_check_matrix(self): r""" - Returns the check matrix of ``self``. + Returns the parity check matrix of ``self``. EXAMPLES:: @@ -1403,6 +1399,8 @@ def parity_check_matrix(self): Cperp = self.dual_code() return Cperp.generator_matrix() + check_mat = deprecated_function_alias(17973, parity_check_matrix) + def covering_radius(self): r""" Wraps Guava's ``CoveringRadius`` command. @@ -1839,11 +1837,6 @@ def __getitem__(self, i): codeword.set_immutable() return codeword - def gen_mat(self): - from sage.misc.superseded import deprecation - deprecation(17973, "gen_mat method is now deprecated, please call generator_matrix instead") - return self.generator_matrix() - def generator_matrix(self): r""" Return a generator matrix of this code. @@ -1864,10 +1857,7 @@ def generator_matrix(self): """ return self._generator_matrix - def gen_mat_systematic(self): - from sage.misc.superseded import deprecation - deprecation(17973, "gen_mat_systematic method is now deprecated, please call generator_matrix_systematic instead") - return self.generator_matrix_systematic() + gen_mat = deprecated_function_alias(17973, generator_matrix) def generator_matrix_systematic(self): """ @@ -1889,6 +1879,8 @@ def generator_matrix_systematic(self): """ return self.generator_matrix().echelon_form() + gen_mat_systematic = deprecated_function_alias(17973, generator_matrix_systematic) + def gens(self): r""" Returns the generators of this code as a list of vectors. From 7df4c041fe4ae26e46879599d6ecff1fb1eaa824 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 17:44:59 +0100 Subject: [PATCH 112/129] Fixed 2 broken doctests --- src/sage/coding/binary_code.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/coding/binary_code.pyx b/src/sage/coding/binary_code.pyx index d8a595f62e9..cc8a85ff07f 100644 --- a/src/sage/coding/binary_code.pyx +++ b/src/sage/coding/binary_code.pyx @@ -1097,7 +1097,7 @@ cdef class BinaryCode: EXAMPLE: sage: from sage.coding.binary_code import * - sage: B = BinaryCode(codes.ExtendedBinaryGolayCode().gen_mat()) + sage: B = BinaryCode(codes.ExtendedBinaryGolayCode().generator_matrix()) sage: B Binary [24,12] linear code, generator matrix [100000000000101011100011] @@ -3855,7 +3855,7 @@ cdef class BinaryCodeClassifier: EXAMPLE: sage: from sage.coding.binary_code import * sage: BC = BinaryCodeClassifier() - sage: B = BinaryCode(codes.ExtendedBinaryGolayCode().gen_mat()) + sage: B = BinaryCode(codes.ExtendedBinaryGolayCode().generator_matrix()) sage: B.apply_permutation(range(24,-1,-1)) sage: B Binary [24,12] linear code, generator matrix From 30598742cd61ce831fb80373673b93ed76cee003 Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 17:49:15 +0100 Subject: [PATCH 113/129] Fixed 4 broken doctests --- src/doc/en/constructions/linear_codes.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/doc/en/constructions/linear_codes.rst b/src/doc/en/constructions/linear_codes.rst index e40913b91a3..621a9576eab 100644 --- a/src/doc/en/constructions/linear_codes.rst +++ b/src/doc/en/constructions/linear_codes.rst @@ -27,7 +27,7 @@ Sage can compute Hamming codes Linear code of length 13, dimension 10 over Finite Field of size 3 sage: C.minimum_distance() 3 - sage: C.gen_mat() + sage: C.generator_matrix() [1 0 0 0 0 0 0 0 0 0 1 2 0] [0 1 0 0 0 0 0 0 0 0 0 1 2] [0 0 1 0 0 0 0 0 0 0 1 0 2] @@ -51,7 +51,7 @@ the four Golay codes Linear code of length 12, dimension 6 over Finite Field of size 3 sage: C.minimum_distance() 6 - sage: C.gen_mat() + sage: C.generator_matrix() [1 0 0 0 0 0 2 0 1 2 1 2] [0 1 0 0 0 0 1 2 2 2 1 0] [0 0 1 0 0 0 1 1 1 0 1 1] @@ -79,12 +79,12 @@ a check matrix, and the dual code: sage: C; Cperp Linear code of length 7, dimension 4 over Finite Field of size 2 Linear code of length 7, dimension 3 over Finite Field of size 2 - sage: C.gen_mat() + sage: C.generator_matrix() [1 0 0 0 0 1 1] [0 1 0 0 1 0 1] [0 0 1 0 1 1 0] [0 0 0 1 1 1 1] - sage: C.check_mat() + sage: C.parity_check_matrix() [1 0 1 0 1 0 1] [0 1 1 0 0 1 1] [0 0 0 1 1 1 1] From ebca0a110bcae19783ea09561c8035759617786f Mon Sep 17 00:00:00 2001 From: Franco Saliola Date: Tue, 17 Mar 2015 13:45:42 -0400 Subject: [PATCH 114/129] 17975: typo fixes --- src/sage/combinat/descent_algebra.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/descent_algebra.py b/src/sage/combinat/descent_algebra.py index acb405ecf7b..56538855bbd 100644 --- a/src/sage/combinat/descent_algebra.py +++ b/src/sage/combinat/descent_algebra.py @@ -99,8 +99,8 @@ class DescentAlgebra(Parent, UniqueRepresentation): sage: I(elt) 7/6*I[1, 1, 1, 1] + 2*I[1, 1, 2] + 3*I[1, 2, 1] + 4*I[1, 3] - There is the following syntatic sugar for calling elements of a basis, note - that for the empty set one must use ``D[[]]`` due to python's syntax:: + There is the following syntactic sugar for calling elements of a basis, note + that for the empty set one must use ``D[[]]`` due to Python's syntax:: sage: D[[]] + D[2] + 2*D[1,2] D{} + 2*D{1, 2} + D{2} From 5c325d8ba41404e24c4eb466c0d0e031bf891e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20M=2E=20Thi=C3=A9ry?= Date: Tue, 17 Mar 2015 11:14:39 -0700 Subject: [PATCH 115/129] 17975: use the occasion to do further rephrasing --- src/sage/combinat/descent_algebra.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sage/combinat/descent_algebra.py b/src/sage/combinat/descent_algebra.py index 56538855bbd..b7ff208c65e 100644 --- a/src/sage/combinat/descent_algebra.py +++ b/src/sage/combinat/descent_algebra.py @@ -99,11 +99,16 @@ class DescentAlgebra(Parent, UniqueRepresentation): sage: I(elt) 7/6*I[1, 1, 1, 1] + 2*I[1, 1, 2] + 3*I[1, 2, 1] + 4*I[1, 3] - There is the following syntactic sugar for calling elements of a basis, note - that for the empty set one must use ``D[[]]`` due to Python's syntax:: + + As syntactic sugar, one can use the notation ``D[i,...,l]`` to + construct elements of the basis; note that for the empty set one + must use ``D[[]]`` due to Python's syntax:: sage: D[[]] + D[2] + 2*D[1,2] D{} + 2*D{1, 2} + D{2} + + The same syntax works for the other bases:: + sage: I[1,2,1] + 3*I[4] + 2*I[3,1] I[1, 2, 1] + 2*I[3, 1] + 3*I[4] From cd3c6eea6fc2f7b4c6412728a5c8ae6a73aa14bd Mon Sep 17 00:00:00 2001 From: David Lucas Date: Tue, 17 Mar 2015 19:36:59 +0100 Subject: [PATCH 116/129] Fixed 2 broken doctests --- src/doc/en/thematic_tutorials/coding_theory.rst | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/doc/en/thematic_tutorials/coding_theory.rst b/src/doc/en/thematic_tutorials/coding_theory.rst index 378f8c3036c..5e5f7992183 100644 --- a/src/doc/en/thematic_tutorials/coding_theory.rst +++ b/src/doc/en/thematic_tutorials/coding_theory.rst @@ -142,7 +142,7 @@ call GUAVA: sage: G.order() # not tested (see trac #17617) 168 sage: C = codes.HammingCode(3,GF(2)) - sage: C.gen_mat() # not tested (see trac #17617) + sage: C.generator_matrix() # not tested (see trac #17617) [1 0 0 1 0 1 0] [0 1 0 1 0 1 1] [0 0 1 1 0 0 1] @@ -152,7 +152,7 @@ call GUAVA: [1 1 1] [1 0 1] [0 1 1] - sage: C.standard_form()[0].gen_mat() # not tested (see trac #17617) + sage: C.standard_form()[0].generator_matrix() # not tested (see trac #17617) [1 0 0 0 1 1 0] [0 1 0 0 1 1 1] [0 0 1 0 1 0 1] @@ -417,7 +417,7 @@ Python: sage: g = x^3+x+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(7,g); C Linear code of length 7, dimension 4 over Finite Field of size 2 - sage: C.gen_mat() + sage: C.generator_matrix() [1 1 0 1 0 0 0] [0 1 1 0 1 0 0] [0 0 1 1 0 1 0] @@ -425,7 +425,7 @@ Python: sage: g = x+1 sage: C = codes.CyclicCodeFromGeneratingPolynomial(4,g); C Linear code of length 4, dimension 3 over Finite Field of size 2 - sage: C.gen_mat() + sage: C.generator_matrix() [1 1 0 0] [0 1 1 0] [0 0 1 1] @@ -434,7 +434,7 @@ Python: Linear code of length 4, dimension 1 over Finite Field of size 3 sage: C = codes.CyclicCodeFromCheckPolynomial(4,x^3 + x^2 + x + 1); C Linear code of length 4, dimension 3 over Finite Field of size 3 - sage: C.gen_mat() + sage: C.generator_matrix() [2 1 0 0] [0 2 1 0] [0 0 2 1] @@ -515,20 +515,20 @@ Python: sage: C = codes.HammingCode(3,GF(2)) - sage: H = C.check_mat(); H # not tested (see trac #17617) + sage: H = C.parity_check_matrix(); H # not tested (see trac #17617) [1 0 0 1 1 0 1] [0 1 0 1 0 1 1] [0 0 1 1 1 1 0] sage: codes.LinearCodeFromCheckMatrix(H) == C # not tested (see trac #17617) True sage: C = codes.HammingCode(2,GF(3)) - sage: H = C.check_mat(); H # not tested (see trac #17617) + sage: H = C.parity_check_matrix(); H # not tested (see trac #17617) [1 0 2 2] [0 1 2 1] sage: codes.LinearCodeFromCheckMatrix(H) == C # not tested (see trac #17617) True sage: C = codes.RandomLinearCode(10,5,GF(4,"a")) - sage: H = C.check_mat() + sage: H = C.parity_check_matrix() sage: codes.LinearCodeFromCheckMatrix(H) == C # not tested (see trac #17617) True From 028e5f75cba440c4435ce8082a101a47d057748f Mon Sep 17 00:00:00 2001 From: Kevin Dilks Date: Tue, 17 Mar 2015 11:52:27 -0700 Subject: [PATCH 117/129] corrected code and incorrect example --- src/sage/combinat/alternating_sign_matrix.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 53fe9f379e6..a288b0939d8 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -349,13 +349,13 @@ def corner_sum_matrix(self): sage: asm = A([[0, 0, 1],[1, 0, 0],[0, 1, 0]]) sage: asm.corner_sum_matrix() [0 0 0 0] - [0 0 1 1] - [0 0 1 2] + [0 0 0 1] + [0 1 1 2] [0 1 2 3] """ asm = self.to_matrix() n = asm.nrows() + 1 - return matrix([[nw_corner_sum(asm,i,j) for i in range(n)] for j in range(n)]) + return matrix([[nw_corner_sum(asm,i,j) for j in range(n)] for i in range(n)]) def height_function(self): r""" From d888be6da157913d5d426ecef34f98e3584d4f27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20Chapoton?= Date: Tue, 17 Mar 2015 20:38:30 +0100 Subject: [PATCH 118/129] trac #17976 typo correction --- src/sage/combinat/permutation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/combinat/permutation.py b/src/sage/combinat/permutation.py index 389bf284fd0..90382a085e5 100644 --- a/src/sage/combinat/permutation.py +++ b/src/sage/combinat/permutation.py @@ -360,7 +360,7 @@ class Permutation(CombinatorialObject, Element): .. WARNING:: Since :trac:`13742` the input is checked for correctness : it is not - accepted unless actually is a permutation on `\{1, \ldots, n\}`. It + accepted unless it actually is a permutation on `\{1, \ldots, n\}`. It means that some :meth:`Permutation` objects cannot be created anymore without setting ``check_input = False``, as there is no certainty that its functions can handle them, and this should be fixed in a much From 05704adb9c97c87a3a8469596b50a67cd6617ed4 Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Tue, 17 Mar 2015 20:41:59 +0100 Subject: [PATCH 119/129] fill out BackendIPython.display_immediately --- src/sage/repl/rich_output/backend_ipython.py | 57 +++++++++++--------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/sage/repl/rich_output/backend_ipython.py b/src/sage/repl/rich_output/backend_ipython.py index e0889f0ac44..3b0ae9cea91 100644 --- a/src/sage/repl/rich_output/backend_ipython.py +++ b/src/sage/repl/rich_output/backend_ipython.py @@ -16,6 +16,7 @@ #***************************************************************************** import os +from IPython.display import publish_display_data from sage.repl.rich_output.backend_base import BackendBase from sage.repl.rich_output.output_catalog import * @@ -79,7 +80,36 @@ def set_underscore_variable(self, obj): """ pass - + def display_immediately(self, plain_text, rich_output): + """ + Show output immediately. + + This method is similar to the rich output :meth:`displayhook`, + except that it can be invoked at any time. + + INPUT: + + Same as :meth:`displayhook`. + + OUTPUT: + + This method does not return anything. + + EXAMPLES:: + + sage: from sage.repl.rich_output.output_basic import OutputPlainText + sage: plain_text = OutputPlainText.example() + sage: from sage.repl.rich_output.backend_ipython import BackendIPythonNotebook + sage: backend = BackendIPythonNotebook() + sage: _ = backend.display_immediately(plain_text, plain_text) + Example plain text output + """ + formatted, metadata = self.displayhook(plain_text, rich_output) + if not formatted: + return + publish_display_data(data=formatted, metadata=metadata) + + class BackendIPythonCommandline(BackendIPython): """ Backend for the IPython Command Line @@ -460,28 +490,3 @@ def displayhook(self, plain_text, rich_output): raise TypeError('rich_output type not supported') - def display_immediately(self, plain_text, rich_output): - """ - Show output immediately. - - This method is similar to the rich output :meth:`displayhook`, - except that it can be invoked at any time. - - .. TODO:: - - This does not work currently. - - INPUT/OUTPUT: - - Same as :meth:`displayhook`. - - EXAMPLES:: - - sage: from sage.repl.rich_output.output_basic import OutputPlainText - sage: plain_text = OutputPlainText.example() - sage: from sage.repl.rich_output.backend_ipython import BackendIPythonNotebook - sage: backend = BackendIPythonNotebook() - sage: backend.display_immediately(plain_text, plain_text) - ({u'text/plain': 'Example plain text output'}, {}) - """ - return self.displayhook(plain_text, rich_output) From 8a49b3edb941052663a4e89d8c1b11b589c8d286 Mon Sep 17 00:00:00 2001 From: Jan Keitel Date: Tue, 17 Mar 2015 14:07:24 -0700 Subject: [PATCH 120/129] 17305: Typos and replace some $-signs. --- src/sage/rings/invariant_theory.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index fbd987f56b6..7dbda6530b1 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2303,7 +2303,7 @@ def second(self): class TwoTernaryQuadratics(TwoAlgebraicForms): """ - Invariant theory of two ternany quadratics. + Invariant theory of two ternary quadratics. You should use the :class:`invariant_theory ` factory object to construct instances @@ -2470,7 +2470,7 @@ def syzygy(self, Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J): OUTPUT: - Zero if the ``S`` is the first polynomial, ``S_prime`` the + Zero if ``S`` is the first polynomial, ``S_prime`` the second polynomial, and the remaining input are the invariants and covariants of a ternary biquadratic. @@ -2780,7 +2780,7 @@ def T01(a0, a1, a2, a3, b0, b1, b2, b3, b4, b5, A0, A1, A2, A3, B0, B1, B2, B3, def T_covariant(self): """ - The $T$-covariant. + The `T`-covariant. EXAMPLES:: @@ -2802,7 +2802,7 @@ def T_covariant(self): def T_prime_covariant(self): """ - The $T'$-covariant. + The `T'`-covariant. EXAMPLES:: @@ -2825,10 +2825,10 @@ def T_prime_covariant(self): def J_covariant(self): """ - The $J$-covariant. + The `J`-covariant. This is the Jacobian determinant of the two biquadratics, the - $T$-covariant, and the $T'$-covariant with respect to the four + `T`-covariant, and the `T'`-covariant with respect to the four homogeneous variables. EXAMPLES:: @@ -3273,7 +3273,7 @@ def ternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): INPUT: - - ``quadratic1``, ``quadratic2`` -- two polynomias. Either + - ``quadratic1``, ``quadratic2`` -- two polynomials. Either homogeneous quadratic in 3 homogeneous variables, or inhomogeneous quadratic in 2 variables. From 00b6f9417b9bfac1a218292770b5dc859295ea50 Mon Sep 17 00:00:00 2001 From: Jan Keitel Date: Tue, 17 Mar 2015 14:31:58 -0700 Subject: [PATCH 121/129] 17305: Remove whitespaces --- src/sage/rings/invariant_theory.py | 49 +++++++++++++++--------------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 7dbda6530b1..54ddb1951fa 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2323,7 +2323,7 @@ class TwoTernaryQuadratics(TwoAlgebraicForms): sage: R. = QQ[] sage: inv = invariant_theory.ternary_biquadratic(x^2+y^2+z^2, x*y+y*z+x*z, [x, y, z]) sage: inv - Joint ternary quadratic with coefficients (1, 1, 1, 0, 0, 0) and ternary + Joint ternary quadratic with coefficients (1, 1, 1, 0, 0, 0) and ternary quadratic with coefficients (0, 0, 0, 1, 1, 1) sage: TestSuite(inv).run() @@ -2343,7 +2343,7 @@ def Delta_invariant(self): Return the `\Delta` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 @@ -2359,7 +2359,7 @@ def Delta_prime_invariant(self): Return the `\Delta'` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 @@ -2393,7 +2393,7 @@ def Theta_invariant(self): Return the `\Theta` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 @@ -2409,7 +2409,7 @@ def Theta_prime_invariant(self): Return the `\Theta'` invariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 @@ -2425,13 +2425,13 @@ def F_covariant(self): Return the `F` covariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = 73*x^2 + 96*x*y - 11*y^2 + 4*x + 63*y + 57 sage: p2 = 61*x^2 - 100*x*y - 72*y^2 - 81*x + 39*y - 7 sage: q = invariant_theory.ternary_biquadratic(p1, p2, [x, y]) sage: q.F_covariant() - -32566577*x^2 + 29060637/2*x*y + 20153633/4*y^2 - + -32566577*x^2 + 29060637/2*x*y + 20153633/4*y^2 - 30250497/2*x - 241241273/4*y - 323820473/16 """ C = self.first().covariant_conic(self.second()) @@ -2443,14 +2443,14 @@ def J_covariant(self): Return the `J` covariant. EXAMPLES:: - + sage: R. = QQ[] sage: p1 = 73*x^2 + 96*x*y - 11*y^2 + 4*x + 63*y + 57 sage: p2 = 61*x^2 - 100*x*y - 72*y^2 - 81*x + 39*y - 7 sage: q = invariant_theory.ternary_biquadratic(p1, p2, [x, y]) sage: q.J_covariant() - 1057324024445*x^3 + 1209531088209*x^2*y + 942116599708*x*y^2 + - 984553030871*y^3 + 543715345505/2*x^2 - 3065093506021/2*x*y + + 1057324024445*x^3 + 1209531088209*x^2*y + 942116599708*x*y^2 + + 984553030871*y^3 + 543715345505/2*x^2 - 3065093506021/2*x*y + 755263948570*y^2 - 1118430692650*x - 509948695327/4*y + 3369951531745/8 """ return self._jacobian_determinant( @@ -2473,9 +2473,9 @@ def syzygy(self, Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J): Zero if ``S`` is the first polynomial, ``S_prime`` the second polynomial, and the remaining input are the invariants and covariants of a ternary biquadratic. - + EXAMPLES:: - + sage: R. = QQ[] sage: monomials = [x^2, x*y, y^2, x*z, y*z, z^2] sage: def q_rnd(): return sum(randint(-1000,1000)*m for m in monomials) @@ -2497,10 +2497,11 @@ def syzygy(self, Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J): sage: biquadratic.syzygy(1, 1, 1, 1, 1, 1, 1, x) 1/64*x^2 + 1 """ - return (J**2 / 64 + R = self._ring.base_ring() + return (J**2 / R(64) + F**3 - 2 * F**2 * Theta*S_prime - - 2 * F**2 * Theta_prime*S + - 2 * F**2 * Theta_prime*S + F * S**2 * (Delta_prime * Theta + Theta_prime**2) + F * S_prime**2 * (Delta * Theta_prime + Theta**2) + 3 * F * S * S_prime * (Theta*Theta_prime - Delta*Delta_prime) @@ -2512,7 +2513,7 @@ def syzygy(self, Delta, Theta, Theta_prime, Delta_prime, S, S_prime, F, J): Delta * Delta_prime * Theta - Theta_prime * Theta**2) ) - + ###################################################################### class TwoQuaternaryQuadratics(TwoAlgebraicForms): @@ -3290,7 +3291,7 @@ def ternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): Distance between two circles:: - + sage: R. = QQ[] sage: S1 = -r1^2 + x^2 + y^2 sage: S2 = -r2^2 + (x-a)^2 + (y-b)^2 @@ -3304,16 +3305,16 @@ def ternary_biquadratic(self, quadratic1, quadratic2, *args, **kwds): sage: inv.Theta_prime_invariant() a^2 + b^2 - r1^2 - 2*r2^2 sage: inv.F_covariant() - 2*x^2*a^2 + y^2*a^2 - 2*x*a^3 + a^4 + 2*x*y*a*b - 2*y*a^2*b + x^2*b^2 + - 2*y^2*b^2 - 2*x*a*b^2 + 2*a^2*b^2 - 2*y*b^3 + b^4 - 2*x^2*r1^2 - 2*y^2*r1^2 + - 2*x*a*r1^2 - 2*a^2*r1^2 + 2*y*b*r1^2 - 2*b^2*r1^2 + r1^4 - 2*x^2*r2^2 - - 2*y^2*r2^2 + 2*x*a*r2^2 - 2*a^2*r2^2 + 2*y*b*r2^2 - 2*b^2*r2^2 + 2*r1^2*r2^2 + + 2*x^2*a^2 + y^2*a^2 - 2*x*a^3 + a^4 + 2*x*y*a*b - 2*y*a^2*b + x^2*b^2 + + 2*y^2*b^2 - 2*x*a*b^2 + 2*a^2*b^2 - 2*y*b^3 + b^4 - 2*x^2*r1^2 - 2*y^2*r1^2 + + 2*x*a*r1^2 - 2*a^2*r1^2 + 2*y*b*r1^2 - 2*b^2*r1^2 + r1^4 - 2*x^2*r2^2 - + 2*y^2*r2^2 + 2*x*a*r2^2 - 2*a^2*r2^2 + 2*y*b*r2^2 - 2*b^2*r2^2 + 2*r1^2*r2^2 + r2^4 sage: inv.J_covariant() - -8*x^2*y*a^3 + 8*x*y*a^4 + 8*x^3*a^2*b - 16*x*y^2*a^2*b - 8*x^2*a^3*b + - 8*y^2*a^3*b + 16*x^2*y*a*b^2 - 8*y^3*a*b^2 + 8*x*y^2*b^3 - 8*x^2*a*b^3 + - 8*y^2*a*b^3 - 8*x*y*b^4 + 8*x*y*a^2*r1^2 - 8*y*a^3*r1^2 - 8*x^2*a*b*r1^2 + - 8*y^2*a*b*r1^2 + 8*x*a^2*b*r1^2 - 8*x*y*b^2*r1^2 - 8*y*a*b^2*r1^2 + 8*x*b^3*r1^2 - + -8*x^2*y*a^3 + 8*x*y*a^4 + 8*x^3*a^2*b - 16*x*y^2*a^2*b - 8*x^2*a^3*b + + 8*y^2*a^3*b + 16*x^2*y*a*b^2 - 8*y^3*a*b^2 + 8*x*y^2*b^3 - 8*x^2*a*b^3 + + 8*y^2*a*b^3 - 8*x*y*b^4 + 8*x*y*a^2*r1^2 - 8*y*a^3*r1^2 - 8*x^2*a*b*r1^2 + + 8*y^2*a*b*r1^2 + 8*x*a^2*b*r1^2 - 8*x*y*b^2*r1^2 - 8*y*a*b^2*r1^2 + 8*x*b^3*r1^2 - 8*x*y*a^2*r2^2 + 8*x^2*a*b*r2^2 - 8*y^2*a*b*r2^2 + 8*x*y*b^2*r2^2 """ q1 = TernaryQuadratic(3, 2, quadratic1, *args, **kwds) From e648939bc5504af8343851b2b8dbdd6ea113a256 Mon Sep 17 00:00:00 2001 From: Jan Keitel Date: Tue, 17 Mar 2015 14:46:42 -0700 Subject: [PATCH 122/129] 17305: React to deprecation from 17518 --- src/sage/rings/invariant_theory.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 54ddb1951fa..3c4db21cb08 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2348,7 +2348,7 @@ def Delta_invariant(self): sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) - sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coefficients(sparse=False) sage: q.Delta_invariant() == coeffs[3] True """ From ea2d286c51e116e9dd70bcf40c541a79bee9365f Mon Sep 17 00:00:00 2001 From: Jan Keitel Date: Tue, 17 Mar 2015 15:01:53 -0700 Subject: [PATCH 123/129] 17305: Another coeffs and fix ambiguous citation --- src/sage/rings/invariant_theory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 3c4db21cb08..62e3521c40e 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2313,7 +2313,7 @@ class TwoTernaryQuadratics(TwoAlgebraicForms): REFERENCES: - .. [Salmon] + .. [Salmon2] G. Salmon: A Treatise on Conic Sections, Section on "Invariants and Covariants of Systems of Conics", Art. 388 (a). @@ -2364,7 +2364,7 @@ def Delta_prime_invariant(self): sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) - sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coefficients(sparse=False) sage: q.Delta_prime_invariant() == coeffs[0] True """ From 7f09e7eb242101e2963a50cd83334d5b02e096d5 Mon Sep 17 00:00:00 2001 From: James Campbell Date: Tue, 17 Mar 2015 15:19:39 -0700 Subject: [PATCH 124/129] 17977: adds some non-symmetric tests --- src/sage/combinat/alternating_sign_matrix.py | 79 +++++++++++++------- 1 file changed, 50 insertions(+), 29 deletions(-) diff --git a/src/sage/combinat/alternating_sign_matrix.py b/src/sage/combinat/alternating_sign_matrix.py index 8fc48eb2680..02e82bb016b 100644 --- a/src/sage/combinat/alternating_sign_matrix.py +++ b/src/sage/combinat/alternating_sign_matrix.py @@ -263,12 +263,12 @@ def to_monotone_triangle(self): triangle[n-1-j] = list(reversed(line)) prev = add_row return MonotoneTriangles(n)(triangle) - + @combinatorial_map(name='rotate counterclockwise') def rotate_ccw(self): r""" Return the counterclockwise quarter turn rotation of ``self``. - + EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -290,7 +290,7 @@ def rotate_ccw(self): def rotate_cw(self): r""" Return the clockwise quarter turn rotation of ``self``. - + EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -312,7 +312,7 @@ def rotate_cw(self): def transpose(self): r""" Return the counterclockwise quarter turn rotation of ``self``. - + EXAMPLES:: sage: A = AlternatingSignMatrices(3) @@ -352,11 +352,32 @@ def corner_sum_matrix(self): [0 0 0 1] [0 1 1 2] [0 1 2 3] + + TESTS: + + Some non-symmetric tests:: + + sage: A = AlternatingSignMatrices(3) + sage: asm = A([[0, 1, 0], [0, 0, 1], [1, 0, 0]]) + sage: asm.corner_sum_matrix() + [0 0 0 0] + [0 0 1 1] + [0 0 1 2] + [0 1 2 3] + sage: B = AlternatingSignMatrices(4) + sage: asm = B([[0, 0, 1, 0], [1, 0, 0, 0], [0, 1, -1, 1], [0, 0, 1, 0]]) + sage: asm.corner_sum_matrix() + [0 0 0 0 0] + [0 0 0 1 1] + [0 1 1 2 2] + [0 1 2 2 3] + [0 1 2 3 4] + """ asm = self.to_matrix() n = asm.nrows() + 1 return matrix([[nw_corner_sum(asm,i,j) for j in range(n)] for i in range(n)]) - + def height_function(self): r""" Return the height function from ``self``. A height function @@ -389,11 +410,11 @@ def height_function(self): asm = self.to_matrix() n = asm.nrows() + 1 return matrix([[i+j-2*nw_corner_sum(asm,i,j) for i in range(n)] for j in range(n)]) - - @combinatorial_map(name='gyration') + + @combinatorial_map(name='gyration') def gyration(self): r""" - Return the alternating sign matrix obtained by applying the gyration + Return the alternating sign matrix obtained by applying the gyration action to the height function in bijection with ``self``. Gyration acts on height functions as follows. Go through the entries of @@ -405,7 +426,7 @@ def gyration(self): REFERENCES: - .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of + .. [Wieland00] B. Wieland. *A large dihedral symmetry of the set of alternating sign matrices*. Electron. J. Combin. 7 (2000). EXAMPLES:: @@ -438,29 +459,29 @@ def gyration(self): else: hf[i][j] -= 2 for i in range(1,k): - for j in range(1,k): + for j in range(1,k): if (i+j) % 2 == 1 \ and hf[i-1][j] == hf[i+1][j] == hf[i][j+1] == hf[i][j-1]: if hf[i][j] < hf[i+1][j]: hf[i][j] += 2 else: - hf[i][j] -= 2 + hf[i][j] -= 2 return A.from_height_function(matrix(hf)) - + def ASM_compatible(self, B): r""" - Return ``True`` if ``self`` and ``B`` are compatible alternating sign + Return ``True`` if ``self`` and ``B`` are compatible alternating sign matrices in the sense of [EKLP92]_. (If ``self`` is of size `n`, ``B`` - must be of size `n+1`.) + must be of size `n+1`.) - In [EKLP92]_, there is a notion of a pair of ASM's with sizes differing - by 1 being compatible, in the sense that they can be combined to encode + In [EKLP92]_, there is a notion of a pair of ASM's with sizes differing + by 1 being compatible, in the sense that they can be combined to encode a tiling of the Aztec Diamond. REFERENCES: - .. [EKLP92] N. Elkies, G. Kuperberg, M. Larsen, J. Propp, - *Alternating-Sign Matrices and Domino Tilings*, Journal of Algebraic + .. [EKLP92] N. Elkies, G. Kuperberg, M. Larsen, J. Propp, + *Alternating-Sign Matrices and Domino Tilings*, Journal of Algebraic Combinatorics, volume 1 (1992), p. 111-132. EXAMPLES:: @@ -485,10 +506,10 @@ def ASM_compatible(self, B): and AA[i,j]<=BB[i+1,j] and AA[i,j]<=BB[i,j+1]): return False return True - + def ASM_compatible_bigger(self): r""" - Return all ASM's compatible with ``self`` that are of size one greater + Return all ASM's compatible with ``self`` that are of size one greater than ``self``. Given an `n \times n` alternating sign matrix `A`, there are as many @@ -504,13 +525,13 @@ def ASM_compatible_bigger(self): [ 1 -1 1] [0 0 1] [1 0 0] [0 1 0] [ 0 1 0], [0 1 0], [0 0 1], [0 0 1] ] - sage: B = AlternatingSignMatrix(matrix([[0,1],[1,0]])) + sage: B = AlternatingSignMatrix(matrix([[0,1],[1,0]])) sage: B.ASM_compatible_bigger() [ [0 0 1] [0 0 1] [0 1 0] [ 0 1 0] [0 1 0] [1 0 0] [0 0 1] [ 1 -1 1] [1 0 0], [0 1 0], [1 0 0], [ 0 1 0] - ] + ] """ n = self.parent()._n + 1 M = AlternatingSignMatrices(n) @@ -543,9 +564,9 @@ def ASM_compatible_bigger(self): output.append(d) for k in range(len(output)): - output[k] = M.from_height_function(output[k]/2) + output[k] = M.from_height_function(output[k]/2) return(output) - + def ASM_compatible_smaller(self): r""" Return the list of all ASMs compatible with ``self`` that are of size @@ -562,7 +583,7 @@ def ASM_compatible_smaller(self): [ [0 0 1] [ 0 1 0] [1 0 0] [ 1 -1 1] - [0 1 0], [ 0 1 0] + [0 1 0], [ 0 1 0] ] sage: B = AlternatingSignMatrix(matrix([[1,0,0],[0,0,1],[0,1,0]])) sage: B.ASM_compatible_smaller() @@ -601,7 +622,7 @@ def ASM_compatible_smaller(self): d[sign[b][0],sign[b][1]] = -d[sign[b][0], sign[b][1]]-3 output.append(d) for k in range(0,len(output)): - output[k] = M.from_height_function((output[k]-matrix.ones(n,n))/2) + output[k] = M.from_height_function((output[k]-matrix.ones(n,n))/2) return(output) @combinatorial_map(name='to Dyck word') @@ -968,7 +989,7 @@ def from_monotone_triangle(self, triangle): prev = v return self.element_class(self, self._matrix_space(asm)) - + def from_corner_sum(self, corner): r""" Return an alternating sign matrix from a corner sum matrix. @@ -998,7 +1019,7 @@ def from_corner_sum(self, corner): - sum([asm_list[i][j2] for j2 in range(j)]) asm_list[i].append(y) return AlternatingSignMatrix(asm_list) - + def from_height_function(self,height): r""" Return an alternating sign matrix from a height function. @@ -1014,7 +1035,7 @@ def from_height_function(self,height): [ 0 1 0] [ 1 -1 1] [ 0 1 0] - """ + """ return self.from_corner_sum(matrix( [[((i+j-height[i][j])/int(2)) for i in range(len(list(height)))] for j in range(len(list(height)))] )) From 9f543f3ef59fe6362b97116e2d7560f7696d5319 Mon Sep 17 00:00:00 2001 From: Jan Keitel Date: Tue, 17 Mar 2015 15:21:22 -0700 Subject: [PATCH 125/129] 17305: Two more coeffs() --- src/sage/rings/invariant_theory.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sage/rings/invariant_theory.py b/src/sage/rings/invariant_theory.py index 62e3521c40e..79b112456f4 100644 --- a/src/sage/rings/invariant_theory.py +++ b/src/sage/rings/invariant_theory.py @@ -2398,7 +2398,7 @@ def Theta_invariant(self): sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) - sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coefficients(sparse=False) sage: q.Theta_invariant() == coeffs[2] True """ @@ -2414,7 +2414,7 @@ def Theta_prime_invariant(self): sage: p1 = a00*y0^2 + 2*a01*y0*y1 + a11*y1^2 + 2*a02*y0*y2 + 2*a12*y1*y2 + a22*y2^2 sage: p2 = b00*y0^2 + 2*b01*y0*y1 + b11*y1^2 + 2*b02*y0*y2 + 2*b12*y1*y2 + b22*y2^2 sage: q = invariant_theory.ternary_biquadratic(p1, p2, [y0, y1, y2]) - sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coeffs() + sage: coeffs = det(t * q[0].matrix() + q[1].matrix()).polynomial(t).coefficients(sparse=False) sage: q.Theta_prime_invariant() == coeffs[1] True """ From b49e60116aa1b2169c592a478468bc26e10e8e9b Mon Sep 17 00:00:00 2001 From: Jeroen Demeyer Date: Wed, 18 Mar 2015 10:19:43 +0100 Subject: [PATCH 126/129] Convert database_pari to new-style package and update --- build/pkgs/database_pari/SPKG.txt | 29 ++++++++++++++++++++ build/pkgs/database_pari/checksums.ini | 4 +++ build/pkgs/database_pari/package-version.txt | 1 + build/pkgs/database_pari/spkg-check | 9 ++++++ build/pkgs/database_pari/spkg-install | 10 +++++++ 5 files changed, 53 insertions(+) create mode 100644 build/pkgs/database_pari/SPKG.txt create mode 100644 build/pkgs/database_pari/checksums.ini create mode 100644 build/pkgs/database_pari/package-version.txt create mode 100755 build/pkgs/database_pari/spkg-check create mode 100755 build/pkgs/database_pari/spkg-install diff --git a/build/pkgs/database_pari/SPKG.txt b/build/pkgs/database_pari/SPKG.txt new file mode 100644 index 00000000000..fe264e9ca9c --- /dev/null +++ b/build/pkgs/database_pari/SPKG.txt @@ -0,0 +1,29 @@ += database_pari = + +== Description == + +The collection of optional PARI packages elldata, seadata, galpol, +nftables (galdata is included in the standard PARI spkg). +See http://pari.math.u-bordeaux.fr/packages.html + +== License == + +GNU General Public License (GPL version 2 or any later version). + +== SPKG Maintainers == + +* Jeroen Demeyer + +== Upstream Contact == + +http://pari.math.u-bordeaux.fr/ + +== Dependencies == + +* Installation: None +* Runtime: PARI/GP + +== Special Update/Build Instructions == + +Download the four mentioned tarballs and extract them, then move them +out of the top-level directory data. diff --git a/build/pkgs/database_pari/checksums.ini b/build/pkgs/database_pari/checksums.ini new file mode 100644 index 00000000000..59dd77df74b --- /dev/null +++ b/build/pkgs/database_pari/checksums.ini @@ -0,0 +1,4 @@ +tarball=database_pari-VERSION.tar.bz2 +sha1=92dcb68e7a6def53ffc5fb82e3593d5b80c940cb +md5=652d36d18ea300193957120815298be7 +cksum=2257060384 diff --git a/build/pkgs/database_pari/package-version.txt b/build/pkgs/database_pari/package-version.txt new file mode 100644 index 00000000000..bc51480f53c --- /dev/null +++ b/build/pkgs/database_pari/package-version.txt @@ -0,0 +1 @@ +20140908 diff --git a/build/pkgs/database_pari/spkg-check b/build/pkgs/database_pari/spkg-check new file mode 100755 index 00000000000..dde57035983 --- /dev/null +++ b/build/pkgs/database_pari/spkg-check @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +if [ -z "$SAGE_SRC" ]; then + echo >&2 "SAGE_SRC undefined ... exiting" + echo >&2 "Maybe run 'sage --sh'?" + exit 1 +fi + +sage -tp --long --optional=database_pari,sage "$SAGE_SRC/sage/tests/parigp.py" "$SAGE_SRC/sage/libs/pari" diff --git a/build/pkgs/database_pari/spkg-install b/build/pkgs/database_pari/spkg-install new file mode 100755 index 00000000000..d28a48d3bc4 --- /dev/null +++ b/build/pkgs/database_pari/spkg-install @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +if [ -z "$GP_DATA_DIR" ]; then + echo >&2 "GP_DATA_DIR undefined ... exiting" + echo >&2 "Maybe run 'sage --sh'?" + exit 1 +fi + +cd src +cp -pR elldata galpol nftables seadata "$GP_DATA_DIR" From 4294f15d17a6cbd8214c364d88c676e9ae4eccba Mon Sep 17 00:00:00 2001 From: James Campbell Date: Wed, 18 Mar 2015 19:45:51 +0000 Subject: [PATCH 127/129] adds SageMathCloud files to .gitignore --- .gitignore | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 5edef4a5619..b209681b9b5 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,10 @@ $RECYCLE.BIN/ # SublimeText *.sublime-workspace + +################# +# SageMathCloud # +################# +*.sage-chat +*.sage-history +*.syncdoc* \ No newline at end of file From 99f9ac53bd3e42dd00f5d05b7b126a632161ca55 Mon Sep 17 00:00:00 2001 From: Travis Scrimshaw Date: Wed, 18 Mar 2015 16:36:27 -0700 Subject: [PATCH 128/129] Fix doctest because of new doc category. --- src/doc/common/builder.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/doc/common/builder.py b/src/doc/common/builder.py index 72e10bf19ca..0033697f455 100644 --- a/src/doc/common/builder.py +++ b/src/doc/common/builder.py @@ -633,7 +633,10 @@ def get_all_documents(self, refdir): sage: b = builder.ReferenceBuilder('reference') sage: refdir = os.path.join(os.environ['SAGE_DOC'], 'en', b.name) sage: sorted(b.get_all_documents(refdir)) - ['reference/algebras', 'reference/arithgroup', ..., 'reference/tensor'] + ['reference/algebras', + 'reference/arithgroup', + ..., + 'reference/tensor_free_modules'] """ documents = [] From 2a02a9edb99a1c8c12d424464d6035fd51d5e37f Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Thu, 19 Mar 2015 18:04:13 +0100 Subject: [PATCH 129/129] Updated Sage version to 6.6.beta6 --- VERSION.txt | 2 +- build/pkgs/configure/checksums.ini | 6 +++--- build/pkgs/configure/package-version.txt | 2 +- src/bin/sage-banner | 2 +- src/bin/sage-version.sh | 4 ++-- src/sage/version.py | 4 ++-- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index 05177f694e4..f830b82a3c6 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -Sage version 6.6.beta5, released 2015-03-13 +Sage version 6.6.beta6, released 2015-03-19 diff --git a/build/pkgs/configure/checksums.ini b/build/pkgs/configure/checksums.ini index 86140368451..27be31a7155 100644 --- a/build/pkgs/configure/checksums.ini +++ b/build/pkgs/configure/checksums.ini @@ -1,4 +1,4 @@ tarball=configure-VERSION.tar.gz -sha1=12e8d35579711c8bf8beddb890aaebd6b2c51db1 -md5=dbb592c16e66f4f2e3345071fb115abf -cksum=693230472 +sha1=89ece2f5d378cda9468a3437854b10809d1e57c3 +md5=6bf787d800c20ead4a74e5ff00b1c9c8 +cksum=913450101 diff --git a/build/pkgs/configure/package-version.txt b/build/pkgs/configure/package-version.txt index 78eb67cee1a..dd475631bae 100644 --- a/build/pkgs/configure/package-version.txt +++ b/build/pkgs/configure/package-version.txt @@ -1 +1 @@ -75 +76 diff --git a/src/bin/sage-banner b/src/bin/sage-banner index 7105eb2b386..f34d3d4258a 100644 --- a/src/bin/sage-banner +++ b/src/bin/sage-banner @@ -1,5 +1,5 @@ ┌────────────────────────────────────────────────────────────────────┐ -│ Sage Version 6.6.beta5, Release Date: 2015-03-13 │ +│ Sage Version 6.6.beta6, Release Date: 2015-03-19 │ │ Type "notebook()" for the browser-based notebook interface. │ │ Type "help()" for help. │ └────────────────────────────────────────────────────────────────────┘ diff --git a/src/bin/sage-version.sh b/src/bin/sage-version.sh index c30b38b1b7a..031407ec8e9 100644 --- a/src/bin/sage-version.sh +++ b/src/bin/sage-version.sh @@ -1,4 +1,4 @@ # Sage version information for shell scripts # This file is auto-generated by the sage-update-version script, do not edit! -SAGE_VERSION='6.6.beta5' -SAGE_RELEASE_DATE='2015-03-13' +SAGE_VERSION='6.6.beta6' +SAGE_RELEASE_DATE='2015-03-19' diff --git a/src/sage/version.py b/src/sage/version.py index d60f075501c..f476e288a0f 100644 --- a/src/sage/version.py +++ b/src/sage/version.py @@ -1,4 +1,4 @@ # Sage version information for Python scripts # This file is auto-generated by the sage-update-version script, do not edit! -version = '6.6.beta5' -date = '2015-03-13' +version = '6.6.beta6' +date = '2015-03-19'