From ccc742ed7c0a3af2e9aa1de3897809bdaf46aead Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Wed, 20 Jan 2016 18:53:17 +0000 Subject: [PATCH 1/4] Fix libgap tab completion --- src/sage/libs/gap/assigned_names.py | 143 +++++++++++++++++++++++++++ src/sage/libs/gap/libgap.pyx | 32 +++--- src/sage/libs/gap/saved_workspace.py | 11 ++- 3 files changed, 163 insertions(+), 23 deletions(-) create mode 100644 src/sage/libs/gap/assigned_names.py diff --git a/src/sage/libs/gap/assigned_names.py b/src/sage/libs/gap/assigned_names.py new file mode 100644 index 00000000000..d246ed8b706 --- /dev/null +++ b/src/sage/libs/gap/assigned_names.py @@ -0,0 +1,143 @@ +""" +List of assigned names in GAP + +EXAMPLES:: + + sage: from sage.libs.gap.assigned_names import KEYWORDS, GLOBALS, FUNCTIONS + sage: 'fi' in KEYWORDS + True + sage: 'ZassenhausIntersection' in GLOBALS + True + sage: 'SubdirectProduct' in FUNCTIONS + True +""" + +############################################################################### +# Copyright (C) 2016, Volker Braun +# +# Distributed under the terms of the GNU General Public License (GPL) +# as published by the Free Software Foundation; either version 2 of +# the License, or (at your option) any later version. +# http://www.gnu.org/licenses/ +############################################################################### + + +import cPickle +import string +from sage.libs.gap.libgap import libgap +from sage.libs.gap.saved_workspace import workspace + + +def load_or_compute(name, function): + """ + Helper to load a cached value or compute it + + INPUT: + + - ``name`` -- string. Part of the cache filename + + - ``function`` -- function. To compute the value if not cached. + + OUTPUT: + + The value of ``function``, possibly cached. + + EXAMPLES:: + + sage: from sage.libs.gap.assigned_names import GLOBALS + sage: len(GLOBALS) > 1000 # indirect doctest + True + sage: from sage.libs.gap.saved_workspace import workspace + sage: workspace(name='globals') + ('...', True) + """ + filename, up_to_date = workspace(name=name) + if up_to_date: + with open(filename, 'rb') as f: + return cPickle.load(f) + else: + value = function() + from sage.misc.temporary_file import atomic_write + with atomic_write(filename) as f: + cPickle.dump(value, f) + return value + + +def list_keywords(): + """ + Return the GAP reserved keywords + + OUTPUT: + + Tuple of strings. + + EXAMPLES: + + sage: from sage.libs.gap.assigned_names import KEYWORDS + sage: 'fi' in KEYWORDS + True + """ + keywords = libgap.get_global('GAPInfo')['Keywords'].sage() + return tuple(sorted(keywords)) + + +KEYWORDS = list_keywords() + + +def list_globals(): + """ + Return the GAP reserved keywords + + OUTPUT: + + Tuple of strings. + + EXAMPLES: + + sage: from sage.libs.gap.assigned_names import GLOBALS + sage: 'ZassenhausIntersection' in GLOBALS + True + """ + NamesGVars = libgap.function_factory('NamesGVars') + IsBoundGlobal = libgap.function_factory('IsBoundGlobal') + gvars = set( + name.sage() for name in NamesGVars() + if IsBoundGlobal(name) + ) + gvars.difference_update(KEYWORDS) + return tuple(sorted(gvars)) + + +GLOBALS = load_or_compute('globals', list_globals) + + +def list_functions(): + """ + Return the GAP documented global functions + + OUTPUT: + + Tuple of strings. + + EXAMPLES: + + sage: from sage.libs.gap.assigned_names import FUNCTIONS + sage: 'IsBound' in FUNCTIONS # is a keyword + False + sage: 'SubdirectProduct' in FUNCTIONS + True + """ + Filtered =libgap.function_factory('Filtered') + IsDocumentedWord = libgap.function_factory('IsDocumentedWord') + fnames = libgap.eval('GLOBAL_FUNCTION_NAMES') + documented = Filtered(fnames, IsDocumentedWord) + return tuple(sorted(documented.sage())) + + +FUNCTIONS = load_or_compute('functions', list_functions) + + + + + + diff --git a/src/sage/libs/gap/libgap.pyx b/src/sage/libs/gap/libgap.pyx index 734cf69843a..913000e51b5 100644 --- a/src/sage/libs/gap/libgap.pyx +++ b/src/sage/libs/gap/libgap.pyx @@ -630,7 +630,6 @@ class Gap(Parent): from sage.rings.integer_ring import ZZ Parent.__init__(self, base=ZZ) - def __repr__(self): r""" Return a string representation of ``self``. @@ -646,23 +645,18 @@ class Gap(Parent): """ return 'C library interface to GAP' - - def trait_names(self): + @cached_method + def __dir__(self): """ - Return all Gap function names. - - OUTPUT: - - A list of strings. + Customize tab completion EXAMPLES:: - sage: len(libgap.trait_names()) > 1000 - True + sage: 'OctaveAlgebra' in dir(libgap) + True """ - import gap_functions - return gap_functions.common_gap_functions - + from sage.libs.gap.gap_functions import common_gap_functions + return dir(self.__class__) + list(common_gap_functions) def __getattr__(self, name): r""" @@ -683,7 +677,10 @@ class Gap(Parent): sage: libgap.List """ - if name in self.trait_names(): + if name in dir(self.__class__): + return getattr(self.__class__, name) + from sage.libs.gap.gap_functions import common_gap_functions + if name in common_gap_functions: f = make_GapElement_Function(self, gap_eval(str(name))) assert f.is_function() self.__dict__[name] = f @@ -691,7 +688,6 @@ class Gap(Parent): else: raise AttributeError, 'No such attribute: '+name+'.' - def show(self): """ Print statistics about the GAP owned object list @@ -717,7 +713,6 @@ class Gap(Parent): print self.eval('GasmanStatistics()') # print_gasman_objects() - def count_GAP_objects(self): """ Return the number of GAP objects that are being tracked by @@ -734,7 +729,6 @@ class Gap(Parent): """ return sum([1 for obj in get_owned_objects()]) - def mem(self): """ Return information about libGAP memory usage @@ -785,7 +779,6 @@ class Gap(Parent): """ return memory_usage() - def collect(self): """ Manually run the garbage collector @@ -797,11 +790,10 @@ class Gap(Parent): sage: libgap.collect() """ libgap_enter() - rc = libGAP_CollectBags(0,1) + rc = libGAP_CollectBags(0, 1) libgap_exit() if rc != 1: raise RuntimeError('Garbage collection failed.') - libgap = Gap() diff --git a/src/sage/libs/gap/saved_workspace.py b/src/sage/libs/gap/saved_workspace.py index ba916f8ef17..ef12fe1b6d2 100644 --- a/src/sage/libs/gap/saved_workspace.py +++ b/src/sage/libs/gap/saved_workspace.py @@ -34,10 +34,15 @@ def timestamp(): return max(map(os.path.getmtime, files)) -def workspace(): +def workspace(name='workspace'): """ Return the filename of the gap workspace and whether it is up to date. + INPUT: + + - ``name`` -- string. A name that will become part of the + workspace filename. + OUTPUT: Pair consisting of a string and a boolean. The string is the @@ -58,8 +63,8 @@ def workspace(): import os import glob from sage.env import SAGE_LOCAL, DOT_SAGE - workspace = os.path.join(DOT_SAGE, 'gap', 'libgap-workspace-{0}' - .format(abs(hash(SAGE_LOCAL)))) + workspace = os.path.join(DOT_SAGE, 'gap', 'libgap-{0}-{1}' + .format(name, abs(hash(SAGE_LOCAL)))) try: workspace_mtime = os.path.getmtime(workspace) except OSError: From 807d1a578f9f7725c63f578eb9f961b5832683d2 Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Wed, 20 Jan 2016 23:04:02 +0000 Subject: [PATCH 2/4] Improve tab completion on LibGAP elements --- src/sage/libs/gap/all_documented_functions.py | 24 ++++ src/sage/libs/gap/assigned_names.py | 28 ++-- src/sage/libs/gap/element.pyx | 27 ++-- src/sage/libs/gap/operations.py | 133 ++++++++++++++++++ 4 files changed, 185 insertions(+), 27 deletions(-) create mode 100644 src/sage/libs/gap/all_documented_functions.py create mode 100644 src/sage/libs/gap/operations.py diff --git a/src/sage/libs/gap/all_documented_functions.py b/src/sage/libs/gap/all_documented_functions.py new file mode 100644 index 00000000000..a70c755d454 --- /dev/null +++ b/src/sage/libs/gap/all_documented_functions.py @@ -0,0 +1,24 @@ +""" +All Documented GAP Functions + +This Python module contains all documented GAP functions, they can be +thought of as the official API of GAP. + +EXAMPLES:: + + sage: from sage.libs.gap.all_documented_functions import * + sage: DihedralGroup(8) + + sage: GeneratorsOfGroup(_) + [ f1, f2, f3 ] + sage: List(_, Order) + [ 2, 4, 2 ] +""" + +from sage.libs.gap.libgap import libgap +from sage.libs.gap.assigned_names import FUNCTIONS as _FUNCTIONS + + + +for _f in _FUNCTIONS: + globals()[_f] = libgap.function_factory(_f) diff --git a/src/sage/libs/gap/assigned_names.py b/src/sage/libs/gap/assigned_names.py index d246ed8b706..e651c3effa7 100644 --- a/src/sage/libs/gap/assigned_names.py +++ b/src/sage/libs/gap/assigned_names.py @@ -28,6 +28,14 @@ from sage.libs.gap.saved_workspace import workspace +NamesGVars = libgap.function_factory('NamesGVars') +Filtered =libgap.function_factory('Filtered') +ValueGlobal = libgap.function_factory('ValueGlobal') +IsBoundGlobal = libgap.function_factory('IsBoundGlobal') +IsFunction = libgap.function_factory('IsFunction') +IsDocumentedWord = libgap.function_factory('IsDocumentedWord') + + def load_or_compute(name, function): """ Helper to load a cached value or compute it @@ -71,10 +79,10 @@ def list_keywords(): Tuple of strings. - EXAMPLES: + EXAMPLES:: sage: from sage.libs.gap.assigned_names import KEYWORDS - sage: 'fi' in KEYWORDS + sage: 'fi' in KEYWORDS # indirect doctest True """ keywords = libgap.get_global('GAPInfo')['Keywords'].sage() @@ -92,14 +100,12 @@ def list_globals(): Tuple of strings. - EXAMPLES: + EXAMPLES:: sage: from sage.libs.gap.assigned_names import GLOBALS - sage: 'ZassenhausIntersection' in GLOBALS + sage: 'ZassenhausIntersection' in GLOBALS # indirect doctest True """ - NamesGVars = libgap.function_factory('NamesGVars') - IsBoundGlobal = libgap.function_factory('IsBoundGlobal') gvars = set( name.sage() for name in NamesGVars() if IsBoundGlobal(name) @@ -119,18 +125,16 @@ def list_functions(): Tuple of strings. - EXAMPLES: + EXAMPLES:: sage: from sage.libs.gap.assigned_names import FUNCTIONS sage: 'IsBound' in FUNCTIONS # is a keyword False - sage: 'SubdirectProduct' in FUNCTIONS + sage: 'SubdirectProduct' in FUNCTIONS # indirect doctest True """ - Filtered =libgap.function_factory('Filtered') - IsDocumentedWord = libgap.function_factory('IsDocumentedWord') - fnames = libgap.eval('GLOBAL_FUNCTION_NAMES') - documented = Filtered(fnames, IsDocumentedWord) + fnames = set(GLOBALS).difference(KEYWORDS) + documented = Filtered(list(fnames), IsDocumentedWord) return tuple(sorted(documented.sage())) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index a9a39ad224e..b15b6a35f3c 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -18,6 +18,7 @@ elements. For general information about libGAP, you should read the include "sage/ext/interrupt.pxi" from cpython.object cimport * +from sage.misc.cachefunc import cached_method from sage.structure.sage_object cimport SageObject from sage.structure.parent import Parent from sage.rings.all import ZZ @@ -373,7 +374,6 @@ cdef class GapElement(RingElement): return dereference_obj(self.value) - cpdef _type_number(self): """ Return the GAP internal type number. @@ -395,24 +395,22 @@ cdef class GapElement(RingElement): name = decode_type_number.get(n, 'unknown') return (n, name) - - def trait_names(self): + def __dir__(self): """ - Return all Gap function names. - - OUTPUT: - - A list of strings. + Customize tab completion EXAMPLES:: + sage: G = libgap.DihedralGroup(4) + sage: 'GeneratorsOfGroup' in dir(G) + True sage: x = libgap(1) - sage: len(x.trait_names()) > 1000 + sage: len(dir(x)) > 100 True """ - import gap_functions - return gap_functions.common_gap_functions - + from sage.libs.gap.operations import OperationInspector + ops = OperationInspector(self).op_names() + return dir(self.__class__) + ops def __getattr__(self, name): r""" @@ -421,6 +419,8 @@ cdef class GapElement(RingElement): 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 ] @@ -458,7 +458,6 @@ cdef class GapElement(RingElement): raise AttributeError('name "'+str(name)+'" does not define a GAP function.') return proxy - def _repr_(self): r""" Return a string representation of ``self``. @@ -486,7 +485,6 @@ cdef class GapElement(RingElement): libgap_finish_interaction() libgap_exit() - cpdef _set_compare_by_id(self): """ Set comparison to compare by ``id`` @@ -525,7 +523,6 @@ cdef class GapElement(RingElement): """ self._compare_by_id = True - cpdef _assert_compare_by_id(self): """ Ensure that comparison is by ``id`` diff --git a/src/sage/libs/gap/operations.py b/src/sage/libs/gap/operations.py new file mode 100644 index 00000000000..b237c0a2f09 --- /dev/null +++ b/src/sage/libs/gap/operations.py @@ -0,0 +1,133 @@ +""" +Operations for LibGAP Elements +""" + +import re +import string +from sage.structure.sage_object import SageObject +from sage.misc.cachefunc import cached_method +from sage.libs.gap.libgap import libgap + +Length = libgap.function_factory('Length') +FlagsType = libgap.function_factory('FlagsType') +TypeObj = libgap.function_factory('TypeObj') +IS_SUBSET_FLAGS = libgap.function_factory('IS_SUBSET_FLAGS') +OPERATIONS = libgap.get_global('OPERATIONS') +NameFunction = libgap.function_factory('NameFunction') + + +NAME_RE = re.compile('(Setter|Getter|Tester)\((.*)\)') + + +class OperationInspector(SageObject): + + def __init__(self, libgap_element): + """ + Information about operations that can act on a given LibGAP element + + INPUT: + + - ``libgap_element`` -- libgap element. + + EXAMPLES:: + + sage: from sage.libs.gap.operations import OperationInspector + sage: OperationInspector(libgap(123)) + Operations on 123 + """ + self._obj = libgap_element + self.flags = FlagsType(TypeObj(self.obj)) + + def _repr_(self): + """ + Return the string representation + + OUTPUT: + + String + + EXAMPLES: + + sage: from sage.libs.gap.operations import OperationInspector + sage: opr = OperationInspector(libgap(123)) + sage: opr._repr_() + 'Operations on 123' + """ + return 'Operations on {0}'.format(repr(self._obj)) + + @property + def obj(self): + """ + The first argument for the operations + + OUTPUT: + + A Libgap object. + + EXAMPLES:: + + sage: from sage.libs.gap.operations import OperationInspector + sage: x = OperationInspector(libgap(123)) + sage: x.obj + 123 + """ + return self._obj + + def operations(self): + """ + Return the GAP operations for :meth:`obj` + + OUTPUT: + + List of GAP operations + + EXAMPLES:: + + sage: from sage.libs.gap.operations import OperationInspector + sage: x = OperationInspector(libgap(123)) + sage: Unknown = libgap.function_factory('Unknown') + sage: Unknown in x.operations() + True + """ + result = [] + for i in range(len(OPERATIONS) // 2): + match = False + for flag_list in OPERATIONS[2*i + 1]: + if Length(flag_list) == 0: + continue + first_flag = flag_list[0] + if IS_SUBSET_FLAGS(self.flags, first_flag): + match = True + break + if match: + op = OPERATIONS[2*i] + result.append(op) + return result + + def op_names(self): + """ + Return the names of the operations + + OUTPUT: + + List of strings + + EXAMPLES:: + + sage: from sage.libs.gap.operations import OperationInspector + sage: x = OperationInspector(libgap(123)) + sage: 'Sqrt' in x.op_names() + True + """ + result = set() + for f in self.operations(): + name = NameFunction(f).sage() + if name[0] not in string.letters: + continue + match = NAME_RE.match(name) + if match: + result.add(match.groups()[1]) + else: + result.add(name) + return sorted(result) + From 0dccd8dec0ec7ff7d56674a367aaafb875d3ee7c Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Thu, 21 Jan 2016 12:16:41 +0000 Subject: [PATCH 3/4] Fix doctest --- src/sage/libs/gap/element.pyx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sage/libs/gap/element.pyx b/src/sage/libs/gap/element.pyx index b15b6a35f3c..888d06a2779 100644 --- a/src/sage/libs/gap/element.pyx +++ b/src/sage/libs/gap/element.pyx @@ -402,8 +402,10 @@ cdef class GapElement(RingElement): EXAMPLES:: sage: G = libgap.DihedralGroup(4) - sage: 'GeneratorsOfGroup' in dir(G) + sage: 'GeneratorsOfMagmaWithInverses' in dir(G) True + sage: 'GeneratorsOfGroup' in dir(G) # known bug + False sage: x = libgap(1) sage: len(dir(x)) > 100 True From 47bc138c13530e553a22642be9de6efb8fdcd80c Mon Sep 17 00:00:00 2001 From: Volker Braun Date: Sun, 14 Feb 2016 00:34:37 +0100 Subject: [PATCH 4/4] Add explanation to the operations.py module docstring --- src/sage/libs/gap/operations.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/sage/libs/gap/operations.py b/src/sage/libs/gap/operations.py index b237c0a2f09..45f786806f0 100644 --- a/src/sage/libs/gap/operations.py +++ b/src/sage/libs/gap/operations.py @@ -1,5 +1,12 @@ """ Operations for LibGAP Elements + +GAP functions for which several methods can be available are called +operations, so GAP ``Size`` is an example of an operation. This module +is for inspecting GAP operations from Python. In particular, it can +list the operations that take a particular LibGAP element as first +argument. This is used in tab completion, where Python ``x.[TAB]`` +lists all GAP operations for which ``Operation(x, ...)`` is defined. """ import re