├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .readthedocs.yml ├── CHANGES.rst ├── LICENSE ├── README.rst ├── docs ├── Makefile ├── _static │ └── custom.css ├── api.rst ├── conf.py ├── images │ ├── gappy-logo.svg │ └── gappy-logo.svg.png ├── index.rst └── make.bat ├── environment.yml ├── gappy ├── __init__.py ├── _dev │ ├── __init__.py │ └── scm_version.py ├── conftest.py ├── context_managers.py ├── core.pxd ├── core.pyx ├── exceptions.py ├── gap_functions.py ├── gap_globals.py ├── gap_includes.pxd ├── gapobj.pxd ├── gapobj.pyx ├── gmp.pxd ├── operations.py ├── utils.pyx └── version.py ├── pyproject.toml ├── setup.cfg ├── setup.py └── tests ├── conftest.py ├── test_long.py └── test_misc.py /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # vim: tabstop=2 shiftwidth=2 2 | name: "Tests" 3 | on: ["push", "pull_request"] 4 | jobs: 5 | tests: 6 | name: > 7 | Test on ${{ matrix.platform }} with Python ${{ matrix.python-version }} 8 | and GAP ${{ matrix.gap-version }} 9 | defaults: 10 | run: 11 | shell: "bash -l {0}" 12 | strategy: 13 | fail-fast: false 14 | matrix: 15 | platform: ["ubuntu-latest", "macos-latest"] 16 | python-version: ["3.7", "3.8"] 17 | gap-version: ["4.10.2", "4.11.0"] 18 | exclude: 19 | - python-version: "3.7" 20 | gap-version: "4.11.0" 21 | - python-version: "3.8" 22 | gap-version: "4.10.2" 23 | - platform: "macos-latest" 24 | python-version: "3.7" 25 | runs-on: "${{ matrix.platform }}" 26 | steps: 27 | - uses: actions/checkout@v4 28 | - uses: actions/cache@v4 29 | env: 30 | # Increase this value to reset cache manually 31 | CACHE_NUMBER: 0 32 | with: 33 | path: ~/conda_pkgs_dir 34 | key: "${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-${{ matrix.python-version }}-${{ matrix.gap-version }}" 35 | - uses: "conda-incubator/setup-miniconda@v3" 36 | with: 37 | auto-update-conda: true 38 | python-version: "${{ matrix.python-version }}" 39 | use-only-tar-bz2: false 40 | - name: "Conda info" 41 | run: "conda info" 42 | - name: "Conda install dependencies" 43 | run: "conda install -q -c conda-forge gap-defaults=${{ matrix.gap-version }}" 44 | - name: "Install gappy" 45 | run: "pip install -e .[tests]" 46 | - name: "Run tests" 47 | run: "pytest -v --color=yes" 48 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vim files 2 | *.sw[nop] 3 | 4 | # Python files 5 | __pycache__/ 6 | 7 | ## Python build files 8 | build/ 9 | gappy/*.so 10 | gappy/*.dll 11 | 12 | ## Python packaging files 13 | *.dist-info/ 14 | *.egg-info/ 15 | .eggs/ 16 | dist/ 17 | 18 | ## Local development virtual env 19 | .venv* 20 | 21 | ## setuptools_scm files 22 | gappy/_version.py 23 | 24 | ## Cython generated files 25 | gappy/*.c 26 | 27 | ## cysignals logs 28 | cysignals_crash_logs/ 29 | 30 | ## pytest files 31 | pytestdebug.log 32 | 33 | ## Sphinx files 34 | docs/_build 35 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | version: 2 5 | conda: 6 | environment: environment.yml 7 | python: 8 | install: 9 | - method: pip 10 | path: . 11 | extra_requirements: 12 | - docs 13 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | v0.1.0a4 (unreleased) 5 | --------------------- 6 | 7 | Breaking changes 8 | ^^^^^^^^^^^^^^^^ 9 | 10 | * Dropped support for Python 3.6; Python 3.7+ is officially supported (3.6 may 11 | still work but is not tested). 12 | 13 | Enhancements 14 | ^^^^^^^^^^^^ 15 | 16 | * Prevent needless line-wrapping of exception messages. This was inspired 17 | by similar code in GAP.jl. 18 | 19 | Bug Fixes 20 | ^^^^^^^^^ 21 | 22 | * Fixed segmentation fault that could occur upon all GAPErrors after a 23 | large number of errors from GAP have been caught and handled [#12]. 24 | 25 | * Improved error handling around more parts of the code, particularly 26 | around any code that can result in executing arbitrary GAP code. 27 | 28 | 29 | v0.1.0a3 (2021-02-15) 30 | --------------------- 31 | 32 | Enhancements 33 | ^^^^^^^^^^^^ 34 | 35 | * Renamed the special method ``_gap_``, for converting arbitrary Python 36 | objects to GAP objects, to ``__gap__`` as inspired by the discussion at 37 | https://trac.sagemath.org/ticket/31297#comment:23 38 | 39 | * Likewise, the special method ``_gap_init_`` is now named 40 | ``__gap_eval__`` to emphasize that it returns a string to be passed 41 | to ``Gap.eval()``. It still does not take any arguments. 42 | 43 | * Added ``GapObj.python()`` method for converting a ``GapObj`` to its 44 | equivalent type if one exists (it does not always, but it does in the 45 | cases where there is an equivalent type built into Python). 46 | 47 | * ``GapList.python()`` and ``GapRecord.python()`` also recursively convert 48 | the values they contain to equivalent Python types if possible. 49 | 50 | * New interface for registering converters to/from GAP object types: 51 | 52 | * ``Gap.register_converter`` is replaced with the ``Gap.convert_from`` 53 | decorator. 54 | 55 | * The ``GapObj.convert_to`` decorator can be used to register new 56 | conversion methods on ``GapObj``, or specific subclasses thereof. 57 | 58 | * Added some C-level utility methods on ``GapInteger`` to help convert to 59 | different integer types (C long ints and mpz_t, depending on the size of 60 | the int). This helps with more efficient conversion to Sage Integers 61 | without having to pass through an intermediary Python ``int``. 62 | 63 | * Implemented the ``__invert__`` and ``__neg__`` magic methods for 64 | ``GapObj``. 65 | 66 | * Implemented a default ``__bool__`` for all ``GapObj`` which returns 67 | ``False`` if its value is equal to zero. 68 | 69 | * Install the ``.pyx`` sources so that Cython tracebacks can work better. 70 | 71 | Bug fixes 72 | ^^^^^^^^^ 73 | 74 | * When converting a ``GapRecord`` to a ``dict`` with ``dict(rec)`` the 75 | keys remain as ``GapString`` instead of ``str``. This is more consistent 76 | with the fact that the values are not converted to Python equivalents. 77 | 78 | * If an arbitrary GAP error occurs while looking up a global variable with 79 | ``Gap.__getattr__`` it is handled and re-raised as an ``AttributeError``. 80 | 81 | * The ``Gap.__repr__`` method displays names of subclasses correctly. 82 | 83 | 84 | v0.1.0a2 (2021-02-03) 85 | --------------------- 86 | 87 | Bug fixes 88 | ^^^^^^^^^ 89 | 90 | * Made fixes for MacOS and Cygwin support. 91 | 92 | 93 | v0.1.0a1 (2021-02-03) 94 | --------------------- 95 | 96 | Enhancements 97 | ^^^^^^^^^^^^ 98 | 99 | * Added LRU cache for functions defined with ``gap.gap_functions``, 100 | restoring some of the caching functionality from Sage's 101 | ``Gap.function_factory``. 102 | 103 | Bug fixes 104 | ^^^^^^^^^ 105 | 106 | * Fixed bug in multi-indexing of nested lists. 107 | 108 | * Fixed minor formatting difference in the IndexError message when indexing 109 | single lists versus multi-indexing nested lists. 110 | 111 | * Fixed a bug when using functions defined with ``gap.gap_function`` as 112 | arguments to another GAP function before they have been called once. 113 | 114 | 115 | v0.1.0a0 (2021-01-26) 116 | --------------------- 117 | 118 | * Initial alpha release for testing against SageMath. 119 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | |logo| 2 | 3 | ################################# 4 | gappy — a Python interface to GAP 5 | ################################# 6 | 7 | |docs-badge| |tests-badge| 8 | 9 | gappy provides a Python interface to the `GAP 10 | `_ computer algebra system by linking to its 11 | library interface. 12 | 13 | It allows calling functions in GAP directly from Python, and passing 14 | supported Python objects back to GAP. 15 | 16 | gappy is based on SageMath's `LibGAP 17 | `_ 18 | interface to GAP, originally developed by Volker Braun, but is completely 19 | independent of Sage--it does not require or use Sage at all, and can be used 20 | in any Python code. If there is enough interest, it may also be enhanced 21 | with a complementary GAP package for interacting with Python from within 22 | GAP. 23 | 24 | 25 | .. contents:: 26 | :local: 27 | :depth: 3 28 | 29 | 30 | Quickstart 31 | ========== 32 | 33 | To start using GAP functions from Python, just run: 34 | 35 | .. code-block:: python 36 | 37 | >>> from gappy import gap 38 | 39 | Then any global variable in GAP, including functions, can be accessed as 40 | attributes on `gap` like: 41 | 42 | .. code-block:: python 43 | 44 | >>> gap.Cite() 45 | Please use one of the following samples 46 | to cite GAP version from this installation 47 | 48 | Text: 49 | 50 | [GAP] GAP – Groups, Algorithms, and Programming, Version 4.dev, The GAP Group, https://www.gap-system.org. 51 | ... 52 | 53 | All global variables that would be available in a GAP session can be 54 | accessed in this way: 55 | 56 | .. code-block:: python 57 | 58 | >>> gap.GAPInfo.Version 59 | "4.dev" 60 | 61 | Most basic Python types have direct equivalents in GAP, and can be passed 62 | directly to GAP functions without explicit conversion to their equivalent 63 | GAP types: 64 | 65 | .. code-block:: python 66 | 67 | >>> S4 = gap.SymmetricGroup(4) 68 | >>> S4 69 | Sym( [ 1 .. 4 ] ) 70 | 71 | You can also call "methods" on ``GapObj``\s. This is just syntactic sugar 72 | for calling a GAP function with that object as its first argument, in cases 73 | where that function supports the object bound to the method. For example: 74 | 75 | .. code-block:: python 76 | 77 | >>> S4.GeneratorsOfGroup() 78 | [ (1,2,3,4), (1,2) ] 79 | 80 | Values returned from GAP functions are GAP objects wrapped in a Python class 81 | for containing them called ``GapObj``: 82 | 83 | .. code-block:: python 84 | 85 | >>> type(S4) 86 | 87 | 88 | There are also specialized subclasses of ``GapObj`` for many types of objects 89 | in GAP. To explicitly convert a Python object directly to its GAP 90 | equivalent, you can *call* ``gap`` like: 91 | 92 | .. code-block:: python 93 | 94 | >>> one = gap(1) 95 | >>> type(one) 96 | 97 | 98 | GAP objects are displayed (with `repr`) or stringified (with `str`) the same 99 | way they would be in GAP, when displaying the object in the REPL or when 100 | calling GAP's ``Print()`` function on the object, respectively: 101 | 102 | .. code-block:: python 103 | 104 | >>> one 105 | 1 106 | >>> s = gap("Hello GAP!") 107 | >>> s 108 | "Hello GAP!" 109 | >>> print(s) 110 | Hello GAP! 111 | 112 | Not all GAP objects have an equivalent in basic Python types, so there is 113 | no implicit conversion from GAP back to Python. However, all Python types 114 | that can be converted to GAP objects can be converted back to their 115 | equivalent Python types in a symmetrical manner: 116 | 117 | .. code-block:: python 118 | 119 | >>> int(one) 120 | 1 121 | >>> type(int(one)) 122 | 123 | >>> str(s) 124 | 'Hello GAP!' 125 | >>> type(str(s)) 126 | 127 | 128 | Likewise for `float`\s, `list`\s, `dict`\s, among others. 129 | 130 | You can also call ``obj.python()`` to convert to its equivalent Python type 131 | if one exists: 132 | 133 | .. code-block:: python 134 | 135 | >>> type(one.python()) 136 | 137 | 138 | To register your own converters for GAP objects to custom Python types, see 139 | the ``gap.convert_to`` decorator. 140 | 141 | Finally, you can execute arbitrary GAP code directly with ``gap.eval``. 142 | This is often the easiest way to construct more complicated GAP objects, 143 | especially if you are more familiar with GAP syntax. The return value of 144 | ``gap.eval`` is the result of evaluating the same statement in GAP (the 145 | semicolon is optional when evaluating a single statement): 146 | 147 | .. code-block:: python 148 | 149 | >>> rec = gap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))') 150 | >>> rec['Sym3'] 151 | Sym( [ 1 .. 3 ] ) 152 | 153 | This is also an easy way to declare new GAP functions from gappy: 154 | 155 | .. code-block:: python 156 | 157 | >>> sign = gap.eval("""sign := function(n) 158 | ... if n < 0 then 159 | ... return -1; 160 | ... elif n = 0 then 161 | ... return 0; 162 | ... else 163 | ... return 1; 164 | ... fi; 165 | ... end;""") 166 | >>> sign 167 | 168 | >>> sign(0) 169 | 0 170 | >>> sign(-99) 171 | -1 172 | 173 | See the full API documentation for many additional examples of how to use 174 | the ``gap`` object as well as the built-in ``GapObj`` types. 175 | 176 | 177 | Installation 178 | ============ 179 | 180 | .. note:: 181 | 182 | These instructions will be updated once there are releases on PyPI. 183 | 184 | Prerequisites 185 | ------------- 186 | 187 | * Supported platforms: Linux, MacOS, Cygwin. 188 | 189 | * Likely works with most other \*BSD flavors but has not been tested. 190 | 191 | * Python 3.6 or up with development headers installed. On Debian-based 192 | systems this means: 193 | 194 | .. code-block:: shell 195 | 196 | $ sudo apt-get install python3.7-dev 197 | 198 | * GAP 4.10.2 or greater 199 | 200 | It is possible to install from PyPI (note the distribution name 201 | **gappy-system**, **do not** install the package "gappy" which is an 202 | unrelated obsolete package): 203 | 204 | .. code-block:: shell 205 | 206 | $ pip install gappy-system 207 | 208 | or from source: 209 | 210 | .. code-block:: shell 211 | 212 | $ git clone https://github.com/embray/gappy.git 213 | $ cd gappy/ 214 | $ pip install . 215 | 216 | However, depending on how GAP is installed, some extra steps may be 217 | required. In particular, if you installed GAP from source using the 218 | typical instructions on the `GAP website 219 | `_ you will need to make 220 | sure the libgap shared library is built by running: 221 | 222 | .. code-block:: shell 223 | 224 | $ make install-libgap 225 | 226 | in the GAP source directory. 227 | 228 | You will also need to point to the location of your GAP installation by 229 | setting the ``GAP_ROOT`` environment variable like: 230 | 231 | .. code-block:: shell 232 | 233 | $ GAP_ROOT= pip install . 234 | 235 | If you needed to provide ``GAP_ROOT`` for the installation, it is also 236 | generally necessary to set this environment variable *before* using gappy, 237 | so that it can find the path to your GAP installation. See the 238 | documentation for the ``Gap`` class for more information. 239 | 240 | If using GAP from a distribution system such as APT on Debian/Ubuntu or from 241 | Conda, however, the GAP library (libgap) is typically installed in a 242 | standard system location, and it may not be necessary to provide 243 | ``GAP_ROOT``. See the next section for example. 244 | 245 | Conda installation 246 | ------------------ 247 | 248 | To give an example of the above point, you can install gappy in a Conda 249 | environment as follows: 250 | 251 | .. code-block:: shell 252 | 253 | $ conda create -n gap 254 | $ conda activate gap 255 | $ conda install -c conda-forge gap-defaults==4.11 python==3.8 256 | $ pip install . 257 | 258 | Alternatively, you can create the conda environment using the supplied 259 | `environment.yml 260 | `_ file: 261 | 262 | .. code-block:: shell 263 | 264 | $ conda env create 265 | 266 | .. note:: 267 | 268 | With Conda and other distributions that install libgap to a standard 269 | system location (e.g. ``/usr/lib/libgap.so``) it may not be necessary to 270 | set the ``GAP_ROOT`` environment variable, as the library can locate 271 | your GAP root automatically in most cases. 272 | 273 | .. warning:: 274 | 275 | The conda package for GAP 4.11 had dependency conflicts with Python 3.7 276 | so you must use Python 3.8 or above, or GAP 4.10.2 with Python 3.7. 277 | 278 | Cygwin installation 279 | ------------------- 280 | 281 | Additional notes for installation on Cygwin: 282 | 283 | * The dependency ``psutil`` does not support Cygwin. However, there is an 284 | unofficial fork which does at: 285 | https://github.com/embray/psutil/tree/cygwin/v3. You can install it by 286 | running: 287 | 288 | .. code-block:: shell 289 | 290 | $ pip install git+https://github.com/embray/psutil.git@cygwin/v3 291 | 292 | * The path to the libgap DLL (filename ``cyggap-0.dll``) needs to be on 293 | your ``PATH`` environment variable in order for gappy to be importable. 294 | To do this you can either copy it from your GAP installation to a standard 295 | location like: 296 | 297 | .. code-block:: shell 298 | 299 | $ cp /path/to/gap_root/.libs/cyggap-0.dll /usr/local/bin 300 | 301 | or you can modify your environment to point to where GAP places the built 302 | DLL: 303 | 304 | .. code-block:: shell 305 | 306 | $ export PATH="/path/to/gap_root/.libs:$PATH" 307 | 308 | and add this to your ``.profile``. 309 | 310 | .. |logo| image:: https://raw.githubusercontent.com/embray/gappy/master/docs/images/gappy-logo.svg.png 311 | :alt: gappy logo 312 | :align: middle 313 | 314 | .. |docs-badge| image:: https://readthedocs.org/projects/gappy/badge/?version=latest 315 | :target: https://gappy.readthedocs.io/en/latest/?badge=latest 316 | :alt: Documentation Status 317 | 318 | .. |tests-badge| image:: https://github.com/embray/gappy/workflows/Tests/badge.svg 319 | :target: https://github.com/embray/gappy/actions?query=workflow%3ATests 320 | :alt: Test Status 321 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | /* Fix lack of margin between object docs. */ 2 | dl { 3 | margin-bottom: 20px 4 | } 5 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Documentation 2 | ################# 3 | 4 | .. contents:: 5 | :local: 6 | :depth: 3 7 | 8 | .. py:data:: gap 9 | :type: gappy.core.Gap 10 | 11 | The default GAP interpreter instance. Most users can run:: 12 | 13 | >>> from gappy import gap 14 | 15 | and immediately begin using GAP from here. However, if you wish to 16 | customize the initialization parameters of the GAP interpreter (e.g. 17 | set the ``gap_root`` path) you can run:: 18 | 19 | >>> from gappy import Gap 20 | >>> gap = Gap(...) 21 | 22 | .. note:: 23 | 24 | Upon first using ``gap``, whether to access a global variable run 25 | a function, there may be a noticeable delay upon GAP initialization; 26 | after the first use it will be faster. 27 | 28 | ``gappy.core`` 29 | ============== 30 | 31 | .. automodule:: gappy.core 32 | :members: 33 | 34 | 35 | ``gappy.gapobj`` 36 | ================ 37 | 38 | .. automodule:: gappy.gapobj 39 | :members: 40 | 41 | 42 | ``gappy.exceptions`` 43 | ==================== 44 | 45 | .. automodule:: gappy.exceptions 46 | :members: 47 | 48 | 49 | ``gappy.context_managers`` 50 | ========================== 51 | 52 | .. automodule:: gappy.context_managers 53 | :members: 54 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # https://www.sphinx-doc.org/en/master/usage/configuration.html 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | sys.path.insert(0, os.path.abspath('.')) 16 | 17 | 18 | # -- Project information ----------------------------------------------------- 19 | 20 | project = 'gappy' 21 | copyright = '2021, E. Madison Bray' 22 | author = 'E. Madison Bray' 23 | 24 | # The full version, including alpha/beta/rc tags 25 | from gappy import __version__ 26 | release = __version__ 27 | 28 | 29 | # -- General configuration --------------------------------------------------- 30 | 31 | default_role = 'py:obj' 32 | 33 | # Add any Sphinx extension module names here, as strings. They can be 34 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 35 | # ones. 36 | extensions = [ 37 | 'sphinx.ext.autodoc', 38 | 'sphinx.ext.intersphinx', 39 | 'sphinx.ext.napoleon' 40 | ] 41 | 42 | # Add any paths that contain templates here, relative to this directory. 43 | templates_path = ['_templates'] 44 | 45 | # List of patterns, relative to source directory, that match files and 46 | # directories to ignore when looking for source files. 47 | # This pattern also affects html_static_path and html_extra_path. 48 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 49 | 50 | 51 | # -- Options for HTML output ------------------------------------------------- 52 | 53 | # The theme to use for HTML and HTML Help pages. See the documentation for 54 | # a list of builtin themes. 55 | # 56 | html_theme = 'alabaster' 57 | 58 | # Add any paths that contain custom static files (such as style sheets) here, 59 | # relative to this directory. They are copied after the builtin static files, 60 | # so a file named "default.css" will overwrite the builtin "default.css". 61 | html_static_path = ['_static'] 62 | html_css_files = ['custom.css'] 63 | html_logo = 'images/gappy-logo.svg.png' 64 | 65 | 66 | # -- Options for intersphinx ------------------------------------------------- 67 | 68 | intersphinx_mapping = { 69 | 'python': ('https://docs.python.org/3/', None) 70 | } 71 | -------------------------------------------------------------------------------- /docs/images/gappy-logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 25 | 27 | 30 | 34 | 38 | 39 | 48 | 51 | 55 | 59 | 60 | 69 | 75 | 80 | 86 | 90 | 91 | 101 | 106 | 114 | 118 | 119 | 129 | 135 | 136 | 142 | 147 | 153 | 157 | 158 | 168 | 173 | 181 | 185 | 186 | 196 | 202 | 207 | 213 | 219 | 224 | 229 | 235 | 236 | 237 | 263 | 268 | 269 | 271 | 272 | 274 | image/svg+xml 275 | 277 | 278 | 279 | 280 | 281 | 287 | 295 | 303 | 304 | 310 | 313 | 318 | 323 | 324 | 325 | 331 | 336 | 337 | 361 | 366 | 372 | 376 | 380 | 384 | 388 | 392 | 393 | 394 | 395 | -------------------------------------------------------------------------------- /docs/images/gappy-logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/embray/gappy/579e410021b107582c8ea2d5c2b04d310d3efc24/docs/images/gappy-logo.svg.png -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../README.rst 2 | :start-line: 3 3 | 4 | 5 | Full documentation 6 | ================== 7 | 8 | .. toctree:: 9 | :maxdepth: 2 10 | :caption: Contents: 11 | 12 | api 13 | 14 | 15 | Changelog 16 | ========= 17 | 18 | .. include:: ../CHANGES.rst 19 | :start-line: 3 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | 15 | %SPHINXBUILD% >NUL 2>NUL 16 | if errorlevel 9009 ( 17 | echo. 18 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 19 | echo.installed, then set the SPHINXBUILD environment variable to point 20 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 21 | echo.may add the Sphinx directory to PATH. 22 | echo. 23 | echo.If you don't have Sphinx installed, grab it from 24 | echo.http://sphinx-doc.org/ 25 | exit /b 1 26 | ) 27 | 28 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 29 | goto end 30 | 31 | :help 32 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 33 | 34 | :end 35 | popd 36 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | # Conda environment file 2 | # Use `conda env create` in this directory to create a conda environment 3 | # suitable for installing and running gappy; the default name of the 4 | # environment will be "gappy" unless `-n ` is also passed to `conda 5 | # env create` 6 | name: gappy 7 | channels: 8 | - conda-forge 9 | dependencies: 10 | - gap-defaults=4.11 11 | - python=3.8 12 | -------------------------------------------------------------------------------- /gappy/__init__.py: -------------------------------------------------------------------------------- 1 | from .version import version as __version__ 2 | from .core import gap, Gap 3 | 4 | 5 | __all__ = ['gap', 'Gap'] 6 | -------------------------------------------------------------------------------- /gappy/_dev/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Utilities used only for build and development of gappy. 3 | 4 | This package is excluded from installations. 5 | """ 6 | -------------------------------------------------------------------------------- /gappy/_dev/scm_version.py: -------------------------------------------------------------------------------- 1 | import os 2 | import os.path as pth 3 | import warnings 4 | try: 5 | from setuptools_scm import get_version 6 | if 'CI' in os.environ: 7 | # ignore warning about shallow git repositories when running 8 | # CI builds where this is not so important I think 9 | warnings.filterwarnings('ignore', '.*is shallow and may cause errors') 10 | version = get_version(root=pth.join('..', '..'), relative_to=__file__) 11 | except Exception: 12 | raise ImportError( 13 | 'setuptools_scm broken or not installed; could not determine package ' 14 | 'version') 15 | -------------------------------------------------------------------------------- /gappy/conftest.py: -------------------------------------------------------------------------------- 1 | """Additional pytest configuration.""" 2 | 3 | import pytest 4 | 5 | 6 | @pytest.fixture(autouse=True) 7 | def inject_globals(doctest_namespace): 8 | """ 9 | Make certain variables available globally to all doctests; in particular 10 | the global `~gappy.core.Gap` instance ``gap`` which is used in most tests. 11 | """ 12 | 13 | from gappy import gap 14 | doctest_namespace['gap'] = gap 15 | -------------------------------------------------------------------------------- /gappy/context_managers.py: -------------------------------------------------------------------------------- 1 | """ 2 | Context Managers for gappy. 3 | 4 | This module implements a context manager for global variables. This is 5 | useful since the behavior of GAP is sometimes controlled by global 6 | variables, which you might want to switch to a different value for a 7 | computation. Here is an example how you are suppose to use it from 8 | your code. First, let us set a dummy global variable for our example:: 9 | 10 | >>> gap.set_global('FooBar', 123) 11 | 12 | Then, if you want to switch the value momentarily you can write:: 13 | 14 | >>> with gap.global_context('FooBar', 'test'): 15 | ... print(gap.get_global('FooBar')) 16 | test 17 | 18 | Afterward, the global variable reverts to the previous value:: 19 | 20 | >>> print(gap.get_global('FooBar')) 21 | 123 22 | 23 | The value is reset even if exceptions occur:: 24 | 25 | >>> with gap.global_context('FooBar', 'test'): 26 | ... print(gap.get_global('FooBar')) 27 | ... raise ValueError(gap.get_global('FooBar')) 28 | Traceback (most recent call last): 29 | ... 30 | ValueError: test 31 | >>> print(gap.get_global('FooBar')) 32 | 123 33 | """ 34 | 35 | 36 | ############################################################################### 37 | # Copyright (C) 2012, Volker Braun 38 | # Copyright (C) 2021, E. Madison Bray 39 | # 40 | # Distributed under the terms of the GNU General Public License (GPL) 41 | # as published by the Free Software Foundation; either version 2 of 42 | # the License, or (at your option) any later version. 43 | # http://www.gnu.org/licenses/ 44 | ############################################################################### 45 | 46 | 47 | __all__ = ['GlobalVariableContext'] 48 | 49 | 50 | class GlobalVariableContext: 51 | """ 52 | Context manager for GAP global variables. 53 | 54 | It is recommended that you use the :meth:`~gappy.core.Gap.global_context` 55 | method and not construct objects of this class manually. 56 | 57 | Parameters 58 | ---------- 59 | variable : str 60 | The GAP variable name. 61 | value 62 | Anything that defines or can be converted to a GAP object. 63 | 64 | Examples 65 | -------- 66 | 67 | >>> gap.set_global('FooBar', 1) 68 | >>> with gap.global_context('FooBar', 2): 69 | ... print(gap.get_global('FooBar')) 70 | 2 71 | >>> gap.get_global('FooBar') 72 | 1 73 | """ 74 | 75 | def __init__(self, gap, variable, value): 76 | self._gap = gap 77 | self._variable = variable 78 | self._new_value = value 79 | 80 | def __enter__(self): 81 | """ 82 | Called when entering the with-block 83 | 84 | Examples 85 | -------- 86 | 87 | >>> gap.set_global('FooBar', 1) 88 | >>> with gap.global_context('FooBar', 2): 89 | ... print(gap.get_global('FooBar')) 90 | 2 91 | >>> gap.get_global('FooBar') 92 | 1 93 | """ 94 | self._old_value = self._gap.get_global(self._variable) 95 | self._gap.set_global(self._variable, self._new_value) 96 | 97 | def __exit__(self, exc_type, exc_val, exc_tb): 98 | """ 99 | Called when exiting the with-block 100 | 101 | Examples 102 | -------- 103 | 104 | >>> gap.set_global('FooBar', 1) 105 | >>> with gap.global_context('FooBar', 2): 106 | ... print(gap.get_global('FooBar')) 107 | 2 108 | >>> gap.get_global('FooBar') 109 | 1 110 | """ 111 | self._gap.set_global(self._variable, self._old_value) 112 | return False 113 | -------------------------------------------------------------------------------- /gappy/core.pxd: -------------------------------------------------------------------------------- 1 | #***************************************************************************** 2 | # Copyright (C) 2012 Volker Braun 3 | # Copyright (C) 2021 E. Madison Bray 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # http://www.gnu.org/licenses/ 10 | #***************************************************************************** 11 | 12 | from .gap_includes cimport Obj 13 | from .gmp cimport gmp_randstate_t 14 | 15 | ############################################################################ 16 | ### Hooking into the GAP memory management ################################# 17 | ############################################################################ 18 | 19 | cdef class ObjWrapper(object): 20 | cdef Obj value 21 | 22 | cdef ObjWrapper wrap_obj(Obj obj) 23 | 24 | # returns the refcount dictionary for debugging purposes 25 | cpdef get_owned_objects() 26 | 27 | # Reference count GAP objects that you want to prevent from being 28 | # garbage collected 29 | cdef void reference_obj(Obj obj) 30 | cdef void dereference_obj(Obj obj) 31 | 32 | 33 | ############################################################################ 34 | ### Initialization of GAP ################################################## 35 | ############################################################################ 36 | 37 | 38 | cdef class Gap: 39 | cdef dict _init_kwargs 40 | cdef readonly tuple supported_builtins 41 | cdef readonly dict _convert_from_registry 42 | cdef gmp_randstate_t _gmp_state 43 | cpdef initialize(self) 44 | cpdef eval(self, gap_command) 45 | cpdef get_global(self, variable) 46 | -------------------------------------------------------------------------------- /gappy/core.pyx: -------------------------------------------------------------------------------- 1 | """Top-level Python interface to GAP.""" 2 | 3 | #***************************************************************************** 4 | # Copyright (C) 2012 Volker Braun 5 | # Copyright (C) 2021 E. Madison Bray 6 | # 7 | # This program is free software: you can redistribute it and/or modify 8 | # it under the terms of the GNU General Public License as published by 9 | # the Free Software Foundation, either version 2 of the License, or 10 | # (at your option) any later version. 11 | # http://www.gnu.org/licenses/ 12 | #***************************************************************************** 13 | 14 | from libc.signal cimport signal, SIGCHLD, SIG_DFL 15 | from cpython.exc cimport PyErr_Fetch, PyErr_Restore 16 | from cpython.object cimport Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT, Py_GE 17 | from cpython.ref cimport PyObject, Py_XINCREF, Py_XDECREF 18 | from cysignals.signals cimport sig_on, sig_off 19 | 20 | import locale 21 | import os 22 | import re 23 | import sys 24 | import warnings 25 | from functools import lru_cache 26 | from numbers import Integral, Rational, Real 27 | 28 | from .context_managers import GlobalVariableContext 29 | from .exceptions import GAPError 30 | from .gap_globals import common_gap_globals as GAP_GLOBALS 31 | from .gap_includes cimport * 32 | from .gapobj cimport * 33 | from .gmp cimport * 34 | from .utils import (get_gap_memory_pool_size, get_gap_root, _SPECIAL_ATTRS, 35 | _FS_ENCODING, _converter_for_type) 36 | 37 | 38 | ############################################################################ 39 | ### Hooking into the GAP memory management ################################# 40 | ############################################################################ 41 | 42 | 43 | cdef class ObjWrapper(object): 44 | """ 45 | Wrapper for GAP master pointers 46 | 47 | Examples 48 | -------- 49 | 50 | >>> from gappy.core import ObjWrapper 51 | >>> x = ObjWrapper() 52 | >>> y = ObjWrapper() 53 | >>> x == y 54 | True 55 | """ 56 | 57 | def __richcmp__(ObjWrapper self, ObjWrapper other, int op): 58 | r""" 59 | Comparison wrapped Obj. 60 | 61 | Parameters 62 | ---------- 63 | 64 | other : `ObjWrapper` 65 | The other `ObjWrapper` to compare to. 66 | 67 | op : int 68 | The comparison operation to be performed. 69 | 70 | Returns 71 | ------- 72 | 73 | bool 74 | The result of the comparison. 75 | 76 | Examples 77 | -------- 78 | 79 | >>> from gappy.core import ObjWrapper 80 | >>> x = ObjWrapper() 81 | >>> y = ObjWrapper() 82 | >>> x == y 83 | True 84 | """ 85 | cdef result 86 | cdef Obj self_value = self.value 87 | cdef Obj other_value = other.value 88 | if op == Py_LT: 89 | return self_value < other_value 90 | elif op == Py_LE: 91 | return self_value <= other_value 92 | elif op == Py_EQ: 93 | return self_value == other_value 94 | elif op == Py_GT: 95 | return self_value > other_value 96 | elif op == Py_GE: 97 | return self_value >= other_value 98 | elif op == Py_NE: 99 | return self_value != other_value 100 | else: 101 | assert False # unreachable 102 | 103 | def __hash__(self): 104 | """ 105 | Return a hash value 106 | 107 | Examples 108 | -------- 109 | 110 | >>> from gappy.core import ObjWrapper 111 | >>> x = ObjWrapper() 112 | >>> hash(x) 113 | 0 114 | """ 115 | return (self.value) 116 | 117 | 118 | cdef ObjWrapper wrap_obj(Obj obj): 119 | """ 120 | Constructor function for :class:`ObjWrapper` 121 | """ 122 | cdef ObjWrapper result = ObjWrapper.__new__(ObjWrapper) 123 | result.value = obj 124 | return result 125 | 126 | 127 | # a dictionary to keep all GAP elements 128 | # needed for GASMAN callbacks 129 | # 130 | cdef dict owned_objects_refcount = dict() 131 | 132 | # 133 | # used in Gap.count_GAP_objects 134 | # 135 | cpdef get_owned_objects(): 136 | """ 137 | Helper to access the refcount dictionary from Python code 138 | """ 139 | return owned_objects_refcount 140 | 141 | 142 | cdef void reference_obj(Obj obj): 143 | """ 144 | Reference ``obj`` 145 | """ 146 | cdef ObjWrapper wrapped = wrap_obj(obj) 147 | global owned_objects_refcount 148 | # print("reference_obj called "+ crepr(obj) +"\n") 149 | if wrapped in owned_objects_refcount: 150 | owned_objects_refcount[wrapped] += 1 151 | else: 152 | owned_objects_refcount[wrapped] = 1 153 | 154 | 155 | cdef void dereference_obj(Obj obj): 156 | """ 157 | Reference ``obj`` 158 | """ 159 | cdef ObjWrapper wrapped = wrap_obj(obj) 160 | global owned_objects_refcount 161 | refcount = owned_objects_refcount.pop(wrapped) 162 | if refcount > 1: 163 | owned_objects_refcount[wrapped] = refcount - 1 164 | 165 | 166 | cdef void gasman_callback() with gil: 167 | """ 168 | Callback before each GAP garbage collection 169 | """ 170 | global owned_objects_refcount 171 | for obj in owned_objects_refcount: 172 | GAP_MarkBag((obj).value) 173 | 174 | 175 | ############################################################################ 176 | ### Initialization of GAP ################################################## 177 | ############################################################################ 178 | 179 | 180 | # To ensure that we call initialize_libgap only once. 181 | cdef bint _gap_is_initialized = False 182 | cdef Gap _gap_instance = None 183 | 184 | 185 | cdef char* _reset_error_output_cmd = r"""\ 186 | \$GAPPY_ERROUT := ""; 187 | MakeReadWriteGlobal("ERROR_OUTPUT"); 188 | ERROR_OUTPUT := OutputTextString(\$GAPPY_ERROUT, false); 189 | SetPrintFormattingStatus(ERROR_OUTPUT, false); 190 | MakeReadOnlyGlobal("ERROR_OUTPUT"); 191 | """ 192 | 193 | cdef char* _close_error_output_cmd = """\ 194 | CloseStream(ERROR_OUTPUT); 195 | MakeReadWriteGlobal("ERROR_OUTPUT"); 196 | ERROR_OUTPUT := "*errout*"; 197 | MakeReadOnlyGlobal("ERROR_OUTPUT"); 198 | MakeImmutable(\$GAPPY_ERROUT); 199 | """ 200 | 201 | 202 | # TODO: Change autoload=True by default 203 | cdef initialize(gap_root=None, gaprc=None, workspace=None, workspace_valid=False, autoload=False): 204 | """ 205 | Initialize the GAP library, if it hasn't already been initialized. 206 | 207 | It is safe to call this multiple times. 208 | """ 209 | 210 | global _gap_is_initialized 211 | 212 | if _gap_is_initialized: 213 | return 214 | 215 | gap_root = get_gap_root(gap_root=gap_root) 216 | 217 | # Define argv variable, which we will pass in to 218 | # initialize GAP. Note that we must pass define the memory pool 219 | # size! 220 | cdef char* argv[19] 221 | cdef int argc = 14 222 | 223 | argv[0] = '' 224 | argv[1] = '-l' 225 | _gap_root = gap_root.encode(_FS_ENCODING, 'surrogateescape') 226 | argv[2] = _gap_root 227 | 228 | memory_pool = get_gap_memory_pool_size().encode('ascii') 229 | argv[3] = '-o' 230 | argv[4] = memory_pool 231 | argv[5] = '-s' 232 | argv[6] = memory_pool 233 | 234 | argv[7] = '-m' 235 | argv[8] = '64m' 236 | 237 | argv[9] = '-q' # no prompt! 238 | argv[10] = '-E' # don't use readline as this will interfere with Python 239 | argv[11] = '--nointeract' # Implies -T 240 | argv[12] = '-x' # set the "screen" width so that GAP is less likely to 241 | argv[13] = '4096' # insert newlines when printing objects 242 | # 4096 unfortunately is the hard-coded max, but should 243 | # be long enough for most cases 244 | 245 | if not autoload: 246 | argv[argc] = '-A' 247 | argc += 1 248 | 249 | if workspace_valid: 250 | # Try opening the workspace file, raising the appropriate OSError 251 | # if not found/readable 252 | workspace = os.path.normpath(workspace) 253 | 254 | with open(workspace, 'rb'): 255 | pass 256 | 257 | workspace_ = workspace.encode(_FS_ENCODING, 'surrogateescape') 258 | argv[argc] = "-L" 259 | argv[argc + 1] = workspace_ 260 | argc += 2 261 | 262 | if gaprc is not None: 263 | # Try opening the gaprc file, raising the appropriate OSError 264 | # if not found/readable 265 | gaprc = os.path.normpath(gaprc) 266 | 267 | with open(gaprc, 'rb'): 268 | pass 269 | 270 | gaprc_ = gaprc.encode(_FS_ENCODING, 'surrogateescape') 271 | argv[argc] = gaprc_ 272 | argc += 1 273 | 274 | # argv[argc] must be NULL 275 | argv[argc] = NULL 276 | 277 | sig_on() 278 | # Initialize GAP but disable their SIGINT handler 279 | GAP_Initialize(argc, argv, gasman_callback, error_handler, 280 | handleSignals=False) 281 | sig_off() 282 | 283 | # Disable GAP's SIGCHLD handler ChildStatusChanged(), which calls 284 | # waitpid() on random child processes. 285 | signal(SIGCHLD, SIG_DFL) 286 | 287 | # Set the ERROR_OUTPUT global in GAP to an output stream in which to 288 | # receive error output 289 | GAP_EvalString(_reset_error_output_cmd) 290 | 291 | # Finished! 292 | _gap_is_initialized = True 293 | 294 | # Return a dict of the final initialization args (after handling defaults) 295 | return { 296 | 'gap_root': gap_root, 297 | 'gaprc': gaprc, 298 | 'workspace': workspace, 299 | 'autoload': autoload 300 | } 301 | 302 | 303 | ############################################################################ 304 | ### Evaluate string in GAP ################################################# 305 | ############################################################################ 306 | 307 | cdef Obj gap_eval(str gap_string) except? NULL: 308 | r""" 309 | Evaluate a string in GAP. 310 | 311 | Parameters 312 | ---------- 313 | 314 | gap_string : str 315 | A valid statement in GAP. 316 | 317 | Returns 318 | ------- 319 | GapObj 320 | The resulting GAP object or NULL+Python Exception in case of error. 321 | The result object may also be NULL without a Python exception set for 322 | statements that do not return a value. 323 | 324 | Raises 325 | ------ 326 | GAPError 327 | If there was any error in evaluating the statement, be it a syntax 328 | error, an error in the arguments, etc. 329 | 330 | Examples 331 | -------- 332 | 333 | >>> gap.eval('if 4>3 then\nPrint("hi");\nfi') 334 | >>> gap.eval('1+1') # testing that we have successfully recovered 335 | 2 336 | 337 | >>> gap.eval('if 4>3 thenPrint("hi");\nfi') 338 | Traceback (most recent call last): 339 | ... 340 | gappy.exceptions.GAPError: Syntax error: then expected in stream:1 341 | if 4>3 thenPrint("hi"); 342 | ^^^^^^^^^ 343 | >>> gap.eval('1+1') # testing that we have successfully recovered 344 | 2 345 | 346 | Tests 347 | ^^^^^ 348 | 349 | A bad eval string that results in multiple statement evaluations by GAP 350 | and hence multiple errors should still result in a single exception 351 | with a message capturing all errors that occurrer: 352 | 353 | >>> gap.eval('Complex Field with 53 bits of precision;') 354 | Traceback (most recent call last): 355 | ... 356 | gappy.exceptions.GAPError: Error, Variable: 'Complex' must have a value 357 | Syntax error: ; expected in stream:1 358 | Complex Field with 53 bits of precision;; 359 | ^^^^^^^^^^^^ 360 | Error, Variable: 'with' must have a value 361 | Syntax error: ; expected in stream:1 362 | Complex Field with 53 bits of precision;; 363 | ^^^^^^^^^^^^^^^^^^^^ 364 | Error, Variable: 'bits' must have a value 365 | Syntax error: ; expected in stream:1 366 | Complex Field with 53 bits of precision;; 367 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 368 | Error, Variable: 'precision' must have a value 369 | 370 | Test that on a subsequent attempt we get the same message (no garbage was 371 | left in the error stream): 372 | 373 | >>> gap.eval('Complex Field with 53 bits of precision;') 374 | Traceback (most recent call last): 375 | ... 376 | gappy.exceptions.GAPError: Error, Variable: 'Complex' must have a value 377 | ... 378 | Error, Variable: 'precision' must have a value 379 | 380 | >>> gap.eval('1+1') # test that we successfully recover 381 | 2 382 | """ 383 | 384 | cdef Obj result 385 | cdef int i, j, nresults 386 | cdef bytes cmd 387 | 388 | # Careful: We need to keep a reference to the bytes object here 389 | # so that Cython doesn't deallocate it before GAP is done with 390 | # its contents. 391 | cmd = (gap_string.strip() + ';\n').encode('utf-8') 392 | sig_on() 393 | try: 394 | GAP_Enter() 395 | result = GAP_EvalString(cmd) 396 | # We can assume that the result object is a GAP PList (plain list) 397 | # and we should use functions for PLists directly for now; see 398 | # https://github.com/gap-system/gap/pull/2988/files#r233021437 399 | 400 | # If an error occurred in GAP_EvalString we won't even get 401 | # here if the error handler was set; but in case it wasn't 402 | # let's still check the result... 403 | nresults = GAP_LenList(result) 404 | if nresults > 1: # to mimick the old libGAP 405 | # TODO: Get rid of this restriction eventually? 406 | raise GAPError("can only evaluate a single statement") 407 | 408 | # Get the result of the first statement 409 | result = GAP_ElmList(result, 1) # 1-indexed! 410 | 411 | if GAP_ElmList(result, 1) != GAP_True: 412 | # An otherwise unhandled error occurred in GAP (such as a 413 | # syntax error). Try running the error handler manually 414 | # to capture the error output, if any. 415 | # This should result in a GAPError being set. 416 | error_handler_check_exception() 417 | 418 | # The actual resultant object, if any, is in the second entry 419 | # (which may be unassigned--see previous github comment; in this case 420 | # 0 is returned without setting a a Python exception, so we should treat 421 | # this like returning None) 422 | 423 | return GAP_ElmList(result, 2) 424 | finally: 425 | GAP_Leave() 426 | sig_off() 427 | 428 | 429 | ############################################################################ 430 | ### Error handler ########################################################## 431 | ############################################################################ 432 | 433 | cdef str extract_errout(): 434 | """ 435 | Reads the global variable $GAPPY_ERROUT and returns a Python string 436 | containing the error message (with some boilerplate removed). 437 | """ 438 | cdef Obj r 439 | cdef char *msg 440 | 441 | r = GAP_ValueGlobalVariable("$GAPPY_ERROUT") 442 | 443 | # Grab a pointer to the C string underlying the GAP string $GAPPY_ERROUT 444 | # then copy it to a Python str 445 | msg = GAP_CSTR_STRING(r) 446 | if msg != NULL: 447 | msg_py = msg.decode('utf-8', 'surrogateescape') 448 | msg_py = msg_py.replace('For debugging hints type ?Recovery from ' 449 | 'NoMethodFound\n', '').strip() 450 | else: 451 | # Shouldn't happen but just in case... 452 | msg_py = "" 453 | 454 | return msg_py 455 | 456 | 457 | cdef void error_handler() with gil: 458 | """ 459 | The gappy error handler. 460 | 461 | If an error occurred, we raise a ``GAPError``; when the original 462 | ``GAP_EvalString`` returns, this exception will be seen. 463 | 464 | TODO: We should probably prevent re-entering this function if we 465 | are already handling an error; if there is an error in our stream 466 | handling code below it could result in a stack overflow. 467 | """ 468 | cdef PyObject* exc_type = NULL 469 | cdef PyObject* exc_val = NULL 470 | cdef PyObject* exc_tb = NULL 471 | 472 | try: 473 | GAP_EnterStack() 474 | 475 | # Close the error stream: this flushes any remaining output and 476 | # closes the stream for further writing; reset ERROR_OUTPUT to 477 | # something sane just in case (trying to print to a closed 478 | # stream segfaults GAP) 479 | GAP_EvalStringNoExcept(_close_error_output_cmd) 480 | 481 | # Fetch any existing exception before calling extract_errout() so that 482 | # the exception indicator is cleared 483 | PyErr_Fetch(&exc_type, &exc_val, &exc_tb) 484 | 485 | msg = extract_errout() 486 | # Sometimes error_handler() can be called multiple times 487 | # from a single GAP_EvalString call before it returns. 488 | # In this case, we just update the exception by appending 489 | # to the existing exception message 490 | if exc_type is GAPError and exc_val is not NULL: 491 | msg = str(exc_val) + '\n' + msg 492 | elif not msg: 493 | msg = "an unknown error occurred in GAP" 494 | 495 | # Raise an exception using PyErr_Restore(). 496 | # This way, we can keep any existing traceback object. 497 | # Note that we manually need to deal with refcounts here. 498 | Py_XDECREF(exc_type) 499 | Py_XDECREF(exc_val) 500 | exc_type = GAPError 501 | exc_val = msg 502 | Py_XINCREF(exc_type) 503 | Py_XINCREF(exc_val) 504 | PyErr_Restore(exc_type, exc_val, exc_tb) 505 | finally: 506 | # Reset ERROR_OUTPUT with a new text string stream 507 | GAP_EvalStringNoExcept(_reset_error_output_cmd) 508 | GAP_LeaveStack() 509 | 510 | 511 | cdef void error_handler_check_exception() except *: 512 | error_handler() 513 | 514 | 515 | ############################################################################ 516 | ### Gap ################################################################### 517 | ############################################################################ 518 | 519 | cdef class Gap: 520 | r""" 521 | The GAP interpreter object. 522 | 523 | .. note:: 524 | 525 | When initializing this class, it does not immediately initialize the 526 | underlying GAP interpreter unless passed ``autoinit=True``. Otherwise 527 | the GAP interpreter is not initialized until the first `Gap.eval` call 528 | or the first GAP global lookup. 529 | 530 | The default interpreter instance `gap` is initialized with some default 531 | parameters, but before its first use you may initialize your own `Gap` 532 | instance with different parameters. 533 | 534 | Parameters 535 | ---------- 536 | 537 | gap_root : `str` or `pathlib.Path` 538 | Path to the GAP installation (GAP_ROOT) you want to use for the GAP 539 | interpreter. This should be the path containing the ``lib/`` and 540 | ``pkg/`` directories for the standard GAP libraries. Equivalent to 541 | the ``-l`` command-line argument to ``gap``. 542 | gaprc : `str` or `pathlib.Path` 543 | A GAP "runtime config" file containing GAP commands to run immediately 544 | upon GAP interpreter startup. Equivalent to passing a GAP file to 545 | ``gap`` on the command-line. 546 | workspace : `str` or `pathlib.Path` 547 | An existing GAP workspace to restore upon interpreter startup. 548 | Equivalent to the ``-L`` command-line argument to ``gap``. 549 | autoinit : bool 550 | Immediately initialize the GAP interpreter when initializing this 551 | `Gap` instance. Otherwise the interpreter is initialized "lazily" 552 | when the first interaction with the interpreter is needed (either 553 | an `~Gap.eval` call or global variable lookup) (default: `False`). 554 | autoload : bool 555 | Automatically load the default recommended GAP packages when starting 556 | the GAP interpreter. If `False` this is equivalent to passing the 557 | ``-A`` command-line argument to ``gap`` (default: `False`). 558 | 559 | Attributes 560 | ---------- 561 | supported_builtins : tuple 562 | The basic Python types for which gappy has built-in support for 563 | conversion to equivalent GAP objects; currently: `str`, `bytes`, 564 | `bool`, `int`, `numbers.Integral`, `numbers.Rational`, `float`, 565 | `numbers.Real`, `list`, `tuple`, `dict`, `None`. 566 | 567 | Examples 568 | -------- 569 | 570 | >>> gap.eval('SymmetricGroup(4)') 571 | Sym( [ 1 .. 4 ] ) 572 | """ 573 | 574 | def __cinit__(self): 575 | # NOTE: When updating this please also update the docstring above; 576 | # unfortunately there is no straightforward way for Sphinx to extract 577 | # its value automatically. 578 | self.supported_builtins = ( 579 | str, bytes, bool, int, Integral, Rational, float, Real, list, 580 | tuple, dict, type(None) 581 | ) 582 | self._init_kwargs = {} 583 | self._convert_from_registry = {} 584 | gmp_randinit_default(self._gmp_state) 585 | 586 | cpdef initialize(self): 587 | """ 588 | Manually initialize the underlying GAP interpreter if it is has not 589 | already been automatically initialized. 590 | 591 | Returns `True` if this initialized the GAP interpreter for the first 592 | time, or `False` if the interpreter was already initialized. 593 | """ 594 | 595 | global _gap_instance 596 | 597 | if _gap_is_initialized: 598 | if _gap_instance is not self: 599 | raise RuntimeError( 600 | 'a different Gap instance has already been initialized; ' 601 | 'only one Gap instance can be used at a time') 602 | return False 603 | 604 | self._init_kwargs.update(initialize( 605 | gap_root=self._init_kwargs['gap_root'], 606 | gaprc=self._init_kwargs['gaprc'], 607 | workspace=self._init_kwargs['workspace'], 608 | workspace_valid=self._init_kwargs['workspace_valid'], 609 | autoload=self._init_kwargs['autoload'] 610 | )) 611 | _gap_instance = self 612 | return True 613 | 614 | def __init__(self, gap_root=None, gaprc=None, workspace=None, workspace_valid=False, 615 | autoinit=False, autoload=False): 616 | if _gap_is_initialized: 617 | raise RuntimeError( 618 | "the GAP interpreter has already been initialized; only one " 619 | "GAP interpreter may be initialized in the current process") 620 | 621 | self._init_kwargs.update({ 622 | 'gap_root': gap_root, 623 | 'gaprc': gaprc, 624 | 'workspace': workspace, 625 | 'workspace_valid': workspace_valid, 626 | 'autoload': autoload 627 | }) 628 | 629 | if autoinit: 630 | self.initialize() 631 | 632 | def __call__(self, x): 633 | r""" 634 | Construct GapObj instances from a given object that either has a 635 | registered converter or a ``__gap__`` or ``__gap_eval__`` method. 636 | 637 | Parameters 638 | ---------- 639 | 640 | x 641 | Any Python object that can be converted to a GAP object. For the 642 | list of types that have built-in support for conversion to GAP 643 | objects, see `.supported_builtins`. 644 | 645 | Returns 646 | ------- 647 | 648 | `GapObj` 649 | 650 | Examples 651 | -------- 652 | 653 | >>> gap(0) 654 | 0 655 | >>> gap([]) 656 | [ ] 657 | >>> gap({}) 658 | rec( ) 659 | >>> gap(False) 660 | false 661 | >>> gap('') 662 | "" 663 | >>> gap(None) 664 | NULL 665 | 666 | A class with a ``__gap__`` method to convert itself to an equivalent 667 | `~gappy.gapobj.GapObj`; it is also passed the active `Gap` instance: 668 | 669 | >>> class MyGroup: 670 | ... def __gap__(self, gap): 671 | ... return gap.SymmetricGroup(3) 672 | ... 673 | >>> gap(MyGroup()) 674 | Sym( [ 1 .. 3 ] ) 675 | 676 | A class with a ``__gap_eval__`` method; same concept but returns a 677 | string containing any arbitrary GAP code for initializing the object: 678 | 679 | >>> class MyGroup2: 680 | ... def __gap_eval__(self): 681 | ... return 'SymmetricGroup(3)' 682 | ... 683 | >>> gap(MyGroup2()) 684 | Sym( [ 1 .. 3 ] ) 685 | 686 | .. note:: 687 | 688 | Both the ``__gap__`` method and any converter function registered 689 | with `Gap.convert_from` may return either a `~gappy.gapobj.GapObj` 690 | *or* one of the built-in types in `Gap.supported_builtins` which is 691 | then in turn converted to the appropriate GAP object. 692 | 693 | """ 694 | self.initialize() 695 | if isinstance(x, GapObj): 696 | return x 697 | elif isinstance(x, (str, bytes)): 698 | return make_GapString(self, make_gap_string(x)) 699 | elif isinstance(x, bool): 700 | # attention: must come before int 701 | return make_GapBoolean(self, GAP_True if x else GAP_False) 702 | elif isinstance(x, (int, Integral)): 703 | return make_GapInteger(self, make_gap_integer(int(x))) 704 | elif isinstance(x, Rational): 705 | num, denom = x.numerator, x.denominator 706 | # Special hack for Sage which doesn't implement the Rational 707 | # ABC correctly; see https://trac.sagemath.org/ticket/28234 708 | if callable(num): 709 | num = num() 710 | if callable(denom): 711 | denom = denom() 712 | return self(num) / self(denom) 713 | elif isinstance(x, (list, tuple)): 714 | return make_GapList(self, make_gap_list(self, x)) 715 | elif isinstance(x, dict): 716 | return make_GapRecord(self, make_gap_record(self, x)) 717 | elif isinstance(x, (float, Real)): 718 | return make_GapFloat(self, make_gap_float(float(x))) 719 | elif x is None: 720 | return make_GapObj(self, NULL) 721 | else: 722 | converter = _converter_for_type(self._convert_from_registry, 723 | type(x)) 724 | 725 | if converter is None: 726 | if hasattr(x, '__gap__'): 727 | ret = x.__gap__(self) 728 | elif hasattr(x, '__gap_eval__'): 729 | gap_str = str(x.__gap_eval__()) 730 | ret = make_any_gap_obj(self, gap_eval(gap_str)) 731 | else: 732 | raise ValueError( 733 | f'could not convert {x} to a GAP object') 734 | else: 735 | ret = converter(self, x) 736 | 737 | if not isinstance(ret, GapObj): 738 | if isinstance(ret, self.supported_builtins): 739 | return self.__call__(ret) 740 | 741 | builtins = ', '.join( 742 | t.__name__ for t in self.supported_builtins) 743 | raise ValueError( 744 | f'converter for {type(x).__name__} must return an ' 745 | f'instance of GapObj or one of {builtins}; ' 746 | f'got {type(ret).__name__}') 747 | return ret 748 | 749 | def convert_from(self, cls): 750 | """ 751 | Decorator for registering a converter from a Python type to any type of 752 | `.GapObj`. 753 | 754 | This allows providing converters for objects that do not have a 755 | ``_gap_`` or ``_gap_init_`` method. While it is preferable to use 756 | the special methods, this allows adapting classes that do not have 757 | these methods, without having to subclass or wrap them. 758 | 759 | The type must not be one of the types that already have built-in 760 | converters; see `Gap.supported_builtins`. 761 | 762 | When converting a Python object to a GAP object, first the built-in 763 | converters are tried. Then the registry of converters is checked for 764 | an *exact* type match. If no exact match is found, an `isinstance` 765 | check is performed for each type in the registry. 766 | 767 | The converter is any callable which is passed the `Gap` interpreter 768 | instance and the object to convert as its first two arguments, and must 769 | return a *either* `.GapObj` instance *or* one of the built-in types in 770 | `Gap.supported_builtins` which can be converted to the appropriate GAP 771 | object. This frees the converter function from having to directly 772 | construct a `.GapObj` itself, but also eliminates the possibility of 773 | infinite recursions when performing conversion. 774 | 775 | Examples 776 | -------- 777 | 778 | Say we have a class ``PermGroup`` which represents a permutation group 779 | as a list of permutation generators, themselves representing 780 | permutations as tuples (which happen to have the same syntax as GAP 781 | permutations, a fact we can exploit). This example does not give the 782 | full implementation of such a class, just the general data structure 783 | to demonstrate implementing the converter function: 784 | 785 | >>> class PermGroup: 786 | ... def __init__(self, *gens): 787 | ... self.gens = list(gens) 788 | ... 789 | >>> @gap.convert_from(PermGroup) 790 | ... def PermGroup_to_gap(gap, group): 791 | ... return gap.eval(f'Group({group.gens})') 792 | ... 793 | >>> group = PermGroup((1, 2), (1, 2, 3, 4, 5, 6, 7, 8)) 794 | >>> gap(group) 795 | Group([ (1,2), (1,2,3,4,5,6,7,8) ]) 796 | """ 797 | 798 | if isinstance(cls, self.supported_builtins): 799 | builtin_names = ', '.join(t.__name__ 800 | for t in self.supported_builtins) 801 | raise ValueError( 802 | f'type must not be a subclass of one of the types with ' 803 | f'built-in converters: {builtin_names}') 804 | 805 | if not isinstance(cls, type): 806 | raise ValueError('cls must be a Python class') 807 | 808 | def decorator(converter): 809 | if not callable(converter): 810 | # TODO: Maybe check the signature as well? 811 | raise ValueError( 812 | f'{type.__name__} converter {converter} must be callable') 813 | 814 | if cls in self._convert_from_registry: 815 | warnings.warn( 816 | f'{cls} already has a registered converter ' 817 | f'{self._convert_from_registry[cls]}; it will be ' 818 | f'replaced by {converter}') 819 | 820 | self._convert_from_registry[cls] = converter 821 | return converter 822 | 823 | return decorator 824 | 825 | @property 826 | def gap_root(self): 827 | """ 828 | The path to the GAP installation being used for this interpreter 829 | instance. 830 | 831 | Examples 832 | -------- 833 | 834 | >>> gap.gap_root # doctest: +IGNORE_OUTPUT 835 | '/path/to/gap_installation' 836 | """ 837 | 838 | return self._init_kwargs.get('gap_root') 839 | 840 | @property 841 | def gaprc(self): 842 | """ 843 | The path to the GAP runtime configuration file being used for this 844 | interpreter instance. 845 | 846 | Examples 847 | -------- 848 | 849 | >>> gap.gaprc # doctest: +IGNORE_OUTPUT 850 | '/path/to/gaprc' 851 | """ 852 | 853 | return self._init_kwargs.get('gaprc') 854 | 855 | @property 856 | def workspace(self): 857 | """ 858 | The path to the GAP workspace loaded by this interpreter instance at 859 | startup. 860 | 861 | Examples 862 | -------- 863 | 864 | >>> gap.workspace # doctest: +IGNORE_OUTPUT 865 | '/path/to/gaprc' 866 | """ 867 | 868 | return self._init_kwargs.get('workspace') 869 | 870 | cpdef eval(self, gap_command): 871 | """ 872 | Evaluate a gap command and wrap the result. 873 | 874 | Parameters 875 | ---------- 876 | 877 | gap_command : str 878 | A string containing a valid GAP command with or without the 879 | trailing semicolon. 880 | 881 | Returns 882 | ------- 883 | 884 | `.GapObj` 885 | The result of the GAP statement. 886 | 887 | Examples 888 | -------- 889 | 890 | >>> gap.eval('0') 891 | 0 892 | >>> gap.eval('"string"') 893 | "string" 894 | """ 895 | cdef GapObj elem 896 | 897 | if not isinstance(gap_command, str): 898 | gap_command = str(gap_command.__gap_eval__()) 899 | 900 | self.initialize() 901 | elem = make_any_gap_obj(self, gap_eval(gap_command)) 902 | 903 | # If the element is NULL just return None instead 904 | if elem.value == NULL: 905 | return None 906 | 907 | return elem 908 | 909 | _gap_function_re = re.compile(r'^\s*function\s*\(\s*\w*(\s*,\s*\w+)*\s*\)', 910 | re.M) 911 | """ 912 | Regular expression roughly matching the syntax for a GAP function 913 | declaration ``function([a, ...])``. Used for `Gap.gap_function`. 914 | """ 915 | 916 | def gap_function(self, func): 917 | """ 918 | Create GAP functions from decorated Python functions. 919 | 920 | Examples 921 | -------- 922 | 923 | The code for the GAP function is actually written in the Python 924 | function's docstring like so: 925 | 926 | >>> @gap.gap_function 927 | ... def one(): 928 | ... ''' 929 | ... Returns the multiplicative identity of the ring of integers. 930 | ... 931 | ... function() 932 | ... return 1; 933 | ... end; 934 | ... ''' 935 | ... 936 | >>> one 937 | 938 | >>> one() 939 | 1 940 | 941 | Any text in the docstring before the first line beginning the text 942 | ``function()`` is used as the function's docstring. Any following 943 | text is considered part of the function definition: 944 | 945 | >>> one.help() 946 | 'Returns the multiplicative identity of the ring of integers.' 947 | 948 | Note that using this decorator does *not* cause the GAP interpreter 949 | to be initialized, so it can be used in module or class-level code. 950 | The GAP interpreter will only be initialized (if needed) the first time 951 | the function is called. 952 | 953 | Any Python code in the function's body will be disregarded, so this is 954 | in effect syntactic sugar for: 955 | 956 | >>> one = gap.eval('function() return 1; end;') 957 | 958 | with the difference being that it can be used to pre-define GAP 959 | functions without invoking the GAP interpreter directly. 960 | 961 | This decorator may also be used on methods in classes. In this case 962 | the ``self``--the instance of the class on which it is defined, is 963 | always passed as the first argument to the GAP function, *if* it has 964 | a conversion to a GAP type: 965 | 966 | >>> class MyInt(int): 967 | ... @gap.gap_function 968 | ... def n_partitions(self): 969 | ... ''' 970 | ... Compute the number of integer partitions. 971 | ... 972 | ... function(n) 973 | ... local np; 974 | ... if n < 0 then 975 | ... Error("must be a non-negative integer"); 976 | ... fi; 977 | ... np:= function(n, m) 978 | ... local i, res; 979 | ... if n = 0 then 980 | ... return 1; 981 | ... fi; 982 | ... res:= 0; 983 | ... for i in [1..Minimum(n,m)] do 984 | ... res:= res + np(n-i, i); 985 | ... od; 986 | ... return res; 987 | ... end; 988 | ... return np(n,n); 989 | ... end; 990 | ... ''' 991 | ... 992 | >>> ten = MyInt(10) 993 | >>> ten.n_partitions() 994 | 42 995 | """ 996 | 997 | match = self._gap_function_re.search(func.__doc__ or '') 998 | if match is None: 999 | raise ValueError( 1000 | f'the docstring for {func} does not contain a GAP function ' 1001 | f'definition; it should contain one line beginning with ' 1002 | f'function() and the rest should be valid GAP code') 1003 | name = func.__name__ 1004 | doc = func.__doc__[:match.start()].strip() 1005 | source = func.__doc__[match.start():].strip() 1006 | return self._gap_function(name, doc, source) 1007 | 1008 | @lru_cache(maxsize=128) 1009 | def _gap_function(self, name, doc, source): 1010 | """ 1011 | Internal implementation of `Gap.gap_function` which caches the created 1012 | functions (up to 128) based on their name, docstring, and source. 1013 | """ 1014 | 1015 | return make_GapLazyFunction(self, name, doc, source) 1016 | 1017 | def load_package(self, pkg): 1018 | """ 1019 | If loading fails, raise a RuntimeError exception. 1020 | 1021 | Examples 1022 | -------- 1023 | 1024 | >>> gap.load_package("chevie") 1025 | Traceback (most recent call last): 1026 | ... 1027 | RuntimeError: Error loading GAP package chevie. 1028 | """ 1029 | # Note: For some reason the default package loading error messages are 1030 | # controlled with InfoWarning and not InfoPackageLoading 1031 | prev_infolevel = self.InfoLevel(self.InfoWarning) 1032 | self.SetInfoLevel(self.InfoWarning, 0) 1033 | ret = self.LoadPackage(pkg) 1034 | self.SetInfoLevel(self.InfoWarning, prev_infolevel) 1035 | if str(ret) == 'fail': 1036 | raise RuntimeError(f'Error loading GAP package {pkg}.') 1037 | return ret 1038 | 1039 | def set_global(self, variable, value, force=False): 1040 | """ 1041 | Set a GAP global variable 1042 | 1043 | Parameters 1044 | ---------- 1045 | 1046 | variable : str 1047 | The GAP global variable name. 1048 | value 1049 | Any `~gappy.gapobj.GapObj` or Python object that can be converted 1050 | to a GAP object. Passing `None` is equivalent to `Gap.unset_global`. 1051 | force : bool 1052 | If `True`, sets the value of the global even if it is read-only; 1053 | otherwise an `AttributeError` is raised. 1054 | 1055 | Examples 1056 | -------- 1057 | 1058 | >>> gap.set_global('FooBar', 1) 1059 | >>> gap.get_global('FooBar') 1060 | 1 1061 | >>> gap.unset_global('FooBar') 1062 | >>> gap.get_global('FooBar') is None 1063 | True 1064 | >>> gap.set_global('FooBar', 1) 1065 | >>> gap.get_global('FooBar') 1066 | 1 1067 | >>> gap.set_global('FooBar', None) 1068 | >>> gap.get_global('FooBar') is None 1069 | True 1070 | """ 1071 | 1072 | cdef bytes name 1073 | 1074 | self.initialize() 1075 | name = variable.encode('utf-8') 1076 | restore_readonly = False 1077 | 1078 | if not GAP_CanAssignGlobalVariable(name): 1079 | if force: 1080 | self.MakeReadWriteGlobal(name) 1081 | restore_readonly = True 1082 | else: 1083 | raise AttributeError( 1084 | f'Cannot set read-only GAP global variable {variable}') 1085 | 1086 | try: 1087 | obj = self(value) 1088 | GAP_AssignGlobalVariable(name, (obj).value) 1089 | finally: 1090 | if restore_readonly: 1091 | self.MakeReadOnlyGlobal(name) 1092 | 1093 | def unset_global(self, variable): 1094 | """ 1095 | Remove a GAP global variable 1096 | 1097 | Parameters 1098 | ---------- 1099 | 1100 | variable : str 1101 | The GAP global variable name. 1102 | 1103 | Examples 1104 | -------- 1105 | 1106 | >>> gap.set_global('FooBar', 1) 1107 | >>> gap.get_global('FooBar') 1108 | 1 1109 | >>> gap.unset_global('FooBar') 1110 | >>> gap.get_global('FooBar') is None 1111 | True 1112 | """ 1113 | 1114 | cdef bytes name 1115 | 1116 | self.initialize() 1117 | name = variable.encode('utf-8') 1118 | 1119 | if not GAP_CanAssignGlobalVariable(name): 1120 | raise AttributeError( 1121 | f'Cannot unset read-only GAP global variable {variable}') 1122 | 1123 | GAP_AssignGlobalVariable(name, NULL) 1124 | 1125 | cpdef get_global(self, variable): 1126 | """ 1127 | Get a GAP global variable 1128 | 1129 | Parameters 1130 | ---------- 1131 | 1132 | variable : str 1133 | The GAP global variable name. 1134 | 1135 | Returns 1136 | ------- 1137 | 1138 | `.GapObj` or `None` 1139 | `.GapObj` wrapping the GAP output. `None` is returned if there is 1140 | no such variable in GAP. 1141 | 1142 | Examples 1143 | -------- 1144 | 1145 | >>> gap.set_global('FooBar', 1) 1146 | >>> gap.get_global('FooBar') 1147 | 1 1148 | >>> gap.unset_global('FooBar') 1149 | >>> gap.get_global('FooBar') is None 1150 | True 1151 | """ 1152 | cdef Obj obj 1153 | cdef bytes name 1154 | 1155 | self.initialize() 1156 | name = variable.encode('utf-8') 1157 | 1158 | try: 1159 | GAP_Enter() 1160 | obj = GAP_ValueGlobalVariable(name) 1161 | if obj == NULL: 1162 | return None 1163 | 1164 | return make_any_gap_obj(self, obj) 1165 | finally: 1166 | GAP_Leave() 1167 | 1168 | def global_context(self, variable, value): 1169 | """ 1170 | Temporarily change a global variable 1171 | 1172 | Parameters 1173 | ---------- 1174 | 1175 | variable : str 1176 | The GAP global variable name. 1177 | value 1178 | Any `~gappy.gapobj.GapObj` or Python object that can be converted 1179 | to a GAP object. Passing `None` is equivalent to `Gap.unset_global`. 1180 | 1181 | Returns 1182 | ------- 1183 | 1184 | `.GlobalVariableContext` 1185 | A context manager that sets/reverts the given global variable. 1186 | 1187 | Examples 1188 | -------- 1189 | 1190 | >>> gap.set_global('FooBar', 1) 1191 | >>> with gap.global_context('FooBar', 2): 1192 | ... print(gap.get_global('FooBar')) 1193 | 2 1194 | >>> gap.get_global('FooBar') 1195 | 1 1196 | """ 1197 | self.initialize() 1198 | return GlobalVariableContext(self, variable, value) 1199 | 1200 | def set_seed(self, seed=None): 1201 | """ 1202 | Reseed the standard GAP pseudo-random sources with the given seed. 1203 | 1204 | Uses a random 128-bit integer as the seed given by GMP's 1205 | ``mpz_rrandomm`` if ``seed=None``. Otherwise the seed should be an 1206 | integer. 1207 | 1208 | Examples 1209 | -------- 1210 | 1211 | >>> gap.set_seed(0) 1212 | 0 1213 | >>> [gap.Random(1, 10) for i in range(5)] 1214 | [2, 3, 3, 4, 2] 1215 | """ 1216 | cdef mpz_t z_seed 1217 | cdef Obj gap_seed 1218 | 1219 | self.initialize() 1220 | 1221 | if seed is None: 1222 | mpz_init(z_seed) 1223 | mpz_rrandomb(z_seed, self._gmp_state, 128) 1224 | gap_seed = GAP_MakeObjInt(mpz_limbs_read(z_seed), 1225 | mpz_size(z_seed)) 1226 | seed = make_GapInteger(self, gap_seed) 1227 | 1228 | Reset = self.Reset 1229 | Reset(self.GlobalMersenneTwister, seed) 1230 | Reset(self.GlobalRandomSource, seed) 1231 | return seed 1232 | 1233 | def __repr__(self): 1234 | r""" 1235 | Return a string representation of ``self``. 1236 | 1237 | Returns 1238 | ------- 1239 | 1240 | str 1241 | 1242 | Examples 1243 | -------- 1244 | 1245 | >>> gap 1246 | 1247 | """ 1248 | return f'<{self.__class__.__name__}(gap_root={self.gap_root!r})>' 1249 | 1250 | def __dir__(self): 1251 | """ 1252 | Customize tab completion 1253 | 1254 | Examples 1255 | -------- 1256 | 1257 | >>> 'OctaveAlgebra' in dir(gap) 1258 | True 1259 | """ 1260 | return dir(self.__class__) + sorted(GAP_GLOBALS) 1261 | 1262 | def __getattr__(self, name): 1263 | r""" 1264 | The attributes of the GAP object are the GAP functions, and in some 1265 | cases other global variables from GAP. 1266 | 1267 | Parameters 1268 | ---------- 1269 | 1270 | name : str 1271 | The name of the GAP function you want to call or another GAP 1272 | global. 1273 | 1274 | Returns 1275 | ------- 1276 | 1277 | `GapObj` 1278 | A `GapObj` wrapping the specified global variable in GAP. An 1279 | `AttributeError` is raised if there is no such function or global 1280 | variable. 1281 | 1282 | Raises 1283 | ------ 1284 | 1285 | AttributeError 1286 | The global variable with the name of the attribute is not bound in 1287 | GAP. 1288 | 1289 | Examples 1290 | -------- 1291 | 1292 | >>> gap.List 1293 | 1294 | >>> gap.GlobalRandomSource 1295 | 1296 | """ 1297 | if name in _SPECIAL_ATTRS: 1298 | # Prevent unintended GAP initialization when displaying in IPython 1299 | raise AttributeError(name) 1300 | 1301 | try: 1302 | val = self.get_global(name) 1303 | except GAPError: 1304 | # If an arbitrary GAP error occurs also raise an AttributeError 1305 | # but raise the exception from here so that it gets chained with 1306 | # with the original exception 1307 | raise AttributeError(f'no GAP global variable bound to {name!r}') 1308 | 1309 | if val is None: 1310 | raise AttributeError(f'no GAP global variable bound to {name!r}') 1311 | return val 1312 | 1313 | def show(self): 1314 | """ 1315 | Return statistics about the GAP owned object list 1316 | 1317 | This includes the total memory allocated by GAP as returned by 1318 | ``gap.eval('TotalMemoryAllocated()'), as well as garbage collection 1319 | / object count statistics as returned by 1320 | ``gap.eval('GasmanStatistics')``, and finally the total number of GAP 1321 | objects held by gappy as :class:`~gappy.gapobj.GapObj` instances. 1322 | 1323 | The value ``livekb + deadkb`` will roughly equal the total memory 1324 | allocated for GAP objects (see 1325 | ``gap.eval('TotalMemoryAllocated()')``). 1326 | 1327 | .. note:: 1328 | 1329 | Slight complication is that we want to do it without accessing 1330 | GAP objects, so we don't create new GapObjs as a side effect. 1331 | 1332 | Examples 1333 | -------- 1334 | 1335 | >>> a = gap(123) 1336 | >>> b = gap(456) 1337 | >>> c = gap(789) 1338 | >>> del b 1339 | >>> gap.collect() 1340 | >>> gap.show() # doctest: +IGNORE_OUTPUT 1341 | {'gasman_stats': {'full': {'cumulative': 110, 1342 | 'deadbags': 321400, 1343 | 'deadkb': 12967, 1344 | 'freekb': 15492, 1345 | 'livebags': 396645, 1346 | 'livekb': 37730, 1347 | 'time': 110, 1348 | 'totalkb': 65536}, 1349 | 'nfull': 1, 1350 | 'npartial': 1}, 1351 | 'nelements': 23123, 1352 | 'total_alloc': 3234234} 1353 | """ 1354 | 1355 | d = {'nelements': self.count_GAP_objects()} 1356 | d['total_alloc'] = int(self.eval('TotalMemoryAllocated()')) 1357 | d['gasman_stats'] = dict(self.eval('GasmanStatistics()')) 1358 | return d 1359 | 1360 | def count_GAP_objects(self): 1361 | """ 1362 | Return the number of GAP objects that are being tracked by 1363 | GAP. 1364 | 1365 | Returns 1366 | ------- 1367 | 1368 | int 1369 | 1370 | Examples 1371 | -------- 1372 | 1373 | >>> gap.count_GAP_objects() # doctest: +IGNORE_OUTPUT 1374 | 5 1375 | """ 1376 | return len(get_owned_objects()) 1377 | 1378 | def collect(self): 1379 | """ 1380 | Manually run the garbage collector 1381 | 1382 | Examples 1383 | -------- 1384 | 1385 | >>> a = gap(123) 1386 | >>> del a 1387 | >>> gap.collect() 1388 | """ 1389 | self.initialize() 1390 | rc = GAP_CollectBags(1) 1391 | if rc != 1: 1392 | raise RuntimeError('Garbage collection failed.') 1393 | 1394 | 1395 | gap = Gap() 1396 | -------------------------------------------------------------------------------- /gappy/exceptions.py: -------------------------------------------------------------------------------- 1 | # **************************************************************************** 2 | # Copyright (C) 2021 E. Madison Bray 3 | # 4 | # This program is free software: you can redistribute it and/or modify 5 | # it under the terms of the GNU General Public License as published by 6 | # the Free Software Foundation, either version 2 of the License, or 7 | # (at your option) any later version. 8 | # https://www.gnu.org/licenses/ 9 | # **************************************************************************** 10 | 11 | 12 | class GAPError(ValueError): # ValueError for historical reasons 13 | """Exceptions raised by the GAP library.""" 14 | -------------------------------------------------------------------------------- /gappy/gap_functions.py: -------------------------------------------------------------------------------- 1 | """Common global functions defined by GAP.""" 2 | 3 | ############################################################################### 4 | # Copyright (C) 2009, William Stein 5 | # Copyright (C) 2012, Volker Braun 6 | # 7 | # Distributed under the terms of the GNU General Public License (GPL) 8 | # as published by the Free Software Foundation; either version 2 of 9 | # the License, or (at your option) any later version. 10 | # http://www.gnu.org/licenses/ 11 | ############################################################################### 12 | 13 | 14 | __all__ = ['common_gap_functions'] 15 | 16 | 17 | # selected gap functions to use in tab completion 18 | common_gap_functions = set([ 19 | 'AbelianGroup', 20 | 'AbelianInvariants', 21 | 'AbelianInvariantsMultiplier', 22 | 'AbelianInvariantsOfList', 23 | 'AbelianNumberField', 24 | 'AbsInt', 25 | 'AbsoluteValue', 26 | 'Action', 27 | 'ActionHomomorphism', 28 | 'Add', 29 | 'AddCoeffs', 30 | 'AddGenerator', 31 | 'AddRelator', 32 | 'AddRowVector', 33 | 'AddRule', 34 | 'AddSet', 35 | 'AdjointMatrix', 36 | 'Algebra', 37 | 'AlternatingGroup', 38 | 'AntiSymmetricParts', 39 | 'Append', 40 | 'AppendTo', 41 | 'Apply', 42 | 'AsGroup', 43 | 'AtlasGroup', 44 | 'AutomorphismGroup', 45 | 'BaseOfGroup', 46 | 'Basis', 47 | 'BasisVectors', 48 | 'Bell', 49 | 'Binomial', 50 | 'BlockMatrix', 51 | 'Blocks', 52 | 'CartanMatrix', 53 | 'CartanSubalgebra', 54 | 'Cartesian', 55 | 'Center', 56 | 'CentralCharacter', 57 | 'Centralizer', 58 | 'CentralizerInGLnZ', 59 | 'CentralizerModulo', 60 | 'Centre', 61 | 'CentreOfCharacter', 62 | 'Character', 63 | 'CharacterDegrees', 64 | 'CharacterNames', 65 | 'CharacterTable', 66 | 'Characteristic', 67 | 'CharacteristicPolynomial', 68 | 'CheckFixedPoints', 69 | 'ChevalleyBasis', 70 | 'ChiefNormalSeriesByPcgs', 71 | 'ChiefSeries', 72 | 'ChineseRem', 73 | 'Chomp', 74 | 'ClassElementLattice', 75 | 'ClassFunction', 76 | 'ClassFunctionSameType', 77 | 'ClassOrbit', 78 | 'ClassPermutation', 79 | 'ClassRoots', 80 | 'ClassesSolvableGroup', 81 | 'CoKernel', 82 | 'Coefficients', 83 | 'CoefficientsRing', 84 | 'CoeffsCyc', 85 | 'CoeffsMod', 86 | 'CollapsedMat', 87 | 'Collected', 88 | 'Combinations', 89 | 'CombinatorialCollector', 90 | 'CommutatorFactorGroup', 91 | 'CommutatorLength', 92 | 'CommutatorSubgroup', 93 | 'Compacted', 94 | 'CompanionMat', 95 | 'ComplexConjugate', 96 | 'ComplexificationQuat', 97 | 'CompositionMapping', 98 | 'CompositionMapping2', 99 | 'CompositionMaps', 100 | 'Concatenation', 101 | 'Conductor', 102 | 'ConjugacyClass', 103 | 'ConjugacyClassSubgroups', 104 | 'ConjugacyClasses', 105 | 'ConjugateGroup', 106 | 'ConjugateSubgroup', 107 | 'ConjugateSubgroups', 108 | 'ConstituentsCompositionMapping', 109 | 'ContainedMaps', 110 | 'ContinuedFractionApproximationOfRoot', 111 | 'ContinuedFractionExpansionOfRoot', 112 | 'ConvertToCharacterTable', 113 | 'ConvertToMatrixRep', 114 | 'ConvertToRangeRep', 115 | 'ConvertToStringRep', 116 | 'ConvertToTableOfMarks', 117 | 'ConvertToVectorRep', 118 | 'ConwayPolynomial', 119 | 'CosetTable', 120 | 'CosetTableInWholeGroup', 121 | 'Cycle', 122 | 'CycleLength', 123 | 'CycleLengths', 124 | 'CycleStructureClass', 125 | 'CycleStructurePerm', 126 | 'Cycles', 127 | 'CyclicGroup', 128 | 'CyclotomicField', 129 | 'CyclotomicPolynomial', 130 | 'DefiningPolynomial', 131 | 'Degree', 132 | 'DegreeFFE', 133 | 'DenominatorCyc', 134 | 'DenominatorOfRationalFunction', 135 | 'DenominatorRat', 136 | 'Derivations', 137 | 'Derivative', 138 | 'DerivedLength', 139 | 'DerivedSeries', 140 | 'DerivedSeriesOfGroup', 141 | 'DerivedSubgroup', 142 | 'Determinant', 143 | 'DeterminantIntMat', 144 | 'DeterminantMat', 145 | 'DeterminantMatDivFree', 146 | 'DeterminantOfCharacter', 147 | 'DiagonalMat', 148 | 'DihedralGroup', 149 | 'Dimension', 150 | 'DimensionOfMatrixGroup', 151 | 'DimensionsMat', 152 | 'DirectProduct', 153 | 'Discriminant', 154 | 'Display', 155 | 'DivisorsInt', 156 | 'DnLattice', 157 | 'DominantCharacter', 158 | 'DominantWeights', 159 | 'DoubleCoset', 160 | 'DoubleCosetRepsAndSizes', 161 | 'DoubleCosets', 162 | 'DoubleHashArraySize', 163 | 'DuplicateFreeList', 164 | 'E', 165 | 'Eigenspaces', 166 | 'Eigenvalues', 167 | 'Eigenvectors', 168 | 'ElementOfFpGroup', 169 | 'ElementOfFpSemigroup', 170 | 'ElementOrdersPowerMap', 171 | 'Elements', 172 | 'ElementsStabChain', 173 | 'EpimorphismFromFreeGroup', 174 | 'EpimorphismNilpotentQuotient', 175 | 'EpimorphismPGroup', 176 | 'EpimorphismQuotientSystem', 177 | 'EpimorphismSchurCover', 178 | 'EuclideanQuotient', 179 | 'EuclideanRemainder', 180 | 'EulerianFunction', 181 | 'Exponent', 182 | 'Extension', 183 | 'ExteriorCentre', 184 | 'ExteriorPower', 185 | 'Extract', 186 | 'FactorGroup', 187 | 'Factorial', 188 | 'Factorization', 189 | 'Factors', 190 | 'FactorsInt', 191 | 'Fibonacci', 192 | 'Field', 193 | 'FieldExtension', 194 | 'FieldOfMatrixGroup', 195 | 'Filtered', 196 | 'First', 197 | 'FittingSubgroup', 198 | 'Flat', 199 | 'ForAll', 200 | 'ForAny', 201 | 'FreeGroup', 202 | 'FreeProduct', 203 | 'FreeSemigroup', 204 | 'FrobeniusAutomorphism', 205 | 'FullRowSpace', 206 | 'GF', 207 | 'GL', 208 | 'GQuotients', 209 | 'GaloisCyc', 210 | 'GaloisField', 211 | 'GaloisGroup', 212 | 'GaloisMat', 213 | 'GaloisStabilizer', 214 | 'Gcd', 215 | 'GcdInt', 216 | 'GcdOp', 217 | 'GeneralLinearGroup', 218 | 'GeneralOrthogonalGroup', 219 | 'GeneralUnitaryGroup', 220 | 'GeneralisedEigenspaces', 221 | 'GeneralisedEigenvalues', 222 | 'GeneralizedEigenspaces', 223 | 'GeneralizedEigenvalues', 224 | 'GeneratorsOfField', 225 | 'GeneratorsOfGroup', 226 | 'GeneratorsOfIdeal', 227 | 'GroebnerBasis', 228 | 'Group', 229 | 'GroupHomomorphismByFunction', 230 | 'GroupHomomorphismByImages', 231 | 'GroupRing', 232 | 'HermiteNormalFormIntegerMat', 233 | 'HermiteNormalFormIntegerMatTransform', 234 | 'Hom', 235 | 'IdGroup', 236 | 'Ideal', 237 | 'IdealByGenerators', 238 | 'Idempotents', 239 | 'Identifier', 240 | 'Identity', 241 | 'Image', 242 | 'Images', 243 | 'Index', 244 | 'InfoLevel', 245 | 'InfoText', 246 | 'InnerAutomorphism', 247 | 'InnerAutomorphismsAutomorphismGroup', 248 | 'Int', 249 | 'IntFFE', 250 | 'IntFFESymm', 251 | 'IntHexString', 252 | 'IntScalarProducts', 253 | 'IntVecFFE', 254 | 'IntersectSet', 255 | 'Intersection', 256 | 'InvariantBilinearForm', 257 | 'InvariantElementaryAbelianSeries', 258 | 'InvariantLattice', 259 | 'InvariantQuadraticForm', 260 | 'InvariantSesquilinearForm', 261 | 'Inverse', 262 | 'InverseMap', 263 | 'Irr', 264 | 'IrrBaumClausen', 265 | 'IrrConlon', 266 | 'IrrDixonSchneider', 267 | 'IrreducibleModules', 268 | 'IrreducibleRepresentations', 269 | 'IrreducibleRepresentationsDixon', 270 | 'IsAbelian', 271 | 'IsAbelianNumberField', 272 | 'IsAbelianNumberFieldPolynomialRing', 273 | 'IsAdditiveElement', 274 | 'IsAdditiveElementWithInverse', 275 | 'IsAdditiveElementWithZero', 276 | 'IsAdditiveGroup', 277 | 'IsAdditiveGroupGeneralMapping', 278 | 'IsAdditiveGroupHomomorphism', 279 | 'IsAdditivelyCommutative', 280 | 'IsAdditivelyCommutativeElement', 281 | 'IsAlgebra', 282 | 'IsAlgebraGeneralMapping', 283 | 'IsAlgebraHomomorphism', 284 | 'IsAlgebraModule', 285 | 'IsAlgebraWithOne', 286 | 'IsAlgebraWithOneHomomorphism', 287 | 'IsAlgebraicElement', 288 | 'IsAlgebraicExtension', 289 | 'IsAlternatingGroup', 290 | 'IsAnticommutative', 291 | 'IsAntisymmetricBinaryRelation', 292 | 'IsAssocWord', 293 | 'IsAssocWordWithInverse', 294 | 'IsAssocWordWithOne', 295 | 'IsAssociated', 296 | 'IsAssociative', 297 | 'IsAutomorphismGroup', 298 | 'IsBasis', 299 | 'IsBijective', 300 | 'IsBinaryRelation', 301 | 'IsBlockMatrixRep', 302 | 'IsBool', 303 | 'IsBoundGlobal', 304 | 'IsBrauerTable', 305 | 'IsBravaisGroup', 306 | 'IsBuiltFromGroup', 307 | 'IsBuiltFromSemigroup', 308 | 'IsCanonicalBasis', 309 | 'IsCanonicalBasisFullMatrixModule', 310 | 'IsCanonicalBasisFullRowModule', 311 | 'IsCanonicalNiceMonomorphism', 312 | 'IsCentral', 313 | 'IsCentralFactor', 314 | 'IsChar', 315 | 'IsCharacter', 316 | 'IsCharacterTable', 317 | 'IsCharacterTableInProgress', 318 | 'IsCharacteristicSubgroup', 319 | 'IsClosedStream', 320 | 'IsCochain', 321 | 'IsCochainCollection', 322 | 'IsCommutative', 323 | 'IsComponentObjectRep', 324 | 'IsCompositionMappingRep', 325 | 'IsConfluent', 326 | 'IsConjugate', 327 | 'IsCopyable', 328 | 'IsCyc', 329 | 'IsCyclic', 330 | 'IsCyclotomic', 331 | 'IsCyclotomicField', 332 | 'IsCyclotomicMatrixGroup', 333 | 'IsDenseList', 334 | 'IsDiagonalMat', 335 | 'IsDictionary', 336 | 'IsDigitChar', 337 | 'IsDivisionRing', 338 | 'IsDomain', 339 | 'IsDoneIterator', 340 | 'IsDoubleCoset', 341 | 'IsDuplicateFree', 342 | 'IsDuplicateFreeList', 343 | 'IsElementaryAbelian', 344 | 'IsEmpty', 345 | 'IsEmptyString', 346 | 'IsEuclideanRing', 347 | 'IsFFE', 348 | 'IsField', 349 | 'IsFinite', 350 | 'IsFiniteDimensional', 351 | 'IsFinitelyGeneratedGroup', 352 | 'IsFixedStabilizer', 353 | 'IsFpGroup', 354 | 'IsFpMonoid', 355 | 'IsFpSemigroup', 356 | 'IsFreeGroup', 357 | 'IsFreeLeftModule', 358 | 'IsFullHomModule', 359 | 'IsFullMatrixModule', 360 | 'IsFullRowModule', 361 | 'IsFunction', 362 | 'IsGL', 363 | 'IsGaussInt', 364 | 'IsGaussRat', 365 | 'IsGaussianIntegers', 366 | 'IsGaussianRationals', 367 | 'IsGaussianSpace', 368 | 'IsGeneralLinearGroup', 369 | 'IsGroup', 370 | 'IsGroupHomomorphism', 371 | 'IsGroupOfAutomorphisms', 372 | 'IsGroupRing', 373 | 'IsHasseDiagram', 374 | 'IsHomogeneousList', 375 | 'IsIdempotent', 376 | 'IsInfinity', 377 | 'IsInjective', 378 | 'IsInnerAutomorphism', 379 | 'IsInt', 380 | 'IsIntegerMatrixGroup', 381 | 'IsIntegers', 382 | 'IsIntegralBasis', 383 | 'IsIntegralCyclotomic', 384 | 'IsIntegralRing', 385 | 'IsIrreducible', 386 | 'IsIrreducibleCharacter', 387 | 'IsIrreducibleRingElement', 388 | 'IsIterator', 389 | 'IsJacobianRing', 390 | 'IsLaurentPolynomial', 391 | 'IsLaurentPolynomialDefaultRep', 392 | 'IsLexicographicallyLess', 393 | 'IsLieAbelian', 394 | 'IsLieAlgebra', 395 | 'IsLieMatrix', 396 | 'IsLieObject', 397 | 'IsLieObjectCollection', 398 | 'IsLieSolvable', 399 | 'IsLinearMapping', 400 | 'IsLinearMappingsModule', 401 | 'IsList', 402 | 'IsMapping', 403 | 'IsMatchingSublist', 404 | 'IsMatrix', 405 | 'IsMatrixGroup', 406 | 'IsMatrixModule', 407 | 'IsMatrixSpace', 408 | 'IsMonomial', 409 | 'IsMonomialGroup', 410 | 'IsMonomialMatrix', 411 | 'IsMonomialOrdering', 412 | 'IsMultiplicativeZero', 413 | 'IsMutable', 414 | 'IsMutableBasis', 415 | 'IsNilpotent', 416 | 'IsNilpotentElement', 417 | 'IsNilpotentGroup', 418 | 'IsNormal', 419 | 'IsNormalBasis', 420 | 'IsNotIdenticalObj', 421 | 'IsNumberField', 422 | 'IsObject', 423 | 'IsOddInt', 424 | 'IsOne', 425 | 'IsOrdering', 426 | 'IsOrdinaryMatrix', 427 | 'IsOrdinaryTable', 428 | 'IsPGroup', 429 | 'IsPSolvable', 430 | 'IsPcGroup', 431 | 'IsPcgs', 432 | 'IsPerfect', 433 | 'IsPerfectGroup', 434 | 'IsPerm', 435 | 'IsPermGroup', 436 | 'IsPolycyclicGroup', 437 | 'IsPolynomial', 438 | 'IsPolynomialRing', 439 | 'IsPosInt', 440 | 'IsPosRat', 441 | 'IsPositiveIntegers', 442 | 'IsPrime', 443 | 'IsPrimeField', 444 | 'IsPrimeInt', 445 | 'IsPrimePowerInt', 446 | 'IsPrimitive', 447 | 'IsPrimitiveCharacter', 448 | 'IsPrimitivePolynomial', 449 | 'IsProbablyPrimeInt', 450 | 'IsPurePadicNumber', 451 | 'IsQuaternion', 452 | 'IsQuickPositionList', 453 | 'IsQuotientSemigroup', 454 | 'IsRandomSource', 455 | 'IsRange', 456 | 'IsRat', 457 | 'IsRationalFunction', 458 | 'IsRationalMatrixGroup', 459 | 'IsRationals', 460 | 'IsRecord', 461 | 'IsReduced', 462 | 'IsReductionOrdering', 463 | 'IsReflexiveBinaryRelation', 464 | 'IsRegular', 465 | 'IsRegularSemigroup', 466 | 'IsRegularSemigroupElement', 467 | 'IsRing', 468 | 'IsRingElement', 469 | 'IsRingGeneralMapping', 470 | 'IsRingWithOne', 471 | 'IsRingWithOneGeneralMapping', 472 | 'IsRingWithOneHomomorphism', 473 | 'IsRowModule', 474 | 'IsRowSpace', 475 | 'IsRowVector', 476 | 'IsSL', 477 | 'IsSSortedList', 478 | 'IsScalar', 479 | 'IsSet', 480 | 'IsSimple', 481 | 'IsSimpleAlgebra', 482 | 'IsSimpleGroup', 483 | 'IsSimpleSemigroup', 484 | 'IsSingleValued', 485 | 'IsSolvable', 486 | 'IsSolvableGroup', 487 | 'IsSortedList', 488 | 'IsSpecialLinearGroup', 489 | 'IsSporadicSimple', 490 | 'IsStandardIterator', 491 | 'IsString', 492 | 'IsStringRep', 493 | 'IsSubgroup', 494 | 'IsSubgroupFpGroup', 495 | 'IsSubgroupOfWholeGroupByQuotientRep', 496 | 'IsSubgroupSL', 497 | 'IsSubset', 498 | 'IsSubsetSet', 499 | 'IsSubspace', 500 | 'IsSupersolvable', 501 | 'IsSupersolvableGroup', 502 | 'IsSurjective', 503 | 'IsSymmetricGroup', 504 | 'IsTable', 505 | 'IsTotal', 506 | 'IsTotalOrdering', 507 | 'IsTransformation', 508 | 'IsTransitive', 509 | 'IsTransitiveBinaryRelation', 510 | 'IsTrivial', 511 | 'IsUniqueFactorizationRing', 512 | 'IsUnit', 513 | 'IsUnivariatePolynomial', 514 | 'IsUnivariatePolynomialRing', 515 | 'IsUnivariateRationalFunction', 516 | 'IsUpperAlphaChar', 517 | 'IsUpperTriangularMat', 518 | 'IsValidIdentifier', 519 | 'IsVector', 520 | 'IsVectorSpace', 521 | 'IsVirtualCharacter', 522 | 'IsWeylGroup', 523 | 'IsWord', 524 | 'IsZero', 525 | 'IsZeroGroup', 526 | 'IsZeroSimpleSemigroup', 527 | 'IsZeroSquaredRing', 528 | 'IsZmodnZObj', 529 | 'IsZmodnZObjNonprime', 530 | 'IsZmodpZObj', 531 | 'IsZmodpZObjLarge', 532 | 'IsZmodpZObjSmall', 533 | 'IsomorphicSubgroups', 534 | 'IsomorphismFpAlgebra', 535 | 'IsomorphismFpGroup', 536 | 'IsomorphismFpGroupByGenerators', 537 | 'IsomorphismFpGroupByPcgs', 538 | 'IsomorphismFpSemigroup', 539 | 'IsomorphismGroups', 540 | 'IsomorphismMatrixAlgebra', 541 | 'IsomorphismPcGroup', 542 | 'IsomorphismPermGroup', 543 | 'IsomorphismPermGroupImfGroup', 544 | 'IsomorphismReesMatrixSemigroup', 545 | 'IsomorphismRefinedPcGroup', 546 | 'IsomorphismSimplifiedFpGroup', 547 | 'IsomorphismSpecialPcGroup', 548 | 'IsomorphismTransformationSemigroup', 549 | 'IsomorphismTypeInfoFiniteSimpleGroup', 550 | 'Iterated', 551 | 'Iterator', 552 | 'IteratorByBasis', 553 | 'IteratorByFunctions', 554 | 'IteratorList', 555 | 'IteratorSorted', 556 | 'Jacobi', 557 | 'JenningsLieAlgebra', 558 | 'JenningsSeries', 559 | 'JordanDecomposition', 560 | 'Kernel', 561 | 'KernelOfAdditiveGeneralMapping', 562 | 'KernelOfCharacter', 563 | 'KernelOfMultiplicativeGeneralMapping', 564 | 'KernelOfTransformation', 565 | 'KillingMatrix', 566 | 'KnuthBendixRewritingSystem', 567 | 'KroneckerProduct', 568 | 'KuKGenerators', 569 | 'LLL', 570 | 'LLLReducedBasis', 571 | 'LLLReducedGramMat', 572 | 'Lambda', 573 | 'LargestElementGroup', 574 | 'LargestElementStabChain', 575 | 'LargestMovedPoint', 576 | 'LastSystemError', 577 | 'LatticeByCyclicExtension', 578 | 'LatticeSubgroups', 579 | 'Lcm', 580 | 'LcmInt', 581 | 'LcmOp', 582 | 'LeadingCoefficient', 583 | 'LeadingCoefficientOfPolynomial', 584 | 'LeadingExponentOfPcElement', 585 | 'LeadingMonomial', 586 | 'LeadingMonomialOfPolynomial', 587 | 'LeadingTermOfPolynomial', 588 | 'Legendre', 589 | 'Length', 590 | 'LenstraBase', 591 | 'LessThanFunction', 592 | 'LessThanOrEqualFunction', 593 | 'LetterRepAssocWord', 594 | 'LevelsOfGenerators', 595 | 'LeviMalcevDecomposition', 596 | 'LexicographicOrdering', 597 | 'LieAlgebra', 598 | 'LieAlgebraByStructureConstants', 599 | 'LieBracket', 600 | 'LieCenter', 601 | 'LieCentralizer', 602 | 'LieCentre', 603 | 'LieCoboundaryOperator', 604 | 'LieDerivedSeries', 605 | 'LieDerivedSubalgebra', 606 | 'LieLowerCentralSeries', 607 | 'LieNilRadical', 608 | 'LieNormalizer', 609 | 'LieObject', 610 | 'LieSolvableRadical', 611 | 'LieUpperCentralSeries', 612 | 'LiftedInducedPcgs', 613 | 'LiftedPcElement', 614 | 'LinearAction', 615 | 'LinearActionLayer', 616 | 'LinearCharacters', 617 | 'LinearCombination', 618 | 'LinearCombinationPcgs', 619 | 'LinearIndependentColumns', 620 | 'LinearOperation', 621 | 'LinearOperationLayer', 622 | 'LinesOfStraightLineProgram', 623 | 'List', 624 | 'ListN', 625 | 'ListPerm', 626 | 'ListStabChain', 627 | 'ListWithIdenticalEntries', 628 | 'ListX', 629 | 'LoadDynamicModule', 630 | 'LoadPackage', 631 | 'Log', 632 | 'LogFFE', 633 | 'LogInt', 634 | 'LogMod', 635 | 'LogModShanks', 636 | 'LogTo', 637 | 'LongestWeylWordPerm', 638 | 'LookupDictionary', 639 | 'LowIndexSubgroupsFpGroup', 640 | 'LowIndexSubgroupsFpGroupIterator', 641 | 'LowerCentralSeries', 642 | 'LowerCentralSeriesOfGroup', 643 | 'LowercaseString', 644 | 'Lucas', 645 | 'MakeConfluent', 646 | 'MakeImmutable', 647 | 'MakeReadOnlyGlobal', 648 | 'MakeReadWriteGlobal', 649 | 'MappedWord', 650 | 'MappingByFunction', 651 | 'MappingPermListList', 652 | 'MatAlgebra', 653 | 'MatClassMultCoeffsCharTable', 654 | 'MatLieAlgebra', 655 | 'MatScalarProducts', 656 | 'MathieuGroup', 657 | 'MatrixAlgebra', 658 | 'MatrixAutomorphisms', 659 | 'MatrixByBlockMatrix', 660 | 'MatrixLieAlgebra', 661 | 'MatrixOfAction', 662 | 'MaximalAbelianQuotient', 663 | 'MaximalBlocks', 664 | 'MaximalNormalSubgroups', 665 | 'MaximalSubgroupClassReps', 666 | 'MaximalSubgroups', 667 | 'MaximalSubgroupsLattice', 668 | 'Maximum', 669 | 'MaximumList', 670 | 'MeetEquivalenceRelations', 671 | 'MeetMaps', 672 | 'MinimalElementCosetStabChain', 673 | 'MinimalGeneratingSet', 674 | 'MinimalNonmonomialGroup', 675 | 'MinimalNormalSubgroups', 676 | 'MinimalPolynomial', 677 | 'MinimalStabChain', 678 | 'MinimalSupergroupsLattice', 679 | 'MinimizedBombieriNorm', 680 | 'Minimum', 681 | 'MinimumList', 682 | 'MinusCharacter', 683 | 'ModuleByRestriction', 684 | 'ModuleOfExtension', 685 | 'ModuloPcgs', 686 | 'MoebiusMu', 687 | 'MolienSeries', 688 | 'MolienSeriesInfo', 689 | 'MolienSeriesWithGivenDenominator', 690 | 'Monoid', 691 | 'MonoidByGenerators', 692 | 'MonoidByMultiplicationTable', 693 | 'MonoidOfRewritingSystem', 694 | 'MonomialComparisonFunction', 695 | 'MonomialExtGrlexLess', 696 | 'MonomialExtrepComparisonFun', 697 | 'MonomialGrevlexOrdering', 698 | 'MonomialGrlexOrdering', 699 | 'MonomialLexOrdering', 700 | 'MostFrequentGeneratorFpGroup', 701 | 'MovedPoints', 702 | 'MultRowVector', 703 | 'MultiplicationTable', 704 | 'MultiplicativeNeutralElement', 705 | 'MultiplicativeZero', 706 | 'MultiplicativeZeroOp', 707 | 'NF', 708 | 'NK', 709 | 'NameFunction', 710 | 'NaturalCharacter', 711 | 'NaturalHomomorphismByGenerators', 712 | 'NaturalHomomorphismByIdeal', 713 | 'NaturalHomomorphismByNormalSubgroup', 714 | 'NaturalHomomorphismBySubAlgebraModule', 715 | 'NaturalHomomorphismBySubspace', 716 | 'NearAdditiveGroup', 717 | 'NearAdditiveGroupByGenerators', 718 | 'NegativeRootVectors', 719 | 'NegativeRoots', 720 | 'NextIterator', 721 | 'NextPrimeInt', 722 | 'NiceBasis', 723 | 'NiceFreeLeftModule', 724 | 'NiceFreeLeftModuleInfo', 725 | 'NiceMonomorphism', 726 | 'NiceMonomorphismAutomGroup', 727 | 'NiceObject', 728 | 'NiceVector', 729 | 'NilpotencyClassOfGroup', 730 | 'NonabelianExteriorSquare', 731 | 'Norm', 732 | 'NormalBase', 733 | 'NormalClosure', 734 | 'NormalFormIntMat', 735 | 'NormalIntersection', 736 | 'NormalSeriesByPcgs', 737 | 'NormalSubgroups', 738 | 'NormalizeWhitespace', 739 | 'NormalizedWhitespace', 740 | 'Normalizer', 741 | 'NormalizerInGLnZ', 742 | 'NormalizerInGLnZBravaisGroup', 743 | 'NormedRowVector', 744 | 'NrArrangements', 745 | 'NrBasisVectors', 746 | 'NrCombinations', 747 | 'NrConjugacyClasses', 748 | 'NrConjugacyClassesGL', 749 | 'NrConjugacyClassesGU', 750 | 'NrConjugacyClassesPGL', 751 | 'NrConjugacyClassesPGU', 752 | 'NrConjugacyClassesPSL', 753 | 'NrConjugacyClassesPSU', 754 | 'NrConjugacyClassesSL', 755 | 'NrConjugacyClassesSLIsogeneous', 756 | 'NrConjugacyClassesSU', 757 | 'NrConjugacyClassesSUIsogeneous', 758 | 'NrDerangements', 759 | 'NrInputsOfStraightLineProgram', 760 | 'NrMovedPoints', 761 | 'NrOrderedPartitions', 762 | 'NrPartitionTuples', 763 | 'NrPartitions', 764 | 'NrPartitionsSet', 765 | 'NrPermutationsList', 766 | 'NrPolyhedralSubgroups', 767 | 'NrPrimitiveGroups', 768 | 'NrRestrictedPartitions', 769 | 'NrTransitiveGroups', 770 | 'NrTuples', 771 | 'NrUnorderedTuples', 772 | 'NullAlgebra', 773 | 'NullMat', 774 | 'NullspaceIntMat', 775 | 'NullspaceMat', 776 | 'NullspaceMatDestructive', 777 | 'NullspaceModQ', 778 | 'Number', 779 | 'NumberArgumentsFunction', 780 | 'NumberFFVector', 781 | 'NumberPerfectGroups', 782 | 'NumberPerfectLibraryGroups', 783 | 'NumberSmallGroups', 784 | 'NumberSyllables', 785 | 'NumeratorOfModuloPcgs', 786 | 'NumeratorOfRationalFunction', 787 | 'NumeratorRat', 788 | 'Objectify', 789 | 'ObjectifyWithAttributes', 790 | 'OctaveAlgebra', 791 | 'OldGeneratorsOfPresentation', 792 | 'Omega', 793 | 'OnBreak', 794 | 'OnBreakMessage', 795 | 'OnIndeterminates', 796 | 'OnLeftInverse', 797 | 'OnLines', 798 | 'OnPairs', 799 | 'OnPoints', 800 | 'OnRight', 801 | 'OnSets', 802 | 'OnSetsDisjointSets', 803 | 'OnSetsSets', 804 | 'OnSetsTuples', 805 | 'OnSubspacesByCanonicalBasis', 806 | 'OnTuples', 807 | 'OnTuplesSets', 808 | 'OnTuplesTuples', 809 | 'One', 810 | 'OneAttr', 811 | 'OneCoboundaries', 812 | 'OneCocycles', 813 | 'OneFactorBound', 814 | 'OneImmutable', 815 | 'OneMutable', 816 | 'OneOfPcgs', 817 | 'OneOp', 818 | 'OneSM', 819 | 'OneSameMutability', 820 | 'OperationAlgebraHomomorphism', 821 | 'Orbit', 822 | 'OrbitFusions', 823 | 'OrbitLength', 824 | 'OrbitLengths', 825 | 'OrbitLengthsDomain', 826 | 'OrbitPerms', 827 | 'OrbitPowerMaps', 828 | 'OrbitStabChain', 829 | 'OrbitStabilizer', 830 | 'OrbitStabilizerAlgorithm', 831 | 'Orbits', 832 | 'OrbitsDomain', 833 | 'OrbitsPerms', 834 | 'Order', 835 | 'OrderMod', 836 | 'OrderingOnGenerators', 837 | 'Ordinal', 838 | 'OrdinaryCharacterTable', 839 | 'OrthogonalComponents', 840 | 'OrthogonalEmbeddings', 841 | 'OrthogonalEmbeddingsSpecialDimension', 842 | 'PCentralLieAlgebra', 843 | 'PCentralNormalSeriesByPcgsPGroup', 844 | 'PCentralSeries', 845 | 'PClassPGroup', 846 | 'PCore', 847 | 'PGL', 848 | 'PGU', 849 | 'POW', 850 | 'PQuotient', 851 | 'PROD', 852 | 'PSL', 853 | 'PSP', 854 | 'PSU', 855 | 'PSp', 856 | 'PadicCoefficients', 857 | 'PadicNumber', 858 | 'PadicValuation', 859 | 'Parametrized', 860 | 'Parent', 861 | 'ParentPcgs', 862 | 'PartialFactorization', 863 | 'PartialOrderByOrderingFunction', 864 | 'PartialOrderOfHasseDiagram', 865 | 'Partition', 866 | 'PartitionTuples', 867 | 'Partitions', 868 | 'PartitionsGreatestEQ', 869 | 'PartitionsGreatestLE', 870 | 'PartitionsSet', 871 | 'PcGroupCode', 872 | 'PcGroupCodeRec', 873 | 'PcGroupFpGroup', 874 | 'PcGroupWithPcgs', 875 | 'PcSeries', 876 | 'Pcgs', 877 | 'PcgsCentralSeries', 878 | 'PcgsChiefSeries', 879 | 'PcgsElementaryAbelianSeries', 880 | 'PcgsPCentralSeriesPGroup', 881 | 'Pcgs_OrbitStabilizer', 882 | 'PerfectGroup', 883 | 'PerfectIdentification', 884 | 'PerfectResiduum', 885 | 'Perform', 886 | 'PermBounds', 887 | 'PermCharInfo', 888 | 'PermCharInfoRelative', 889 | 'PermChars', 890 | 'PermComb', 891 | 'PermLeftQuoTransformation', 892 | 'PermList', 893 | 'PermListList', 894 | 'Permanent', 895 | 'Permutation', 896 | 'PermutationCharacter', 897 | 'PermutationCycle', 898 | 'PermutationCycleOp', 899 | 'PermutationGModule', 900 | 'PermutationMat', 901 | 'PermutationsList', 902 | 'Permuted', 903 | 'Phi', 904 | 'PolynomialByExtRep', 905 | 'PolynomialCoefficientsOfPolynomial', 906 | 'PolynomialDivisionAlgorithm', 907 | 'PolynomialModP', 908 | 'PolynomialReducedRemainder', 909 | 'PolynomialReduction', 910 | 'PolynomialRing', 911 | 'PopOptions', 912 | 'Position', 913 | 'PositionBound', 914 | 'PositionCanonical', 915 | 'PositionNonZero', 916 | 'PositionNot', 917 | 'PositionNthOccurrence', 918 | 'PositionProperty', 919 | 'PositionSet', 920 | 'PositionSorted', 921 | 'PositionStream', 922 | 'PositionSublist', 923 | 'PositionWord', 924 | 'PositionsOp', 925 | 'PositiveRoots', 926 | 'PossibleClassFusions', 927 | 'PossiblePowerMaps', 928 | 'PowerMap', 929 | 'PowerMapOp', 930 | 'PowerModCoeffs', 931 | 'PowerModInt', 932 | 'PowerPartition', 933 | 'PreImage', 934 | 'PreImageElm', 935 | 'PreImages', 936 | 'PreImagesElm', 937 | 'PreImagesRange', 938 | 'PreImagesRepresentative', 939 | 'PreImagesSet', 940 | 'PrefrattiniSubgroup', 941 | 'PreimagesOfTransformation', 942 | 'PresentationFpGroup', 943 | 'PresentationNormalClosure', 944 | 'PresentationNormalClosureRrs', 945 | 'PresentationSubgroup', 946 | 'PresentationSubgroupMtc', 947 | 'PresentationSubgroupRrs', 948 | 'PresentationViaCosetTable', 949 | 'PrevPrimeInt', 950 | 'PrimaryGeneratorWords', 951 | 'PrimeBlocks', 952 | 'PrimeBlocksOp', 953 | 'PrimeField', 954 | 'PrimePGroup', 955 | 'PrimePowersInt', 956 | 'PrimeResidues', 957 | 'PrimitiveElement', 958 | 'PrimitiveGroup', 959 | 'PrimitiveIdentification', 960 | 'PrimitivePolynomial', 961 | 'PrimitiveRoot', 962 | 'PrimitiveRootMod', 963 | 'Print', 964 | 'PrintAmbiguity', 965 | 'PrintArray', 966 | 'PrintCharacterTable', 967 | 'PrintFactorsInt', 968 | 'PrintFormattingStatus', 969 | 'PrintHashWithNames', 970 | 'PrintObj', 971 | 'PrintTo', 972 | 'Process', 973 | 'Product', 974 | 'ProductCoeffs', 975 | 'ProductSpace', 976 | 'ProductX', 977 | 'ProjectedInducedPcgs', 978 | 'ProjectedPcElement', 979 | 'Projection', 980 | 'ProjectionMap', 981 | 'ProjectiveActionHomomorphismMatrixGroup', 982 | 'ProjectiveActionOnFullSpace', 983 | 'ProjectiveGeneralLinearGroup', 984 | 'ProjectiveGeneralUnitaryGroup', 985 | 'ProjectiveOrder', 986 | 'ProjectiveSpecialLinearGroup', 987 | 'ProjectiveSpecialUnitaryGroup', 988 | 'ProjectiveSymplecticGroup', 989 | 'PseudoRandom', 990 | 'PthPowerImage', 991 | 'PthPowerImages', 992 | 'PushOptions', 993 | 'QUO', 994 | 'Quadratic', 995 | 'QuaternionAlgebra', 996 | 'QuoInt', 997 | 'QuotRemLaurpols', 998 | 'Quotient', 999 | 'QuotientMod', 1000 | 'QuotientPolynomialsExtRep', 1001 | 'QuotientRemainder', 1002 | 'READ', 1003 | 'RadicalGroup', 1004 | 'RadicalOfAlgebra', 1005 | 'Random', 1006 | 'RandomBinaryRelationOnPoints', 1007 | 'RandomHashKey', 1008 | 'RandomInvertibleMat', 1009 | 'RandomIsomorphismTest', 1010 | 'RandomList', 1011 | 'RandomMat', 1012 | 'RandomPrimitivePolynomial', 1013 | 'RandomSource', 1014 | 'RandomTransformation', 1015 | 'RandomUnimodularMat', 1016 | 'Range', 1017 | 'Rank', 1018 | 'RankAction', 1019 | 'RankFilter', 1020 | 'RankMat', 1021 | 'RankOfTransformation', 1022 | 'RankPGroup', 1023 | 'Rat', 1024 | 'RationalClass', 1025 | 'RationalClasses', 1026 | 'RationalizedMat', 1027 | 'Read', 1028 | 'ReadAll', 1029 | 'ReadAllLine', 1030 | 'ReadAsFunction', 1031 | 'ReadByte', 1032 | 'ReadLine', 1033 | 'ReadPackage', 1034 | 'ReadPkg', 1035 | 'ReadTest', 1036 | 'RealClasses', 1037 | 'RealPart', 1038 | 'RealizableBrauerCharacters', 1039 | 'RecFields', 1040 | 'RecNames', 1041 | 'RedispatchOnCondition', 1042 | 'ReduceCoeffs', 1043 | 'ReduceCoeffsMod', 1044 | 'ReduceRules', 1045 | 'ReduceStabChain', 1046 | 'Reduced', 1047 | 'ReducedAdditiveInverse', 1048 | 'ReducedCharacters', 1049 | 'ReducedClassFunctions', 1050 | 'ReducedComm', 1051 | 'ReducedConfluentRewritingSystem', 1052 | 'ReducedConjugate', 1053 | 'ReducedDifference', 1054 | 'ReducedForm', 1055 | 'ReducedGroebnerBasis', 1056 | 'ReducedInverse', 1057 | 'ReducedLeftQuotient', 1058 | 'ReducedOne', 1059 | 'ReducedPcElement', 1060 | 'ReducedPower', 1061 | 'ReducedProduct', 1062 | 'ReducedQuotient', 1063 | 'ReducedScalarProduct', 1064 | 'ReducedSum', 1065 | 'ReducedZero', 1066 | 'Ree', 1067 | 'ReeGroup', 1068 | 'ReesCongruenceOfSemigroupIdeal', 1069 | 'ReesMatrixSemigroup', 1070 | 'ReesMatrixSemigroupElement', 1071 | 'ReesZeroMatrixSemigroup', 1072 | 'ReesZeroMatrixSemigroupElement', 1073 | 'RefinedPcGroup', 1074 | 'RegularActionHomomorphism', 1075 | 'RegularModule', 1076 | 'RelationsOfFpSemigroup', 1077 | 'RelativeBasis', 1078 | 'RelativeOrders', 1079 | 'RelatorsOfFpGroup', 1080 | 'RemInt', 1081 | 'Remove', 1082 | 'RemoveCharacters', 1083 | 'RemoveFile', 1084 | 'RemoveOuterCoeffs', 1085 | 'RemoveRelator', 1086 | 'RemoveSet', 1087 | 'RemoveStabChain', 1088 | 'ReplacedString', 1089 | 'Representative', 1090 | 'RepresentativeAction', 1091 | 'RepresentativeLinearOperation', 1092 | 'RepresentativeSmallest', 1093 | 'RepresentativesContainedRightCosets', 1094 | 'RepresentativesFusions', 1095 | 'RepresentativesMinimalBlocks', 1096 | 'RepresentativesPerfectSubgroups', 1097 | 'RepresentativesPowerMaps', 1098 | 'RepresentativesSimpleSubgroups', 1099 | 'Reread', 1100 | 'RereadPackage', 1101 | 'Reset', 1102 | 'RestrictOutputsOfSLP', 1103 | 'Restricted', 1104 | 'RestrictedClassFunction', 1105 | 'RestrictedClassFunctions', 1106 | 'RestrictedMapping', 1107 | 'RestrictedPartitions', 1108 | 'RestrictedPerm', 1109 | 'RestrictedTransformation', 1110 | 'ResultOfStraightLineProgram', 1111 | 'Resultant', 1112 | 'Reversed', 1113 | 'RewriteWord', 1114 | 'RightCoset', 1115 | 'RightCosets', 1116 | 'RightDerivations', 1117 | 'Ring', 1118 | 'RingWithOne', 1119 | 'Root', 1120 | 'RootInt', 1121 | 'RootMod', 1122 | 'RootOfDefiningPolynomial', 1123 | 'RootSystem', 1124 | 'RootsMod', 1125 | 'RoundCyc', 1126 | 'Rules', 1127 | 'SL', 1128 | 'SO', 1129 | 'SP', 1130 | 'SQ', 1131 | 'SSortedList', 1132 | 'SU', 1133 | 'SameBlock', 1134 | 'SaveWorkspace', 1135 | 'ScalarProduct', 1136 | 'SchurCover', 1137 | 'SemiSimpleType', 1138 | 'SemidirectProduct', 1139 | 'Semigroup', 1140 | 'Set', 1141 | 'SetAssertionLevel', 1142 | 'SetCommutator', 1143 | 'SetConjugate', 1144 | 'SetCrystGroupDefaultAction', 1145 | 'SetEntrySCTable', 1146 | 'SetFilterObj', 1147 | 'SetHashEntry', 1148 | 'SetHashEntryAtLastIndex', 1149 | 'SetHelpViewer', 1150 | 'SetIndeterminateName', 1151 | 'SetInfoLevel', 1152 | 'SetName', 1153 | 'SetParent', 1154 | 'SetPower', 1155 | 'ShallowCopy', 1156 | 'ShiftedCoeffs', 1157 | 'ShiftedPadicNumber', 1158 | 'ShortLexOrdering', 1159 | 'ShortestVectors', 1160 | 'Sigma', 1161 | 'SignInt', 1162 | 'SignPartition', 1163 | 'SignPerm', 1164 | 'SimpleLieAlgebra', 1165 | 'SimpleSystem', 1166 | 'SimplifiedFpGroup', 1167 | 'SimplifyPresentation', 1168 | 'SimultaneousEigenvalues', 1169 | 'SingleCollector', 1170 | 'Size', 1171 | 'SizeConsiderFunction', 1172 | 'SizeNumbersPerfectGroups', 1173 | 'SizeOfFieldOfDefinition', 1174 | 'SizeScreen', 1175 | 'SizeStabChain', 1176 | 'SizesCentralizers', 1177 | 'SizesConjugacyClasses', 1178 | 'SizesPerfectGroups', 1179 | 'SmallGeneratingSet', 1180 | 'SmallGroup', 1181 | 'SmallerDegreePermutationRepresentation', 1182 | 'SmallestGeneratorPerm', 1183 | 'SmallestMovedPoint', 1184 | 'SmallestRootInt', 1185 | 'SmithNormalFormIntegerMat', 1186 | 'Socle', 1187 | 'SocleTypePrimitiveGroup', 1188 | 'SolutionIntMat', 1189 | 'SolutionMat', 1190 | 'SolutionMatDestructive', 1191 | 'SolutionNullspaceIntMat', 1192 | 'Sort', 1193 | 'SortParallel', 1194 | 'SortedCharacterTable', 1195 | 'SortedCharacters', 1196 | 'SortedList', 1197 | 'SortedSparseActionHomomorphism', 1198 | 'SortingPerm', 1199 | 'Sp', 1200 | 'SparseActionHomomorphism', 1201 | 'SparseCartanMatrix', 1202 | 'SparseHashTable', 1203 | 'SparseIntKey', 1204 | 'SpecialLinearGroup', 1205 | 'SpecialOrthogonalGroup', 1206 | 'SpecialPcgs', 1207 | 'SpecialUnitaryGroup', 1208 | 'SplitCharacters', 1209 | 'SplitExtension', 1210 | 'SplitString', 1211 | 'SplittingField', 1212 | 'Sqrt', 1213 | 'SquareRoots', 1214 | 'StabChain', 1215 | 'StabChainBaseStrongGenerators', 1216 | 'StabChainImmutable', 1217 | 'StabChainMutable', 1218 | 'StabChainOp', 1219 | 'StabChainOptions', 1220 | 'Stabilizer', 1221 | 'StabilizerOfExternalSet', 1222 | 'StabilizerPcgs', 1223 | 'StandardAssociate', 1224 | 'StandardizeTable', 1225 | 'StarCyc', 1226 | 'Stirling1', 1227 | 'Stirling2', 1228 | 'StratMeetPartition', 1229 | 'StretchImportantSLPElement', 1230 | 'String', 1231 | 'StringDate', 1232 | 'StringOfResultOfStraightLineProgram', 1233 | 'StringPP', 1234 | 'StringTime', 1235 | 'StructuralCopy', 1236 | 'StructureConstantsTable', 1237 | 'StructureDescription', 1238 | 'SubAlgebraModule', 1239 | 'Subalgebra', 1240 | 'SubdirectProduct', 1241 | 'SubdirectProducts', 1242 | 'Subfield', 1243 | 'Subfields', 1244 | 'Subgroup', 1245 | 'SubgroupByPcgs', 1246 | 'SubgroupByProperty', 1247 | 'SubgroupOfWholeGroupByCosetTable', 1248 | 'SubgroupOfWholeGroupByQuotientSubgroup', 1249 | 'SubgroupProperty', 1250 | 'SubgroupShell', 1251 | 'SubgroupsSolvableGroup', 1252 | 'Submodule', 1253 | 'Submonoid', 1254 | 'SubnearAdditiveGroup', 1255 | 'SubnormalSeries', 1256 | 'Subring', 1257 | 'SubringWithOne', 1258 | 'Subsemigroup', 1259 | 'Subspace', 1260 | 'Subspaces', 1261 | 'SubstitutedWord', 1262 | 'SubtractSet', 1263 | 'Subword', 1264 | 'Successors', 1265 | 'Sum', 1266 | 'SumFactorizationFunctionPcgs', 1267 | 'SumIntersectionMat', 1268 | 'SumX', 1269 | 'SupersolvableResiduum', 1270 | 'SurjectiveActionHomomorphismAttr', 1271 | 'SuzukiGroup', 1272 | 'SylowComplement', 1273 | 'SylowSubgroup', 1274 | 'SylowSystem', 1275 | 'SymmetricClosureBinaryRelation', 1276 | 'SymmetricGroup', 1277 | 'SymmetricParentGroup', 1278 | 'SymmetricParts', 1279 | 'SymmetricPower', 1280 | 'SymmetricPowerOfAlgebraModule', 1281 | 'Symmetrizations', 1282 | 'SymplecticComponents', 1283 | 'SymplecticGroup', 1284 | 'TableAutomorphisms', 1285 | 'TableOfMarks', 1286 | 'TableOfMarksByLattice', 1287 | 'TableOfMarksCyclic', 1288 | 'TableOfMarksDihedral', 1289 | 'TableOfMarksFrobenius', 1290 | 'Tau', 1291 | 'TensorProduct', 1292 | 'TensorProductGModule', 1293 | 'TensorProductOfAlgebraModules', 1294 | 'Tensored', 1295 | 'TietzeWordAbstractWord', 1296 | 'Trace', 1297 | 'TraceImmediateMethods', 1298 | 'TraceMat', 1299 | 'TraceMethods', 1300 | 'TracePolynomial', 1301 | 'TracedCosetFpGroup', 1302 | 'TransferDiagram', 1303 | 'Transformation', 1304 | 'TransformingPermutations', 1305 | 'TransformingPermutationsCharacterTables', 1306 | 'TransitiveClosureBinaryRelation', 1307 | 'TransitiveIdentification', 1308 | 'Transitivity', 1309 | 'TranslatorSubalgebra', 1310 | 'TransposedMat', 1311 | 'TransposedMatAttr', 1312 | 'TransposedMatDestructive', 1313 | 'TransposedMatImmutable', 1314 | 'TransposedMatMutable', 1315 | 'TransposedMatOp', 1316 | 'TransposedMatrixGroup', 1317 | 'TriangulizeIntegerMat', 1318 | 'TriangulizeMat', 1319 | 'TriangulizedIntegerMat', 1320 | 'TriangulizedIntegerMatTransform', 1321 | 'TriangulizedNullspaceMat', 1322 | 'TriangulizedNullspaceMatDestructive', 1323 | 'TrivialCharacter', 1324 | 'TrivialGroup', 1325 | 'TrivialIterator', 1326 | 'TrivialSubalgebra', 1327 | 'TrivialSubgroup', 1328 | 'TrivialSubmagmaWithOne', 1329 | 'TrivialSubmodule', 1330 | 'TrivialSubmonoid', 1331 | 'TrivialSubspace', 1332 | 'Tuple', 1333 | 'Tuples', 1334 | 'TypeObj', 1335 | 'UnbindElmWPObj', 1336 | 'UnbindGlobal', 1337 | 'UnderlyingCharacterTable', 1338 | 'UnderlyingCharacteristic', 1339 | 'UnderlyingElement', 1340 | 'UnderlyingElementOfReesMatrixSemigroupElement', 1341 | 'UnderlyingElementOfReesZeroMatrixSemigroupElement', 1342 | 'UnderlyingExternalSet', 1343 | 'UnderlyingGeneralMapping', 1344 | 'UnderlyingGroup', 1345 | 'UnderlyingLeftModule', 1346 | 'UnderlyingLieAlgebra', 1347 | 'UnderlyingRelation', 1348 | 'Union', 1349 | 'Union2', 1350 | 'Unique', 1351 | 'UniteSet', 1352 | 'Units', 1353 | 'UnivariatePolynomial', 1354 | 'UnivariatePolynomialByCoefficients', 1355 | 'UnivariatePolynomialRing', 1356 | 'UnivariateRationalFunctionByCoefficients', 1357 | 'UnivariatenessTestRationalFunction', 1358 | 'UniversalEnvelopingAlgebra', 1359 | 'Unknown', 1360 | 'UnorderedTuples', 1361 | 'UnprofileFunctions', 1362 | 'UnprofileMethods', 1363 | 'UntraceMethods', 1364 | 'UpdateMap', 1365 | 'UpperCentralSeries', 1366 | 'UpperCentralSeriesOfGroup', 1367 | 'UpperSubdiagonal', 1368 | 'UseBasis', 1369 | 'UseFactorRelation', 1370 | 'UseIsomorphismRelation', 1371 | 'UseSubsetRelation', 1372 | 'Valuation', 1373 | 'Value', 1374 | 'ValueCochain', 1375 | 'ValueGlobal', 1376 | 'ValueMolienSeries', 1377 | 'ValueOption', 1378 | 'ValuePol', 1379 | 'ValuesOfClassFunction', 1380 | 'VectorSpace', 1381 | 'VectorSpaceByPcgsOfElementaryAbelianGroup', 1382 | 'View', 1383 | 'VirtualCharacter', 1384 | 'WeakPointerObj', 1385 | 'WedgeGModule', 1386 | 'WeekDay', 1387 | 'WeightLexOrdering', 1388 | 'WeightOfGenerators', 1389 | 'WeightVecFFE', 1390 | 'WeylGroup', 1391 | 'WeylOrbitIterator', 1392 | 'Where', 1393 | 'WreathProduct', 1394 | 'WreathProductImprimitiveAction', 1395 | 'WreathProductOrdering', 1396 | 'WreathProductProductAction', 1397 | 'WriteAll', 1398 | 'WriteByte', 1399 | 'WriteLine', 1400 | 'ZClassRepsQClass', 1401 | 'Zero', 1402 | 'ZeroAttr', 1403 | 'ZeroCoefficient', 1404 | 'ZeroCoefficientRatFun', 1405 | 'ZeroMapping', 1406 | 'ZeroMutable', 1407 | 'ZeroOp', 1408 | 'ZeroSM', 1409 | 'ZeroSameMutability', 1410 | 'GASMAN_STATS', 1411 | 'GASMAN', 1412 | ]) 1413 | -------------------------------------------------------------------------------- /gappy/gap_globals.py: -------------------------------------------------------------------------------- 1 | """Common globals defined by GAP.""" 2 | 3 | ############################################################################### 4 | # Copyright (C) 2009, William Stein 5 | # Copyright (C) 2012, Volker Braun 6 | # 7 | # Distributed under the terms of the GNU General Public License (GPL) 8 | # as published by the Free Software Foundation; either version 2 of 9 | # the License, or (at your option) any later version. 10 | # http://www.gnu.org/licenses/ 11 | ############################################################################### 12 | 13 | 14 | from .gap_functions import common_gap_functions 15 | 16 | 17 | __all__ = ['common_gap_globals'] 18 | 19 | 20 | # selected gap globals to use in tab completion 21 | common_gap_globals = set([ 22 | 'Assert', 23 | 'Cyclotomics', 24 | 'GaussianIntegers', 25 | 'GaussianRationals', 26 | 'GlobalMersenneTwister', 27 | 'GlobalRandomSource', 28 | 'InfoAlgebra', 29 | 'InfoAttributes', 30 | 'InfoBckt', 31 | 'InfoCharacterTable', 32 | 'InfoCoh', 33 | 'InfoComplement', 34 | 'InfoCoset', 35 | 'InfoFpGroup', 36 | 'InfoGroebner', 37 | 'InfoGroup', 38 | 'InfoLattice', 39 | 'InfoMatrix', 40 | 'InfoMonomial', 41 | 'InfoNumtheor', 42 | 'InfoOptions', 43 | 'InfoPackageLoading', 44 | 'InfoPcSubgroup', 45 | 'InfoWarning', 46 | 'Integers', 47 | 'NiceBasisFiltersInfo', 48 | 'Primes', 49 | 'Rationals', 50 | 'TableOfMarksComponents' 51 | ]) | common_gap_functions 52 | -------------------------------------------------------------------------------- /gappy/gap_includes.pxd: -------------------------------------------------------------------------------- 1 | # distutils: libraries = gap gmp m 2 | ############################################################################### 3 | # Copyright (C) 2009, William Stein 4 | # Copyright (C) 2012, Volker Braun 5 | # Copyright (C) 2021 E. Madison Bray 6 | # 7 | # Distributed under the terms of the GNU General Public License (GPL) 8 | # as published by the Free Software Foundation; either version 2 of 9 | # the License, or (at your option) any later version. 10 | # http://www.gnu.org/licenses/ 11 | ############################################################################### 12 | 13 | from libc.stdint cimport uintptr_t, uint8_t, uint16_t, uint32_t, uint64_t 14 | 15 | 16 | cdef extern from "gap/libgap-api.h" nogil: 17 | r""" 18 | /* The recursion_depth stuff is a temporary work-around to 19 | * https://github.com/embray/gappy/issues/12 and necessitates some 20 | * use of GAP internals; it can go away after GAP 4.12 (we should 21 | * maybe do a GAP version check before enabling this hack) 22 | */ 23 | #include "gap/funcs.h" 24 | static volatile Int _gappy_recursion_depth = 0; 25 | #define sig_GAP_Enter() { \ 26 | int _gappy_ok = GAP_Enter(); \ 27 | if (_gappy_ok) { \ 28 | if (_gappy_recursion_depth == 0) { \ 29 | _gappy_recursion_depth = GetRecursionDepth(); \ 30 | } \ 31 | } else { \ 32 | sig_error(); \ 33 | } \ 34 | } 35 | #define sig_GAP_Leave() { \ 36 | if (_gappy_recursion_depth != 0) { \ 37 | SetRecursionDepth(_gappy_recursion_depth); \ 38 | _gappy_recursion_depth = 0; \ 39 | } \ 40 | GAP_Leave(); \ 41 | } 42 | """ 43 | 44 | # Basic types 45 | ctypedef char Char 46 | ctypedef int Int 47 | ctypedef uintptr_t UInt 48 | ctypedef uint8_t UInt1 49 | ctypedef uint16_t UInt2 50 | ctypedef uint32_t UInt4 51 | ctypedef uint64_t UInt8 52 | ctypedef void* Obj 53 | 54 | # Stack management 55 | cdef void GAP_EnterStack() 56 | cdef void GAP_LeaveStack() 57 | cdef int GAP_Enter() except 0 58 | cdef void sig_GAP_Enter() 59 | cdef void GAP_Leave() 60 | cdef void sig_GAP_Leave() 61 | cdef int GAP_Error_Setjmp() except 0 62 | 63 | # Initialization 64 | ctypedef void (*GAP_CallbackFunc)() 65 | void GAP_Initialize(int argc, char ** argv, 66 | GAP_CallbackFunc markBagsCallback, GAP_CallbackFunc errorCallback, 67 | int handleSignals) 68 | 69 | # Arithmetic and operators 70 | Obj GAP_SUM(Obj, Obj) 71 | Obj GAP_DIFF(Obj, Obj) 72 | Obj GAP_PROD(Obj, Obj) 73 | Obj GAP_QUO(Obj, Obj) 74 | Obj GAP_POW(Obj, Obj) 75 | Obj GAP_MOD(Obj, Obj) 76 | int GAP_EQ(Obj opL, Obj opR) 77 | int GAP_LT(Obj opL, Obj opR) 78 | int GAP_IN(Obj, Obj) 79 | 80 | # Booleans 81 | cdef Obj GAP_True 82 | cdef Obj GAP_False 83 | 84 | # Evaluation 85 | Obj GAP_EvalString(const char *) except * 86 | Obj GAP_EvalStringNoExcept "GAP_EvalString"(const char *) 87 | 88 | # Global variables 89 | void GAP_AssignGlobalVariable(const char *, Obj) 90 | int GAP_CanAssignGlobalVariable(const char *) 91 | Obj GAP_ValueGlobalVariable(const char *) 92 | 93 | # Calls 94 | Obj GAP_CallFuncArray(Obj, UInt, Obj *) 95 | Obj GAP_CallFuncList(Obj, Obj) 96 | 97 | # Ints 98 | cdef int GAP_IsInt(Obj) 99 | cdef int GAP_IsSmallInt(Obj) 100 | cdef Obj GAP_MakeObjInt(UInt *, Int) 101 | cdef Int GAP_SizeInt(Obj) 102 | cdef UInt *GAP_AddrInt(Obj) 103 | 104 | # Floats 105 | cdef Obj GAP_NewMacFloat(double) 106 | double GAP_ValueMacFloat(Obj) 107 | 108 | # Strings 109 | cdef char *GAP_CSTR_STRING(Obj) 110 | cdef int GAP_IsString(Obj) 111 | cdef UInt GAP_LenString(Obj) 112 | cdef Obj GAP_MakeString(const char *) 113 | 114 | # Lists 115 | void GAP_AssList(Obj, UInt, Obj val) 116 | Obj GAP_ElmList(Obj, UInt) 117 | UInt GAP_LenList(Obj) 118 | int GAP_IsList(Obj) 119 | Obj GAP_NewPlist(Int) 120 | 121 | # Records 122 | void GAP_AssRecord(Obj, Obj, Obj) 123 | int GAP_IsRecord(Obj) 124 | Obj GAP_ElmRecord(Obj, Obj) 125 | Obj GAP_NewPrecord(Int) 126 | 127 | 128 | cdef extern from "gap/gasman.h" nogil: 129 | """ 130 | #define GAP_CollectBags(full) CollectBags(0, full) 131 | """ 132 | void GAP_MarkBag "MarkBag" (Obj bag) 133 | UInt GAP_CollectBags(UInt full) 134 | 135 | 136 | cdef extern from "gap/io.h" nogil: 137 | UInt OpenOutputStream(Obj stream) 138 | UInt CloseOutput() 139 | 140 | 141 | # TODO: Replace this with a GAP_MakeStringWithLen from the public API; 142 | # see https://github.com/gap-system/gap/issues/4211 143 | cdef extern from "gap/stringobj.h" nogil: 144 | """ 145 | static inline Obj GAP_MakeStringWithLen(char *s, size_t len) { 146 | Obj ret; 147 | C_NEW_STRING(ret, len, s); 148 | return ret; 149 | } 150 | """ 151 | Obj GAP_MakeStringWithLen(char *, size_t) 152 | -------------------------------------------------------------------------------- /gappy/gapobj.pxd: -------------------------------------------------------------------------------- 1 | #***************************************************************************** 2 | # Copyright (C) 2012 Volker Braun 3 | # Copyright (C) 2021 E. Madison Bray 4 | # 5 | # This program is free software: you can redistribute it and/or modify 6 | # it under the terms of the GNU General Public License as published by 7 | # the Free Software Foundation, either version 2 of the License, or 8 | # (at your option) any later version. 9 | # http://www.gnu.org/licenses/ 10 | #***************************************************************************** 11 | 12 | from .gap_includes cimport Obj, UInt 13 | from .gmp cimport mpz_t 14 | 15 | cdef Obj make_gap_list(parent, lst) except NULL 16 | cdef Obj make_gap_record(parent, dct) except NULL 17 | cdef Obj make_gap_integer(x) except NULL 18 | cdef Obj make_gap_float(x) except NULL 19 | cdef Obj make_gap_string(s) except NULL 20 | cdef GapObj make_any_gap_obj(parent, Obj obj) 21 | 22 | cdef GapObj make_GapObj(parent, Obj obj) 23 | cdef GapList make_GapList(parent, Obj obj) 24 | cdef GapRecord make_GapRecord(parent, Obj obj) 25 | cdef GapInteger make_GapInteger(parent, Obj obj) 26 | cdef GapFloat make_GapFloat(parent, Obj obj) 27 | cdef GapRational make_GapRational(parent, Obj obj) 28 | cdef GapString make_GapString(parent, Obj obj) 29 | cdef GapBoolean make_GapBoolean(parent, Obj obj) 30 | cdef GapFunction make_GapFunction(parent, Obj obj) 31 | cdef _GapLazyFunction make_GapLazyFunction(parent, str name, str doc, 32 | str source) 33 | cdef GapPermutation make_GapPermutation(parent, Obj obj) 34 | 35 | cdef void capture_stdout(Obj, Obj, Obj) 36 | cdef void gap_obj_str(Obj, Obj) 37 | cdef void gap_obj_repr(Obj, Obj) 38 | 39 | 40 | cdef class GapObj: 41 | # the instance of the Gap interpreter class; currently for compatibility 42 | # with Sage's Element class though not clear yet if it will make entire 43 | # sense to keep. 44 | cdef object _parent 45 | 46 | # the pointer to the GAP object (memory managed by GASMAN) 47 | cdef Obj value 48 | 49 | # comparison 50 | cdef bint _compare_by_id 51 | cdef bint _compare_equal(self, GapObj other) except -2 52 | cdef bint _compare_less(self, GapObj other) except -2 53 | cpdef _set_compare_by_id(self) 54 | cpdef _assert_compare_by_id(self) 55 | 56 | cdef _initialize(self, parent, Obj obj) 57 | cpdef is_bool(self) 58 | cpdef _add_(self, other) 59 | cpdef _div_(self, other) 60 | cpdef _sub_(self, other) 61 | cpdef _mul_(self, other) 62 | cpdef _mod_(self, other) 63 | cpdef _pow_(self, other) 64 | cpdef _richcmp_(self, other, int op) 65 | 66 | cpdef GapObj deepcopy(self, bint mut) 67 | 68 | cdef class GapInteger(GapObj): 69 | cpdef is_C_int(self) 70 | cdef long int to_C_int(self) 71 | cdef void to_mpz(self, mpz_t) 72 | 73 | cdef class GapFloat(GapObj): 74 | pass 75 | 76 | cdef class GapRational(GapObj): 77 | pass 78 | 79 | cdef class GapIntegerMod(GapObj): 80 | cpdef GapInteger lift(self) 81 | 82 | cdef class GapFiniteField(GapObj): 83 | cpdef GapInteger lift(self) 84 | 85 | cdef class GapCyclotomic(GapObj): 86 | pass 87 | 88 | cdef class GapRing(GapObj): 89 | pass 90 | 91 | cdef class GapString(GapObj): 92 | pass 93 | 94 | cdef class GapBoolean(GapObj): 95 | pass 96 | 97 | cdef class GapFunction(GapObj): 98 | cdef str name 99 | cdef str doc 100 | 101 | cdef class _GapLazyFunction(GapFunction): 102 | cdef str source 103 | cdef resolve(self) 104 | 105 | cdef class GapMethodProxy(GapFunction): 106 | cdef GapFunction func 107 | cdef object self 108 | 109 | cdef class GapList(GapObj): 110 | pass 111 | 112 | cdef class GapRecord(GapObj): 113 | cdef GapList _names(self) 114 | cdef GapObj _getitem(self, GapString name) 115 | 116 | cdef class GapPermutation(GapObj): 117 | pass 118 | -------------------------------------------------------------------------------- /gappy/gmp.pxd: -------------------------------------------------------------------------------- 1 | """Cython definitions for miscellaneous bits from GMP.""" 2 | 3 | 4 | from .gap_includes cimport UInt 5 | 6 | 7 | cdef extern from "": 8 | ctypedef struct __mpz_struct: 9 | pass 10 | ctypedef __mpz_struct mpz_t[1] 11 | 12 | ctypedef struct gmp_randstate_t: 13 | pass 14 | 15 | ctypedef unsigned long mp_bitcnt_t 16 | # GAP ensures at compile time that sizeof(mp_limb_t) == sizeof(UInt) 17 | ctypedef UInt mp_limb_t 18 | 19 | void mpz_init(mpz_t) 20 | void mpz_clear(mpz_t) 21 | void mpz_neg(mpz_t, const mpz_t) 22 | void mpz_import(mpz_t, size_t, int, int, int, size_t, void *) 23 | void *mpz_export(void *, size_t *, int, size_t, int, size_t, const mpz_t) 24 | size_t mpz_size(const mpz_t) 25 | size_t mpz_sizeinbase(const mpz_t, int) 26 | int mpz_sgn(const mpz_t) 27 | const mp_limb_t* mpz_limbs_read(const mpz_t) 28 | 29 | void gmp_randinit_default(gmp_randstate_t) 30 | void mpz_rrandomb(mpz_t, gmp_randstate_t, mp_bitcnt_t) 31 | -------------------------------------------------------------------------------- /gappy/operations.py: -------------------------------------------------------------------------------- 1 | """ 2 | Operations for gappy objects. 3 | 4 | GAP functions for which several methods can be available are called operations, 5 | so GAP ``Size`` is an example of an operation. This module is for inspecting 6 | GAP operations from Python. In particular, it can list the operations that take 7 | a particular gappy object as its first argument. This is used in tab 8 | completion, where Python ``x.[TAB]`` lists all GAP operations for which 9 | ``Operation(x, ...)`` is defined. 10 | """ 11 | 12 | import re 13 | import string 14 | 15 | 16 | __all__ = ['OperationInspector'] 17 | 18 | 19 | _NAME_RE = re.compile(r'(Setter|Getter|Tester)\((.*)\)') 20 | 21 | 22 | class OperationInspector: 23 | def __init__(self, obj): 24 | """ 25 | Information about operations that can act on a given GAP object. 26 | 27 | Parameters 28 | ---------- 29 | 30 | obj : `~gappy.element.GapObj` 31 | A `~gappy.element.GapObj` to query. 32 | 33 | Examples 34 | -------- 35 | 36 | >>> from gappy.operations import OperationInspector 37 | >>> OperationInspector(gap(123)) 38 | Operations on 123 39 | """ 40 | 41 | self._obj = obj 42 | self._gap = obj.parent() 43 | self.flags = self._gap.FlagsType(self._gap.TypeObj(self.obj)) 44 | 45 | def __repr__(self): 46 | """ 47 | Return the string representation 48 | 49 | Returns 50 | ------- 51 | 52 | str 53 | 54 | Examples 55 | -------- 56 | 57 | >>> from gappy.operations import OperationInspector 58 | >>> opr = OperationInspector(gap(123)) 59 | >>> opr.__repr__() 60 | 'Operations on 123' 61 | """ 62 | return 'Operations on {0}'.format(repr(self._obj)) 63 | 64 | @property 65 | def obj(self): 66 | """ 67 | The first argument for the operations 68 | 69 | Returns 70 | ------- 71 | 72 | `GapObj` 73 | 74 | Examples 75 | -------- 76 | 77 | >>> from gappy.operations import OperationInspector 78 | >>> x = OperationInspector(gap(123)) 79 | >>> print(x.obj) 80 | 123 81 | """ 82 | return self._obj 83 | 84 | def operations(self): 85 | """ 86 | Return the GAP operations for :meth:`obj` 87 | 88 | Returns 89 | ------- 90 | 91 | generator 92 | Generator iterating over all operations. 93 | 94 | Examples 95 | -------- 96 | 97 | >>> from gappy.operations import OperationInspector 98 | >>> x = OperationInspector(gap(123)) 99 | >>> gap.Unknown in x.operations() 100 | True 101 | """ 102 | IS_SUBSET_FLAGS = self._gap.IS_SUBSET_FLAGS 103 | GET_OPER_FLAGS = self._gap.GET_OPER_FLAGS 104 | OPERATIONS = self._gap.OPERATIONS 105 | 106 | def mfi(o): 107 | filts = GET_OPER_FLAGS(o) 108 | return any(all(IS_SUBSET_FLAGS(self.flags, fl) for fl in fls) 109 | for fls in filts) 110 | 111 | return (op for op in OPERATIONS if mfi(op)) 112 | 113 | def op_names(self): 114 | """ 115 | Return the names of the operations. 116 | 117 | Returns 118 | ------- 119 | list 120 | Sorted list of names. 121 | 122 | Examples 123 | -------- 124 | 125 | >>> from gappy.operations import OperationInspector 126 | >>> x = OperationInspector(gap(123)) 127 | >>> 'Sqrt' in x.op_names() 128 | True 129 | """ 130 | NameFunction = self._gap.NameFunction 131 | result = set() 132 | for f in self.operations(): 133 | name = str(NameFunction(f)) 134 | if name[0] not in string.ascii_letters: 135 | continue 136 | match = _NAME_RE.match(name) 137 | if match: 138 | result.add(match.groups()[1]) 139 | else: 140 | result.add(name) 141 | return sorted(result) 142 | -------------------------------------------------------------------------------- /gappy/utils.pyx: -------------------------------------------------------------------------------- 1 | # **************************************************************************** 2 | # Copyright (C) 2006 William Stein 3 | # Copyright (C) 2021 E. Madison Bray 4 | # 5 | # Distributed under the terms of the GNU General Public License (GPL) 6 | # as published by the Free Software Foundation; either version 2 of 7 | # the License, or (at your option) any later version. 8 | # https://www.gnu.org/licenses/ 9 | # **************************************************************************** 10 | 11 | import locale 12 | import os 13 | import resource 14 | import sys 15 | 16 | from posix.dlfcn cimport dlopen, dlsym, dlclose, dlerror, RTLD_NOW, RTLD_GLOBAL 17 | 18 | import psutil 19 | 20 | 21 | # _repr_mimebundle_ should not be needed here; this seems to be a bug in 22 | # Ipython 23 | _SPECIAL_ATTRS = set([ 24 | '_ipython_canary_method_should_not_exist_', 25 | '_repr_mimebundle_' 26 | ]) 27 | """ 28 | Special attributes which should not be looked up in GAP. 29 | 30 | Mostly intended to prevent IPython's custom display hook from unintentially 31 | initializing the GAP interpreter. 32 | """ 33 | 34 | 35 | _FS_ENCODING = sys.getfilesystemencoding() 36 | _LOC_ENCODING = locale.getpreferredencoding() 37 | 38 | 39 | cdef extern from "" nogil: 40 | ctypedef struct Dl_info: 41 | const char *dli_fname 42 | 43 | # dladdr() is non-POSIX, but it appears to be available on Linux, MacOS, 44 | # BSD and Cygwin at a minimum 45 | int dladdr(const void *, Dl_info *) 46 | 47 | 48 | cpdef get_gap_root(gap_root=None): 49 | """ 50 | Return the path to the GAP installation directory, or "GAP root" where 51 | the GAP standard library and standard packages are installed. 52 | 53 | Raises an `RuntimeError` if the "GAP root" cannot be found or an `OSError` 54 | if a system error occurs in the process of searching for it. 55 | 56 | Parameters 57 | ---------- 58 | 59 | gap_root : str or `pathlib.Path` 60 | Optional user-defined path to the GAP root; if given this foregoes 61 | other searches and just checks that it looks like a valid GAP root 62 | (the ``lib/init.g`` file can be found, specifically). 63 | 64 | Examples 65 | -------- 66 | 67 | The exact output of this example will depend on where libgap is installed 68 | on your system: 69 | 70 | >>> from gappy.utils import get_gap_root 71 | >>> get_gap_root() # doctest: +IGNORE_OUTPUT 72 | '/usr/local/lib/libgap.so' 73 | """ 74 | 75 | cdef void *handle 76 | cdef void *addr 77 | cdef char *error 78 | cdef Dl_info info 79 | 80 | # This code could easily be made more generic, but since it's really only 81 | # needed for libgap we hard-code the expected library filenames, as well 82 | # as the known external symbol to look up. 83 | if sys.platform.startswith('linux') or 'bsd' in sys.platform: 84 | dylib_name = b'libgap.so' 85 | elif sys.platform == 'darwin': 86 | dylib_name = b'libgap.dylib' 87 | elif sys.platform == 'cygwin': 88 | dylib_name = b'cyggap-0.dll' 89 | else: 90 | # platform not supported 91 | raise RuntimeError(f'platform not supported by gappy: {sys.platform}') 92 | 93 | # Hack to ensure that all symbols provided by libgap are loaded into the 94 | # global symbol table 95 | # Note: we could use RTLD_NOLOAD and avoid the subsequent dlclose() but 96 | # this isn't portable 97 | handle = dlopen(dylib_name, RTLD_NOW | RTLD_GLOBAL) 98 | if handle is NULL: 99 | error = dlerror() 100 | raise OSError( 101 | f'could not open the libgap dynamic library {dylib_name}: ' 102 | f'{error.decode(_LOC_ENCODING, "surrogateescape")}') 103 | 104 | if gap_root is None: 105 | gap_root = os.environ.get('GAP_ROOT') 106 | if gap_root is None: 107 | # Use dlsym() to get the address of a known exported symbol in 108 | # libgap 109 | addr = dlsym(handle, b'GAP_Initialize') 110 | if addr is NULL: 111 | error = dlerror() 112 | dlclose(handle) 113 | raise OSError(error.decode(_LOC_ENCODING, 'surrogateescape')) 114 | 115 | if not dladdr(addr, &info): 116 | error = dlerror() 117 | dlclose(handle) 118 | raise OSError(error.decode(_LOC_ENCODING, 'surrogateescape')) 119 | 120 | if info.dli_fname != NULL: 121 | dylib_path = info.dli_fname.decode(_FS_ENCODING, 122 | 'surrogateescape') 123 | 124 | # if libgap is in GAP_ROOT/.libs/ 125 | gap_root = os.path.dirname(os.path.dirname(dylib_path)) 126 | # On conda and sage (and maybe some other distros) we are in 127 | # / and gap is in share/gap 128 | # TODO: Add some other paths to try here as we find them 129 | for pth in [('.',), ('share', 'gap')]: 130 | gap_root = os.path.join(gap_root, *pth) 131 | if os.path.isfile(os.path.join(gap_root, 'lib', 'init.g')): 132 | break 133 | else: 134 | gap_root = None 135 | 136 | dlclose(handle) 137 | 138 | # If gap_root is still None we cannot proceed because GAP actually crashes 139 | # if we try to do anything without loading GAP's stdlib 140 | hint = ('Either pass gap_root when initializing the Gap class, ' 141 | 'or pass it via the GAP_ROOT environment variable.') 142 | if gap_root is None: 143 | raise RuntimeError(f"Could not determine path to GAP_ROOT. {hint}") 144 | elif not os.path.exists(os.path.join(gap_root, 'lib', 'init.g')): 145 | raise RuntimeError( 146 | f'GAP_ROOT path {gap_root} does not contain lib/init.g which is ' 147 | f'needed for GAP to work. {hint}') 148 | 149 | return os.path.normpath(gap_root) 150 | 151 | 152 | cpdef get_gap_memory_pool_size(unit='m'): 153 | """ 154 | Get the gap memory pool size for new GAP processes. 155 | 156 | Examples 157 | -------- 158 | 159 | >>> from gappy.utils import get_gap_memory_pool_size 160 | >>> get_gap_memory_pool_size() # system-specific output 161 | '...m' 162 | """ 163 | allowed_units = ('k', 'm', 'g') 164 | unit = unit.lower() 165 | 166 | if unit not in allowed_units: 167 | raise ValueError(f'unit must be one of {", ".join(allowed_units)}') 168 | 169 | mem = psutil.virtual_memory() 170 | swap = psutil.swap_memory() 171 | vmax = virtual_memory_limit() 172 | 173 | suggested_size = max(swap.free // 10, mem.available // 50) 174 | # Don't eat all address space if the user set ulimit -v 175 | suggested_size = min(suggested_size, vmax // 10) 176 | # ~220MB is the minimum for long doctests 177 | suggested_size = max(suggested_size, 400 * 1024**2) 178 | unit_bytes = 1024**(allowed_units.index(unit) + 1) 179 | suggested_size //= unit_bytes 180 | return str(suggested_size) + unit 181 | 182 | 183 | cpdef virtual_memory_limit(): 184 | """ 185 | Return the upper limit for virtual memory usage. 186 | 187 | This is the value set by ``ulimit -v`` at the command line or a 188 | practical limit if no limit is set. In any case, the value is 189 | bounded by ``sys.maxsize``. 190 | 191 | Returns 192 | ------- 193 | 194 | int 195 | The virtual memory limit in bytes. 196 | 197 | Examples 198 | -------- 199 | 200 | >>> from gappy.utils import virtual_memory_limit 201 | >>> virtual_memory_limit() > 0 202 | True 203 | >>> virtual_memory_limit() <= sys.maxsize 204 | True 205 | """ 206 | try: 207 | vmax = resource.getrlimit(resource.RLIMIT_AS)[0] 208 | except resource.error: 209 | vmax = resource.RLIM_INFINITY 210 | if vmax == resource.RLIM_INFINITY: 211 | vmax = psutil.virtual_memory().total + psutil.swap_memory().total 212 | return min(vmax, sys.maxsize) 213 | 214 | 215 | def _converter_for_type(registry, type_): 216 | r""" 217 | Helper function for determining the most appropriate converter in a 218 | type->converter map for the given type. 219 | 220 | First checks the registry for direct type matches, then tries to find the 221 | most specific converter for super-classes of the given type. 222 | 223 | Examples 224 | -------- 225 | 226 | If a converter is not defined directly on a `~gappy.gapobj.GapObj` 227 | subclass, but is defined on one of its bases, the one from the base 228 | will be used: 229 | 230 | >>> from gappy.gapobj import GapObj 231 | >>> class Foo: 232 | ... '''Wrapper for GapObjs''' 233 | ... def __init__(self, obj): 234 | ... self.obj = obj 235 | ... def __repr__(self): 236 | ... return f'' 237 | ... 238 | >>> @GapObj.convert_to('foo') 239 | ... def gapobj_to_foo(obj): 240 | ... return Foo(obj) 241 | ... 242 | 243 | Works for `.GapObj`\s that don't have a more specific type: 244 | 245 | >>> S3 = gap.SymmetricGroup(3) 246 | >>> type(S3) 247 | 248 | >>> S3.foo() 249 | 250 | 251 | As well as for `.GapObj` subclasses: 252 | 253 | >>> one = gap(1) 254 | >>> type(one) 255 | 256 | >>> one.foo() 257 | 258 | """ 259 | 260 | converter = registry.get(type_) 261 | if converter is None: 262 | candidates = set() 263 | for parent_type, conv in registry.items(): 264 | if issubclass(type_, parent_type): 265 | candidates.add(parent_type) 266 | 267 | if candidates: 268 | mro = type_.__mro__ 269 | def sort_key(c): 270 | try: 271 | return mro.index(c) 272 | except ValueError: 273 | # a virtual subclass, i.e. and ABC; sort these 274 | # lowest 275 | return len(mro) 276 | 277 | converter = registry[min(candidates, key=sort_key)] 278 | 279 | return converter 280 | -------------------------------------------------------------------------------- /gappy/version.py: -------------------------------------------------------------------------------- 1 | # NOTE: First try _dev.scm_version if it exists and setuptools_scm is installed 2 | # This file is not included in gappy wheels/tarballs, so otherwise it will 3 | # fall back on the generated _version module. 4 | try: 5 | try: 6 | from ._dev.scm_version import version 7 | except ImportError: 8 | from ._version import version 9 | except Exception: 10 | import warnings 11 | warnings.warn( 12 | f'could not determine {__name__.split(".")[0]} package version; ' 13 | f'this indicates a broken installation') 14 | del warnings 15 | 16 | version = '0.0.0' 17 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "Cython<3.0.0", 4 | "cysignals", 5 | "setuptools>=42", 6 | "setuptools_scm[toml]>=3.4", 7 | "wheel" 8 | ] 9 | build-backend = "setuptools.build_meta" 10 | 11 | [tool.setuptools_scm] 12 | write_to = "gappy/_version.py" 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = gappy-system 3 | author = E. Madison Bray 4 | author_email = erik.m.bray@gmail.com 5 | description = Python interface to GAP 6 | url = https://github.com/embray/gappy 7 | long_description = file: README.rst, CHANGES.rst 8 | long_description_content_type = text/x-rst 9 | license = GPLv3+ 10 | license_files = LICENSE 11 | platform = linux 12 | classifiers = 13 | Development Status :: 3 - Alpha 14 | Intended Audience :: Education 15 | Intended Audience :: Science/Research 16 | License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) 17 | Operating System :: POSIX :: Linux 18 | Programming Language :: Cython 19 | Programming Language :: Python :: 3.7 20 | Programming Language :: Python :: 3.8 21 | Topic :: Scientific/Engineering :: Mathematics 22 | 23 | [options] 24 | # We set packages to find: to automatically find all sub-packages 25 | packages = find: 26 | python_requires = >=3.7 27 | setup_requires = setuptools_scm 28 | install_requires = 29 | cysignals 30 | psutil 31 | 32 | [options.packages.find] 33 | exclude = gappy._dev 34 | 35 | [options.package_data] 36 | gappy = *.pxd, *.pyx 37 | 38 | [options.extras_require] 39 | tests = 40 | numpy 41 | pytest 42 | pytest-cython>=0.1.1 43 | pytest-doctestplus 44 | setuptools_scm 45 | docs = 46 | Sphinx 47 | 48 | [tool:pytest] 49 | addopts = 50 | -m 'not long' 51 | --doctest-cython 52 | --doctest-modules 53 | --doctest-plus 54 | doctest_optionflags = NORMALIZE_WHITESPACE ELLIPSIS 55 | # 1) ignore warning that can come from older versions of virtualenv 56 | # 2) ignore warning from setuptools_scm when running on CI 57 | filterwarnings = 58 | ignore:Using or importing the ABCs from 'collections' 59 | ignore:.*is shallow and may cause errors 60 | markers = 61 | long: mark a test as taking a long time 62 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import glob 3 | import os 4 | import tempfile 5 | import sys 6 | from distutils.command.build_ext import build_ext as _build_ext 7 | from distutils.errors import DistutilsOptionError 8 | 9 | from setuptools import setup, Extension 10 | from Cython.Build import cythonize 11 | 12 | 13 | class build_ext(_build_ext): 14 | user_options = _build_ext.user_options + [ 15 | ('gap-root=', None, 16 | 'path to the GAP installation directory (GAP_ROOT); may also be ' 17 | 'given by the GAP_ROOT environment variable which is overridden by ' 18 | 'this flag'), 19 | ('gap-include=', None, 20 | 'path to the GAP header files; by default they are assumed installed ' 21 | 'in a standard system header path unless --gap-root/GAP_ROOT is ' 22 | 'specified, in which case they will be relative to GAP_ROOT; may ' 23 | 'also be overridden with the GAP_INCLUDE environment variable'), 24 | ('gap-lib=', None, 25 | 'directory containing the libgap binary; by default it is assumed ' 26 | 'installed in a standard system library path unless ' 27 | '--gap-root/GAP_ROOT is specified, in which case it will be relative ' 28 | 'to GAP_ROOT; may also be overridden with the GAP_LIB environment ' 29 | 'variable') 30 | ] 31 | 32 | def initialize_options(self): 33 | super().initialize_options() 34 | self.gap_root = None 35 | self.gap_include = None 36 | self.gap_lib = None 37 | self._using_gap_root = False 38 | 39 | def finalize_options(self): 40 | super().finalize_options() 41 | 42 | self.gap_root = self._get_directory_option('gap_root') 43 | self.gap_include = self._get_directory_option('gap_include') 44 | self.get_lib = self._get_directory_option('gap_lib') 45 | 46 | if self.gap_root is not None: 47 | self._using_gap_root = True 48 | 49 | if self.gap_include is None: 50 | self.gap_include = os.path.join(self.gap_root, 'src') 51 | 52 | if self.gap_lib is None: 53 | self.gap_lib = os.path.join(self.gap_root, '.libs') 54 | 55 | # Automatic support for building in a conda environment with the 56 | # conda-provided Python; this is more reliable than using CONDA_PREFIX 57 | # which may not be set if a conda Python is run without activating 58 | # the environment 59 | if os.path.exists(os.path.join(sys.prefix, 'conda-meta')): 60 | self.include_dirs.insert(0, os.path.join(sys.prefix, 'include')) 61 | self.library_dirs.insert(0, os.path.join(sys.prefix, 'lib')) 62 | 63 | def run(self): 64 | if self._using_gap_root: 65 | # We are using the headers from a GAP_ROOT installation of GAP, 66 | # so the headers are not prefixed by a directory named gap/ as 67 | # expected by our sources (as would be the case when the headers 68 | # are installed as system headers) so we must make a symlink to 69 | # the gap headers named gap/ 70 | # 71 | # We must also add the GAP_ROOT/gen directory for config.h 72 | # TODO: In newer GAP versions this appears to be renamed 73 | # GAP_ROOT/build 74 | include_temp = os.path.join(self.build_temp, 'include') 75 | gap_includes = [ 76 | include_temp, 77 | os.path.join(self.gap_root, 'gen') 78 | ] 79 | gap_dir = os.path.join(include_temp, 'gap') 80 | if not os.path.exists(gap_dir): 81 | os.makedirs(os.path.join(include_temp)) 82 | os.symlink(self.gap_include, gap_dir) 83 | elif self.gap_include is not None: 84 | gap_includes = [self.gap_include] 85 | else: 86 | gap_includes = [] 87 | 88 | self.include_dirs = gap_includes + self.include_dirs 89 | 90 | if self.gap_lib is not None: 91 | self.library_dirs.insert(0, self.gap_lib) 92 | self.rpath.insert(0, self.gap_lib) 93 | 94 | if self.extensions: 95 | nthreads = getattr(self, 'parallel', None) # -j option in Py3.5+ 96 | nthreads = int(nthreads) if nthreads else None 97 | self.extensions[:] = cythonize( 98 | self.extensions, nthreads=nthreads, force=self.force, 99 | language_level=3, compiler_directives={'autotestdict': False}) 100 | super().run() 101 | 102 | def _get_directory_option(self, opt): 103 | val = getattr(self, opt) 104 | 105 | if val is None: 106 | val = os.environ.get(opt.upper()) 107 | 108 | if val is not None and not os.path.isdir(val): 109 | raise DistutilsOptionError( 110 | f'--{opt}/{opt.upper()} directory {val} does not exist or is ' 111 | f'not a directory') 112 | 113 | return val 114 | 115 | 116 | setup( 117 | cmdclass={'build_ext': build_ext}, 118 | ext_modules=[Extension('*', ['gappy/*.pyx'])], 119 | use_scm_version={'write_to': 'gappy/_version.py'} 120 | ) 121 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | """Additional pytest configuration.""" 2 | 3 | 4 | def pytest_report_header(config): 5 | try: 6 | from gappy import gap 7 | except Exception as exc: 8 | return f'gap: could not import gappy: {exc}' 9 | 10 | try: 11 | gap.initialize() 12 | return (f'gap: GAP_ROOT={gap.gap_root} ' 13 | f'GAPInfo.Version={gap.GAPInfo.Version}') 14 | except Exception as exc: 15 | return f'gap: GAP installation not detected or broken: {exc}' 16 | -------------------------------------------------------------------------------- /tests/test_long.py: -------------------------------------------------------------------------------- 1 | """ 2 | Long tests for gappy. 3 | 4 | These stress test the garbage collection inside GAP. 5 | """ 6 | 7 | 8 | from random import randint 9 | 10 | import pytest 11 | 12 | from gappy import gap 13 | 14 | 15 | pytestmark = pytest.mark.long 16 | 17 | 18 | def test_loop_1(): 19 | gap.collect() 20 | for i in range(10000): 21 | G = gap.CyclicGroup(2) 22 | 23 | 24 | def test_loop_2(): 25 | G = gap.FreeGroup(2) 26 | a, b = G.GeneratorsOfGroup() 27 | for i in range(100): 28 | rel = gap([a**2, b**2, a*b*a*b]) 29 | H = G / rel 30 | H1 = H.GeneratorsOfGroup()[0] 31 | n = H1.Order() 32 | assert n == 2 33 | 34 | for i in range(300000): 35 | n = gap.Order(H1) 36 | 37 | 38 | def test_loop_3(): 39 | G = gap.FreeGroup(2) 40 | a, b = G.GeneratorsOfGroup() 41 | for i in range(300000): 42 | lst = gap([]) 43 | lst.Add(a ** 2) 44 | lst.Add(b ** 2) 45 | lst.Add(b * a) 46 | 47 | 48 | def test_recursion_depth_overflow_on_error(): 49 | """Regression test for https://github.com/embray/gappy/issues/12""" 50 | 51 | for i in range(0, 5000): 52 | rnd = [randint(-10, 10) for i in range(0, randint(0, 7))] 53 | # compute the sum in GAP 54 | _ = gap.Sum(rnd) 55 | try: 56 | gap.Sum(*rnd) 57 | pytest.fail( 58 | 'This should have triggered a ValueError' 59 | 'because Sum needs a list as argument' 60 | ) 61 | except ValueError: 62 | pass 63 | 64 | # There's no reason this should ever go very high; 10 is a reasonable 65 | # upper-limit but in practice it never seems to go above 8 if this 66 | # is fixed properly 67 | assert gap.GetRecursionDepth() < 10 68 | -------------------------------------------------------------------------------- /tests/test_misc.py: -------------------------------------------------------------------------------- 1 | """Additional tests for gappy""" 2 | 3 | 4 | from gappy import gap 5 | 6 | 7 | def test_write_to_file(tmp_path): 8 | """ 9 | Test that libgap can write to files 10 | 11 | See :trac:`16502`, :trac:`15833`. 12 | """ 13 | fname = str(tmp_path / 'test.txt') 14 | message = "Ceci n'est pas une groupe" 15 | gap.PrintTo(fname, message) 16 | with open(fname, 'r') as f: 17 | assert f.read() == message 18 | 19 | assert gap.StringFile(fname) == message 20 | 21 | 22 | def test_gap_exec(capfd): 23 | """A regression test originally from Sage.""" 24 | 25 | gap.Exec('echo hello from the shell') 26 | stdio = capfd.readouterr() 27 | assert stdio.out.rstrip() == 'hello from the shell' 28 | 29 | 30 | def test_gap_function_re(): 31 | """Tests of the regular expression for GAP function declarations.""" 32 | 33 | m = gap._gap_function_re.match('function()') 34 | assert m and m.group() == 'function()' 35 | m = gap._gap_function_re.search(''' 36 | blah blah blah 37 | 38 | function ( a, b ) 39 | ''') 40 | assert m and m.group().strip() == 'function ( a, b )' 41 | 42 | 43 | def test_lazy_function_as_argument(): 44 | """ 45 | Regression test for bug with lazy functions. 46 | 47 | When a lazy function is used as an argument to another GAP function, ensure 48 | that the function is initialized. 49 | """ 50 | 51 | def make_gap_function(): 52 | gap._gap_function.cache_clear() 53 | 54 | @gap.gap_function 55 | def OnTuples(omega, g): 56 | """ 57 | Just a wrapper for OnTuples to demonstrate the bug. 58 | 59 | function(omega, g) 60 | return OnTuples(omega, g); 61 | end; 62 | """ 63 | 64 | return OnTuples 65 | 66 | G = gap.Group(gap.eval('(1,2,3)'), gap.eval('(2,3,4)')) 67 | O = gap.Orbit(G, [1], make_gap_function()) 68 | assert O == [[1], [2], [3], [4]] 69 | 70 | # Make sure it works even if the lazy function is wrapped in some other 71 | # object that has a converter to GapObj registered (regression test from 72 | # the Sage integration) 73 | class MyGapFunction: 74 | def __init__(self, obj): 75 | self.obj = obj 76 | 77 | @gap.convert_from(MyGapFunction) 78 | def my_gap_function_to_gapobj(gap, obj): 79 | return obj.obj 80 | 81 | wrapped_OnTuples = MyGapFunction(make_gap_function()) 82 | O = gap.Orbit(G, [1], wrapped_OnTuples) 83 | assert O == [[1], [2], [3], [4]] 84 | --------------------------------------------------------------------------------