From 86e4b1ba175c9971ae5a4e9ff511506c3bcfed09 Mon Sep 17 00:00:00 2001 From: "E. Madison Bray" Date: Tue, 16 Feb 2021 13:18:17 +0100 Subject: [PATCH] Deprecate sage.libs.gap.element and convert Sage to use gappy.gapobj.GapObj directly as the primary wrapper for GAP objects. The sage.libs.gap.element module is kept for now but deprecated, and GapElement and all its subclasses are just aliases for the equivalent GapObjs, so existing code using these classes might still work as long as they're not using them at the C/Cython level. So it's not perfect backwards-compatibility but it's better than nothing. By necessity this is something of a patchbomb, but most of the changes are simply changing GapElement -> GapObj. A few changes deserve specific mention, however: * Added sage.libs.gap.converters--this uses gappy's GapObj.convert_to API to add `.sage()` methods to most of the GapObj subclasses, so they behave the same as their GapElement progenitors w.r.t. conversion to equivalent Sage types. * Integer, Rational, and IntegerMod all have explicit cases in their constructors for construction from equivalent GapObjs. * As mentioned in the ticket #31404, Polynomial.__call__ has a special case for handling GapObjs, since nothing else can be coerced to them when evaluating a polynomial on a GapObj. * MatrixArgs now has an explicit case for converting GapLists to Sage matrices. * sage.groups.class_function had some doctests for ClassFunction_libgap which were copy/pasted from ClassFunction_gap without modification, so it was not properly testing the libgap implementation of ClassFunction; this has been fixed * Added an explicit _libgap_ method for efficient conversion of NumberFields to their GAP equivalents. Also cleaned up the existing _gap_ implementation. * Fixed a bit of code in sage.groups.matrix_gps.matrix_group.MatrixGroup_gap that was using libgap inefficiently. * Ditto in sage.groups.perm_gps.partn_ref2.refinement_generic * Removed the late_import stuff from sage.rings.universal_cyclotomic_field since it does not appear to be necessary any longer. * In PermutationGroup_generic some doctests which compared libgap objects to gap (pexpect) objects had to be changed--previously they did not compare as equal, but as a surprising but welcome change (perhaps due to going outside the coercion system) GapObjs now compare equal to their equivalent pexect GapElements. * Updated more tests to account for slight differences in iteration order on groups. --- .../groups/abelian_gps/abelian_group_gap.py | 8 +- src/sage/groups/class_function.py | 17 +- src/sage/groups/cubic_braid.py | 7 +- src/sage/groups/finitely_presented.py | 41 +- src/sage/groups/fqf_orthogonal.py | 5 +- src/sage/groups/free_group.py | 17 +- src/sage/groups/libgap_mixin.py | 6 +- src/sage/groups/libgap_morphism.py | 9 +- src/sage/groups/libgap_wrapper.pxd | 6 +- src/sage/groups/libgap_wrapper.pyx | 29 +- src/sage/groups/matrix_gps/group_element.pyx | 5 +- src/sage/groups/matrix_gps/matrix_group.py | 9 +- src/sage/groups/perm_gps/constructor.py | 6 +- .../partn_ref2/refinement_generic.pxd | 12 +- .../partn_ref2/refinement_generic.pyx | 30 +- src/sage/groups/perm_gps/permgroup.py | 54 +- .../groups/perm_gps/permgroup_element.pxd | 7 +- .../groups/perm_gps/permgroup_element.pyx | 20 +- src/sage/groups/perm_gps/permgroup_named.py | 8 +- src/sage/groups/raag.py | 9 +- src/sage/libs/gap/converters.pyx | 777 +++++ src/sage/libs/gap/element.pxd | 90 +- src/sage/libs/gap/element.pyx | 2731 +---------------- src/sage/libs/gap/libgap.pyx | 540 +--- src/sage/matrix/args.pyx | 5 + src/sage/matrix/matrix_gap.pxd | 6 +- src/sage/matrix/matrix_gap.pyx | 6 +- .../rings/finite_rings/integer_mod_ring.py | 8 +- src/sage/rings/integer.pyx | 9 +- src/sage/rings/number_field/number_field.py | 73 +- .../number_field/number_field_element.pyx | 2 +- .../rings/polynomial/multi_polynomial.pyx | 9 +- .../rings/polynomial/polynomial_element.pyx | 30 +- .../polynomial_integer_dense_flint.pyx | 2 + src/sage/rings/rational.pyx | 12 +- src/sage/rings/universal_cyclotomic_field.py | 34 +- src/sage/structure/sage_object.pyx | 5 +- .../judson-abstract-algebra/sylow-sage.py | 4 +- 38 files changed, 1176 insertions(+), 3472 deletions(-) create mode 100644 src/sage/libs/gap/converters.pyx diff --git a/src/sage/groups/abelian_gps/abelian_group_gap.py b/src/sage/groups/abelian_gps/abelian_group_gap.py index 285b5378bb3..b3b0eae5c31 100644 --- a/src/sage/groups/abelian_gps/abelian_group_gap.py +++ b/src/sage/groups/abelian_gps/abelian_group_gap.py @@ -32,13 +32,15 @@ from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP from sage.groups.libgap_mixin import GroupMixinLibGAP from sage.groups.group import AbelianGroup as AbelianGroupBase -from sage.libs.gap.element import GapElement from sage.libs.gap.libgap import libgap from sage.misc.cachefunc import cached_method from sage.rings.integer_ring import ZZ from sage.structure.unique_representation import UniqueRepresentation from sage.categories.groups import Groups +from gappy.gapobj import GapObj + + class AbelianGroupElement_gap(ElementLibGAP): r""" An element of an abelian group via libgap. @@ -59,7 +61,7 @@ def __init__(self, parent, x, check=True): INPUT: - ``parent`` -- an instance of :class:`AbelianGroup_gap` - - ``x`` -- an instance of :class:`sage.libs.gap.element.GapElement` + - ``x`` -- an instance of :class:`gappy.gapobj.GapObj` - ``check`` -- boolean (default: ``True``); check if ``x`` is an element of the group @@ -337,7 +339,7 @@ def _element_constructor_(self, x, check=True): x = x.gap() elif x == 1 or x == (): x = self.gap().Identity() - elif not isinstance(x, GapElement): + elif not isinstance(x, GapObj): from sage.groups.abelian_gps.abelian_group_element import AbelianGroupElement from sage.groups.additive_abelian.additive_abelian_group import AdditiveAbelianGroupElement from sage.modules.fg_pid.fgp_element import FGP_Element diff --git a/src/sage/groups/class_function.py b/src/sage/groups/class_function.py index e86c32e589d..cbe808e2a4c 100644 --- a/src/sage/groups/class_function.py +++ b/src/sage/groups/class_function.py @@ -25,12 +25,12 @@ from sage.structure.sage_object import SageObject from sage.structure.richcmp import richcmp, richcmp_method -from sage.interfaces.gap import gap +from sage.interfaces.gap import gap, GapElement from sage.rings.all import Integer from sage.rings.all import CyclotomicField -from sage.libs.gap.element import GapElement from sage.libs.gap.libgap import libgap -from sage.libs.gap.element import GapElement as LibGapElement + +from gappy.gapobj import GapObj # TODO: # @@ -68,7 +68,7 @@ class function on the conjugacy classes, in that order. except AttributeError: pass - if isinstance(values, LibGapElement): + if isinstance(values, GapObj): return ClassFunction_libgap(group, values) return ClassFunction_gap(group, values) @@ -847,7 +847,7 @@ def __init__(self, G, values): Character of Cyclic group of order 4 as a permutation group """ self._group = G - if isinstance(values, LibGapElement) and values.IsClassFunction(): + if isinstance(values, GapObj) and values.IsClassFunction(): self._gap_classfunction = values else: self._gap_classfunction = libgap.ClassFunction(G._libgap_(), @@ -868,16 +868,15 @@ def gap(self): Character of Cyclic group of order 4 as a permutation group sage: type(chi) - sage: gap(chi) - ClassFunction( CharacterTable( Group( [ (1,2,3,4) ] ) ), [ 1, -1, 1, -1 ] ) + sage: libgap(chi) + ClassFunction( CharacterTable( Group([ (1,2,3,4) ]) ), [ 1, -1, 1, -1 ] ) sage: type(_) - + """ return self._gap_classfunction _libgap_ = _gap_ = gap - def _repr_(self): r""" Return a string representation. diff --git a/src/sage/groups/cubic_braid.py b/src/sage/groups/cubic_braid.py index a5dc49b0d46..1508e2bfe48 100644 --- a/src/sage/groups/cubic_braid.py +++ b/src/sage/groups/cubic_braid.py @@ -81,7 +81,6 @@ from sage.misc.cachefunc import cached_method -from sage.libs.gap.element import GapElement from sage.groups.free_group import FreeGroup from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement from sage.groups.braid import BraidGroup @@ -91,6 +90,8 @@ from sage.rings.finite_rings.finite_field_constructor import GF from sage.structure.element import Element +from gappy.gapobj import GapObj + from enum import Enum @@ -263,15 +264,13 @@ def __init__(self, parent, x, check=True): """ if type(x) in (tuple, list): x = tuple(_reduce_tietze(tuple(x))) - elif isinstance(x, GapElement): + elif isinstance(x, GapObj): tietze_list = x.UnderlyingElement().TietzeWordAbstractWord().sage() tietze_red = _reduce_tietze(tietze_list) if tietze_red != tietze_list: x = tuple(tietze_red) super(CubicBraidElement, self).__init__(parent, x, check=check) - - def _richcmp_(self, other, op): """ overwrite comparison since the inherited one from FinitelyPresentedGroupElement diff --git a/src/sage/groups/finitely_presented.py b/src/sage/groups/finitely_presented.py index 4d1dd4c5485..bb0ef4893b3 100644 --- a/src/sage/groups/finitely_presented.py +++ b/src/sage/groups/finitely_presented.py @@ -75,7 +75,7 @@ sage: a.gap().Order() 2 sage: type(_) # note that the above output is not a Sage integer - + You can use call syntax to replace the generators with a set of arbitrary ring elements. For example, take the free abelian group @@ -133,22 +133,23 @@ from sage.groups.libgap_mixin import GroupMixinLibGAP from sage.structure.unique_representation import UniqueRepresentation from sage.libs.gap.libgap import libgap -from sage.libs.gap.element import GapElement from sage.misc.cachefunc import cached_method from sage.groups.free_group import FreeGroupElement from sage.functions.generalized import sign from sage.matrix.constructor import matrix from sage.categories.morphism import SetMorphism +from gappy.gapobj import GapObj + class GroupMorphismWithGensImages(SetMorphism): r""" Class used for morphisms from finitely presented groups to other groups. It just adds the images of the generators at the end of the representation. - + EXAMPLES:: - + sage: F = FreeGroup(3) sage: G = F / [F([1, 2, 3, 1, 2, 3]), F([1, 1, 1])] sage: H = AlternatingGroup(3) @@ -166,9 +167,9 @@ class GroupMorphismWithGensImages(SetMorphism): def _repr_defn(self): r""" Return the part of the representation that includes the images of the generators. - + EXAMPLES:: - + sage: F = FreeGroup(3) sage: G = F / [F([1,2,3,1,2,3]),F([1,1,1])] sage: H = AlternatingGroup(3) @@ -230,7 +231,7 @@ def __init__(self, parent, x, check=True): sage: TestSuite(x).run() sage: TestSuite(G.one()).run() """ - if not isinstance(x, GapElement): + if not isinstance(x, GapObj): F = parent.free_group() free_element = F(x) fp_family = parent.gap().Identity().FamilyObj() @@ -286,7 +287,7 @@ def _repr_(self): if self.Tietze() == (): return '1' else: - return self.gap()._repr_() + return self.gap().__repr__() @cached_method def Tietze(self): @@ -376,9 +377,9 @@ def wrap_FpGroup(libgap_fpgroup): ``libgap_free_group`` to comparison by Python ``id``. If you want to put the LibGAP free group into a container ``(set, dict)`` then you should understand the implications of - :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To - be safe, it is recommended that you just work with the resulting - Sage :class:`FinitelyPresentedGroup`. + :meth:`~gappy.gapobj.GapObj._set_compare_by_id`. To be safe, it is + recommended that you just work with the resulting Sage + :class:`FinitelyPresentedGroup`. INPUT: @@ -397,7 +398,7 @@ def wrap_FpGroup(libgap_fpgroup): sage: P = F / libgap([ a_cubed ]); P sage: type(P) - + Now wrap it:: @@ -775,7 +776,7 @@ class FinitelyPresentedGroup(GroupMixinLibGAP, UniqueRepresentation, sage: H.gap() sage: type(_) - + """ Element = FinitelyPresentedGroupElement @@ -827,7 +828,7 @@ def _repr_(self): """ gens = ', '.join(self.variable_names()) rels = ', '.join([ str(r) for r in self.relations() ]) - return 'Finitely presented group ' + '< '+ gens + ' | ' + rels + ' >' + return f'Finitely presented group < {gens} | {rels} >' def _latex_(self): """ @@ -1426,17 +1427,17 @@ def simplified(self): Finitely presented group < e0 | e0^2 > """ return self.simplification_isomorphism().codomain() - + def epimorphisms(self, H): r""" Return the epimorphisms from `self` to `H`, up to automorphism of `H`. - + INPUT: - + - `H` -- Another group - + EXAMPLES:: - + sage: F = FreeGroup(3) sage: G = F / [F([1, 2, 3, 1, 2, 3]), F([1, 1, 1])] sage: H = AlternatingGroup(3) @@ -1467,7 +1468,7 @@ def epimorphisms(self, H): x2 |--> (1,2,3)] ALGORITHM: - + Uses libgap's GQuotients function. """ from sage.misc.misc_c import prod diff --git a/src/sage/groups/fqf_orthogonal.py b/src/sage/groups/fqf_orthogonal.py index 3c5190589b5..966aa7e5c56 100644 --- a/src/sage/groups/fqf_orthogonal.py +++ b/src/sage/groups/fqf_orthogonal.py @@ -55,6 +55,8 @@ from sage.matrix.all import matrix from sage.categories.action import Action +from gappy.gapobj import GapObj + class FqfIsometry(AbelianGroupAutomorphism): r""" @@ -251,8 +253,7 @@ def _element_constructor_(self, x, check=True): sage: assert Oq(OL.0) == Oq(OL.0.matrix()) sage: assert Oq(Oq.0.matrix()) == Oq.0 """ - from sage.libs.gap.element import GapElement - if not isinstance(x, GapElement): + if not isinstance(x, GapObj): try: # if there is an action try that gen = self.invariant_form().smith_form_gens() diff --git a/src/sage/groups/free_group.py b/src/sage/groups/free_group.py index e3c3f785b7b..9e1a30e688d 100644 --- a/src/sage/groups/free_group.py +++ b/src/sage/groups/free_group.py @@ -65,7 +65,6 @@ from sage.groups.libgap_wrapper import ParentLibGAP, ElementLibGAP from sage.structure.unique_representation import UniqueRepresentation from sage.libs.gap.libgap import libgap -from sage.libs.gap.element import GapElement from sage.rings.integer import Integer from sage.rings.integer_ring import IntegerRing from sage.misc.cachefunc import cached_method @@ -73,6 +72,8 @@ from sage.structure.sequence import Sequence from sage.structure.element import coercion_model, parent +from gappy.gapobj import GapObj + def is_FreeGroup(x): """ @@ -164,8 +165,8 @@ class FreeGroupElement(ElementLibGAP): INPUT: - ``x`` -- something that determines the group element. Either a - :class:`~sage.libs.gap.element.GapElement` or the Tietze list - (see :meth:`Tietze`) of the group element. + :class:`~gappy.gapobj.GapObj` or the Tietze list (see :meth:`Tietze`) of + the group element. - ``parent`` -- the parent :class:`FreeGroup`. @@ -207,7 +208,7 @@ def __init__(self, parent, x): sage: TestSuite(G).run() sage: TestSuite(x).run() """ - if not isinstance(x, GapElement): + if not isinstance(x, GapObj): try: l = x.Tietze() except AttributeError: @@ -691,9 +692,9 @@ def wrap_FreeGroup(libgap_free_group): ``libgap_free_group`` to comparison by Python ``id``. If you want to put the LibGAP free group into a container (set, dict) then you should understand the implications of - :meth:`~sage.libs.gap.element.GapElement._set_compare_by_id`. To - be safe, it is recommended that you just work with the resulting - Sage :class:`FreeGroup_class`. + :meth:`~gappy.gapobj.GapObj._set_compare_by_id`. To be safe, it is + recommended that you just work with the resulting Sage + :class:`FreeGroup_class`. INPUT: @@ -709,7 +710,7 @@ def wrap_FreeGroup(libgap_free_group): sage: F = libgap.FreeGroup(['a', 'b']) sage: type(F) - + Now wrap it:: diff --git a/src/sage/groups/libgap_mixin.py b/src/sage/groups/libgap_mixin.py index 9b77ef61c48..b05e1f0575a 100644 --- a/src/sage/groups/libgap_mixin.py +++ b/src/sage/groups/libgap_mixin.py @@ -12,12 +12,14 @@ """ from sage.libs.gap.libgap import libgap -from sage.libs.gap.element import GapElement from sage.structure.element import parent from sage.misc.cachefunc import cached_method from sage.groups.class_function import ClassFunction_libgap from sage.groups.libgap_wrapper import ElementLibGAP +from gappy.gapobj import GapObj + + class GroupMixinLibGAP(object): def __contains__(self, elt): r""" @@ -39,7 +41,7 @@ def __contains__(self, elt): """ if parent(elt) is self: return True - elif isinstance(elt, GapElement): + elif isinstance(elt, GapObj): return elt in self.gap() elif isinstance(elt, ElementLibGAP): return elt.gap() in self.gap() diff --git a/src/sage/groups/libgap_morphism.py b/src/sage/groups/libgap_morphism.py index 4b29cbed4a7..e0c6b7a5a5c 100644 --- a/src/sage/groups/libgap_morphism.py +++ b/src/sage/groups/libgap_morphism.py @@ -32,9 +32,10 @@ from sage.categories.morphism import Morphism from sage.rings.integer_ring import ZZ from sage.groups.libgap_wrapper import ParentLibGAP -from sage.libs.gap.element import GapElement from sage.misc.latex import latex +from gappy.gapobj import GapObj + class GroupMorphism_libgap(Morphism): r""" @@ -46,7 +47,7 @@ class GroupMorphism_libgap(Morphism): INPUT: - ``homset`` -- the parent - - ``gap_hom`` -- a :class:`sage.libs.gap.element.GapElement` consisting of a group homomorphism + - ``gap_hom`` -- a :class:`gappy.gapobj.GapObj` consisting of a group homomorphism - ``check`` -- (default: ``True``) check if the ``gap_hom`` is a group homomorphism; this can be expensive @@ -133,7 +134,7 @@ class GroupMorphism_libgap(Morphism): CompositionMapping( [ (6,7,8,10,9)(11,13,14,12,15)(16,19,20,18,17)(21,25,22,24,23) ] -> [ [ [ Z(5)^0, 0*Z(5) ], [ Z(5)^0, Z(5)^0 ] ] ], ) sage: type(_) - + sage: F = GF(7); MS = MatrixSpace(F,2,2) sage: F.multiplicative_generator() @@ -691,7 +692,7 @@ def _element_constructor_(self, x, check=True, **options): else: phi = libgap.GroupHomomorphismByImagesNC(dom.gap(), codom.gap(), gens, imgs) return self.element_class(self, phi, check=check, **options) - if isinstance(x, GapElement): + if isinstance(x, GapObj): try: return self.element_class(self, x, check=True, **options) except ValueError: diff --git a/src/sage/groups/libgap_wrapper.pxd b/src/sage/groups/libgap_wrapper.pxd index 0c43b098140..263da479416 100644 --- a/src/sage/groups/libgap_wrapper.pxd +++ b/src/sage/groups/libgap_wrapper.pxd @@ -1,8 +1,8 @@ from sage.structure.element cimport MultiplicativeGroupElement -from sage.libs.gap.element cimport GapElement +from gappy.gapobj cimport GapObj cdef class ElementLibGAP(MultiplicativeGroupElement): - cdef GapElement _libgap - cpdef GapElement gap(self) + cdef GapObj _libgap + cpdef GapObj gap(self) cpdef _mul_(self, other) diff --git a/src/sage/groups/libgap_wrapper.pyx b/src/sage/groups/libgap_wrapper.pyx index d06bebe881b..a0adbbc0def 100644 --- a/src/sage/groups/libgap_wrapper.pyx +++ b/src/sage/groups/libgap_wrapper.pyx @@ -27,7 +27,7 @@ its output via LibGAP:: sage: FooGroup() sage: type(FooGroup().gap()) - + The element class is a subclass of :class:`~sage.structure.element.MultiplicativeGroupElement`. To use @@ -60,7 +60,6 @@ AUTHORS: ############################################################################## from sage.libs.gap.libgap import libgap -from sage.libs.gap.element cimport GapElement from sage.rings.integer import Integer from sage.rings.integer_ring import IntegerRing from sage.misc.cachefunc import cached_method @@ -68,6 +67,8 @@ from sage.structure.sage_object import SageObject from sage.structure.element cimport Element from sage.structure.richcmp cimport richcmp +from gappy.gapobj cimport GapObj + class ParentLibGAP(SageObject): """ @@ -126,7 +127,7 @@ class ParentLibGAP(SageObject): sage: g in H True """ - assert isinstance(libgap_parent, GapElement) + assert isinstance(libgap_parent, GapObj) self._libgap = libgap_parent self._ambient = ambient if ambient is not None: @@ -262,7 +263,7 @@ class ParentLibGAP(SageObject): sage: all(g*h in G and h*g in G for g in G for h in H) True """ - generators = [ g if isinstance(g, GapElement) else self(g).gap() + generators = [ g if isinstance(g, GapObj) else self(g).gap() for g in generators ] G = self.gap() H = G.Subgroup(generators) @@ -274,7 +275,7 @@ class ParentLibGAP(SageObject): OUTPUT: - A :class:`~sage.libs.gap.element.GapElement` + A :class:`~gappy.gapobj.GapObj` EXAMPLES:: @@ -283,9 +284,9 @@ class ParentLibGAP(SageObject): sage: G.gap() sage: G.gap().parent() - C library interface to GAP + sage: type(G.gap()) - + This can be useful, for example, to call GAP functions that are not wrapped in Sage:: @@ -344,7 +345,7 @@ class ParentLibGAP(SageObject): sage: ParentLibGAP._repr_(G) '' """ - return self._libgap._repr_() + return self._libgap.__repr__() @cached_method def gens(self): @@ -478,7 +479,7 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): """ MultiplicativeGroupElement.__init__(self, parent) assert isinstance(parent, ParentLibGAP) - if isinstance(libgap_element, GapElement): + if isinstance(libgap_element, GapObj): self._libgap = libgap_element else: if libgap_element == 1: @@ -486,13 +487,13 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): else: raise TypeError('need a libgap group element or "1" in constructor') - cpdef GapElement gap(self): + cpdef GapObj gap(self): """ Return a LibGAP representation of the element. OUTPUT: - A :class:`~sage.libs.gap.element.GapElement` + A :class:`~gappy.gapobj.GapObj` EXAMPLES:: @@ -504,14 +505,14 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): sage: xg a*b*a^-1*b^-1 sage: type(xg) - + TESTS:: sage: libgap(FreeGroup('a, b').an_element()) a*b sage: type(libgap(FreeGroup('a, b').an_element())) - + """ return self._libgap @@ -584,7 +585,7 @@ cdef class ElementLibGAP(MultiplicativeGroupElement): if self.is_one(): return '1' else: - return self._libgap._repr_() + return self._libgap.__repr__() def _latex_(self): r""" diff --git a/src/sage/groups/matrix_gps/group_element.pyx b/src/sage/groups/matrix_gps/group_element.pyx index 1bf2b88e6d5..5824696c052 100644 --- a/src/sage/groups/matrix_gps/group_element.pyx +++ b/src/sage/groups/matrix_gps/group_element.pyx @@ -78,7 +78,6 @@ AUTHORS: from sage.structure.element cimport MultiplicativeGroupElement, Element, MonoidElement, Matrix from sage.structure.parent cimport Parent from sage.structure.richcmp cimport richcmp -from sage.libs.gap.element cimport GapElement, GapElement_List from sage.groups.libgap_wrapper cimport ElementLibGAP from sage.structure.element import is_Matrix @@ -86,6 +85,8 @@ from sage.structure.factorization import Factorization from sage.misc.cachefunc import cached_method from sage.rings.all import ZZ +from gappy.gapobj cimport GapObj + cpdef is_MatrixGroupElement(x): """ @@ -447,7 +448,7 @@ cdef class MatrixGroupElement_gap(ElementLibGAP): sage: g = G.random_element() sage: TestSuite(g).run() """ - if isinstance(M, GapElement): + if isinstance(M, GapObj): ElementLibGAP.__init__(self, parent, M) return if convert: diff --git a/src/sage/groups/matrix_gps/matrix_group.py b/src/sage/groups/matrix_gps/matrix_group.py index 4a55883dc08..2f31ef73626 100644 --- a/src/sage/groups/matrix_gps/matrix_group.py +++ b/src/sage/groups/matrix_gps/matrix_group.py @@ -339,9 +339,9 @@ def _latex_(self): def sign_representation(self, base_ring=None, side="twosided"): r""" Return the sign representation of ``self`` over ``base_ring``. - + WARNING: assumes ``self`` is a matrix group over a field which has embedding over real numbers. - + INPUT: - ``base_ring`` -- (optional) the base ring; the default is `\ZZ` @@ -733,10 +733,7 @@ def _check_matrix(self, x_sage, x_gap): ... TypeError: matrix is not in the finitely generated group """ - from sage.libs.gap.libgap import libgap - libgap_contains = libgap.eval(r'\in') - is_contained = libgap_contains(x_gap, self.gap()) - if not is_contained.sage(): + if not x_gap in self.gap(): raise TypeError('matrix is not in the finitely generated group') def _subgroup_constructor(self, libgap_subgroup): diff --git a/src/sage/groups/perm_gps/constructor.py b/src/sage/groups/perm_gps/constructor.py index 1992679efd3..378f7aca519 100644 --- a/src/sage/groups/perm_gps/constructor.py +++ b/src/sage/groups/perm_gps/constructor.py @@ -25,7 +25,9 @@ from sage.interfaces.gap import GapElement lazy_import('sage.combinat.permutation', ['Permutation', 'from_cycles']) from sage.libs.pari.all import pari_gen -from sage.libs.gap.element import GapElement_Permutation + +from gappy.gapobj import GapPermutation + def PermutationGroupElement(g, parent=None, check=True): r""" @@ -225,7 +227,7 @@ def standardize_generator(g, convert_dict=None, as_cycles=False): needs_conversion = True - if isinstance(g, GapElement_Permutation): + if isinstance(g, GapPermutation): g = g.sage() needs_conversion = False if isinstance(g, GapElement): diff --git a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pxd b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pxd index b3081e0b506..b65dcd350f1 100644 --- a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pxd +++ b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pxd @@ -8,9 +8,11 @@ #******************************************************************************* from sage.groups.perm_gps.partn_ref.data_structures cimport OrbitPartition, PartitionStack -from sage.libs.gap.element cimport GapElement, GapElement_Permutation from sage.structure.parent cimport Parent +from gappy.gapobj cimport GapObj, GapPermutation + + cdef extern from "refinement_generic.h": cdef long *global_refine_vals_array cdef int my_comp_func(void *a, void *b) @@ -20,7 +22,7 @@ cdef extern from *: void *qsort(void *base, size_t nmemb, size_t size, int(*compar)(void *, void *)) -cdef tuple PS_refinement(PartitionStack * part, long *refine_vals, long *best, +cdef tuple PS_refinement(PartitionStack * part, long *refine_vals, long *best, int begin, int end, bint * cand_initialized, bint *changed_partition) @@ -35,10 +37,10 @@ cdef class LabelledBranching: cdef int count cdef int *father cdef int *act_perm - cpdef GapElement group, ClosureGroup + cpdef GapObj group, ClosureGroup cdef Parent sym_gp cdef bint has_empty_intersection(self, PartitionStack * part) - cpdef add_gen(self, GapElement_Permutation gen) + cpdef add_gen(self, GapPermutation gen) cdef class PartitionRefinement_generic: cdef PartitionStack * _part @@ -52,7 +54,7 @@ cdef class PartitionRefinement_generic: cdef int _nr_of_inner_min_unmin_calls cdef LabelledBranching _known_automorphisms - cdef GapElement_Permutation _to_best, _to_best_inverse + cdef GapPermutation _to_best, _to_best_inverse # the following allow us to debug the program via latex cdef object _latex_debug_string diff --git a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx index 813218c2e49..2f7337924dc 100644 --- a/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx +++ b/src/sage/groups/perm_gps/partn_ref2/refinement_generic.pyx @@ -188,6 +188,8 @@ from cysignals.memory cimport sig_malloc, sig_realloc, sig_free from sage.groups.perm_gps.partn_ref.data_structures cimport * from sage.data_structures.bitset_base cimport * +from gappy.gapobj cimport GapObj, GapPermutation + cdef tuple PS_refinement(PartitionStack * part, long *refine_vals, long *best, int begin, int end, @@ -336,7 +338,7 @@ cdef class LabelledBranching: self.n = n self.group = libgap.eval("Group(())") - self.ClosureGroup = libgap.eval("ClosureGroup") + self.ClosureGroup = libgap.ClosureGroup self.father = < int *> sig_malloc(n * sizeof(int)) self.sym_gp = SymmetricGroup(self.n) if self.father is NULL: @@ -356,7 +358,7 @@ cdef class LabelledBranching: sig_free(self.father) sig_free(self.act_perm) - cpdef add_gen(self, GapElement_Permutation gen): + cpdef add_gen(self, GapPermutation gen): r""" Add a further generator to the group and update the complete labeled branching. @@ -367,27 +369,28 @@ cdef class LabelledBranching: sage: L = LabelledBranching(3) sage: L.add_gen(libgap.eval('(1,2,3)')) """ - from sage.libs.gap.libgap import libgap self.group = self.ClosureGroup(self.group, gen) - libgap.StabChain(self.group) - cdef GapElement h = self.group + cdef GapObj h = self.group cdef int i, i_1, j, f - if libgap.IsTrivial(h).sage(): + h.StabChain() + + if h.IsTrivial(): return + for i in range(self.n): self.father[i] = -1 while True: - i = libgap.SmallestMovedPoint(h).sage() + i = h.SmallestMovedPoint().sage() i_1 = i - 1 f = self.father[i_1] - for j in libgap.Orbit(h, i).sage(): + for j in h.Orbit(i).sage(): self.father[j - 1] = i_1 self.father[i_1] = f - h = libgap.Stabilizer(h, i) - if libgap.IsTrivial(h).sage(): + h = h.Stabilizer(i) + if h.IsTrivial(): break cdef bint has_empty_intersection(self, PartitionStack * part): @@ -432,8 +435,7 @@ cdef class LabelledBranching: sage: L.small_generating_set() [(1,2,3)] """ - from sage.libs.gap.libgap import libgap - return libgap.SmallGeneratingSet(self.group).sage(parent=self.sym_gp) + return self.group.SmallGeneratingSet().sage(parent=self.sym_gp) def get_order(self): r""" @@ -449,9 +451,7 @@ cdef class LabelledBranching: sage: L.get_order() 3 """ - from sage.libs.gap.libgap import libgap - - return libgap.Order(self.group).sage() + return self.group.Order().sage() cdef class PartitionRefinement_generic: r""" diff --git a/src/sage/groups/perm_gps/permgroup.py b/src/sage/groups/perm_gps/permgroup.py index b6435e3b215..64acd6eb811 100644 --- a/src/sage/groups/perm_gps/permgroup.py +++ b/src/sage/groups/perm_gps/permgroup.py @@ -145,7 +145,6 @@ from sage.interfaces.expect import is_ExpectElement from sage.interfaces.gap import GapElement from sage.libs.gap.libgap import libgap -from sage.libs.gap.element import GapElement as LibGapElement from sage.groups.perm_gps.permgroup_element import PermutationGroupElement from sage.groups.perm_gps.constructor import PermutationGroupElement as PermutationConstructor, standardize_generator from sage.groups.abelian_gps.abelian_group import AbelianGroup @@ -157,6 +156,8 @@ from sage.structure.richcmp import (richcmp_method, richcmp, rich_to_bool, op_EQ, op_NE) +from gappy.gapobj import GapObj + def load_hap(): r""" @@ -340,12 +341,13 @@ def PermutationGroup(gens=None, gap_group=None, domain=None, canonicalize=True, sage: PermutationGroup(SymmetricGroup(5)) Traceback (most recent call last): ... - TypeError: gens must be a tuple, list, or GapElement + TypeError: gens must be a tuple, list, GapElement, or GapObj """ if not is_ExpectElement(gens) and hasattr(gens, '_permgroup_'): return gens._permgroup_() - if gens is not None and not isinstance(gens, (tuple, list, GapElement)): - raise TypeError("gens must be a tuple, list, or GapElement") + if gens is not None and not isinstance(gens, (tuple, list, GapElement, + GapObj)): + raise TypeError("gens must be a tuple, list, GapElement, or GapObj") return PermutationGroup_generic(gens=gens, gap_group=gap_group, domain=domain, canonicalize=canonicalize, category=category) @@ -424,16 +426,16 @@ def __init__(self, gens=None, gap_group=None, canonicalize=True, # lists of long generators (example PSp(8,3), see :trac:`26750` gap_group = libgap.eval(gap_group) - if isinstance(gap_group, LibGapElement): + if isinstance(gap_group, GapObj): self._libgap_.set_cache(gap_group) #Handle the case where only the GAP group is specified. if gens is None: - if not isinstance(gap_group, (GapElement, LibGapElement)): + if not isinstance(gap_group, (GapObj, GapElement)): raise ValueError( - 'the gap_group argument must be provided either a' - 'GapElement or a valid string for constructing the group ' - 'with GAP if gens are not provided') + 'the gap_group argument must be provided either a GapObj ' + 'or a valid string for constructing the group with GAP ' + 'if gens are not provided') gens = [gen for gen in gap_group.GeneratorsOfGroup()] @@ -557,8 +559,7 @@ def _gap_init_(self): @cached_method def _libgap_(self, gap=None): """ - Returns a :class:`~sage.libs.gap.element.GapElement` representing the - group. + Returns a :class:`~gappy.gapobj.GapObj` represeting the group. The return value is cached, and may be overridden when initializing this class with the ``gap_group`` argument. @@ -583,8 +584,7 @@ def gap(self): OUTPUT: - an instance of :class:`sage.libs.gap.element.GapElement` representing - this group + an instance of :class:`gappy.gapobj.GapObj` representing this group EXAMPLES:: @@ -592,12 +592,12 @@ def gap(self): sage: P8.gap() sage: gap(P8) == P8.gap() - False + True sage: S3 = SymmetricGroup(3) sage: S3.gap() Sym( [ 1 .. 3 ] ) sage: gap(S3) == S3.gap() - False + True TESTS: @@ -1858,22 +1858,26 @@ def strong_generating_system(self, base_of_group=None, implementation="sage"): [[()], [()], [(), (3,4), (3,5,4)], [(), (4,5)], [()], [()], [(), (7,8)], [()]] sage: G = PermutationGroup([[(1,2,3,4)],[(1,2)]]) sage: G.strong_generating_system() - [[(), (1,2)(3,4), (1,3)(2,4), (1,4)(2,3)], - [(), (2,4,3), (2,3,4)], - [(), (3,4)], - [()]] + [[(), (1,2)(3,4), (1,3)(2,4), (1,4)(2,3)], + [(), (2,4), (2,3,4)], + [(), (3,4)], + [()]] sage: G = PermutationGroup([[(1,2,3)],[(4,5,7)],[(1,4,6)]]) sage: G.strong_generating_system() [[(), (1,2,3), (1,4,6), (1,3,2), (1,5,7,4,6), (1,6,4), (1,7,5,4,6)], [(), (2,3,6), (2,6,3), (2,7,5,6,3), (2,5,6,3)(4,7), (2,4,5,6,3)], - [(), (3,5,6), (3,4,7,5,6), (3,6)(5,7), (3,7,4,5,6)], - [(), (4,7,5), (4,5,7), (4,6,7)], - [(), (5,6,7), (5,7,6)], [()], [()]] + [(), (3,7,6), (3,5,6), (3,6,7), (3,4,7,5,6)], + [(), (4,7,5), (4,5)(6,7), (4,6,5)], + [(), (5,6,7), (5,7,6)], + [()], + [()]] sage: G = PermutationGroup([[(1,2,3)],[(2,3,4)],[(3,4,5)]]) sage: G.strong_generating_system([5,4,3,2,1]) [[(), (1,5,3,4,2), (1,5,4,3,2), (1,5)(2,3), (1,5,2)], [(1,4)(2,3), (1,4,3), (1,4,2), ()], - [(1,2,3), (1,3,2), ()], [()], [()]] + [(1,2,3), (), (1,3,2)], + [()], + [()]] sage: G = PermutationGroup([[(3,4)]]) sage: G.strong_generating_system() [[()], [()], [(), (3,4)], [()]] @@ -2844,7 +2848,7 @@ def _subgroup_constructor(self, libgap_group): INPUT: - - ``libgap_group`` -- an instance of :class:`sage.libs.gap.element.GapElement` + - ``libgap_group`` -- an instance of :class:`gappy.gapobj.GapObj` representing a GAP group whose generators belong to ``self.gap()`` OUTPUT: @@ -2859,7 +2863,7 @@ def _subgroup_constructor(self, libgap_group): sage: g = g1*g2 sage: Hgap = G.gap().Subgroup([g.gap()]) sage: type(Hgap) - + sage: H = G._subgroup_constructor(Hgap); H Subgroup generated by [(1,6,21,12,20,17)(2,10,15,9,11,5)(3,14,8)(4,18)(7,16)] of (The projective general unitary group of degree 3 over Finite Field of size 2) """ diff --git a/src/sage/groups/perm_gps/permgroup_element.pxd b/src/sage/groups/perm_gps/permgroup_element.pxd index a2ac8f20eaa..ce5ff6c965c 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pxd +++ b/src/sage/groups/perm_gps/permgroup_element.pxd @@ -1,18 +1,19 @@ from sage.structure.element cimport MultiplicativeGroupElement, MonoidElement, Element from sage.structure.list_clone cimport ClonableIntArray from sage.rings.polynomial.polydict cimport ETuple -from sage.libs.gap.element cimport GapElement + +from gappy.gapobj cimport GapObj cdef class PermutationGroupElement(MultiplicativeGroupElement): cdef int* perm cdef int n cdef int perm_buf[15] # to avoid malloc for small elements - cdef GapElement _libgap + cdef GapObj _libgap cdef PermutationGroupElement _new_c(self) cdef _alloc(self, int) cpdef _set_identity(self) cpdef _set_list_images(self, v, bint convert) - cpdef _set_libgap(self, GapElement p) + cpdef _set_libgap(self, GapObj p) cpdef _set_list_cycles(self, c, bint convert) cpdef _set_string(self, str s) cpdef _set_permutation_group_element(self, PermutationGroupElement p, bint convert) diff --git a/src/sage/groups/perm_gps/permgroup_element.pyx b/src/sage/groups/perm_gps/permgroup_element.pyx index fd3d9cd7ae6..1182c5aef90 100644 --- a/src/sage/groups/perm_gps/permgroup_element.pyx +++ b/src/sage/groups/perm_gps/permgroup_element.pyx @@ -126,8 +126,8 @@ from sage.interfaces.gap import GapElement as PExpectGapElement from sage.interfaces.gp import GpElement from sage.libs.gap.libgap import libgap -from sage.libs.gap.element cimport (GapElement, GapElement_List, - GapElement_String, GapElement_Permutation) + +from gappy.gapobj cimport GapObj, GapPermutation, GapList, GapString import operator @@ -464,12 +464,12 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): self._set_permutation_group_element(g, convert or not g.parent()._has_natural_domain()) elif isinstance(g, Gen): self._set_list_images(g, convert) - elif isinstance(g, GapElement): - if isinstance(g, GapElement_Permutation): + elif isinstance(g, GapObj): + if isinstance(g, GapPermutation): self._set_libgap(g) - elif isinstance(g, GapElement_String): - self._set_string(g.sage()) - elif isinstance(g, GapElement_List): + elif isinstance(g, GapString): + self._set_string(str(g)) + elif isinstance(g, GapList): self._set_list_images(g.sage(), convert) else: raise ValueError("invalid data to initialize a permutation") @@ -557,7 +557,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): for i in range(vn, self.n): self.perm[i] = i - cpdef _set_libgap(self, GapElement p): + cpdef _set_libgap(self, GapObj p): r""" TESTS:: @@ -576,7 +576,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): ... ValueError: invalid data to initialize a permutation """ - if isinstance(p, GapElement_Permutation): + if isinstance(p, GapPermutation): try: self._set_list_images(p.ListPerm(), False) except AssertionError: @@ -1317,7 +1317,7 @@ cdef class PermutationGroupElement(MultiplicativeGroupElement): sage: one._generate_new_GAP(perm) (1,4)(2,3) """ - cdef GapElement_List lst = lst_in + cdef GapList lst = lst_in cdef PermutationGroupElement new = self._new_c() cdef Py_ssize_t i, j, vn = len(lst) diff --git a/src/sage/groups/perm_gps/permgroup_named.py b/src/sage/groups/perm_gps/permgroup_named.py index 5754ebce32c..e3299158270 100644 --- a/src/sage/groups/perm_gps/permgroup_named.py +++ b/src/sage/groups/perm_gps/permgroup_named.py @@ -280,7 +280,7 @@ def __init__(self, domain=None): @cached_method def _libgap_(self, gap=None): """ - Return the libgap representation (as a `~sage.libs.gap.element.GapElement`) of this + Return the libgap representation (as a `~gappy.gapobj.GapObj`) of this group. EXAMPLES:: @@ -289,7 +289,7 @@ def _libgap_(self, gap=None): sage: S._libgap_() Sym( [ 1 .. 3 ] ) sage: type(_) - + sage: S = SymmetricGroup(['a', 'b', 'c']) sage: S._libgap_() Sym( [ 1 .. 3 ] ) @@ -695,7 +695,7 @@ def _repr_(self): @cached_method def _libgap_(self, gap=None): """ - Return the libgap representation (as a `~sage.libs.gap.element.GapElement`) of this + Return the libgap representation (as a `~gappy.gapobj.GapObj`) of this group. EXAMPLES:: @@ -704,7 +704,7 @@ def _libgap_(self, gap=None): sage: A._libgap_() Alt( [ 1 .. 3 ] ) sage: type(_) - + sage: A = AlternatingGroup(['a', 'b', 'c']) sage: A._libgap_() Alt( [ 1 .. 3 ] ) diff --git a/src/sage/groups/raag.py b/src/sage/groups/raag.py index 5823b706ef7..4a1b21afd7c 100644 --- a/src/sage/groups/raag.py +++ b/src/sage/groups/raag.py @@ -25,8 +25,6 @@ #***************************************************************************** -from sage.libs.gap.element import GapElement - from sage.misc.cachefunc import cached_method from sage.structure.richcmp import richcmp from sage.groups.finitely_presented import FinitelyPresentedGroup, FinitelyPresentedGroupElement @@ -43,6 +41,9 @@ from sage.typeset.ascii_art import ascii_art from sage.typeset.unicode_art import unicode_art +from gappy.gapobj import GapObj + + class RightAngledArtinGroup(ArtinGroup): r""" The right-angled Artin group defined by a graph `G`. @@ -421,7 +422,7 @@ def __init__(self, parent, lst): sage: assert g.gap() == h.gap() sage: assert g._data == h._data """ - if isinstance(lst, GapElement): + if isinstance(lst, GapObj): # e.g. direct call from GroupLibGAP FinitelyPresentedGroupElement.__init__(self, parent, lst) data = [] @@ -656,7 +657,7 @@ def __init__(self, R, A): if R not in Fields(): raise NotImplementedError("only implemented with coefficients in a field") self._group = A - + names = tuple(['e' + name[1:] for name in A.variable_names()]) from sage.graphs.independent_sets import IndependentSets from sage.sets.finite_enumerated_set import FiniteEnumeratedSet diff --git a/src/sage/libs/gap/converters.pyx b/src/sage/libs/gap/converters.pyx new file mode 100644 index 00000000000..57f0d0f4cee --- /dev/null +++ b/src/sage/libs/gap/converters.pyx @@ -0,0 +1,777 @@ +"""Converters between Sage types and `~gappy.gapobj.GapObj types.""" + + +import copyreg +import itertools + +from sage.combinat.permutation import Permutation +from sage.groups.perm_gps.permgroup_element cimport PermutationGroupElement +from sage.libs.gmp.mpz cimport mpz_set_si +from sage.libs.gmp.mpq cimport mpq_numref, mpq_denref +from sage.matrix.matrix_space import MatrixSpace +from sage.rings.all import ZZ, QQ, RDF +from sage.rings.integer cimport Integer +from sage.rings.rational cimport Rational +from sage.structure.coerce cimport coercion_model as cm +from sage.structure.sage_object cimport SageObject +from .libgap import libgap + +from gappy.core cimport Gap +from gappy.gapobj cimport (GapObj, GapInteger, GapFloat, GapIntegerMod, + GapFiniteField, GapCyclotomic, GapRational, GapRing, GapBoolean, + GapString, GapList, GapPermutation, GapRecord) + + +# TODO: It might be good to implement a "fast lane" for some built-in +# Sage types like Integer (e.g. currently Sage Integers are first +# converted to Python ints, and then to GAP Integers, whereas it would +# be much faster to convert Sage Integers directly to GAP Integers since +# they are both basically mpz_t limbs under the hood). +@libgap.convert_from(SageObject) +def sageobject_to_gapobj(Gap gap, SageObject obj): + r""" + gappy converter for converting generic `.SageObject`\s to their + corresponding `~gappy.gapobj.GapObj` if any. + + This implements the libgap conversion functions already documented for + `.SageObject`\s: `.SageObject._libgap_` and `.SageObject._libgap_init_`. + """ + + # NOTE: In the default implementation of SageObject._libgap_ it defers + # to _libgap_init_, so we just need to try calling _libgap_ + try: + ret = obj._libgap_(gap) + except TypeError: + # Support older code where _libgap_ does not take an argument + ret = obj._libgap_() + if isinstance(ret, gap.supported_builtins): + return gap(ret) + elif isinstance(ret, GapObj): + return ret + else: + raise RuntimeError( + f'{type(obj).__name__}._libgap_ returned something that cannot ' + f'be converted to a GAP object: {ret!r} ({type(ret).__name__})') + + +# Pickling for GapObjs using a Sage-specific protocol which tries to pickle +# the objects' Sage representations where possible. +def _reduce_gapobj(obj): + """ + Attempt to pickle GAP objects from gappy. + + This is inspired in part by ``sage.interfaces.interface.Interface._reduce``, + though for a fallback we use ``str(self)`` instead of ``repr(self)``, since + the former is equivalent in the libgap interface to the latter in the + pexpect interface. + + TESTS: + + This workaround was motivated in particular by this example from the + permutation groups implementation:: + + sage: CC = libgap.eval('ConjugacyClass(SymmetricGroup([ 1 .. 5 ]), (1,2)(3,4))') + sage: CC.sage() + Traceback (most recent call last): + ... + NotImplementedError: cannot construct equivalent Sage object + sage: libgap.eval(str(CC)) + (1,2)(3,4)^G + sage: loads(dumps(CC)) + (1,2)(3,4)^G + """ + + if obj.is_string(): + elem = repr(obj.sage()) + try: + elem = obj.sage() + except NotImplementedError: + elem = str(obj) + + return (_construct_gapobj, (elem,)) + + +def _construct_gapobj(reduced): + """ + Currently just used for unpickling; equivalent to calling ``libgap(elem)`` + to convert a Sage object to a `~gappy.gapobj.GapObj` where possible. + """ + + if isinstance(reduced, str): + return libgap.eval(reduced) + + return libgap(reduced) + + +for cls in itertools.chain([GapObj], GapObj.__subclasses__()): + copyreg.pickle(cls, _reduce_gapobj, _construct_gapobj) + +del cls + + +@GapObj.convert_to('sage') +def gapobj_to_sage(obj): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapObj`. + + EXAMPLES:: + + sage: libgap(1).sage() + 1 + sage: type(_) + + + sage: libgap(3/7).sage() + 3/7 + sage: type(_) + + + sage: libgap.eval('5 + 7*E(3)').sage() + 7*zeta3 + 5 + + sage: libgap(Infinity).sage() + +Infinity + sage: libgap(-Infinity).sage() + -Infinity + + sage: libgap(True).sage() + True + sage: libgap(False).sage() + False + sage: type(_) + <... 'bool'> + + sage: libgap('this is a string').sage() + 'this is a string' + sage: type(_) + <... 'str'> + + sage: x = libgap.Integers.Indeterminate("x") + + sage: p = x^2 - 2*x + 3 + sage: p.sage() + x^2 - 2*x + 3 + sage: p.sage().parent() + Univariate Polynomial Ring in x over Integer Ring + + sage: p = x^-2 + 3*x + sage: p.sage() + x^-2 + 3*x + sage: p.sage().parent() + Univariate Laurent Polynomial Ring in x over Integer Ring + + sage: p = (3 * x^2 + x) / (x^2 - 2) + sage: p.sage() + (3*x^2 + x)/(x^2 - 2) + sage: p.sage().parent() + Fraction Field of Univariate Polynomial Ring in x over Integer Ring + + TESTS: + + Check :trac:`30496`:: + + sage: x = libgap.Integers.Indeterminate("x") + + sage: p = x^2 - 2*x + sage: p.sage() + x^2 - 2*x + """ + + if obj.IsInfinity(): + from sage.rings.infinity import Infinity + return Infinity + + elif obj.IsNegInfinity(): + from sage.rings.infinity import Infinity + return -Infinity + + elif obj.IsUnivariateRationalFunction(): + var = obj.IndeterminateOfUnivariateRationalFunction().String() + var = var.sage() + num, den, val = obj.CoefficientsOfUnivariateRationalFunction() + num = num.sage() + den = den.sage() + val = val.sage() + base_ring = cm.common_parent(*(num + den)) + + if obj.IsUnivariatePolynomial(): + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(base_ring, var) + x = R.gen() + return x**val * R(num) + + elif obj.IsLaurentPolynomial(): + from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing + R = LaurentPolynomialRing(base_ring, var) + x = R.gen() + return x**val * R(num) + + else: + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + R = PolynomialRing(base_ring, var) + x = R.gen() + return x**val * R(num) / R(den) + + elif obj.IsList(): + # May be a list-like collection of some other type of GapElements + # that we can convert + return [item.sage() for item in obj.AsList()] + + raise NotImplementedError('cannot construct equivalent Sage object') + + +@GapInteger.convert_to('sage') +def gapinteger_to_sage(obj, ring=None, z=None): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapInteger` + + - ``ring`` -- Integer ring or ``None`` (default). If not + specified, a the default Sage integer ring is used. + + - ``z`` -- An existing Sage Integer on which to set a value. + + OUTPUT: + + A Sage integer + + EXAMPLES:: + + sage: libgap([ 1, 3, 4 ]).sage() + [1, 3, 4] + sage: all( x in ZZ for x in _ ) + True + + sage: libgap(132).sage(ring=IntegerModRing(13)) + 2 + sage: parent(_) + Ring of integers modulo 13 + + TESTS:: + + sage: large = libgap.eval('2^130'); large + 1361129467683753853853498429727072845824 + sage: large.sage() + 1361129467683753853853498429727072845824 + + sage: huge = libgap.eval('10^9999'); huge # gap abbreviates very long ints + + sage: huge.sage().ndigits() + 10000 + """ + cdef Integer zz + cdef GapInteger gz + + if ring is None: + ring = ZZ + + if z is None: + zz = Integer.__new__(Integer) + else: + zz = z + + gz = obj + + if gz.is_C_int(): + mpz_set_si(zz.value, gz.to_C_int()) + else: + gz.to_mpz(zz.value) + + if ring is ZZ: + return zz + else: + return ring(zz) + + +@GapFloat.convert_to('sage') +def gapfloat_to_sage(obj, ring=None): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapFloat` + + - ``ring`` -- a floating point field or ``None`` (default). If not + specified, the default Sage ``RDF`` is used. + + OUTPUT: + + A Sage double precision floating point number + + EXAMPLES:: + + sage: a = libgap.eval("Float(3.25)").sage() + sage: a + 3.25 + sage: parent(a) + Real Double Field + """ + if ring is None: + ring = RDF + return ring(float(obj)) + + +@GapIntegerMod.convert_to('sage') +def gapintegermod_to_sage(obj, ring=None): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapIntegerMod` + + INPUT: + + - ``ring`` -- Sage integer mod ring or ``None`` (default). If not + specified, a suitable integer mod ringa is used automatically. + + OUTPUT: + + A Sage integer modulo another integer. + + EXAMPLES:: + + sage: n = libgap.eval('One(ZmodnZ(123)) * 13') + sage: n.sage() + 13 + sage: parent(_) + Ring of integers modulo 123 + """ + if ring is None: + # ring = obj.DefaultRing().sage() + characteristic = obj.Characteristic().sage() + ring = ZZ.quotient_ring(characteristic) + return obj.lift().sage(ring=ring) + + +@GapFiniteField.convert_to('sage') +def gapfinitefield_to_sage(obj, ring=None, var='a'): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapFiniteField`. + + INPUT: + + - ``ring`` -- a Sage finite field or ``None`` (default). The field to + return ``self`` in. If not specified, a suitable finite field will be + constructed. + + OUTPUT: + + An Sage finite field element. The isomorphism is chosen such that the Gap + ``PrimitiveRoot()`` maps to the Sage + :meth:`~sage.rings.finite_rings.finite_field_prime_modn.multiplicative_generator`. + + EXAMPLES:: + + sage: n = libgap.eval('Z(25)^2') + sage: n.sage() + a + 3 + sage: parent(_) + Finite Field in a of size 5^2 + + sage: n.sage(ring=GF(5)) + Traceback (most recent call last): + ... + ValueError: the given ring is incompatible ... + + TESTS:: + + sage: n = libgap.eval('Z(2^4)^2 + Z(2^4)^1 + Z(2^4)^0') + sage: n + Z(2^2)^2 + sage: n.sage() + a + 1 + sage: parent(_) + Finite Field in a of size 2^2 + sage: n.sage(ring=ZZ) + Traceback (most recent call last): + ... + ValueError: the given ring is incompatible ... + sage: n.sage(ring=CC) + Traceback (most recent call last): + ... + ValueError: the given ring is incompatible ... + sage: n.sage(ring=GF(5)) + Traceback (most recent call last): + ... + ValueError: the given ring is incompatible ... + sage: n.sage(ring=GF(2^3)) + Traceback (most recent call last): + ... + ValueError: the given ring is incompatible ... + sage: n.sage(ring=GF(2^2, 'a')) + a + 1 + sage: n.sage(ring=GF(2^4, 'a')) + a^2 + a + 1 + sage: n.sage(ring=GF(2^8, 'a')) + a^7 + a^6 + a^4 + a^2 + a + 1 + + Check that :trac:`23153` is fixed:: + + sage: n = libgap.eval('Z(2^4)^2 + Z(2^4)^1 + Z(2^4)^0') + sage: n.sage(ring=GF(2^4, 'a')) + a^2 + a + 1 + """ + deg = obj.DegreeFFE().sage() + char = obj.Characteristic().sage() + if ring is None: + from sage.rings.finite_rings.finite_field_constructor import GF + ring = GF(char**deg, name=var) + elif not (ring.is_field() and ring.is_finite() and \ + ring.characteristic() == char and ring.degree() % deg == 0): + raise ValueError(f'the given ring is incompatible (must be a ' + f'finite field of characteristic {char} and degree ' + f'divisible by {deg})') + + if obj.IsOne(): + return ring.one() + if deg == 1 and char == ring.characteristic(): + return ring(obj.lift().sage()) + else: + gap_field = obj.parent(ring) + exp = obj.LogFFE(gap_field.PrimitiveRoot()) + return ring.multiplicative_generator() ** exp.sage() + + +@GapCyclotomic.convert_to('sage') +def gapcyclotomic_to_sage(obj, ring=None): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapCyclotomic`. + + INPUT: + + - ``ring`` -- a Sage cyclotomic field or ``None`` (default). If not + specified, a suitable minimal cyclotomic field will be constructed. + + OUTPUT: + + A Sage cyclotomic field element. + + EXAMPLES:: + + sage: n = libgap.eval('E(3)') + sage: n.sage() + zeta3 + sage: parent(_) + Cyclotomic Field of order 3 and degree 2 + + sage: n.sage(ring=CyclotomicField(6)) + zeta6 - 1 + + sage: libgap.E(3).sage(ring=CyclotomicField(3)) + zeta3 + sage: libgap.E(3).sage(ring=CyclotomicField(6)) + zeta6 - 1 + + TESTS: + + Check that :trac:`15204` is fixed:: + + sage: libgap.E(3).sage(ring=UniversalCyclotomicField()) + E(3) + sage: libgap.E(3).sage(ring=CC) + -0.500000000000000 + 0.866025403784439*I + """ + if ring is None: + conductor = obj.Conductor() + from sage.rings.number_field.number_field import CyclotomicField + ring = CyclotomicField(conductor.sage()) + else: + try: + conductor = ring._n() + except AttributeError: + from sage.rings.number_field.number_field import CyclotomicField + conductor = obj.Conductor() + cf = CyclotomicField(conductor.sage()) + return ring(cf(obj.CoeffsCyc(conductor).sage())) + coeff = obj.CoeffsCyc(conductor).sage() + return ring(coeff) + + +@GapRational.convert_to('sage') +def gaprational_to_sage(obj, ring=None, q=None): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapRational`. + + INPUT: + + - ``ring`` -- the Sage rational ring or ``None`` (default). If not + specified, the rational ring is used automatically. + + - ``q`` -- An existing Sage Rational on which to set a value. + + OUTPUT: + + A Sage rational number. + + EXAMPLES:: + + sage: r = libgap(123/456); r + 41/152 + sage: type(_) + + sage: r.sage() + 41/152 + sage: type(_) + + """ + + cdef Rational qq + cdef GapInteger num, den + + if ring is None: + ring = QQ + + num = (obj.NumeratorRat()) + den = (obj.DenominatorRat()) + + if ring is QQ: + if q is None: + qq = Rational.__new__(Rational) + else: + qq = q + + if num.is_C_int(): + mpz_set_si(mpq_numref(qq.value), num.to_C_int()) + else: + num.to_mpz(mpq_numref(qq.value)) + if den.is_C_int(): + mpz_set_si(mpq_denref(qq.value), den.to_C_int()) + else: + den.to_mpz(mpq_denref(qq.value)) + + return qq + else: + return num.sage(ring=ring) / den.sage(ring=ring) + + +@GapRing.convert_to('sage') +def gapring_to_sage(obj, var='a'): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapRing`. + + INPUT: + + - ``var`` -- the variable name to label the generator of finite fields + + OUTPUT: + + A Sage ring. + + EXAMPLES:: + + sage: libgap.eval('Integers').sage() + Integer Ring + + sage: libgap.eval('Rationals').sage() + Rational Field + + sage: libgap.eval('ZmodnZ(15)').sage() + Ring of integers modulo 15 + + sage: libgap.GF(3,2).sage(var='A') + Finite Field in A of size 3^2 + + sage: libgap.CyclotomicField(6).sage() + Cyclotomic Field of order 3 and degree 2 + + sage: libgap(QQ['x','y']).sage() + Multivariate Polynomial Ring in x, y over Rational Field + """ + + if obj.IsField(): + if obj.IsRationals(): + return ZZ.fraction_field() + if obj.IsCyclotomicField(): + conductor = obj.Conductor() + from sage.rings.number_field.number_field import CyclotomicField + return CyclotomicField(conductor.sage()) + if obj.IsFinite(): + size = obj.Size().sage() + from sage.rings.finite_rings.finite_field_constructor import GF + return GF(size, name=var) + else: + if obj.IsIntegers(): + return ZZ + if obj.IsFinite(): + characteristic = obj.Characteristic().sage() + return ZZ.quotient_ring(characteristic) + if obj.IsPolynomialRing(): + base_ring = obj.CoefficientsRing().sage() + vars = [x.String().sage() + for x in obj.IndeterminatesOfPolynomialRing()] + from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing + return PolynomialRing(base_ring, vars) + + raise NotImplementedError('cannot convert GAP ring to Sage') + + +@GapBoolean.convert_to('sage') +def gapboolean_to_sage(obj): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapBoolean` + + OUTPUT: + + A Python boolean if the values is either true or false. GAP + booleans can have the third value ``Fail``, in which case a + ``ValueError`` is raised. + + EXAMPLES:: + + sage: b = libgap.eval('true'); b + true + sage: type(_) + + sage: b.sage() + True + sage: type(_) + <... 'bool'> + + sage: libgap.eval('fail') + fail + sage: _.sage() + Traceback (most recent call last): + ... + ValueError: the GAP boolean value "fail" cannot be represented in Sage + """ + if repr(obj) == 'fail': + raise ValueError( + 'the GAP boolean value "fail" cannot be represented in Sage') + return obj.__bool__() + + +@GapString.convert_to('sage') +def gapstring_to_string(obj): + """ + Return the Sage equivalent of the :class:`~gappy.gapobj.GapString` + """ + + return obj.__str__() + + +@GapList.convert_to('sage') +def gaplist_to_sage(obj, **kwds): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapList` + + OUTPUT: + + A Python list. + + EXAMPLES:: + + sage: libgap([ 1, 3, 4 ]).sage() + [1, 3, 4] + sage: all( x in ZZ for x in _ ) + True + """ + return [x.sage(**kwds) for x in obj] + + +# For compatibility with the old libgap interface, GapLists also have a +# conversion named simply 'matrix' which converts them to a Sage matrix. +@GapList.convert_to('matrix') +def gaplist_to_matrix(self, ring=None): + """ + Return the list as a matrix. + + GAP does not have a special matrix data type, they are just lists of lists. + This function converts a GAP list of lists to a Sage matrix. + + OUTPUT: + + A Sage matrix. + + EXAMPLES:: + + sage: F = libgap.GF(4) + sage: a = F.PrimitiveElement() + sage: m = libgap([[a,a^0],[0*a,a^2]]); m + [ [ Z(2^2), Z(2)^0 ], + [ 0*Z(2), Z(2^2)^2 ] ] + sage: m.IsMatrix() + true + sage: matrix(m) + [ a 1] + [ 0 a + 1] + sage: matrix(GF(4,'B'), m) + [ B 1] + [ 0 B + 1] + + sage: M = libgap.eval('SL(2,GF(5))').GeneratorsOfGroup()[1] + sage: type(M) + + sage: M[0][0] + Z(5)^2 + sage: M.IsMatrix() + true + sage: M.matrix() + [4 1] + [4 0] + """ + + if not self.IsMatrix(): + raise ValueError('not a GAP matrix') + + if not self.IsRectangularTable(): + raise ValueError('not a rectangular list of lists') + + n, m = self.DimensionsMat() + entries = self.Flat() + + if ring is None: + ring = entries.DefaultRing().sage() + + MS = MatrixSpace(ring, n, m) + return MS([x.sage(ring=ring) for x in entries]) + + +@GapPermutation.convert_to('sage') +def gappermutation_to_sage(obj, parent=None): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapPermutation` + + If the permutation group is given as parent, this method is *much* faster. + + EXAMPLES:: + + sage: perm_gap = libgap.eval('(1,5,2)(4,3,8)'); perm_gap + (1,5,2)(3,8,4) + sage: perm_gap.sage() + [5, 1, 8, 3, 2, 6, 7, 4] + sage: type(_) + + sage: perm_gap.sage(PermutationGroup([(1,2),(1,2,3,4,5,6,7,8)])) + (1,5,2)(3,8,4) + sage: type(_) + + """ + cdef PermutationGroupElement one_c + + lst = obj.ListPerm() + + if parent is None: + return Permutation(lst.sage(), check_input=False) + else: + return parent.one()._generate_new_GAP(lst) + + +@GapRecord.convert_to('sage') +def sage(obj): + r""" + Return the Sage equivalent of the :class:`~gappy.gapobj.GapRecord` + + EXAMPLES:: + + sage: libgap.eval('rec(a:=1, b:=2)').sage() + {'a': 1, 'b': 2} + sage: all( isinstance(key,str) and val in ZZ for key,val in _.items() ) + True + + sage: rec = libgap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))') + sage: rec.sage() + {'Sym3': NotImplementedError('cannot construct equivalent Sage object'...), + 'a': 123, + 'b': 456} + """ + result = {} + for key, val in obj: + try: + val = val.sage() + except Exception as ex: + val = ex + result[str(key)] = val + return result diff --git a/src/sage/libs/gap/element.pxd b/src/sage/libs/gap/element.pxd index f7fafd9302c..2f012ad414f 100644 --- a/src/sage/libs/gap/element.pxd +++ b/src/sage/libs/gap/element.pxd @@ -8,79 +8,17 @@ # http://www.gnu.org/licenses/ #***************************************************************************** -from gappy.core cimport Gap -from gappy.gapobj cimport GapObj -from gappy.gap_includes cimport Obj - -from sage.structure.element cimport Element, ModuleElement, RingElement -from sage.structure.sage_object cimport SageObject - -cdef GapElement make_any_gap_element(parent, GapObj obj) -cdef GapElement make_GapElement(parent, GapObj obj) -cdef GapElement_List make_GapElement_List(parent, GapObj obj) -cdef GapElement_Record make_GapElement_Record(parent, GapObj obj) -cdef GapElement_Integer make_GapElement_Integer(parent, GapObj obj) -cdef GapElement_Rational make_GapElement_Rational(parent, GapObj obj) -cdef GapElement_String make_GapElement_String(parent, GapObj obj) -cdef GapElement_Boolean make_GapElement_Boolean(parent, GapObj obj) -cdef GapElement_Function make_GapElement_Function(parent, GapObj obj) -cdef GapElement_Permutation make_GapElement_Permutation(parent, GapObj obj) - - -cdef class GapElement(RingElement): - - # the pointer to the gappy GapObj - cdef GapObj obj - - # comparison - cdef bint _compare_by_id - cpdef _set_compare_by_id(self) - cpdef _assert_compare_by_id(self) - - cdef _initialize(self, parent, GapObj obj) - cpdef is_bool(self) - cpdef _add_(self, other) - cpdef _mul_(self, other) - cpdef _mod_(self, other) - cpdef _pow_(self, other) - - cpdef GapElement deepcopy(self, bint mut) - -cdef class GapElement_Integer(GapElement): - pass - -cdef class GapElement_Rational(GapElement): - pass - -cdef class GapElement_IntegerMod(GapElement): - cpdef GapElement_Integer lift(self) - -cdef class GapElement_FiniteField(GapElement): - cpdef GapElement_Integer lift(self) - -cdef class GapElement_Cyclotomic(GapElement): - pass - -cdef class GapElement_Ring(GapElement): - pass - -cdef class GapElement_String(GapElement): - pass - -cdef class GapElement_Boolean(GapElement): - pass - -cdef class GapElement_Function(GapElement): - pass - -cdef class GapElement_MethodProxy(GapElement_Function): - cdef GapElement first_argument - -cdef class GapElement_Record(GapElement): - pass - -cdef class GapElement_List(GapElement): - pass - -cdef class GapElement_Permutation(GapElement): - pass +from gappy.gapobj cimport GapObj as GapElement +from gappy.gapobj cimport GapInteger as GapElement_Integer +from gappy.gapobj cimport GapRational as GapElement_Rational +from gappy.gapobj cimport GapIntegerMod as GapElement_IntegerMod +from gappy.gapobj cimport GapFiniteField as GapElement_FiniteField +from gappy.gapobj cimport GapCyclotomic as GapElement_Cyclotomic +from gappy.gapobj cimport GapRing as GapElement_Ring +from gappy.gapobj cimport GapString as GapElement_String +from gappy.gapobj cimport GapBoolean as GapElement_Boolean +from gappy.gapobj cimport GapFunction as GapElement_Function +from gappy.gapobj cimport GapMethodProxy as GapElement_MethodProxy +from gappy.gapobj cimport GapRecord as GapElement_Record +from gappy.gapobj cimport GapList as GapElement_List +from gappy.gapobj cimport GapPermutation as GapElement_Permutation diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index 39a80b8d647..5833544e590 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -1,9 +1,13 @@ """ GAP element wrapper -This document describes the individual wrappers for various GAP -elements. For general information about GAP, you should read the -:mod:`~sage.libs.gap.libgap` module documentation. +This document describes the individual wrappers for various GAP elements. For +general information about GAP, you should read the :mod:`~sage.libs.gap.libgap` +module documentation. + +This module and the classes in it are deprecated as it has been superseded by +the gappy package; see the documentation for :mod:`gappy.gapobj` for the +documentation on the equivalent classes in gappy. """ # **************************************************************************** @@ -16,2712 +20,51 @@ elements. For general information about GAP, you should read the # https://www.gnu.org/licenses/ # **************************************************************************** -import warnings - -from sage.rings.all import ZZ, QQ, RDF -from sage.groups.perm_gps.permgroup_element cimport PermutationGroupElement -from sage.combinat.permutation import Permutation -from sage.structure.coerce cimport coercion_model as cm +from sage.misc.superseded import deprecation from gappy import gapobj -from gappy.gapobj import GapObj -from gappy.gapobj cimport GapObj -from gappy.gap_includes cimport GAP_NewPlist, GAP_AssList -from gappy.operations import OperationInspector - - -############################################################################ -### generic construction of GapElements #################################### -############################################################################ - -cdef GapElement make_any_gap_element(parent, GapObj obj): - """ - Return the GAP element wrapper of ``obj`` - - The most suitable subclass of GapElement is determined - automatically. Use this function to wrap GAP objects unless you - know exactly which type it is (then you can use the specialized - ``make_GapElement_...``) - - TESTS:: - - sage: T_CHAR = libgap.eval("'c'"); T_CHAR - "c" - sage: type(T_CHAR) - - - sage: libgap.eval("['a', 'b', 'c']") # gap strings are also lists of chars - "abc" - sage: t = libgap.UnorderedTuples('abc', 2); t - [ "aa", "ab", "ac", "bb", "bc", "cc" ] - sage: t[1] - "ab" - sage: t[1].sage() - 'ab' - sage: t.sage() - ['aa', 'ab', 'ac', 'bb', 'bc', 'cc'] - - Check that :trac:`18158` is fixed:: - - sage: S = SymmetricGroup(5) - sage: irr = libgap.Irr(S)[3] - sage: irr[0] - 6 - sage: irr[1] - 0 - """ - cdef GapElement elem - - if obj is None: - return obj - - elem_cls = _gapobj_to_element.get(type(obj), GapElement) - elem = elem_cls.__new__(elem_cls) - elem._initialize(parent, obj) - return elem - - -############################################################################ -### GapElement ############################################################# -############################################################################ - -cdef GapElement make_GapElement(parent, GapObj obj): - r""" - Turn a Gap C object (of type ``Obj``) into a Cython ``GapElement``. - - INPUT: - - - ``parent`` -- the parent of the new :class:`GapElement` - - - ``obj`` -- a GAP object. - - OUTPUT: - - A :class:`GapElement_Function` instance, or one of its derived - classes if it is a better fit for the GAP object. - - EXAMPLES:: - - sage: libgap(0) - 0 - sage: type(_) - - - sage: libgap.eval('') - sage: libgap(None) - NULL - """ - cdef GapElement r = GapElement.__new__(GapElement) - r._initialize(parent, obj) - return r - - -cpdef _from_sage(elem): - """ - Currently just used for unpickling; equivalent to calling ``libgap(elem)`` - to convert a Sage object to a `GapElement` where possible. - """ - from sage.libs.gap.libgap import libgap - - if isinstance(elem, str): - return libgap.eval(elem) - - return libgap(elem) - - -cdef class GapElement(RingElement): - r""" - Wrapper for all Gap objects. - - .. NOTE:: - - In order to create ``GapElements`` you should use the - ``libgap`` instance (the parent of all Gap elements) to - convert things into ``GapElement``. You must not create - ``GapElement`` instances manually. - - EXAMPLES:: - - sage: libgap(0) - 0 - - If Gap finds an error while evaluating, a :class:`GAPError` - exception is raised:: - - sage: libgap.eval('1/0') - Traceback (most recent call last): - ... - GAPError: Error, Rational operations: must not be zero - - Also, a ``GAPError`` is raised if the input is not a simple expression:: - - sage: libgap.eval('1; 2; 3') - Traceback (most recent call last): - ... - GAPError: can only evaluate a single statement - """ - - def __cinit__(self): - """ - The Cython constructor. - - EXAMPLES:: - - sage: libgap.eval('1') - 1 - """ - self.obj = None - self._compare_by_id = False - - def __init__(self): - """ - The ``GapElement`` constructor - - Users must use the ``libgap`` instance to construct instances - of :class:`GapElement`. Cython programmers must use - :func:`make_GapElement` factory function. - - TESTS:: - - sage: from sage.libs.gap.element import GapElement - sage: GapElement() - Traceback (most recent call last): - ... - TypeError: this class cannot be instantiated from Python - """ - raise TypeError('this class cannot be instantiated from Python') - - cdef _initialize(self, parent, GapObj obj): - r""" - Initialize the GapElement. - - This Cython method is called from :func:`make_GapElement` to - initialize the newly-constructed object. You must never call - it manually. - - TESTS:: - - sage: n_before = libgap.count_GAP_objects() - sage: a = libgap.eval('123') - sage: b = libgap.eval('456') - sage: c = libgap.eval('CyclicGroup(3)') - sage: d = libgap.eval('"a string"') - sage: libgap.collect() - sage: del c - sage: libgap.collect() - sage: n_after = libgap.count_GAP_objects() - sage: n_after - n_before - 3 - """ - assert self.obj is None - self._parent = parent - self.obj = obj - - def __copy__(self): - r""" - TESTS:: - - sage: a = libgap(1) - sage: a.__copy__() is a - True - - sage: a = libgap(1/3) - sage: a.__copy__() is a - True - - sage: a = libgap([1,2]) - sage: b = a.__copy__() - sage: a is b - False - sage: a[0] = 3 - sage: a - [ 3, 2 ] - sage: b - [ 1, 2 ] - - sage: a = libgap([[0,1],[2,3,4]]) - sage: b = a.__copy__() - sage: b[0][1] = -2 - sage: b - [ [ 0, -2 ], [ 2, 3, 4 ] ] - sage: a - [ [ 0, -2 ], [ 2, 3, 4 ] ] - """ - copy = self.obj.__copy__() - if copy is self.obj: - return self - else: - return make_any_gap_element(self.parent(), copy) - - cpdef GapElement deepcopy(self, bint mut): - r""" - Return a deepcopy of this Gap object - - Note that this is the same thing as calling ``StructuralCopy`` but much - faster. - - INPUT: - - - ``mut`` - (boolean) wheter to return an mutable copy - - EXAMPLES:: - - sage: a = libgap([[0,1],[2,3]]) - sage: b = a.deepcopy(1) - sage: b[0,0] = 5 - sage: a - [ [ 0, 1 ], [ 2, 3 ] ] - sage: b - [ [ 5, 1 ], [ 2, 3 ] ] - - sage: l = libgap([0,1]) - sage: l.deepcopy(0).IsMutable() - false - sage: l.deepcopy(1).IsMutable() - true - """ - return make_any_gap_element(self.parent(), self.obj.deepcopy(mut)) - - def __deepcopy__(self, memo): - r""" - TESTS:: - - sage: a = libgap([[0,1],[2]]) - sage: b = deepcopy(a) - sage: a[0,0] = -1 - sage: a - [ [ -1, 1 ], [ 2 ] ] - sage: b - [ [ 0, 1 ], [ 2 ] ] - """ - return self.deepcopy(0) - - def __reduce__(self): - """ - Attempt to pickle GAP elements from libgap. - - This is inspired in part by - ``sage.interfaces.interface.Interface._reduce``, though for a fallback - we use ``str(self)`` instead of ``repr(self)``, since the former is - equivalent in the libgap interface to the latter in the pexpect - interface. - - TESTS: - - This workaround was motivated in particular by this example from the - permutation groups implementation:: - - sage: CC = libgap.eval('ConjugacyClass(SymmetricGroup([ 1 .. 5 ]), (1,2)(3,4))') - sage: CC.sage() - Traceback (most recent call last): - ... - NotImplementedError: cannot construct equivalent Sage object - sage: libgap.eval(str(CC)) - (1,2)(3,4)^G - sage: loads(dumps(CC)) - (1,2)(3,4)^G - """ - - if self.is_string(): - elem = repr(self.sage()) - try: - elem = self.sage() - except NotImplementedError: - elem = str(self) - - return (_from_sage, (elem,)) - - def __contains__(self, other): - r""" - TESTS:: - - sage: libgap(1) in libgap.eval('Integers') - True - sage: 1 in libgap.eval('Integers') - True - - sage: 3 in libgap([1,5,3,2]) - True - sage: -5 in libgap([1,5,3,2]) - False - - sage: libgap.eval('Integers') in libgap(1) - Traceback (most recent call last): - ... - GAPError: Error, no method found! Error, no 1st choice method found for `in' on 2 arguments - """ - return self.obj.__contains__(other) - - def __dir__(self): - """ - Customize tab completion - - EXAMPLES:: - - sage: G = libgap.DihedralGroup(4) - sage: 'GeneratorsOfMagmaWithInverses' in dir(G) - True - sage: 'GeneratorsOfGroup' in dir(G) # known bug - False - sage: x = libgap(1) - sage: len(dir(x)) > 100 - True - """ - ops = OperationInspector(self.obj).op_names() - return dir(self.__class__) + ops - - def __getattr__(self, name): - r""" - Return functionoid implementing the function ``name``. - - EXAMPLES:: - - sage: lst = libgap([]) - sage: 'Add' in dir(lst) # This is why tab-completion works - True - sage: lst.Add(1) # this is the syntactic sugar - sage: lst - [ 1 ] - - The above is equivalent to the following calls:: - - sage: lst = libgap.eval('[]') - sage: libgap.eval('Add') (lst, 1) - sage: lst - [ 1 ] - - TESTS:: - - sage: lst.Adddddd(1) - Traceback (most recent call last): - ... - AttributeError: no GAP global variable bound to 'Adddddd' - - sage: libgap.eval('some_name := 1') - 1 - sage: lst.some_name - Traceback (most recent call last): - ... - AttributeError: 'some_name' does not define a GAP function - """ - # TODO: I don't think this is needed but keeping it in case there is - # some weird backwards-compat need for it. Try removing this later - # and see if it breaks anything. - if name in ('__dict__', '_getAttributeNames', '__custom_name', 'keys'): - raise AttributeError('Python special name, not a GAP function.') - - func = self.obj.__getattr__(name) - - if not func.is_function(): - raise AttributeError(f"'{name}' does not define a GAP function") - - return make_GapElement_MethodProxy(self.parent(), func, self) - - def __str__(self): - r""" - Return a string representation of ``self`` for printing. - - EXAMPLES:: - - sage: libgap(0) - 0 - sage: print(libgap.eval('')) - None - sage: print(libgap('a')) - a - sage: print(libgap.eval('SymmetricGroup(3)')) - SymmetricGroup( [ 1 .. 3 ] ) - sage: libgap(0).__str__() - '0' - """ - return self.obj.__str__() - - def _repr_(self): - r""" - Return a string representation of ``self``. - - EXAMPLES:: - - sage: libgap(0) - 0 - sage: libgap.eval('') - sage: libgap('a') - "a" - sage: libgap.eval('SymmetricGroup(3)') - Sym( [ 1 .. 3 ] ) - sage: libgap(0)._repr_() - '0' - """ - return self.obj.__repr__() - - cpdef _set_compare_by_id(self): - """ - Set comparison to compare by ``id`` - - By default, GAP is used to compare GAP objects. However, - this is not defined for all GAP objects. To have GAP play - nice with ``UniqueRepresentation``, comparison must always - work. This method allows one to override the comparison to - sort by the (unique) Python ``id``. - - Obviously it is a bad idea to change the comparison of objects - after you have inserted them into a set/dict. You also must - not mix GAP objects with different sort methods in the same - container. - - EXAMPLES:: - - sage: F1 = libgap.FreeGroup(['a']) - sage: F2 = libgap.FreeGroup(['a']) - sage: F1 < F2 - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `<' on 2 arguments - - sage: F1._set_compare_by_id() - sage: F1 != F2 - Traceback (most recent call last): - ... - ValueError: comparison style must be the same for both operands - - sage: F1._set_compare_by_id() - sage: F2._set_compare_by_id() - sage: F1 != F2 - True - """ - self.obj._set_compare_by_id() - - cpdef _assert_compare_by_id(self): - """ - Ensure that comparison is by ``id`` - - See :meth:`_set_compare_by_id`. - - OUTPUT: - - This method returns nothing. A ``ValueError`` is raised if - :meth:`_set_compare_by_id` has not been called on this libgap - object. - - EXAMPLES:: - - sage: x = libgap.FreeGroup(1) - sage: x._assert_compare_by_id() - Traceback (most recent call last): - ... - ValueError: this requires a GAP object whose comparison is by "id" - - sage: x._set_compare_by_id() - sage: x._assert_compare_by_id() - """ - self.obj._assert_compare_by_id() - - def __hash__(self): - """ - Make hashable. - - EXAMPLES:: - - sage: hash(libgap(123)) # random output - 163512108404620371 - """ - return hash((type(self), str(self))) - - cpdef _richcmp_(self, other, int op): - """ - Compare ``self`` with ``other``. - - Uses the GAP comparison by default, or the Python ``id`` if - :meth:`_set_compare_by_id` was called. - - OUTPUT: - - Boolean, depending on the comparison of ``self`` and - ``other``. Raises a ``ValueError`` if GAP does not support - comparison of ``self`` and ``other``, unless - :meth:`_set_compare_by_id` was called on both ``self`` and - ``other``. - - EXAMPLES:: - - sage: a = libgap(123) - sage: a == a - True - sage: b = libgap('string') - sage: a._richcmp_(b, 0) - 1 - sage: (a < b) or (a > b) - True - sage: a._richcmp_(libgap(123), 2) - True - - GAP does not have a comparison function for two ``FreeGroup`` - objects. LibGAP signals this by raising a ``ValueError`` :: - - sage: F1 = libgap.FreeGroup(['a']) - sage: F2 = libgap.FreeGroup(['a']) - sage: F1 < F2 - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `<' on 2 arguments - - sage: F1._set_compare_by_id() - sage: F1 < F2 - Traceback (most recent call last): - ... - ValueError: comparison style must be the same for both operands - sage: F1._set_compare_by_id() - sage: F2._set_compare_by_id() - sage: F1 < F2 or F1 > F2 - True - Check that :trac:`26388` is fixed:: +deprecation(31404, + 'this module has been subsumed by the gappy package and will be removed ' + 'in a future version; all the GapElement classes are still available but ' + 'as aliases for the equivalent gappy.gapobj.GapObj classes, and are no ' + 'longer subclasses of Element') - sage: 1 > libgap(1) - False - sage: libgap(1) > 1 - False - sage: 1 >= libgap(1) - True - sage: libgap(1) >= 1 - True - """ - return self.obj._richcmp_((other).obj, op) - cpdef _add_(self, right): - r""" - Add two GapElement objects. - - EXAMPLES:: - - sage: g1 = libgap(1) - sage: g2 = libgap(2) - sage: g1._add_(g2) - 3 - sage: g1 + g2 # indirect doctest - 3 - - sage: libgap(1) + libgap.CyclicGroup(2) - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `+' on 2 arguments - """ - cdef GapObj res - res = self.obj._add_((right).obj) - return make_any_gap_element(self.parent(), res) - - cpdef _sub_(self, right): - r""" - Subtract two GapElement objects. - - EXAMPLES:: - - sage: g1 = libgap(1) - sage: g2 = libgap(2) - sage: g1._sub_(g2) - -1 - sage: g1 - g2 # indirect doctest - -1 - - sage: libgap(1) - libgap.CyclicGroup(2) - Traceback (most recent call last): - ... - GAPError: Error, no method found! ... - """ - cdef GapObj res - res = self.obj._sub_((right).obj) - return make_any_gap_element(self.parent(), res) - - cpdef _mul_(self, right): - r""" - Multiply two GapElement objects. - - EXAMPLES:: - - sage: g1 = libgap(3) - sage: g2 = libgap(5) - sage: g1._mul_(g2) - 15 - sage: g1 * g2 # indirect doctest - 15 - - sage: libgap(1) * libgap.CyclicGroup(2) - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `*' on 2 arguments - """ - cdef GapObj res - res = self.obj._mul_((right).obj) - return make_any_gap_element(self.parent(), res) - - cpdef _div_(self, right): - r""" - Divide two GapElement objects. - - EXAMPLES:: - - sage: g1 = libgap(3) - sage: g2 = libgap(5) - sage: g1._div_(g2) - 3/5 - sage: g1 / g2 # indirect doctest - 3/5 - - sage: libgap(1) / libgap.CyclicGroup(2) - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `/' on 2 arguments - - sage: libgap(1) / libgap(0) - Traceback (most recent call last): - ... - GAPError: Error, Rational operations: must not be zero - """ - cdef GapObj res - res = self.obj._div_((right).obj) - return make_any_gap_element(self.parent(), res) - - cpdef _mod_(self, right): - r""" - Modulus of two GapElement objects. - - EXAMPLES:: - - sage: g1 = libgap(5) - sage: g2 = libgap(2) - sage: g1 % g2 - 1 - - sage: libgap(1) % libgap.CyclicGroup(2) - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `mod' on 2 arguments - """ - cdef GapObj res - res = self.obj._mod_((right).obj) - return make_any_gap_element(self.parent(), res) - - cpdef _pow_(self, other): - r""" - Exponentiation of two GapElement objects. - - EXAMPLES:: - - sage: r = libgap(5) ^ 2; r - 25 - sage: parent(r) - C library interface to GAP - sage: r = 5 ^ libgap(2); r - 25 - sage: parent(r) - C library interface to GAP - sage: g, = libgap.CyclicGroup(5).GeneratorsOfGroup() - sage: g ^ 5 - of ... - - TESTS: - - Check that this can be interrupted gracefully:: - - sage: a, b = libgap.GL(1000, 3).GeneratorsOfGroup(); g = a * b - sage: alarm(0.5); g ^ (2 ^ 10000) - Traceback (most recent call last): - ... - AlarmInterrupt - - sage: libgap.CyclicGroup(2) ^ 2 - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `^' on 2 arguments - - sage: libgap(3) ^ Infinity - Traceback (most recent call last): - ... - GAPError: Error, no method found! Error, no 1st choice - method found for `InverseMutable' on 1 arguments - """ - cdef GapObj res - res = self.obj._pow_((other).obj) - return make_any_gap_element(self.parent(), res) - - cpdef _pow_int(self, other): - """ - TESTS:: - - sage: libgap(5)._pow_int(int(2)) - 25 - """ - return self._pow_(self._parent(other)) - - def is_function(self): - """ - Return whether the wrapped GAP object is a function. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: a = libgap.eval("NormalSubgroups") - sage: a.is_function() - True - sage: a = libgap(2/3) - sage: a.is_function() - False - """ - return self.obj.is_function() - - def is_list(self): - r""" - Return whether the wrapped GAP object is a GAP List. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: libgap.eval('[1, 2,,,, 5]').is_list() - True - sage: libgap.eval('3/2').is_list() - False - """ - return self.obj.is_list() - - def is_record(self): - r""" - Return whether the wrapped GAP object is a GAP record. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: libgap.eval('[1, 2,,,, 5]').is_record() - False - sage: libgap.eval('rec(a:=1, b:=3)').is_record() - True - """ - return self.obj.is_record() - - cpdef is_bool(self): - r""" - Return whether the wrapped GAP object is a GAP boolean. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: libgap(True).is_bool() - True - """ - return self.obj.is_bool() - - def is_string(self): - r""" - Return whether the wrapped GAP object is a GAP string. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: libgap('this is a string').is_string() - True - """ - return self.obj.is_string() - - def is_permutation(self): - r""" - Return whether the wrapped GAP object is a GAP permutation. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: perm = libgap.PermList( libgap([1,5,2,3,4]) ); perm - (2,5,4,3) - sage: perm.is_permutation() - True - sage: libgap('this is a string').is_permutation() - False - """ - return self.obj.is_permutation() - - def sage(self): - r""" - Return the Sage equivalent of the :class:`GapElement` - - EXAMPLES:: - - sage: libgap(1).sage() - 1 - sage: type(_) - - - sage: libgap(3/7).sage() - 3/7 - sage: type(_) - - - sage: libgap.eval('5 + 7*E(3)').sage() - 7*zeta3 + 5 - - sage: libgap(Infinity).sage() - +Infinity - sage: libgap(-Infinity).sage() - -Infinity - - sage: libgap(True).sage() - True - sage: libgap(False).sage() - False - sage: type(_) - <... 'bool'> - - sage: libgap('this is a string').sage() - 'this is a string' - sage: type(_) - <... 'str'> - - sage: x = libgap.Integers.Indeterminate("x") - - sage: p = x^2 - 2*x + 3 - sage: p.sage() - x^2 - 2*x + 3 - sage: p.sage().parent() - Univariate Polynomial Ring in x over Integer Ring - - sage: p = x^-2 + 3*x - sage: p.sage() - x^-2 + 3*x - sage: p.sage().parent() - Univariate Laurent Polynomial Ring in x over Integer Ring - - sage: p = (3 * x^2 + x) / (x^2 - 2) - sage: p.sage() - (3*x^2 + x)/(x^2 - 2) - sage: p.sage().parent() - Fraction Field of Univariate Polynomial Ring in x over Integer Ring - - TESTS: - - Check :trac:`30496`:: - - sage: x = libgap.Integers.Indeterminate("x") - - sage: p = x^2 - 2*x - sage: p.sage() - x^2 - 2*x - """ - if self.obj is None: - return None - - if self.IsInfinity(): - from sage.rings.infinity import Infinity - return Infinity - - elif self.IsNegInfinity(): - from sage.rings.infinity import Infinity - return -Infinity - - elif self.IsUnivariateRationalFunction(): - var = self.IndeterminateOfUnivariateRationalFunction().String() - var = var.sage() - num, den, val = self.CoefficientsOfUnivariateRationalFunction() - num = num.sage() - den = den.sage() - val = val.sage() - base_ring = cm.common_parent(*(num + den)) - - if self.IsUnivariatePolynomial(): - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(base_ring, var) - x = R.gen() - return x**val * R(num) - - elif self.IsLaurentPolynomial(): - from sage.rings.polynomial.laurent_polynomial_ring import LaurentPolynomialRing - R = LaurentPolynomialRing(base_ring, var) - x = R.gen() - return x**val * R(num) - - else: - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - R = PolynomialRing(base_ring, var) - x = R.gen() - return x**val * R(num) / R(den) - - elif self.IsList(): - # May be a list-like collection of some other type of GapElements - # that we can convert - return [item.sage() for item in self.AsList()] - - raise NotImplementedError('cannot construct equivalent Sage object') - - -############################################################################ -### GapElement_Integer ##################################################### -############################################################################ - -cdef GapElement_Integer make_GapElement_Integer(parent, GapObj obj): - r""" - Turn a Gap integer object into a GapElement_Integer Sage object - - EXAMPLES:: - - sage: libgap(123) - 123 - sage: type(_) - +def import_gapobjs_as_gapelements(): """ - cdef GapElement_Integer r = GapElement_Integer.__new__(GapElement_Integer) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Integer(GapElement): - r""" - Derived class of GapElement for GAP integers. + Creates aliases for the various `~gappy.gapobj.GapObj` subclasses under + the names of the original ``GapElement`` subclasses to provide a bit of + backwards compatibility for ``isinstance(..., GapElement)`` checks in + existing code. EXAMPLES:: - sage: i = libgap(123) - sage: type(i) - - sage: ZZ(i) - 123 + sage: from gappy.gapobj import GapObj, GapList + sage: from sage.libs.gap.element import GapElement, GapElement_List + sage: GapObj is GapElement + True + sage: GapList is GapElement_List + True + sage: isinstance(libgap.eval('[]'), GapElement_List) + True """ - def is_C_int(self): - r""" - Return whether the wrapped GAP object is a immediate GAP integer. - - An immediate integer is one that is stored as a C integer, and - is subject to the usual size limits. Larger integers are - stored in GAP as GMP integers. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: n = libgap(1) - sage: type(n) - - sage: n.is_C_int() - True - sage: n.IsInt() - true - - sage: N = libgap(2^130) - sage: type(N) - - sage: N.is_C_int() - False - sage: N.IsInt() - true - """ - # TODO: Maybe deprecate this method? It's really only used internally - # (in fact not even anymore since its functionality is delegated to - # gappy) and shouldn't be a detail any other code needs to care about. - return self.obj.is_C_int() - - def _rational_(self): - r""" - EXAMPLES:: - - sage: QQ(libgap(1)) # indirect doctest - 1 - sage: QQ(libgap(-2**200)) == -2**200 - True - """ - return self.sage(ring=QQ) - - def sage(self, ring=None): - r""" - Return the Sage equivalent of the :class:`GapElement_Integer` - - - ``ring`` -- Integer ring or ``None`` (default). If not - specified, a the default Sage integer ring is used. - - OUTPUT: - - A Sage integer - - EXAMPLES:: - - sage: libgap([ 1, 3, 4 ]).sage() - [1, 3, 4] - sage: all( x in ZZ for x in _ ) - True - - sage: libgap(132).sage(ring=IntegerModRing(13)) - 2 - sage: parent(_) - Ring of integers modulo 13 - - TESTS:: - - sage: large = libgap.eval('2^130'); large - 1361129467683753853853498429727072845824 - sage: large.sage() - 1361129467683753853853498429727072845824 - - sage: huge = libgap.eval('10^9999'); huge # gap abbreviates very long ints - - sage: huge.sage().ndigits() - 10000 - """ - if ring is None: - ring = ZZ - - return ring(int(self.obj)) - - _integer_ = sage - - def __int__(self): - r""" - TESTS:: - - sage: int(libgap(3)) - 3 - sage: type(_) - - - sage: int(libgap(2)**128) - 340282366920938463463374607431768211456L - sage: type(_) # py2 - - sage: type(_) # py3 - - """ - return self.sage(ring=int) - - def __index__(self): - r""" - TESTS: - - Check that gap integers can be used as indices (:trac:`23878`):: - - sage: s = 'abcd' - sage: s[libgap(1)] - 'b' - """ - return int(self) - - -########################################################################## -### GapElement_Float ##################################################### -########################################################################## - -cdef GapElement_Float make_GapElement_Float(parent, GapObj obj): - r""" - Turn a Gap macfloat object into a GapElement_Float Sage object - - EXAMPLES:: - - sage: libgap(123.5) - 123.5 - sage: type(_) - - """ - cdef GapElement_Float r = GapElement_Float.__new__(GapElement_Float) - r._initialize(parent, obj) - return r - -cdef class GapElement_Float(GapElement): - r""" - Derived class of GapElement for GAP floating point numbers. - - EXAMPLES:: - - sage: i = libgap(123.5) - sage: type(i) - - sage: RDF(i) - 123.5 - sage: float(i) - 123.5 - - TESTS:: - - sage: a = RDF.random_element() - sage: libgap(a).sage() == a - True - """ - def sage(self, ring=None): - r""" - Return the Sage equivalent of the :class:`GapElement_Float` - - - ``ring`` -- a floating point field or ``None`` (default). If not - specified, the default Sage ``RDF`` is used. - - OUTPUT: - - A Sage double precision floating point number - - EXAMPLES:: - - sage: a = libgap.eval("Float(3.25)").sage() - sage: a - 3.25 - sage: parent(a) - Real Double Field - """ - if ring is None: - ring = RDF - return ring(float(self.obj)) - - def __float__(self): - r""" - TESTS:: - - sage: float(libgap.eval("Float(3.5)")) - 3.5 - """ - return float(self.obj) - - -############################################################################ -### GapElement_IntegerMod ##################################################### -############################################################################ - -cdef GapElement_IntegerMod make_GapElement_IntegerMod(parent, GapObj obj): - r""" - Turn a Gap integer object into a :class:`GapElement_IntegerMod` Sage object - - EXAMPLES:: - - sage: n = IntegerModRing(123)(13) - sage: libgap(n) - ZmodnZObj( 13, 123 ) - sage: type(_) - - """ - cdef GapElement_IntegerMod r = GapElement_IntegerMod.__new__(GapElement_IntegerMod) - r._initialize(parent, obj) - return r - -cdef class GapElement_IntegerMod(GapElement): - r""" - Derived class of GapElement for GAP integers modulo an integer. - - EXAMPLES:: - - sage: n = IntegerModRing(123)(13) - sage: i = libgap(n) - sage: type(i) - - """ - - cpdef GapElement_Integer lift(self): - """ - Return an integer lift. - - OUTPUT: - - A :class:`GapElement_Integer` that equals ``self`` in the - integer mod ring. - - EXAMPLES:: - - sage: n = libgap.eval('One(ZmodnZ(123)) * 13') - sage: n.lift() - 13 - sage: type(_) - - """ - return self.Int() - - def sage(self, ring=None): - r""" - Return the Sage equivalent of the :class:`GapElement_IntegerMod` - - INPUT: - - - ``ring`` -- Sage integer mod ring or ``None`` (default). If - not specified, a suitable integer mod ringa is used - automatically. - - OUTPUT: - - A Sage integer modulo another integer. - - EXAMPLES:: - - sage: n = libgap.eval('One(ZmodnZ(123)) * 13') - sage: n.sage() - 13 - sage: parent(_) - Ring of integers modulo 123 - """ - if ring is None: - # ring = self.DefaultRing().sage() - characteristic = self.Characteristic().sage() - ring = ZZ.quotient_ring(characteristic) - return self.lift().sage(ring=ring) - - -############################################################################ -### GapElement_FiniteField ##################################################### -############################################################################ - -cdef GapElement_FiniteField make_GapElement_FiniteField(parent, GapObj obj): - r""" - Turn a GAP finite field object into a :class:`GapElement_FiniteField` Sage object - - EXAMPLES:: - - sage: libgap.eval('Z(5)^2') - Z(5)^2 - sage: type(_) - - """ - cdef GapElement_FiniteField r = GapElement_FiniteField.__new__(GapElement_FiniteField) - r._initialize(parent, obj) - return r - - -cdef class GapElement_FiniteField(GapElement): - r""" - Derived class of GapElement for GAP finite field elements. - - EXAMPLES:: - - sage: libgap.eval('Z(5)^2') - Z(5)^2 - sage: type(_) - - """ - - cpdef GapElement_Integer lift(self): - """ - Return an integer lift. - - OUTPUT: - - The smallest positive :class:`GapElement_Integer` that equals - ``self`` in the prime finite field. - - EXAMPLES:: - - sage: n = libgap.eval('Z(5)^2') - sage: n.lift() - 4 - sage: type(_) - - - sage: n = libgap.eval('Z(25)') - sage: n.lift() - Traceback (most recent call last): - TypeError: not in prime subfield - """ - degree = self.DegreeFFE().sage() - if degree == 1: - return self.IntFFE() - else: - raise TypeError('not in prime subfield') - - def sage(self, ring=None, var='a'): - r""" - Return the Sage equivalent of the :class:`GapElement_FiniteField`. - - INPUT: - - - ``ring`` -- a Sage finite field or ``None`` (default). The - field to return ``self`` in. If not specified, a suitable - finite field will be constructed. - - OUTPUT: - - An Sage finite field element. The isomorphism is chosen such - that the Gap ``PrimitiveRoot()`` maps to the Sage - :meth:`~sage.rings.finite_rings.finite_field_prime_modn.multiplicative_generator`. - - EXAMPLES:: - - sage: n = libgap.eval('Z(25)^2') - sage: n.sage() - a + 3 - sage: parent(_) - Finite Field in a of size 5^2 - - sage: n.sage(ring=GF(5)) - Traceback (most recent call last): - ... - ValueError: the given ring is incompatible ... - - TESTS:: - - sage: n = libgap.eval('Z(2^4)^2 + Z(2^4)^1 + Z(2^4)^0') - sage: n - Z(2^2)^2 - sage: n.sage() - a + 1 - sage: parent(_) - Finite Field in a of size 2^2 - sage: n.sage(ring=ZZ) - Traceback (most recent call last): - ... - ValueError: the given ring is incompatible ... - sage: n.sage(ring=CC) - Traceback (most recent call last): - ... - ValueError: the given ring is incompatible ... - sage: n.sage(ring=GF(5)) - Traceback (most recent call last): - ... - ValueError: the given ring is incompatible ... - sage: n.sage(ring=GF(2^3)) - Traceback (most recent call last): - ... - ValueError: the given ring is incompatible ... - sage: n.sage(ring=GF(2^2, 'a')) - a + 1 - sage: n.sage(ring=GF(2^4, 'a')) - a^2 + a + 1 - sage: n.sage(ring=GF(2^8, 'a')) - a^7 + a^6 + a^4 + a^2 + a + 1 - - Check that :trac:`23153` is fixed:: - - sage: n = libgap.eval('Z(2^4)^2 + Z(2^4)^1 + Z(2^4)^0') - sage: n.sage(ring=GF(2^4, 'a')) - a^2 + a + 1 - """ - deg = self.DegreeFFE().sage() - char = self.Characteristic().sage() - if ring is None: - from sage.rings.finite_rings.finite_field_constructor import GF - ring = GF(char**deg, name=var) - elif not (ring.is_field() and ring.is_finite() and \ - ring.characteristic() == char and ring.degree() % deg == 0): - raise ValueError(('the given ring is incompatible (must be a ' - 'finite field of characteristic {} and degree ' - 'divisible by {})').format(char, deg)) - - if self.IsOne(): - return ring.one() - if deg == 1 and char == ring.characteristic(): - return ring(self.lift().sage()) - else: - gap_field = make_GapElement_Ring(self.parent(), - self.parent().gap(ring)) - exp = self.LogFFE(gap_field.PrimitiveRoot()) - return ring.multiplicative_generator() ** exp.sage() - - def __int__(self): - r""" - TESTS:: - - sage: int(libgap.eval("Z(53)")) - 2 - """ - return int(self.Int()) - - def _integer_(self, R): - r""" - TESTS:: - - sage: ZZ(libgap.eval("Z(53)")) - 2 - """ - return R(self.Int()) - - -############################################################################ -### GapElement_Cyclotomic ##################################################### -############################################################################ - -cdef GapElement_Cyclotomic make_GapElement_Cyclotomic(parent, GapObj obj): - r""" - Turn a Gap cyclotomic object into a :class:`GapElement_Cyclotomic` Sage - object. - - EXAMPLES:: - - sage: libgap.eval('E(3)') - E(3) - sage: type(_) - - """ - cdef GapElement_Cyclotomic r = GapElement_Cyclotomic.__new__(GapElement_Cyclotomic) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Cyclotomic(GapElement): - r""" - Derived class of GapElement for GAP universal cyclotomics. - - EXAMPLES:: - - sage: libgap.eval('E(3)') - E(3) - sage: type(_) - - """ - - def sage(self, ring=None): - r""" - Return the Sage equivalent of the :class:`GapElement_Cyclotomic`. - - INPUT: - - - ``ring`` -- a Sage cyclotomic field or ``None`` - (default). If not specified, a suitable minimal cyclotomic - field will be constructed. - - OUTPUT: - - A Sage cyclotomic field element. - - EXAMPLES:: - - sage: n = libgap.eval('E(3)') - sage: n.sage() - zeta3 - sage: parent(_) - Cyclotomic Field of order 3 and degree 2 - - sage: n.sage(ring=CyclotomicField(6)) - zeta6 - 1 - - sage: libgap.E(3).sage(ring=CyclotomicField(3)) - zeta3 - sage: libgap.E(3).sage(ring=CyclotomicField(6)) - zeta6 - 1 - - TESTS: - - Check that :trac:`15204` is fixed:: - - sage: libgap.E(3).sage(ring=UniversalCyclotomicField()) - E(3) - sage: libgap.E(3).sage(ring=CC) - -0.500000000000000 + 0.866025403784439*I - """ - if ring is None: - conductor = self.Conductor() - from sage.rings.number_field.number_field import CyclotomicField - ring = CyclotomicField(conductor.sage()) - else: - try: - conductor = ring._n() - except AttributeError: - from sage.rings.number_field.number_field import CyclotomicField - conductor = self.Conductor() - cf = CyclotomicField(conductor.sage()) - return ring(cf(self.CoeffsCyc(conductor).sage())) - coeff = self.CoeffsCyc(conductor).sage() - return ring(coeff) - - -############################################################################ -### GapElement_Rational #################################################### -############################################################################ - -cdef GapElement_Rational make_GapElement_Rational(parent, GapObj obj): - r""" - Turn a Gap Rational number (of type ``Obj``) into a Cython ``GapElement_Rational``. - - EXAMPLES:: - - sage: libgap(123/456) - 41/152 - sage: type(_) - - """ - cdef GapElement_Rational r = GapElement_Rational.__new__(GapElement_Rational) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Rational(GapElement): - r""" - Derived class of GapElement for GAP rational numbers. - - EXAMPLES:: - - sage: r = libgap(123/456) - sage: type(r) - - """ - def _rational_(self): - r""" - EXAMPLES:: - - sage: r = libgap(-1/3) - sage: QQ(r) # indirect doctest - -1/3 - sage: QQ(libgap(2**300 / 3**300)) == 2**300 / 3**300 - True - """ - return self.sage(ring=QQ) - - def sage(self, ring=None): - r""" - Return the Sage equivalent of the :class:`GapElement`. - - INPUT: - - - ``ring`` -- the Sage rational ring or ``None`` (default). If - not specified, the rational ring is used automatically. - - OUTPUT: - - A Sage rational number. - - EXAMPLES:: - - sage: r = libgap(123/456); r - 41/152 - sage: type(_) - - sage: r.sage() - 41/152 - sage: type(_) - - """ - if ring is None: - ring = ZZ - libgap = self.parent() - return libgap.NumeratorRat(self).sage(ring=ring) / libgap.DenominatorRat(self).sage(ring=ring) - - -############################################################################ -### GapElement_Ring ##################################################### -############################################################################ - -cdef GapElement_Ring make_GapElement_Ring(parent, GapObj obj): - r""" - Turn a Gap integer object into a :class:`GapElement_Ring` Sage - object. - - EXAMPLES:: - - sage: libgap(GF(5)) - GF(5) - sage: type(_) - - """ - cdef GapElement_Ring r = GapElement_Ring.__new__(GapElement_Ring) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Ring(GapElement): - r""" - Derived class of GapElement for GAP rings (parents of ring elements). - - EXAMPLES:: - - sage: i = libgap(ZZ) - sage: type(i) - - """ - - def ring_integer(self): - """ - Construct the Sage integers. - - EXAMPLES:: - - sage: libgap.eval('Integers').ring_integer() - Integer Ring - """ - return ZZ - - def ring_rational(self): - """ - Construct the Sage rationals. - - EXAMPLES:: - - sage: libgap.eval('Rationals').ring_rational() - Rational Field - """ - return ZZ.fraction_field() - - def ring_integer_mod(self): - """ - Construct a Sage integer mod ring. - - EXAMPLES:: - - sage: libgap.eval('ZmodnZ(15)').ring_integer_mod() - Ring of integers modulo 15 - """ - characteristic = self.Characteristic().sage() - return ZZ.quotient_ring(characteristic) - - - def ring_finite_field(self, var='a'): - """ - Construct an integer ring. - - EXAMPLES:: - - sage: libgap.GF(3,2).ring_finite_field(var='A') - Finite Field in A of size 3^2 - """ - size = self.Size().sage() - from sage.rings.finite_rings.finite_field_constructor import GF - return GF(size, name=var) - - - def ring_cyclotomic(self): - """ - Construct an integer ring. - - EXAMPLES:: - - sage: libgap.CyclotomicField(6).ring_cyclotomic() - Cyclotomic Field of order 3 and degree 2 - """ - conductor = self.Conductor() - from sage.rings.number_field.number_field import CyclotomicField - return CyclotomicField(conductor.sage()) - - def ring_polynomial(self): - """ - Construct a polynomial ring. - - EXAMPLES:: - - sage: B = libgap(QQ['x']) - sage: B.ring_polynomial() - Univariate Polynomial Ring in x over Rational Field - - sage: B = libgap(ZZ['x','y']) - sage: B.ring_polynomial() - Multivariate Polynomial Ring in x, y over Integer Ring - """ - base_ring = self.CoefficientsRing().sage() - vars = [x.String().sage() - for x in self.IndeterminatesOfPolynomialRing()] - from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing - return PolynomialRing(base_ring, vars) - - def sage(self, **kwds): - r""" - Return the Sage equivalent of the :class:`GapElement_Ring`. - - INPUT: - - - ``**kwds`` -- keywords that are passed on to the ``ring_`` - method. - - OUTPUT: - - A Sage ring. - - EXAMPLES:: - - sage: libgap.eval('Integers').sage() - Integer Ring - - sage: libgap.eval('Rationals').sage() - Rational Field - - sage: libgap.eval('ZmodnZ(15)').sage() - Ring of integers modulo 15 - - sage: libgap.GF(3,2).sage(var='A') - Finite Field in A of size 3^2 - - sage: libgap.CyclotomicField(6).sage() - Cyclotomic Field of order 3 and degree 2 - - sage: libgap(QQ['x','y']).sage() - Multivariate Polynomial Ring in x, y over Rational Field - """ - if self.IsField(): - if self.IsRationals(): - return self.ring_rational(**kwds) - if self.IsCyclotomicField(): - return self.ring_cyclotomic(**kwds) - if self.IsFinite(): - return self.ring_finite_field(**kwds) - else: - if self.IsIntegers(): - return self.ring_integer(**kwds) - if self.IsFinite(): - return self.ring_integer_mod(**kwds) - if self.IsPolynomialRing(): - return self.ring_polynomial(**kwds) - raise NotImplementedError('cannot convert GAP ring to Sage') - - -############################################################################ -### GapElement_Boolean ##################################################### -############################################################################ - -cdef GapElement_Boolean make_GapElement_Boolean(parent, GapObj obj): - r""" - Turn a Gap Boolean number (of type ``Obj``) into a Cython ``GapElement_Boolean``. - - EXAMPLES:: - - sage: libgap(True) - true - sage: type(_) - - """ - cdef GapElement_Boolean r = GapElement_Boolean.__new__(GapElement_Boolean) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Boolean(GapElement): - r""" - Derived class of GapElement for GAP boolean values. - - EXAMPLES:: - - sage: b = libgap(True) - sage: type(b) - - """ - - def sage(self): - r""" - Return the Sage equivalent of the :class:`GapElement` - - OUTPUT: - - A Python boolean if the values is either true or false. GAP - booleans can have the third value ``Fail``, in which case a - ``ValueError`` is raised. - - EXAMPLES:: - - sage: b = libgap.eval('true'); b - true - sage: type(_) - - sage: b.sage() - True - sage: type(_) - <... 'bool'> - - sage: libgap.eval('fail') - fail - sage: _.sage() - Traceback (most recent call last): - ... - ValueError: the GAP boolean value "fail" cannot be represented in Sage - """ - if repr(self) == 'fail': - raise ValueError( - 'the GAP boolean value "fail" cannot be represented in Sage') - return self.obj.__bool__() - - def __bool__(self): - """ - Check that the boolean is "true". - - This is syntactic sugar for using libgap. See the examples below. - - OUTPUT: - - Boolean. - - EXAMPLES:: - - sage: gap_bool = [libgap.eval('true'), libgap.eval('false'), libgap.eval('fail')] - sage: for x in gap_bool: - ....: if x: # this calls __nonzero__ - ....: print("{} {}".format(x, type(x))) - true - - sage: for x in gap_bool: - ....: if not x: # this calls __nonzero__ - ....: print("{} {}".format( x, type(x))) - false - fail - """ - return self.obj.__bool__() - - -############################################################################ -### GapElement_String #################################################### -############################################################################ - -cdef GapElement_String make_GapElement_String(parent, GapObj obj): - r""" - Turn a Gap String (of type ``Obj``) into a Cython ``GapElement_String``. - - EXAMPLES:: - - sage: libgap('this is a string') - "this is a string" - sage: type(_) - - """ - cdef GapElement_String r = GapElement_String.__new__(GapElement_String) - r._initialize(parent, obj) - return r - - -cdef class GapElement_String(GapElement): - r""" - Derived class of GapElement for GAP strings. - - EXAMPLES:: - - sage: s = libgap('string') - sage: type(s) - - sage: s - "string" - sage: print(s) - string - """ - def __str__(self): - r""" - Convert this :class:`GapElement_String` to a Python string. - - OUTPUT: - - A Python string. - - EXAMPLES:: - - sage: s = libgap.eval(' "string" '); s - "string" - sage: type(_) - - sage: str(s) - 'string' - sage: s.sage() - 'string' - sage: type(_) - - """ - return self.obj.__str__() - - sage = __str__ - -############################################################################ -### GapElement_Function #################################################### -############################################################################ - -cdef GapElement_Function make_GapElement_Function(parent, GapObj obj): - r""" - Turn a Gap C function object (of type ``Obj``) into a Cython ``GapElement_Function``. - - INPUT: - - - ``parent`` -- the parent of the new :class:`GapElement` - - - ``obj`` -- a GAP function object. - - OUTPUT: - - A :class:`GapElement_Function` instance. - - EXAMPLES:: - - sage: libgap.CycleLength - - sage: type(_) - - """ - cdef GapElement_Function r = GapElement_Function.__new__(GapElement_Function) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Function(GapElement): - r""" - Derived class of GapElement for GAP functions. - - EXAMPLES:: - - sage: f = libgap.Cycles - sage: type(f) - - """ - - def __repr__(self): - r""" - Return a string representation - - OUTPUT: - - String. - - EXAMPLES:: - - sage: libgap.Orbits - - """ - return self.obj.__repr__() - - def __get__(self, obj, cls): - """ - Descriptor protocol for functions. - - When a `GapElement_Function` is defined at the class level, this - returns a `GapElement_MethodProxy` wrapping it. The class to which - the "method" belongs must be convertable to GAP elements. - - INPUT: - - - ``obj`` -- The instance of the class on which the descriptor has - been accessed, or `None` if it is accessed directly on the class. - - - ``cls`` -- The class on which the descriptor was accessed, or the - class of the instance on which it was accessed. - - EXAMPLES: - - See the examples in `~sage.libs.gap.libgap.Gap.gap_function`. - - """ - - if obj is None: - return self - - return make_GapElement_MethodProxy(self.parent(), - self.obj.__get__(obj, cls), - self.parent(obj)) - - def __call__(self, *args): - """ - Call syntax for functions. - - INPUT: - - - ``*args`` -- arguments. Will be converted to `GapElement` if - they are not already of this type. - - OUTPUT: - - A :class:`GapElement` encapsulating the functions return - value, or ``None`` if it does not return anything. - - EXAMPLES:: - - sage: a = libgap.NormalSubgroups - sage: b = libgap.SymmetricGroup(4) - sage: libgap.collect() - sage: a - - sage: b - Sym( [ 1 .. 4 ] ) - sage: sorted(a(b)) - [Group(()), - Sym( [ 1 .. 4 ] ), - Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,2)(3,4) ])] - - sage: libgap.eval("a := NormalSubgroups") - - sage: libgap.eval("b := SymmetricGroup(4)") - Sym( [ 1 .. 4 ] ) - sage: libgap.collect() - sage: sorted(libgap.eval('a') (libgap.eval('b'))) - [Group(()), - Sym( [ 1 .. 4 ] ), - Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,2)(3,4) ])] - - sage: a = libgap.eval('a') - sage: b = libgap.eval('b') - sage: libgap.collect() - sage: sorted(a(b)) - [Group(()), - Sym( [ 1 .. 4 ] ), - Alt( [ 1 .. 4 ] ), - Group([ (1,4)(2,3), (1,2)(3,4) ])] - - Not every ``GapElement`` is callable:: - - sage: f = libgap(3) - sage: f() - Traceback (most recent call last): - ... - TypeError: 'sage.libs.gap.element.GapElement_Integer' object is not callable - - We illustrate appending to a list which returns None:: - - sage: a = libgap([]); a - [ ] - sage: a.Add(5); a - [ 5 ] - sage: a.Add(10); a - [ 5, 10 ] - - TESTS:: - - sage: s = libgap.Sum - sage: s(libgap([1,2])) - 3 - sage: s(libgap(1), libgap(2)) - Traceback (most recent call last): - ... - GAPError: Error, no method found! - Error, no 1st choice method found for `SumOp' on 2 arguments - - sage: for i in range(0,100): - ....: rnd = [ randint(-10,10) for i in range(0,randint(0,7)) ] - ....: # compute the sum in GAP - ....: _ = libgap.Sum(rnd) - ....: try: - ....: libgap.Sum(*rnd) - ....: print('This should have triggered a ValueError') - ....: print('because Sum needs a list as argument') - ....: except ValueError: - ....: pass - - sage: libgap_exec = libgap.eval("Exec") - sage: libgap_exec('echo hello from the shell') - hello from the shell - """ - - args = [x if isinstance(x, GapElement) else self.parent(x) - for x in args] - - return make_any_gap_element(self.parent(), self.obj(*args)) - - def _instancedoc_(self): - r""" - Return the help string - - EXAMPLES:: - - sage: f = libgap.CyclicGroup - sage: 'constructs the cyclic group' in f.__doc__ - True - - You would get the full help by typing ``f?`` in the command line. - """ - return self.obj.help() - - -############################################################################ -### GapElement_MethodProxy ################################################# -############################################################################ - -cdef GapElement_MethodProxy make_GapElement_MethodProxy(parent, GapObj function, GapElement base_object): - r""" - Turn a Gap C rec object (of type ``Obj``) into a Cython ``GapElement_Record``. - - This class implement syntactic sugar so that you can write - ``gapelement.f()`` instead of ``libgap.f(gapelement)`` for any GAP - function ``f``. - - INPUT: - - - ``parent`` -- the parent of the new :class:`GapElement` - - - ``obj`` -- a GAP function object. - - - ``base_object`` -- The first argument to be inserted into the function. - - OUTPUT: - - A :class:`GapElement_MethodProxy` instance. - - EXAMPLES:: - - sage: lst = libgap([]) - sage: type( lst.Add ) - - """ - cdef GapElement_MethodProxy r = GapElement_MethodProxy.__new__(GapElement_MethodProxy) - r._initialize(parent, function) - r.first_argument = base_object - return r - - -cdef class GapElement_MethodProxy(GapElement_Function): - r""" - Helper class returned by ``GapElement.__getattr__``. - - Derived class of GapElement for GAP functions. Like its parent, - you can call instances to implement function call syntax. The only - difference is that a fixed first argument is prepended to the - argument list. - - EXAMPLES:: - - sage: lst = libgap([]) - sage: lst.Add - - sage: type(_) - - sage: lst.Add(1) - sage: lst - [ 1 ] - """ - - def __call__(self, *args): - """ - Call syntax for methods. - - This method is analogous to - :meth:`GapElement_Function.__call__`, except that it inserts a - fixed :class:`GapElement` in the first slot of the function. - - INPUT: - - - ``*args`` -- arguments. Will be converted to `GapElement` if - they are not already of this type. - - OUTPUT: - - A :class:`GapElement` encapsulating the functions return - value, or ``None`` if it does not return anything. - - EXAMPLES:: - - sage: lst = libgap.eval('[1,,3]') - sage: lst.Add.__call__(4) - sage: lst.Add(5) - sage: lst - [ 1,, 3, 4, 5 ] - """ - return make_any_gap_element(self.parent(), self.obj.__call__(*args)) - - -############################################################################ -### GapElement_List ######################################################## -############################################################################ - -cdef GapElement_List make_GapElement_List(parent, GapObj obj): - r""" - Turn a Gap C List object (of type ``Obj``) into a Cython ``GapElement_List``. - - EXAMPLES:: - - sage: libgap([0, 2, 3]) - [ 0, 2, 3 ] - sage: type(_) - - """ - cdef GapElement_List r = GapElement_List.__new__(GapElement_List) - r._initialize(parent, obj) - return r - - -cdef class GapElement_List(GapElement): - r""" - Derived class of GapElement for GAP Lists. - - .. NOTE:: - - Lists are indexed by `0..len(l)-1`, as expected from - Python. This differs from the GAP convention where lists start - at `1`. - - EXAMPLES:: - - sage: lst = libgap.SymmetricGroup(3).List(); lst - [ (), (1,3), (1,2,3), (2,3), (1,3,2), (1,2) ] - sage: type(lst) - - sage: len(lst) - 6 - sage: lst[3] - (2,3) - - We can easily convert a Gap ``List`` object into a Python ``list``:: - - sage: list(lst) - [(), (1,3), (1,2,3), (2,3), (1,3,2), (1,2)] - sage: type(_) - <... 'list'> - - Range checking is performed:: - - sage: lst[10] - Traceback (most recent call last): - ... - IndexError: index out of range - """ - - def __bool__(self): - r""" - Return True if the list is non-empty, as with Python ``list``s. - - EXAMPLES:: - - sage: lst = libgap.eval('[1,,,4]') - sage: bool(lst) - True - sage: lst = libgap.eval('[]') - sage: bool(lst) - False - """ - return bool(len(self)) - - def __len__(self): - r""" - Return the length of the list. - - OUTPUT: - - Integer. - - EXAMPLES:: - - sage: lst = libgap.eval('[1,,,4]') # a sparse list - sage: len(lst) - 4 - """ - return self.obj.__len__() - - def __getitem__(self, i): - r""" - Return the ``i``-th element of the list. - - As usual in Python, indexing starts at `0` and not at `1` (as - in GAP). This can also be used with multi-indices. - - INPUT: - - - ``i`` -- integer. - - OUTPUT: - - The ``i``-th element as a :class:`GapElement`. - - EXAMPLES:: - - sage: lst = libgap.eval('["first",,,"last"]') # a sparse list - sage: lst[0] - "first" - - sage: l = libgap.eval('[ [0, 1], [2, 3] ]') - sage: l[0,0] - 0 - sage: l[0,1] - 1 - sage: l[1,0] - 2 - sage: l[0,2] - Traceback (most recent call last): - ... - IndexError: index out of range - sage: l[2,0] - Traceback (most recent call last): - ... - IndexError: index out of range - sage: l[0,0,0] - Traceback (most recent call last): - ... - ValueError: too many indices - """ - return make_any_gap_element(self.parent(), self.obj.__getitem__(i)) - - def __setitem__(self, i, elt): - r""" - Set the ``i``-th item of this list - - EXAMPLES:: - - sage: l = libgap.eval('[0, 1]') - sage: l - [ 0, 1 ] - sage: l[0] = 3 - sage: l - [ 3, 1 ] - - Contrarily to Python lists, setting an element beyond the limit extends the list:: - - sage: l[12] = -2 - sage: l - [ 3, 1,,,,,,,,,,, -2 ] - - This function also handles multi-indices:: - - sage: l = libgap.eval('[[[0,1],[2,3]],[[4,5], [6,7]]]') - sage: l[0,1,0] = -18 - sage: l - [ [ [ 0, 1 ], [ -18, 3 ] ], [ [ 4, 5 ], [ 6, 7 ] ] ] - sage: l[0,0,0,0] - Traceback (most recent call last): - ... - ValueError: too many indices - - Assignment to immutable objects gives error:: - - sage: l = libgap([0,1]) - sage: u = l.deepcopy(0) - sage: u[0] = 5 - Traceback (most recent call last): - ... - TypeError: immutable GAP object does not support item assignment - - TESTS:: - - sage: m = libgap.eval('[[0,0],[0,0]]') - sage: m[0,0] = 1 - sage: m[0,1] = 2 - sage: m[1,0] = 3 - sage: m[1,1] = 4 - sage: m - [ [ 1, 2 ], [ 3, 4 ] ] - """ - self.obj.__setitem__(i, elt) - - def sage(self, **kwds): - r""" - Return the Sage equivalent of the :class:`GapElement` - - OUTPUT: - - A Python list. - - EXAMPLES:: - - sage: libgap([ 1, 3, 4 ]).sage() - [1, 3, 4] - sage: all( x in ZZ for x in _ ) - True - """ - return [x.sage(**kwds) for x in self] - - def matrix(self, ring=None): - """ - Return the list as a matrix. - - GAP does not have a special matrix data type, they are just - lists of lists. This function converts a GAP list of lists to - a Sage matrix. - - OUTPUT: - - A Sage matrix. - - EXAMPLES:: - - sage: F = libgap.GF(4) - sage: a = F.PrimitiveElement() - sage: m = libgap([[a,a^0],[0*a,a^2]]); m - [ [ Z(2^2), Z(2)^0 ], - [ 0*Z(2), Z(2^2)^2 ] ] - sage: m.IsMatrix() - true - sage: matrix(m) - [ a 1] - [ 0 a + 1] - sage: matrix(GF(4,'B'), m) - [ B 1] - [ 0 B + 1] - - sage: M = libgap.eval('SL(2,GF(5))').GeneratorsOfGroup()[1] - sage: type(M) - - sage: M[0][0] - Z(5)^2 - sage: M.IsMatrix() - true - sage: M.matrix() - [4 1] - [4 0] - """ - if not self.IsMatrix(): - raise ValueError('not a GAP matrix') - entries = self.Flat() - n = self.Length().sage() - m = len(entries) // n - if len(entries) % n != 0: - raise ValueError('not a rectangular list of lists') - from sage.matrix.matrix_space import MatrixSpace - if ring is None: - ring = entries.DefaultRing().sage() - MS = MatrixSpace(ring, n, m) - return MS([x.sage(ring=ring) for x in entries]) - - _matrix_ = matrix - - def vector(self, ring=None): - """ - Return the list as a vector. - - GAP does not have a special vector data type, they are just - lists. This function converts a GAP list to a Sage vector. - - OUTPUT: - - A Sage vector. - - EXAMPLES:: - - sage: F = libgap.GF(4) - sage: a = F.PrimitiveElement() - sage: m = libgap([0*a, a, a^3, a^2]); m - [ 0*Z(2), Z(2^2), Z(2)^0, Z(2^2)^2 ] - sage: type(m) - - sage: m[3] - Z(2^2)^2 - sage: vector(m) - (0, a, 1, a + 1) - sage: vector(GF(4,'B'), m) - (0, B, 1, B + 1) - """ - if not self.IsVector(): - raise ValueError('not a GAP vector') - from sage.modules.all import vector - entries = self.Flat() - n = self.Length().sage() - if ring is None: - ring = entries.DefaultRing().sage() - return vector(ring, n, self.sage(ring=ring)) - - _vector_ = vector - - - -############################################################################ -### GapElement_Permutation ################################################# -############################################################################ - - -cdef GapElement_Permutation make_GapElement_Permutation(parent, GapObj obj): - r""" - Turn a Gap C permutation object (of type ``Obj``) into a Cython ``GapElement_Permutation``. - - EXAMPLES:: - - sage: libgap.eval('(1,3,2)(4,5,8)') - (1,3,2)(4,5,8) - sage: type(_) - - """ - cdef GapElement_Permutation r = GapElement_Permutation.__new__(GapElement_Permutation) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Permutation(GapElement): - r""" - Derived class of GapElement for GAP permutations. - - .. NOTE:: - - Permutations in GAP act on the numbers starting with 1. - - EXAMPLES:: - - sage: perm = libgap.eval('(1,5,2)(4,3,8)') - sage: type(perm) - - """ - - def sage(self, parent=None): - r""" - Return the Sage equivalent of the :class:`GapElement` - - If the permutation group is given as parent, this method is - *much* faster. - - EXAMPLES:: - - sage: perm_gap = libgap.eval('(1,5,2)(4,3,8)'); perm_gap - (1,5,2)(3,8,4) - sage: perm_gap.sage() - [5, 1, 8, 3, 2, 6, 7, 4] - sage: type(_) - - sage: perm_gap.sage(PermutationGroup([(1,2),(1,2,3,4,5,6,7,8)])) - (1,5,2)(3,8,4) - sage: type(_) - - """ - cdef PermutationGroupElement one_c - - libgap = self.parent() - lst = libgap.ListPerm(self) - - if parent is None: - return Permutation(lst.sage(), check_input=False) - else: - return parent.one()._generate_new_GAP(lst) - -############################################################################ -### GapElement_Record ###################################################### -############################################################################ - -cdef GapElement_Record make_GapElement_Record(parent, GapObj obj): - r""" - Turn a Gap C rec object (of type ``Obj``) into a Cython ``GapElement_Record``. - - EXAMPLES:: - - sage: libgap.eval('rec(a:=0, b:=2, c:=3)') - rec( a := 0, b := 2, c := 3 ) - sage: type(_) - - """ - cdef GapElement_Record r = GapElement_Record.__new__(GapElement_Record) - r._initialize(parent, obj) - return r - - -cdef class GapElement_Record(GapElement): - r""" - Derived class of GapElement for GAP records. - - EXAMPLES:: - - sage: rec = libgap.eval('rec(a:=123, b:=456)') - sage: type(rec) - - sage: len(rec) - 2 - sage: rec['a'] - 123 - - We can easily convert a Gap ``rec`` object into a Python ``dict``:: - - sage: dict(rec) - {"a": 123, "b": 456} - sage: type(_) - <... 'dict'> - - Range checking is performed:: - - sage: rec['no_such_element'] - Traceback (most recent call last): - ... - KeyError: 'no_such_element' - """ - - def __len__(self): - r""" - Return the length of the record. - - OUTPUT: - - Integer. The number of entries in the record. - - EXAMPLES:: - - sage: rec = libgap.eval('rec(a:=123, b:=456, S3:=SymmetricGroup(3))') - sage: len(rec) - 3 - """ - return self.obj.__len__() - - def __iter__(self): - r""" - Iterate over the elements of the record. - - OUTPUT: - - A :class:`GapElement_RecordIterator`. - - EXAMPLES:: - - sage: rec = libgap.eval('rec(a:=123, b:=456)') - sage: iter = rec.__iter__() - sage: type(iter) - - sage: sorted(rec) - [("a", 123), ("b", 456)] - """ - for k, v in self.obj.__iter__(): - # Convert GapObjs to GapElements - v = make_any_gap_element(self.parent(), v) - yield k, v - - def __getitem__(self, name): - r""" - Return the ``name``-th element of the GAP record. - - INPUT: - - - ``name`` -- string. - - OUTPUT: - - The record element labelled by ``name`` as a :class:`GapElement`. - - EXAMPLES:: - - sage: rec = libgap.eval('rec(first:=123, second:=456)') - sage: rec['first'] - 123 - """ - return make_any_gap_element(self.parent(), self.obj.__getitem__(name)) - - def sage(self): - r""" - Return the Sage equivalent of the :class:`GapElement` - - EXAMPLES:: - - sage: libgap.eval('rec(a:=1, b:=2)').sage() - {'a': 1, 'b': 2} - sage: all( isinstance(key,str) and val in ZZ for key,val in _.items() ) - True - - sage: rec = libgap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))') - sage: rec.sage() - {'Sym3': NotImplementedError('cannot construct equivalent Sage object'...), - 'a': 123, - 'b': 456} - """ - result = {} - for key, val in self: - try: - val = val.sage() - except Exception as ex: - val = ex - result[str(key)] = val - return result - - -cdef _gapobj_to_element = {} -"""Maps GapObj subclasses from gappy to Sage GapElement subclasses.""" - - -for name, cls in vars(gapobj).items(): - if name[0] == '_': - continue + for name, cls in vars(gapobj).items(): + if name[0] == '_': + continue - if not (isinstance(cls, type) and issubclass(cls, gapobj.GapObj)): - continue + if not (isinstance(cls, type) and issubclass(cls, gapobj.GapObj)): + continue - elem_name = 'GapElement' - sub = name[3:] # strip 'Gap' prefix - if sub and sub != 'Obj': - elem_name += '_' + sub + elem_name = 'GapElement' + sub = name[3:] # strip 'Gap' prefix + if sub and sub != 'Obj': + elem_name += '_' + sub - try: - _gapobj_to_element[cls] = locals()[elem_name] - except KeyError: - warnings.warn( - f'gappy class {name} does not have a corresponding GapElement ' - f'subclass; it may not be possible to convert these objects to ' - f'their equivalent Sage objects') + locals()[elem_name] = cls -# Add support for _instancedoc_ -from sage.docs.instancedoc import instancedoc -instancedoc(GapElement_Function) -instancedoc(GapElement_MethodProxy) +import_gapobjs_as_gapelements() diff --git a/src/sage/libs/gap/libgap.pyx b/src/sage/libs/gap/libgap.pyx index 2bfed2d68ac..0646f53d06c 100644 --- a/src/sage/libs/gap/libgap.pyx +++ b/src/sage/libs/gap/libgap.pyx @@ -1,10 +1,8 @@ """ Library Interface to GAP -This module implements a fast C library interface to GAP. -To use it, you simply call ``libgap`` (the parent of all -:class:`~sage.libs.gap.element.GapElement` instances) and use it to -convert Sage objects into GAP objects. +This module implements a fast C library interface to GAP. To use it, you +simply call ``libgap`` and use it to convert Sage objects into GAP objects. EXAMPLES:: @@ -12,7 +10,7 @@ EXAMPLES:: sage: a 10 sage: type(a) - + sage: a*a 100 sage: timeit('a*a') # random output @@ -35,11 +33,10 @@ objects to GAP objects, for example strings to strings:: sage: libgap('List([1..10], i->i^2)') "List([1..10], i->i^2)" sage: type(_) - + -You can usually use the :meth:`~sage.libs.gap.element.GapElement.sage` -method to convert the resulting GAP element back to its Sage -equivalent:: +You can usually use the ``.sage()`` method to convert the resulting GAP element +back to its Sage equivalent:: sage: a.sage() 10 @@ -82,9 +79,8 @@ corresponding Sage datatype: #. GAP permutations to Sage permutations. -#. The GAP containers ``List`` and ``rec`` are converted to Sage - containers ``list`` and ``dict``. Furthermore, the - :meth:`~sage.libs.gap.element.GapElement.sage` method is applied +#. The GAP containers ``List`` and ``rec`` are converted to Sage containers + ``list`` and ``dict``. Furthermore, the ``.sage()`` method is applied recursively to the entries. Special support is available for the GAP container classes. GAP lists @@ -93,7 +89,7 @@ can be used as follows:: sage: lst = libgap([1,5,7]); lst [ 1, 5, 7 ] sage: type(lst) - + sage: len(lst) 3 sage: lst[0] @@ -101,14 +97,13 @@ can be used as follows:: sage: [ x^2 for x in lst ] [1, 25, 49] sage: type(_[0]) - + -Note that you can access the elements of GAP ``List`` objects as you -would expect from Python (with indexing starting at 0), but the -elements are still of type -:class:`~sage.libs.gap.element.GapElement`. The other GAP container -type are records, which are similar to Python dictionaries. You can -construct them directly from Python dictionaries:: +Note that you can access the elements of GAP ``List`` objects as you would +expect from Python (with indexing starting at 0), but the elements are still of +type :class:`~gappy.gapobj.GapObj`. The other GAP container type are records, +which are similar to Python dictionaries. You can construct them directly from +Python dictionaries:: sage: libgap({'a':123, 'b':456}) rec( a := 123, b := 456 ) @@ -122,10 +117,10 @@ Or get them as results of computations:: {"Sym3": Sym( [ 1 .. 3 ] ), "a": 123, "b": 456} The output is a Sage dictionary whose keys are GAP strings and whose Values are -instances of :meth:`~sage.libs.gap.element.GapElement`. So, for example, +instances of :meth:`~sage.gap.GapObj`. So, for example, ``rec['a']`` is not a Sage integer. To recursively convert the keys and entries into Sage objects, you should use the -:meth:`~sage.libs.gap.element.GapElement.sage` method:: +``GapObj.sage()`` method:: sage: rec.sage() {'Sym3': NotImplementedError('cannot construct equivalent Sage object'...), @@ -138,10 +133,9 @@ so you end up with a ``NotImplementedError`` exception object. The exception is returned and not raised so that you can work with the partial result. -While we don't directly support matrices yet, you can convert them to -Gap List of Lists. These lists are then easily converted into Sage -using the recursive expansion of the -:meth:`~sage.libs.gap.element.GapElement.sage` method:: +While we don't directly support matrices yet, you can convert them to Gap List +of Lists. These lists are then easily converted into Sage using the recursive +expansion of the ``GapObj.sage`` method:: sage: M = libgap.eval('BlockMatrix([[1,1,[[1, 2],[ 3, 4]]], [1,2,[[9,10],[11,12]]], [2,2,[[5, 6],[ 7, 8]]]],2,2)') sage: M @@ -214,7 +208,7 @@ AUTHORS: import os -from gappy.core cimport Gap as Gappy +from gappy.core cimport Gap from gappy.exceptions import GAPError from gappy.gapobj cimport make_GapList from gappy.gapobj import GapObj @@ -230,7 +224,6 @@ from sage.rings.all import ZZ from sage.structure.parent cimport Parent from sage.structure.sage_object import SageObject -from .element cimport GapElement, make_any_gap_element, make_GapElement_Function from .saved_workspace import workspace as get_workspace from .util import gap_root @@ -241,7 +234,7 @@ from .util import gap_root # The libGap interpreter object Gap is the parent of the GapElements # Provides a wrapper to gappy.core.Gap, which we can't subclass directly # since it has an incompatible binary layout with Parent. -cdef class SageGappy(Gappy): +cdef class SageGap(Gap): """ Subclasses `gappy.core.Gap` to provide some Sage-specific default behaviors. @@ -249,16 +242,21 @@ cdef class SageGappy(Gappy): def __init__(self): workspace, _ = get_workspace() - Gappy.__init__(self, gap_root=gap_root(), workspace=workspace, - autoload=True) + Gap.__init__(self, gap_root=gap_root(), workspace=workspace, + autoload=True) cpdef initialize(self): - initializing = Gappy.initialize(self) + initializing = Gap.initialize(self) if initializing: - # These steps are only performed if Gappy has just been initialized - # for the first time; if we don't check this then we'll cause an - # infinite recursion + # These steps are only performed if SageGap has just been + # initialized for the first time; if we don't check this then we'll + # cause an infinite recursion + + # Import the converters module to register SageObject -> GapObj and + # GapObj.sage() converters on the default libgap. + from . import converters + workspace, workspace_is_up_to_date = get_workspace() if self.workspace == os.path.normpath(workspace): # Save a new workspace if necessary @@ -269,98 +267,7 @@ cdef class SageGappy(Gappy): return initializing - -cdef class Gap(Parent): - r""" - The libgap interpreter object. - - .. NOTE:: - - This object must be instantiated exactly once by the - libgap. Always use the provided ``libgap`` instance, and never - instantiate :class:`Gap` manually. - - EXAMPLES:: - - sage: libgap.eval('SymmetricGroup(4)') - Sym( [ 1 .. 4 ] ) - - TESTS:: - - sage: TestSuite(libgap).run(skip=['_test_category', '_test_elements', '_test_pickling']) - """ - - cdef readonly SageGappy gap - - Element = GapElement - - cpdef _coerce_map_from_(self, S): - """ - Whether a coercion from `S` exists. - - INPUT / OUTPUT: - - See :mod:`sage.structure.parent`. - - EXAMPLES:: - - sage: libgap.has_coerce_map_from(ZZ) - True - sage: libgap.has_coerce_map_from(CyclotomicField(5)['x','y']) - True - """ - # TODO: This seems wrong to me... - return True - - def _element_constructor_(self, x): - r""" - Construct elements of this parent class. - - INPUT: - - - ``x`` -- anything that defines a GAP object. - - OUTPUT: - - A :class:`GapElement`. - - EXAMPLES:: - - sage: libgap(0) # indirect doctest - 0 - sage: libgap(ZZ(0)) - 0 - sage: libgap(int(0)) - 0 - sage: libgap(vector((0,1,2))) - [ 0, 1, 2 ] - sage: libgap(vector((1/3,2/3,4/5))) - [ 1/3, 2/3, 4/5 ] - sage: libgap(vector((1/3, 0.8, 3))) - [ 0.333333, 0.8, 3. ] - sage: v = _ - sage: libgap(v) is v - True - """ - # TODO: It might be good to implement a "fast lane" for some built-in - # Sage types like Integer (e.g. currently Sage Integers are first - # converted to Python ints, and then to GAP Integers, whereas it would - # be much faster to convert Sage Integers directly to GAP Integers since - # they are both basically mpz_t limbs under the hood). - - # If already a GapElement just return it directly - if isinstance(x, GapElement): - return x - elif isinstance(x, SageObject): - # Fast lane for all SageObjects - x = x._libgap_() - if isinstance(x, GapElement): - return x - # Otherwise, pass through self.gap() to convert - - return make_any_gap_element(self, self.gap(x)) - - def eval(self, gap_command): + cpdef eval(self, gap_command): """ Evaluate a gap command and wrap the result. @@ -371,7 +278,7 @@ cdef class Gap(Parent): OUTPUT: - A :class:`GapElement`. + A :class:`~gappy.gapobj.GapObj`. EXAMPLES:: @@ -381,10 +288,10 @@ cdef class Gap(Parent): "string" """ - if not isinstance(gap_command, basestring): + if hasattr(gap_command, '_libgap_init_'): gap_command = str(gap_command._libgap_init_()) - return make_any_gap_element(self, self.gap.eval(gap_command)) + return Gap.eval(self, gap_command) def load_package(self, pkg): """ @@ -399,7 +306,7 @@ cdef class Gap(Parent): install the gap_packages SPKG. """ try: - return self.gap.load_package(pkg) + return super().load_package(pkg) except RuntimeError as exc: # Catch the exception from gappy and amend it with a Sage-specific # hint. @@ -426,10 +333,9 @@ cdef class Gap(Parent): OUTPUT: - A function wrapper - :class:`~sage.libs.gap.element.GapElement_Function` for the - GAP function. Calling it from Sage is equivalent to calling - the wrapped function from GAP. + A function wrapper :class:`~gappy.gapobj.GapFunction` for the GAP + function. Calling it from Sage is equivalent to calling the wrapped + function from GAP. EXAMPLES:: @@ -454,110 +360,6 @@ cdef class Gap(Parent): deprecation(31297, deprecation_msg) return self.eval(function) - def gap_function(self, func): - """ - Create GAP functions from decorated Sage functions. - - EXAMPLES: - - The code for the GAP function is actually written in the Python - function's docstring like so:: - - sage: @libgap.gap_function - ....: def one(): - ....: ''' - ....: Returns the multiplicative identity of the ring of integers. - ....: - ....: function() - ....: return 1; - ....: end; - ....: ''' - sage: one - - sage: one() - 1 - - Any text in the docstring before the first line beginning the text - ``function()`` is used as the function's docstring. Any following - text is considered part of the function definition: - - sage: one.__doc__ - 'Returns the multiplicative identity of the ring of integers.' - - Note that using this decorator does *not* cause the GAP interpreter - to be initialized, so it can be used in module or class-level code. - The GAP interpreter will only be initialized (if needed) the first time - the function is called. - - Any Python code in the function's body will be disregarded, so this is - in effect syntactic sugar for:: - - sage: one = libgap.eval('function() return 1; end;') - - with the difference being that it can be used to pre-define GAP - functions without invoking the GAP interpreter directly. - - This decorator may also be used on methods in classes. In this case - the ``self``--the instance of the class on which it is defined, is - always passed as the first argument to the GAP function, *if* it has - a conversion to a GAP type:: - - sage: class MyInt(int): - ....: @libgap.gap_function - ....: def n_partitions(self): - ....: ''' - ....: Compute the number of integer partitions. - ....: - ....: function(n) - ....: local np; - ....: if n < 0 then - ....: Error("must be a non-negative integer"); - ....: fi; - ....: np:= function(n, m) - ....: local i, res; - ....: if n = 0 then - ....: return 1; - ....: fi; - ....: res:= 0; - ....: for i in [1..Minimum(n,m)] do - ....: res:= res + np(n-i, i); - ....: od; - ....: return res; - ....: end; - ....: return np(n,n); - ....: end; - ....: ''' - ....: - sage: ten = MyInt(10) - sage: ten.n_partitions() - 42 - """ - - return make_GapElement_Function(self, self.gap.gap_function(func)) - - def set_global(self, variable, value): - """ - Set a GAP global variable - - INPUT: - - - ``variable`` -- string. The variable name. - - - ``value`` -- anything that defines a GAP object. - - EXAMPLES:: - - sage: libgap.set_global('FooBar', 1) - sage: libgap.get_global('FooBar') - 1 - sage: libgap.unset_global('FooBar') - sage: libgap.get_global('FooBar') - Traceback (most recent call last): - ... - GAPError: no value bound to FooBar - """ - return self.gap.set_global(variable, value) - def unset_global(self, variable): """ Remove a GAP global variable @@ -577,18 +379,17 @@ cdef class Gap(Parent): ... GAPError: no value bound to FooBar """ - if self.gap.IsReadOnlyGlobal(variable): - # TODO: Set correct ticket number + if self.IsReadOnlyGlobal(variable): deprecation(31297, f'{variable} is a read-only global; unsetting of read-only ' f'globals is deprecated and will be removed in a future ' f'version; if you need to unset a read-only global manually ' f'call libgap.MakeReadWriteGlobal({variable!r}) first') - self.gap.MakeReadWriteGlobal(variable) + self.MakeReadWriteGlobal(variable) - return self.gap.unset_global(variable) + return super().unset_global(variable) - def get_global(self, variable): + cpdef get_global(self, variable): """ Get a GAP global variable @@ -598,9 +399,8 @@ cdef class Gap(Parent): OUTPUT: - A :class:`~sage.libs.gap.element.GapElement` wrapping the GAP - output. A ``ValueError`` is raised if there is no such - variable in GAP. + A :class:`~gappy.gapobj.GapObj` wrapping the GAP output. A + ``ValueError`` is raised if there is no such variable in GAP. EXAMPLES:: @@ -616,36 +416,11 @@ cdef class Gap(Parent): # TODO: Should this return None like gappy does or still raise a GAP # error? Perhaps we could raise a DeprecationWarning on the GAPError # case? - val = make_any_gap_element(self, self.gap.get_global(variable)) + val = Gap.get_global(self, variable) if val is None: raise GAPError(f'no value bound to {variable}') return val - def global_context(self, variable, value): - """ - Temporarily change a global variable - - INPUT: - - - ``variable`` -- string. The variable name. - - - ``value`` -- anything that defines a GAP object. - - OUTPUT: - - A context manager that sets/reverts the given global variable. - - EXAMPLES:: - - sage: libgap.set_global('FooBar', 1) - sage: with libgap.global_context('FooBar', 2): - ....: print(libgap.get_global('FooBar')) - 2 - sage: libgap.get_global('FooBar') - 1 - """ - return self.gap.global_context(variable, value) - def set_seed(self, seed=None): """ Reseed the standard GAP pseudo-random sources with the given seed. @@ -663,22 +438,7 @@ cdef class Gap(Parent): if seed is None: seed = current_randstate().ZZ_seed() - return self.gap.set_seed(seed) - - def _an_element_(self): - r""" - Return a :class:`GapElement`. - - OUTPUT: - - A :class:`GapElement`. - - EXAMPLES:: - - sage: libgap.an_element() # indirect doctest - 0 - """ - return self(0) + return super().set_seed(seed) def zero(self): """ @@ -686,7 +446,7 @@ cdef class Gap(Parent): OUTPUT: - A :class:`GapElement`. + A :class:`~gappy.gapobj.GapInteger`. EXAMPLES:: @@ -699,206 +459,16 @@ cdef class Gap(Parent): r""" Return (integer) one in GAP. - EXAMPLES:: - - sage: libgap.one() - 1 - sage: parent(_) - C library interface to GAP - """ - return self(1) - - def __cinit__(self): - self.gap = SageGappy() - - def __init__(self): - r""" - The Python constructor. - - EXAMPLES:: - - sage: type(libgap) - - sage: type(libgap._get_object()) - - """ - Parent.__init__(self, base=ZZ) - - def __repr__(self): - r""" - Return a string representation of ``self``. - - OUTPUT: - - String. - - EXAMPLES:: - - sage: libgap - C library interface to GAP - """ - return 'C library interface to GAP' - - @cached_method - def __dir__(self): - """ - Customize tab completion - - EXAMPLES:: - - sage: 'OctaveAlgebra' in dir(libgap) - True - """ - return dir(self.__class__) + sorted(common_gap_globals) - - def __getattr__(self, name): - r""" - The attributes of the Gap object are the GAP functions, and in some - cases other global variables from GAP. - - INPUT: - - - ``name`` -- string. The name of the GAP function you want to - call. - OUTPUT: - A :class:`GapElement`. A ``AttributeError`` is raised - if there is no such function or global variable. + A :class:`~gappy.gapobj.GapInteger`. EXAMPLES:: - sage: libgap.List - - sage: libgap.GlobalRandomSource - - """ - - # First try to get attributes from the category which is necessary - # for coercion to work; for some reason when this is a pure Python - # class we don't need this, but for cdef classes it doesn't go - # through CategoryObject's __getattr__ - try: - return self.getattr_from_category(name) - except AttributeError: - pass - - val = getattr(self.gap, name) - if isinstance(val, GapObj): - # Wrap GapObjs as GapElements - val = make_any_gap_element(self, val) - - return val - - def show(self): - """ - Return statistics about the GAP owned object list - - This includes the total memory allocated by GAP as returned by - ``libgap.eval('TotalMemoryAllocated()'), as well as garbage collection - / object count statistics as returned by - ``libgap.eval('GasmanStatistics')``, and finally the total number of - GAP objects held by Sage as :class:`~sage.libs.gap.element.GapElement` - instances. - - The value ``livekb + deadkb`` will roughly equal the total memory - allocated for GAP objects (see - ``libgap.eval('TotalMemoryAllocated()')``). - - .. note:: - - Slight complication is that we want to do it without accessing - libgap objects, so we don't create new GapElements as a side - effect. - - EXAMPLES:: - - sage: a = libgap(123) - sage: b = libgap(456) - sage: c = libgap(789) - sage: del b - sage: libgap.collect() - sage: libgap.show() # random output - {'gasman_stats': {'full': {'cumulative': 110, - 'deadbags': 321400, - 'deadkb': 12967, - 'freekb': 15492, - 'livebags': 396645, - 'livekb': 37730, - 'time': 110, - 'totalkb': 65536}, - 'nfull': 1, - 'npartial': 1}, - 'nelements': 23123, - 'total_alloc': 3234234} - """ - return self.gap.show() - - def count_GAP_objects(self): - """ - Return the number of GAP objects that are being tracked by - GAP. - - OUTPUT: - - An integer - - EXAMPLES:: - - sage: libgap.count_GAP_objects() # random output - 5 - """ - return self.gap.count_GAP_objects() - - def collect(self): - """ - Manually run the garbage collector - - EXAMPLES:: - - sage: a = libgap(123) - sage: del a - sage: libgap.collect() + sage: libgap.one() + 1 """ - return self.gap.collect() - - -libgap = Gap() - - -@libgap.gap.convert_from(SageObject) -def _sageobject_to_gapobj(gap, obj): - r""" - gappy converter for converting generic `.SageObject`\s to their - corresponding `.GapObj` if any. - - This implements the libgap conversion functions already documented for - `.SageObject`\s: `.SageObject._libgap_` and `.SageObject._libgap_init_`. - """ + return self(1) - # NOTE: In the default implementation of SageObject._libgap_ it defers - # to _libgap_init_, so we just need to try calling _libgap_ - ret = obj._libgap_() - if isinstance(ret, GapElement): - return (ret).obj - elif isinstance(ret, gap.supported_builtins): - return gap(ret) - elif isinstance(ret, GapObj): - return ret - else: - raise RuntimeError( - f'{type(obj).__name__}._libgap_ returned something that cannot ' - f'be converted to a GAP object: {ret!r}') - - -@libgap.gap.convert_from(GapElement) -def _gapelement_to_gapobj(gap, elem): - r""" - gappy converter function for converting `.GapElement`\s to their - corresponding `.GapObj`. - - In this case it simply returns the `~gappy.gapobj.GapObj` wrapped by the - `.GapElement`. This allows seamlessly passing `.GapElement`\s to gappy. - """ - return (elem).obj +libgap = SageGap() diff --git a/src/sage/matrix/args.pyx b/src/sage/matrix/args.pyx index 112b2663e24..a7f330fad48 100644 --- a/src/sage/matrix/args.pyx +++ b/src/sage/matrix/args.pyx @@ -20,6 +20,8 @@ from cysignals.signals cimport sig_check from cypari2.gen cimport Gen from cypari2.types cimport typ, t_MAT, t_VEC, t_COL, t_VECSMALL, t_LIST, t_STR, t_CLOSURE +from gappy.gapobj cimport GapList + MatrixSpace = None from sage.rings.integer_ring import ZZ @@ -1269,6 +1271,9 @@ cdef class MatrixArgs: else: self.entries = self.entries.sage() return MA_ENTRIES_SCALAR + if isinstance(self.entries, GapList): + self.entries = self.entries.matrix(ring=self.base) + return MA_ENTRIES_MATRIX if isinstance(self.entries, MatrixArgs): # Prevent recursion return MA_ENTRIES_UNKNOWN diff --git a/src/sage/matrix/matrix_gap.pxd b/src/sage/matrix/matrix_gap.pxd index 0667c158df8..d4f1e5f25a1 100644 --- a/src/sage/matrix/matrix_gap.pxd +++ b/src/sage/matrix/matrix_gap.pxd @@ -1,9 +1,9 @@ from .matrix_dense cimport Matrix_dense -from sage.libs.gap.element cimport GapElement +from gappy.gapobj cimport GapObj cdef class Matrix_gap(Matrix_dense): - cdef GapElement _libgap + cdef GapObj _libgap - cpdef GapElement gap(self) + cpdef GapObj gap(self) cdef Matrix_gap _new(self, Py_ssize_t nrows, Py_ssize_t ncols) diff --git a/src/sage/matrix/matrix_gap.pyx b/src/sage/matrix/matrix_gap.pyx index 0fff7a1f006..d3344324ae5 100644 --- a/src/sage/matrix/matrix_gap.pyx +++ b/src/sage/matrix/matrix_gap.pyx @@ -164,7 +164,7 @@ cdef class Matrix_gap(Matrix_dense): """ return self._parent, (self.list(),) - cpdef GapElement gap(self): + cpdef GapObj gap(self): r""" Return the underlying gap object. @@ -175,7 +175,7 @@ cdef class Matrix_gap(Matrix_dense): sage: m [ [ 1, 2 ], [ 2, 1 ] ] sage: type(m) - + sage: m.MatrixAutomorphisms() Group([ (1,2) ]) @@ -327,7 +327,7 @@ cdef class Matrix_gap(Matrix_dense): def transpose(self): r""" Return the transpose of this matrix. - + EXAMPLES:: sage: M = MatrixSpace(QQ, 2, implementation='gap') diff --git a/src/sage/rings/finite_rings/integer_mod_ring.py b/src/sage/rings/finite_rings/integer_mod_ring.py index 0dcef0d21ae..2597599e1ec 100644 --- a/src/sage/rings/finite_rings/integer_mod_ring.py +++ b/src/sage/rings/finite_rings/integer_mod_ring.py @@ -69,14 +69,16 @@ import sage.rings.integer_ring as integer_ring import sage.rings.quotient_ring as quotient_ring +from sage.interfaces.gap import GapElement from sage.libs.pari.all import pari, PariError -import sage.interfaces.all from sage.misc.cachefunc import cached_method from sage.structure.factory import UniqueFactory from sage.structure.richcmp import richcmp, richcmp_method +from gappy.gapobj import GapObj + class IntegerModFactory(UniqueFactory): r""" @@ -1173,7 +1175,9 @@ def _element_constructor_(self, x): except (NotImplementedError, PariError): raise TypeError("error coercing to finite field") except TypeError: - if sage.interfaces.gap.is_GapElement(x): + if isinstance(x, GapObj): + return integer_mod.IntegerMod(self, x.sage()) + elif isinstance(x, GapElement): from sage.interfaces.gap import intmod_gap_to_sage y = intmod_gap_to_sage(x) return integer_mod.IntegerMod(self, y) diff --git a/src/sage/rings/integer.pyx b/src/sage/rings/integer.pyx index 77a3a18913f..afd48a5c72b 100644 --- a/src/sage/rings/integer.pyx +++ b/src/sage/rings/integer.pyx @@ -174,6 +174,7 @@ from sage.structure.element cimport (Element, EuclideanDomainElement, parent) from sage.structure.parent cimport Parent from cypari2.paridecl cimport * +from gappy.gapobj cimport GapInteger from sage.rings.rational cimport Rational from sage.arith.rational_reconstruction cimport mpq_rational_reconstruction from sage.libs.gmp.pylong cimport * @@ -686,8 +687,10 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): elif isinstance(x, pari_gen): set_from_pari_gen(self, x) - else: + elif isinstance(x, GapInteger): + x.sage(z=self) + else: otmp = getattr(x, "_integer_", None) if otmp is not None: set_from_Integer(self, otmp(the_integer_ring)) @@ -2842,9 +2845,9 @@ cdef class Integer(sage.structure.element.EuclideanDomainElement): sage: ZZ(8).log(int(2)) 3 - + TESTS:: - + sage: (-2).log(3) (I*pi + log(2))/log(3) """ diff --git a/src/sage/rings/number_field/number_field.py b/src/sage/rings/number_field/number_field.py index ebf83f478aa..d4d431349cc 100644 --- a/src/sage/rings/number_field/number_field.py +++ b/src/sage/rings/number_field/number_field.py @@ -142,6 +142,9 @@ from . import maps from . import structure from . import number_field_morphisms + +from gappy.gapobj import GapObj + from itertools import count from builtins import zip @@ -1774,7 +1777,7 @@ def _element_constructor_(self, x, check=True): if self.variable_name() in s: return self._convert_from_str(s) return self._convert_from_str(s.replace('!', '')) - elif isinstance(x,str): + elif isinstance(x, str): return self._convert_from_str(x) elif (isinstance(x, (tuple, list)) or isinstance(x, sage.modules.free_module_element.FreeModuleElement)): @@ -4383,10 +4386,9 @@ def _gap_init_(self): sage: k.GeneratorsOfDivisionRing() [ zeta ] - The following tests that it is possible to use a defining - polynomial in the variable ``E``, even though by default - ``E`` is used as a local variable in the above GAP - ``CallFuncList``:: + The following tests that it is possible to use a defining polynomial in + the variable ``E``, even though by default ``E`` is used as a local + variable in the above GAP ``CallFuncList``:: sage: P. = QQ[] sage: L. = NumberField(E^3 - 2) @@ -4399,13 +4401,52 @@ def _gap_init_(self): """ if not self.is_absolute(): - raise NotImplementedError("Currently, only simple algebraic extensions are implemented in gap") + raise NotImplementedError( + "Currently, only simple algebraic extensions are implemented " + "in gap") G = sage.interfaces.gap.gap q = self.polynomial() - if q.variable_name()!='E': - return 'CallFuncList(function() local %s,E; %s:=Indeterminate(%s,"%s"); E:=AlgebraicExtension(%s,%s,"%s"); return E; end,[])'%(q.variable_name(),q.variable_name(),G(self.base_ring()).name(),q.variable_name(),G(self.base_ring()).name(),repr(self.polynomial()),str(self.gen())) - else: - return 'CallFuncList(function() local %s,F; %s:=Indeterminate(%s,"%s"); F:=AlgebraicExtension(%s,%s,"%s"); return F; end,[])'%(q.variable_name(),q.variable_name(),G(self.base_ring()).name(),q.variable_name(),G(self.base_ring()).name(),repr(self.polynomial()),str(self.gen())) + x = q.variable_name() + q_x = q.change_variable_name('x') + R = G(self.base_ring()).name() + # Honestly not sure why this is implemented like this, but I cleaned + # up the original code and used an f-string to make it more legible + # - embray + return f"""\ + CallFuncList(function() + local x, E; + x := Indeterminate({R}, "{x}"); + E := AlgebraicExtension({R}, {q_x!r}, "{self.gen()}"); + return E; + end, [])""" + + def _libgap_(self, gap=None): + """ + Create a libgap object representing self + + EXAMPLES:: + + sage: z = QQ['z'].0 + sage: K. = NumberField(z^2 - 2) + sage: k = libgap(K) + sage: k + + sage: k.GeneratorsOfDivisionRing() + [ zeta ] + """ + if not self.is_absolute(): + raise NotImplementedError( + "Currently, only simple algebraic extensions are implemented " + "in gap") + + if gap is None: + from sage.libs.gap.libgap import libgap as gap + + R = self.base_ring() + q = self.polynomial() + x = gap.Indeterminate(R, q.variable_name()) + return gap.AlgebraicExtension(R, q(x), str(self.gen())) + def characteristic(self): """ @@ -10372,7 +10413,7 @@ def _gap_init_(self): """ return 'CyclotomicField(%s)'%self.__n - def _libgap_(self): + def _libgap_(self, gap=None): """ Return a LibGAP representation of ``self``. @@ -10384,8 +10425,10 @@ def _libgap_(self): sage: libgap(K) # indirect doctest CF(8) """ - from sage.libs.gap.libgap import libgap - return libgap.CyclotomicField(self.__n) + if gap is None: + from sage.libs.gap.libgap import libgap as gap + + return gap.CyclotomicField(self.__n) def _repr_(self): r""" @@ -10737,9 +10780,9 @@ def _element_constructor_(self, x, check=True): elif isinstance(x, pari_gen): return NumberField_absolute._element_constructor_(self, x, check=check) elif (sage.interfaces.gap.is_GapElement(x) or - isinstance(x, sage.libs.gap.element.GapElement)): + isinstance(x, GapObj)): return self._coerce_from_gap(x) - elif isinstance(x,str): + elif isinstance(x, str): return self._convert_from_str(x) # late import because of speed diff --git a/src/sage/rings/number_field/number_field_element.pyx b/src/sage/rings/number_field/number_field_element.pyx index 4088b8dad1b..6c3c0d0fc16 100644 --- a/src/sage/rings/number_field/number_field_element.pyx +++ b/src/sage/rings/number_field/number_field_element.pyx @@ -569,7 +569,7 @@ cdef class NumberFieldElement(FieldElement): sage: libgap(E8 + 3/2*E8^2 + 100*E8^7) E(8)+3/2*E(8)^2-100*E(8)^3 sage: type(_) - + Check that :trac:`15276` is fixed:: diff --git a/src/sage/rings/polynomial/multi_polynomial.pyx b/src/sage/rings/polynomial/multi_polynomial.pyx index dfec6b2555d..b078c3317db 100644 --- a/src/sage/rings/polynomial/multi_polynomial.pyx +++ b/src/sage/rings/polynomial/multi_polynomial.pyx @@ -1028,7 +1028,7 @@ cdef class MPolynomial(CommutativeRingElement): variables = R.IndeterminatesOfPolynomialRing() return self(*variables) - def _libgap_(self): + def _libgap_(self, gap=None): r""" TESTS:: @@ -1038,8 +1038,11 @@ cdef class MPolynomial(CommutativeRingElement): sage: libgap(R.zero()) # indirect doctest 0 """ - from sage.libs.gap.libgap import libgap - return self._gap_(libgap) + if gap is None: + from sage.libs.gap.libgap import libgap as gap + + return self._gap_(gap) + def _magma_init_(self, magma): """ diff --git a/src/sage/rings/polynomial/polynomial_element.pyx b/src/sage/rings/polynomial/polynomial_element.pyx index e1fc8972917..0d0ae19fb1a 100644 --- a/src/sage/rings/polynomial/polynomial_element.pyx +++ b/src/sage/rings/polynomial/polynomial_element.pyx @@ -128,6 +128,8 @@ from sage.categories.morphism cimport Morphism from sage.misc.superseded import deprecation from sage.misc.cachefunc import cached_method +from gappy.gapobj cimport GapObj + cpdef is_Polynomial(f): """ @@ -774,12 +776,20 @@ cdef class Polynomial(CommutativeAlgebraElement): # This can save lots of coercions when the common parent is the # polynomial's base ring (e.g., for evaluations at integers). if not have_same_parent(a, cst): - cst, aa = coercion_model.canonical_coercion(cst, a) - # Use fast multiplication actions like matrix × scalar. - # If there is no action, replace a by an element of the - # target parent. - if coercion_model.get_action(parent(aa), parent(a)) is None: - a = aa + if isinstance(a, GapObj): + # Don't try to find a coercion to GapObjs since they are not + # Elements (yet). Instead just convert cst directly to GAP + # and evaluate the polynomial on the GapObj + # TODO: Remove this workaround once a better way can be found + # to extend the coercion model to non-Parents + cst = a.parent(cst) + else: + cst, aa = coercion_model.canonical_coercion(cst, a) + # Use fast multiplication actions like matrix × scalar. + # If there is no action, replace a by an element of the + # target parent. + if coercion_model.get_action(parent(aa), parent(a)) is None: + a = aa d = pol.degree() @@ -6270,7 +6280,7 @@ cdef class Polynomial(CommutativeAlgebraElement): var = list(R.IndeterminatesOfPolynomialRing())[0] return self(var) - def _libgap_(self): + def _libgap_(self, gap=None): r""" TESTS:: @@ -6280,8 +6290,10 @@ cdef class Polynomial(CommutativeAlgebraElement): sage: libgap(R.zero()) # indirect doctest 0 """ - from sage.libs.gap.libgap import libgap - return self._gap_(libgap) + if gap is None: + from sage.libs.gap.libgap import libgap as gap + + return self._gap_(gap) ###################################################################### diff --git a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx index 0337b39d55d..94407fb6d24 100644 --- a/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx +++ b/src/sage/rings/polynomial/polynomial_integer_dense_flint.pyx @@ -71,6 +71,8 @@ from sage.rings.real_mpfi cimport RealIntervalFieldElement from sage.rings.polynomial.evaluation_flint cimport fmpz_poly_evaluation_mpfr, fmpz_poly_evaluation_mpfi +from gappy.gapobj cimport GapObj + cdef class Polynomial_integer_dense_flint(Polynomial): r""" diff --git a/src/sage/rings/rational.pyx b/src/sage/rings/rational.pyx index d977e6a72fb..efbbc75ca48 100644 --- a/src/sage/rings/rational.pyx +++ b/src/sage/rings/rational.pyx @@ -102,6 +102,8 @@ from cpython.int cimport PyInt_AS_LONG cimport sage.rings.fast_arith import sage.rings.fast_arith +from gappy.gapobj cimport GapRational, GapInteger + cdef sage.rings.fast_arith.arith_int ai ai = sage.rings.fast_arith.arith_int() @@ -650,6 +652,12 @@ cdef class Rational(sage.structure.element.FieldElement): mpz_set(mpq_numref(self.value), a.value) mpz_set_si(mpq_denref(self.value), 1) + elif isinstance(x, GapRational): + x.sage(q=self) + + elif isinstance(x, GapInteger): + set_from_Integer(self, x.sage()) + elif isinstance(x, list) and len(x) == 1: self.__set_value(x[0], base) @@ -3145,9 +3153,9 @@ cdef class Rational(sage.structure.element.FieldElement): 3 sage: (125/8).log(5/2,prec=53) 3.00000000000000 - + TESTS:: - + sage: (25/2).log(5/2) log(25/2)/log(5/2) sage: (-1/2).log(3) diff --git a/src/sage/rings/universal_cyclotomic_field.py b/src/sage/rings/universal_cyclotomic_field.py index 2c7e09e0ed2..ad3ef84df9b 100644 --- a/src/sage/rings/universal_cyclotomic_field.py +++ b/src/sage/rings/universal_cyclotomic_field.py @@ -180,29 +180,10 @@ from sage.rings.infinity import Infinity from sage.rings.qqbar import AA, QQbar -libgap = GapElement_Integer = GapElement_Rational = GapElement_Cyclotomic = None -gap = gap3 = None +from sage.interfaces import gap, gap3 +from sage.libs.gap.libgap import libgap - -def late_import(): - r""" - This function avoids importing libgap on startup. It is called once through - the constructor of :class:`UniversalCyclotomicField`. - - EXAMPLES:: - - sage: import sage.rings.universal_cyclotomic_field as ucf - sage: _ = UniversalCyclotomicField() # indirect doctest - sage: ucf.libgap is None # indirect doctest - False - """ - global gap, gap3, libgap - global GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic - from sage.libs.gap.libgap import libgap - from sage.libs.gap.element import (GapElement_Integer, - GapElement_Rational, - GapElement_Cyclotomic) - from sage.interfaces import (gap, gap3) +from gappy.gapobj import GapInteger, GapRational, GapCyclotomic def UCF_sqrt_int(N, UCF): @@ -1277,7 +1258,7 @@ def minpoly(self, var='x'): (see :trac:`18266`). It would be faster/safer to not use string to construct the polynomial. """ - gap_p = libgap.MinimalPolynomial(libgap.eval("Rationals"), self._obj) + gap_p = libgap.MinimalPolynomial(libgap.Rationals, self._obj) return QQ[var](QQ['x_1'](str(gap_p))) @@ -1320,7 +1301,6 @@ def __init__(self, names=None): from sage.categories.fields import Fields Field.__init__(self, base_ring=QQ, category=Fields().Infinite()) self._populate_coercion_lists_(embedding=UCFtoQQbar(self)) - late_import() def _first_ngens(self, n): r""" @@ -1477,7 +1457,7 @@ def _element_constructor_(self, elt): Traceback (most recent call last): ... TypeError: [ [ 0, 1 ], [ 0, 2 ] ] - of type not valid + of type not valid to initialize an element of the universal cyclotomic field Some conversions from symbolic functions are possible:: @@ -1516,7 +1496,7 @@ def _element_constructor_(self, elt): if isinstance(elt, (Integer, Rational)): return self.element_class(self, libgap(elt)) - elif isinstance(elt, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): + elif isinstance(elt, (GapInteger, GapRational, GapCyclotomic)): return self.element_class(self, elt) elif not elt: return self.zero() @@ -1529,7 +1509,7 @@ def _element_constructor_(self, elt): elif isinstance(elt, str): obj = libgap.eval(elt) if obj is not None: - if not isinstance(obj, (GapElement_Integer, GapElement_Rational, GapElement_Cyclotomic)): + if not isinstance(obj, (GapInteger, GapRational, GapCyclotomic)): raise TypeError("{} of type {} not valid to initialize an element of the universal cyclotomic field".format(obj, type(obj))) return self.element_class(self, obj) diff --git a/src/sage/structure/sage_object.pyx b/src/sage/structure/sage_object.pyx index f61cb6ad426..bed6a40d3dc 100644 --- a/src/sage/structure/sage_object.pyx +++ b/src/sage/structure/sage_object.pyx @@ -720,8 +720,9 @@ cdef class SageObject: I = sage.interfaces.gap.gap return self._interface_init_(I) - def _libgap_(self): - from sage.libs.gap.libgap import libgap + def _libgap_(self, libgap=None): + if libgap is None: + from sage.libs.gap.libgap import libgap return libgap.eval(self._libgap_init_()) def _libgap_init_(self): diff --git a/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py b/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py index 0051f206528..3f46e998dd0 100644 --- a/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py +++ b/src/sage/tests/books/judson-abstract-algebra/sylow-sage.py @@ -130,7 +130,7 @@ sage: N3 = G.normalizer(S3); N3 Subgroup generated by [(2,18)(3,17)(4,16)(5,15)(6,14)(7,13)(8,12)(9,11), - (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18), + (1,2)(3,18)(4,17)(5,16)(6,15)(7,14)(8,13)(9,12)(10,11), (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18), (1,15,11,7,3,17,13,9,5)(2,16,12,8,4,18,14,10,6)] of (Dihedral group of order 36 as a permutation group) @@ -156,7 +156,7 @@ Subgroup generated by [(1,2)(3,18)(4,17)(5,16)(6,15)(7,14)(8,13)(9,12)(10,11), (1,5)(2,4)(6,18)(7,17)(8,16)(9,15)(10,14)(11,13), - (1,7,13)(2,8,14)(3,9,15)(4,10,16)(5,11,17)(6,12,18)] of (Dihedral group of + (1,13,7)(2,14,8)(3,15,9)(4,16,10)(5,17,11)(6,18,12)] of (Dihedral group of order 36 as a permutation group) ~~~~~~~~~~~~~~~~~~~~~~ ::