├── VERSION ├── cypari2 ├── .gitignore ├── closure.pxd ├── cypari2.pc.in ├── __init__.py ├── handle_error.pxd ├── pycore_long.pxd ├── cypari.h ├── pari_instance.pxd ├── stack.pxd ├── string_utils.pxd ├── custom_block.pyx ├── paripriv.pxd ├── meson.build ├── string_utils.pyx ├── gen.pxd ├── convert.pxd ├── pycore_long.h ├── types.pxd ├── handle_error.pyx ├── closure.pyx ├── stack.pyx └── convert.pyx ├── docs ├── source │ ├── gen.rst │ ├── stack.rst │ ├── closure.rst │ ├── convert.rst │ ├── handle_error.rst │ ├── pari_instance.rst │ ├── index.rst │ └── conf.py └── Makefile ├── readthedocs.yml ├── readthedocs-conda.yml ├── CODE_OF_CONDUCT.md ├── tests ├── meson.build ├── test.c ├── test_backward.py ├── test.pyx ├── rundoctest.py └── test_integers.py ├── CONTRIBUTING.md ├── .cruft.json ├── Makefile ├── autogen ├── __init__.py ├── ret.py ├── parser.py ├── doc.py ├── args.py └── generator.py ├── .gitignore ├── pyproject.toml ├── NEWS ├── .github └── workflows │ ├── dist.yml │ └── main.yml ├── meson.build ├── README.rst ├── .install-pari.sh └── LICENSE /VERSION: -------------------------------------------------------------------------------- 1 | 2.2.4 2 | -------------------------------------------------------------------------------- /cypari2/.gitignore: -------------------------------------------------------------------------------- 1 | auto_gen.pxi 2 | auto_instance.pxi 3 | -------------------------------------------------------------------------------- /docs/source/gen.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: cypari2.gen 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/source/stack.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: cypari2.stack 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/source/closure.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: cypari2.closure 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/source/convert.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: cypari2.convert 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/source/handle_error.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: cypari2.handle_error 2 | :members: 3 | -------------------------------------------------------------------------------- /docs/source/pari_instance.rst: -------------------------------------------------------------------------------- 1 | .. automodule:: cypari2.pari_instance 2 | :members: 3 | -------------------------------------------------------------------------------- /cypari2/closure.pxd: -------------------------------------------------------------------------------- 1 | from .gen cimport Gen 2 | cpdef Gen objtoclosure(f) 3 | cdef int _pari_init_closure() except -1 4 | -------------------------------------------------------------------------------- /readthedocs.yml: -------------------------------------------------------------------------------- 1 | conda: 2 | file: readthedocs-conda.yml 3 | python: 4 | version: 2.7 5 | pip_install: true 6 | -------------------------------------------------------------------------------- /readthedocs-conda.yml: -------------------------------------------------------------------------------- 1 | channels: 2 | - conda-forge 3 | dependencies: 4 | - pari 5 | - sphinx 6 | - cython 7 | - cysignals 8 | -------------------------------------------------------------------------------- /cypari2/cypari2.pc.in: -------------------------------------------------------------------------------- 1 | includedir=${pcfiledir} 2 | 3 | Name: cypari2 4 | Description: cypari2 library 5 | Version: @version@ 6 | Cflags: -I${includedir} 7 | -------------------------------------------------------------------------------- /cypari2/__init__.py: -------------------------------------------------------------------------------- 1 | from .pari_instance import Pari 2 | from .handle_error import PariError 3 | from .gen import Gen 4 | from .custom_block import init_custom_block 5 | 6 | init_custom_block() 7 | -------------------------------------------------------------------------------- /cypari2/handle_error.pxd: -------------------------------------------------------------------------------- 1 | from .types cimport GEN 2 | 3 | cdef void _pari_init_error_handling() noexcept 4 | cdef int _pari_err_handle(GEN E) except 0 5 | cdef void _pari_err_recover(long errnum) noexcept 6 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | CyPari 2 is an open-source project maintained by the SageMath community. 2 | 3 | The [Code of Conduct](https://github.com/sagemath/sage/blob/develop/CODE_OF_CONDUCT.md) 4 | of the Sage community applies. 5 | -------------------------------------------------------------------------------- /tests/meson.build: -------------------------------------------------------------------------------- 1 | extension_data = { 2 | 'test': files('test.pyx'), 3 | } 4 | 5 | foreach name, pyx : extension_data 6 | py.extension_module( 7 | name, 8 | sources: pyx, 9 | subdir: 'cypari2', 10 | install: true, 11 | dependencies: [cysignals, pari], 12 | include_directories: [inc_root, include_directories('./../cypari2')], 13 | ) 14 | endforeach 15 | 16 | -------------------------------------------------------------------------------- /cypari2/pycore_long.pxd: -------------------------------------------------------------------------------- 1 | from cpython.longintrepr cimport py_long, digit 2 | 3 | cdef extern from "pycore_long.h": 4 | digit* ob_digit(py_long o) 5 | bint _PyLong_IsZero(py_long o) 6 | bint _PyLong_IsNegative(py_long o) 7 | bint _PyLong_IsPositive(py_long o) 8 | Py_ssize_t _PyLong_DigitCount(py_long o) 9 | void _PyLong_SetSignAndDigitCount(py_long o, int sign, Py_ssize_t size) 10 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | CyPari 2 is an open-source project maintained by the SageMath community. 2 | 3 | Contributions of all sorts are heartily welcomed. 4 | 5 | See https://github.com/sagemath/sage/blob/develop/CONTRIBUTING.md for general 6 | guidance on how to contribute. 7 | 8 | Open issues or submit pull requests at https://github.com/sagemath/cypari2 9 | and join https://groups.google.com/group/sage-devel to discuss. 10 | -------------------------------------------------------------------------------- /.cruft.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "https://github.com/mkoeppe/sage", 3 | "commit": "b73d4e2fad3b71c74b62740aa83f37e98faa3f15", 4 | "checkout": "sagemath-environment-cookiecutter", 5 | "context": { 6 | "cookiecutter": { 7 | "project_name": "cypari2", 8 | "_template": "https://github.com/mkoeppe/sage" 9 | } 10 | }, 11 | "directory": "pkgs/sage-project-cookiecutter/sage_project_cookiecutter/sagemath-upstream-package-template" 12 | } 13 | -------------------------------------------------------------------------------- /cypari2/cypari.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Additional macros and fixes for the PARI headers. This is meant to 3 | * be included after including 4 | */ 5 | 6 | #undef coeff /* Conflicts with NTL (which is used by SageMath) */ 7 | 8 | 9 | /* Array element assignment */ 10 | #define set_gel(x, n, z) (gel((x), (n)) = (z)) 11 | #define set_gmael(x, i, j, z) (gmael((x), (i), (j)) = (z)) 12 | #define set_gcoeff(x, i, j, z) (gcoeff((x), (i), (j)) = (z)) 13 | #define set_uel(x, n, z) (uel((x), (n)) = (z)) 14 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Optional Makefile for easier development 2 | 3 | VERSION = $(shell cat VERSION) 4 | 5 | PYTHON = python 6 | PIP = $(PYTHON) -m pip -v 7 | 8 | 9 | install: 10 | $(PIP) install --upgrade . 11 | 12 | check: 13 | ulimit -s 8192; $(PYTHON) -u tests/rundoctest.py 14 | ulimit -s 8192; $(PYTHON) tests/test_integers.py 15 | ulimit -s 8192; $(PYTHON) tests/test_backward.py 16 | 17 | dist: 18 | chmod go+rX-w -R . 19 | $(PIP) install build 20 | umask 0022 && $(PYTHON) -m build --sdist 21 | 22 | 23 | .PHONY: install check dist 24 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | .. CyPari2 documentation master file, created by 2 | sphinx-quickstart on Thu May 18 16:07:32 2017. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to CyPari2's documentation! 7 | =================================== 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | :caption: Contents: 12 | 13 | pari_instance 14 | gen 15 | stack 16 | closure 17 | handle_error 18 | convert 19 | 20 | 21 | Indices and tables 22 | ================== 23 | 24 | * :ref:`genindex` 25 | * :ref:`modindex` 26 | * :ref:`search` 27 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = python -msphinx 7 | SPHINXPROJ = CyPari2 8 | SOURCEDIR = source 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) -------------------------------------------------------------------------------- /cypari2/pari_instance.pxd: -------------------------------------------------------------------------------- 1 | from .types cimport * 2 | cimport cython 3 | 4 | from .gen cimport Gen 5 | 6 | # DEPRECATED INTERNAL FUNCTION used (incorrectly) in sagemath < 10.5 7 | cpdef long prec_words_to_bits(long prec_in_words) noexcept 8 | cpdef long default_bitprec() noexcept 9 | 10 | cdef extern from *: 11 | """ 12 | #define DEFAULT_BITPREC prec2nbits(DEFAULTPREC) 13 | """ 14 | long DEFAULT_BITPREC 15 | 16 | cdef class Pari_auto: 17 | pass 18 | 19 | cdef class Pari(Pari_auto): 20 | cdef readonly Gen PARI_ZERO, PARI_ONE, PARI_TWO 21 | cpdef Gen zero(self) 22 | cpdef Gen one(self) 23 | cdef Gen _empty_vector(self, long n) 24 | 25 | cdef long get_var(v) except -2 26 | -------------------------------------------------------------------------------- /cypari2/stack.pxd: -------------------------------------------------------------------------------- 1 | from .types cimport GEN, pari_sp 2 | from .gen cimport Gen_base, Gen 3 | 4 | 5 | cdef Gen new_gen(GEN x) 6 | cdef new_gens2(GEN x, GEN y) 7 | cdef Gen new_gen_noclear(GEN x) 8 | cdef Gen clone_gen(GEN x) 9 | cdef Gen clone_gen_noclear(GEN x) 10 | 11 | cdef void clear_stack() noexcept 12 | cdef void reset_avma() noexcept 13 | 14 | cdef void remove_from_pari_stack(Gen self) noexcept 15 | cdef int move_gens_to_heap(pari_sp lim) except -1 16 | 17 | cdef int before_resize() except -1 18 | cdef int set_pari_stack_size(size_t size, size_t sizemax) except -1 19 | cdef void after_resize() noexcept 20 | 21 | 22 | cdef class DetachGen: 23 | cdef source 24 | 25 | cdef GEN detach(self) except NULL 26 | -------------------------------------------------------------------------------- /cypari2/string_utils.pxd: -------------------------------------------------------------------------------- 1 | cdef extern from *: 2 | int PY_MAJOR_VERSION 3 | 4 | cpdef bytes to_bytes(s) 5 | cpdef unicode to_unicode(s) 6 | 7 | cpdef inline to_string(s): 8 | r""" 9 | Converts a bytes and unicode ``s`` to a string. 10 | 11 | String means bytes in Python2 and unicode in Python3 12 | 13 | Examples: 14 | 15 | >>> from cypari2.string_utils import to_string 16 | >>> s1 = to_string(b'hello') 17 | >>> s2 = to_string('hello') 18 | >>> s3 = to_string(u'hello') 19 | >>> type(s1) == type(s2) == type(s3) == str 20 | True 21 | >>> s1 == s2 == s3 == 'hello' 22 | True 23 | """ 24 | if PY_MAJOR_VERSION <= 2: 25 | return to_bytes(s) 26 | else: 27 | return to_unicode(s) 28 | -------------------------------------------------------------------------------- /cypari2/custom_block.pyx: -------------------------------------------------------------------------------- 1 | #***************************************************************************** 2 | # Distributed under the terms of the GNU General Public License (GPL) 3 | # as published by the Free Software Foundation; either version 2 of 4 | # the License, or (at your option) any later version. 5 | # https://www.gnu.org/licenses/ 6 | #***************************************************************************** 7 | 8 | from cysignals.signals cimport add_custom_signals 9 | 10 | cdef extern from "pari/pari.h": 11 | int PARI_SIGINT_block, PARI_SIGINT_pending 12 | 13 | cdef int custom_signal_is_blocked() noexcept: 14 | return PARI_SIGINT_block 15 | 16 | cdef void custom_signal_unblock() noexcept: 17 | global PARI_SIGINT_block 18 | PARI_SIGINT_block = 0 19 | 20 | cdef void custom_set_pending_signal(int sig) noexcept: 21 | global PARI_SIGINT_pending 22 | PARI_SIGINT_pending = sig 23 | 24 | def init_custom_block(): 25 | add_custom_signals(&custom_signal_is_blocked, 26 | &custom_signal_unblock, 27 | &custom_set_pending_signal) 28 | -------------------------------------------------------------------------------- /cypari2/paripriv.pxd: -------------------------------------------------------------------------------- 1 | """ 2 | Declarations for private functions from PARI 3 | 4 | Ideally, we shouldn't use these, but for technical reasons, we have to. 5 | """ 6 | 7 | from .types cimport * 8 | 9 | cdef extern from "pari/paripriv.h": 10 | int t_FF_FpXQ, t_FF_Flxq, t_FF_F2xq 11 | 12 | int gpd_QUIET, gpd_TEST, gpd_EMACS, gpd_TEXMACS 13 | 14 | struct pariout_t: 15 | char format # e,f,g 16 | long fieldw # 0 (ignored) or field width 17 | long sigd # -1 (all) or number of significant digits printed 18 | int sp # 0 = suppress whitespace from output 19 | int prettyp # output style: raw, prettyprint, etc 20 | int TeXstyle 21 | 22 | struct gp_data: 23 | pariout_t *fmt 24 | unsigned long flags 25 | ulong primelimit # deprecated 26 | 27 | extern gp_data* GP_DATA 28 | 29 | # In older versions of PARI, this is declared in the private 30 | # non-installed PARI header file "anal.h". More recently, this is 31 | # declared in "paripriv.h". Since a double declaration does not hurt, 32 | # we declare it here regardless. 33 | cdef extern const char* closure_func_err() 34 | -------------------------------------------------------------------------------- /autogen/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import 2 | 3 | from pathlib import Path 4 | 5 | from .generator import PariFunctionGenerator 6 | 7 | 8 | def rebuild(pari_data: str, force: bool = False, output: None | str = None): 9 | if output is None: 10 | output = "cypari2" 11 | output_dir = Path(output) 12 | # Ensure output directory exists 13 | output_dir.mkdir(parents=True, exist_ok=True) 14 | 15 | pari_datadir = Path(pari_data) 16 | if not pari_datadir.is_dir(): 17 | raise ValueError(f"PARI data directory {pari_datadir} does not exist or is not a directory") 18 | 19 | src_files = [pari_datadir / "pari.desc"] + list(Path("autogen").glob("*.py")) 20 | gen_files = [ 21 | output_dir / "auto_paridecl.pxd", 22 | output_dir / "auto_gen.pxi", 23 | ] 24 | 25 | if not force and all(f.exists() for f in gen_files): 26 | src_mtime = max(f.stat().st_mtime for f in src_files) 27 | gen_mtime = min(f.stat().st_mtime for f in gen_files) 28 | 29 | if gen_mtime > src_mtime: 30 | return 31 | 32 | G = PariFunctionGenerator(pari_datadir, output_dir) 33 | G() 34 | -------------------------------------------------------------------------------- /tests/test.c: -------------------------------------------------------------------------------- 1 | // Simple test program using the PARI library 2 | // to compute zeta(2) and factor a polynomial over a finite field, as in the README. 3 | // compile with: gcc -v test.c -o test -I/usr/local/include -L/usr/local/bin -lpari -lgmp 4 | 5 | #include 6 | 7 | int main() { 8 | pari_init(100000000,2); 9 | 10 | // Compute zeta(2) 11 | GEN z2 = szeta(2, DEFAULTPREC); 12 | pari_printf("zeta(2) = %Ps\n", z2); 13 | 14 | // p = x^3 + x^2 + x - 1 15 | GEN gen_3 = stoi(3); 16 | GEN x = pol_x(0); 17 | GEN p = gadd(gadd(gadd(gpow(x, gen_3, DEFAULTPREC), gpow(x, gen_2, DEFAULTPREC)), x), gen_m1); 18 | pari_printf("p = %Ps\n", p); 19 | 20 | // modulus = y^3 + y^2 + y - 1 21 | GEN y = pol_x(1); 22 | GEN modulus = gadd(gadd(gadd(gpow(y, gen_3, DEFAULTPREC), gpow(y, gen_2, DEFAULTPREC)), y), gen_m1); 23 | setvarn(modulus, 1); 24 | pari_printf("modulus = %Ps\n", modulus); 25 | 26 | // Factor p over F_3[y]/(modulus) 27 | GEN fq = factorff(p, gen_3, modulus); 28 | GEN centered = centerlift(lift(fq)); 29 | pari_printf("centerlift(lift(fq)) = %Ps\n", centered); 30 | 31 | pari_close(); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /tests/test_backward.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #***************************************************************************** 3 | # Copyright (C) 2020 Vincent Delecroix 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 unittest 12 | 13 | import cypari2 14 | 15 | 16 | class TestBackward(unittest.TestCase): 17 | def test_polisirreducible(self): 18 | pari = cypari2.Pari() 19 | p = pari('x^2 + 1') 20 | self.assertTrue(p.polisirreducible()) 21 | 22 | def test_sqrtint(self): 23 | pari = cypari2.Pari() 24 | self.assertEqual(pari(10).sqrtint(), 3) 25 | 26 | def test_poldegree(self): 27 | pari = cypari2.Pari() 28 | self.assertEqual(pari('x + 1').poldegree(), 1) 29 | self.assertEqual(pari('x*y^2 + 1').poldegree(pari('x')), 1) 30 | self.assertEqual(pari('x*y^2 + 1').poldegree(pari('y')), 2) 31 | 32 | if __name__ == '__main__': 33 | unittest.main() 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | local_settings.py 55 | 56 | # Flask stuff: 57 | instance/ 58 | .webassets-cache 59 | 60 | # Scrapy stuff: 61 | .scrapy 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyBuilder 67 | target/ 68 | 69 | # IPython Notebook 70 | .ipynb_checkpoints 71 | 72 | # pyenv 73 | .python-version 74 | 75 | # celery beat schedule file 76 | celerybeat-schedule 77 | 78 | # dotenv 79 | .env 80 | 81 | # virtualenv 82 | venv/ 83 | ENV/ 84 | 85 | # Spyder project settings 86 | .spyderproject 87 | 88 | # Rope project settings 89 | .ropeproject 90 | 91 | *~ 92 | .*swp 93 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["meson-python>=0.18.0", "cython>=3.0", "cysignals>=1.11.3"] 3 | build-backend = "mesonpy" 4 | 5 | [project] 6 | name = "cypari2" 7 | description = "A Python interface to the number theory library PARI/GP" 8 | authors = [ 9 | { name = "Luca De Feo, Vincent Delecroix, Jeroen Demeyer, Vincent Klein" }, 10 | ] 11 | maintainers = [ 12 | { name = "SageMath developers", email = "sage-devel@googlegroups.com" }, 13 | ] 14 | dependencies = ["cysignals>=1.11.3"] 15 | requires-python = ">=3.12" 16 | readme = "README.rst" 17 | license = "GPL-2.0-or-later" 18 | license-files = ["LICENSE"] 19 | keywords = ["PARI/GP number theory"] 20 | dynamic = ["version"] 21 | 22 | [project.entry-points.pkg_config] 23 | cysignals = 'cysignals' 24 | 25 | [project.urls] 26 | Homepage = "https://github.com/sagemath/cypari2" 27 | 28 | [dependency-groups] 29 | dev = [ 30 | "build>=1.3.0", 31 | "cibuildwheel>=2.23.3", 32 | "cython>=3.1.4", 33 | "meson>=1.9.0", 34 | "meson-python>=0.18.0", 35 | "ninja>=1.13.0", 36 | "pkgconf>=2.4.3.post2", 37 | "pytest>=8.4.2", 38 | ] 39 | doc = ["sphinx"] 40 | 41 | [tool.cibuildwheel] 42 | skip = ["*-win32"] 43 | test-groups = ["dev"] 44 | test-sources = ["tests", "pyproject.toml"] 45 | test-command = "python tests/rundoctest.py" 46 | build-frontend = "build[uv]" 47 | build-verbosity = 1 48 | before-all = "bash -x {package}/.install-pari.sh" 49 | environment-pass = ["CI", "GITHUB_ACTIONS"] 50 | [tool.cibuildwheel.macos] 51 | before-all = "brew install pari" 52 | -------------------------------------------------------------------------------- /tests/test.pyx: -------------------------------------------------------------------------------- 1 | # Simple test program using the PARI library in Cython 2 | # to compute zeta(2) and factor a polynomial over a finite field, as in the README. 3 | 4 | from cypari2.paridecl cimport pari_printf, pari_init, pari_close, DEFAULTPREC, szeta, stoi, pol_x, gpow, gadd, factorff, lift, centerlift, setvarn, gen_2, gen_m1, gen_0, INIT_DFTm, pari_init_opts, pari_mainstack 5 | from cypari2.types cimport GEN 6 | from cypari2.closure cimport _pari_init_closure 7 | from cypari2.stack cimport (new_gen, new_gen_noclear, clear_stack, 8 | set_pari_stack_size, before_resize, after_resize) 9 | from libc.stdio cimport printf 10 | 11 | def main(): 12 | 13 | pari_init(100000000, 2) 14 | 15 | # Compute zeta(2) 16 | cdef GEN z2 = szeta(2, DEFAULTPREC) 17 | pari_printf(b"zeta(2) = %Ps\n", z2) 18 | 19 | # p = x^3 + x^2 + x - 1 20 | cdef GEN gen_3 = stoi(3) 21 | cdef GEN x = pol_x(0) 22 | cdef GEN p = gadd(gadd(gadd(gpow(x, gen_3, DEFAULTPREC), gpow(x, gen_2, DEFAULTPREC)), x), gen_m1) 23 | pari_printf(b"p = %Ps\n", p) 24 | 25 | # modulus = y^3 + y^2 + y - 1 26 | cdef GEN y = pol_x(1) 27 | cdef GEN modulus = gadd(gadd(gadd(gpow(y, gen_3, DEFAULTPREC), gpow(y, gen_2, DEFAULTPREC)), y), gen_m1) 28 | setvarn(modulus, 1) 29 | pari_printf(b"modulus = %Ps\n", modulus) 30 | 31 | # Factor p over F_3[y]/(modulus) 32 | cdef GEN fq = factorff(p, gen_3, modulus) 33 | cdef GEN centered = centerlift(lift(fq)) 34 | pari_printf(b"centerlift(lift(fq)) = %Ps\n", centered) 35 | 36 | pari_close() 37 | -------------------------------------------------------------------------------- /cypari2/meson.build: -------------------------------------------------------------------------------- 1 | py.install_sources( 2 | '__init__.py', 3 | 'closure.pxd', 4 | 'convert.pxd', 5 | 'gen.pxd', 6 | 'handle_error.pxd', 7 | 'pari_instance.pxd', 8 | 'paridecl.pxd', 9 | 'paripriv.pxd', 10 | 'pycore_long.pxd', 11 | 'stack.pxd', 12 | 'string_utils.pxd', 13 | 'types.pxd', 14 | 'cypari.h', 15 | 'pycore_long.h', 16 | subdir: 'cypari2' 17 | ) 18 | 19 | 20 | extension_data = { 21 | 'closure': files('closure.pyx'), 22 | 'stack': files('stack.pyx'), 23 | 'custom_block': files('custom_block.pyx'), 24 | 'convert': files('convert.pyx'), 25 | 'string_utils': files('string_utils.pyx'), 26 | 'handle_error': files('handle_error.pyx'), 27 | 'gen': files('gen.pyx'), 28 | 'pari_instance': files('pari_instance.pyx') 29 | } 30 | 31 | inc_src = include_directories('.') 32 | # Meson currently ignores include_directories for Cython modules, so we 33 | # have to add them manually. 34 | # https://github.com/mesonbuild/meson/issues/9562 35 | add_project_arguments('-I', meson.current_source_dir(), language: 'cython') 36 | add_project_arguments('-I', meson.current_build_dir(), language: 'cython') 37 | 38 | foreach name, pyx : extension_data 39 | py.extension_module( 40 | name, 41 | sources: pyx, 42 | subdir: 'cypari2', 43 | install: true, 44 | dependencies: [cysignals, pari], 45 | include_directories: [inc_root, inc_src], 46 | ) 47 | endforeach 48 | 49 | config_data = configuration_data() 50 | config_data.set('version', meson.project_version()) 51 | configure_file( 52 | input: 'cypari2.pc.in', 53 | output: 'cypari2.pc', 54 | configuration: config_data, 55 | install: true, 56 | install_dir: py.get_install_dir() / 'cypari2' 57 | ) 58 | -------------------------------------------------------------------------------- /cypari2/string_utils.pyx: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | r""" 3 | Conversion functions for bytes/unicode 4 | """ 5 | 6 | import sys 7 | encoding = sys.getfilesystemencoding() 8 | 9 | 10 | cpdef bytes to_bytes(s): 11 | """ 12 | Converts bytes and unicode ``s`` to bytes. 13 | 14 | Examples: 15 | 16 | >>> from cypari2.string_utils import to_bytes 17 | >>> s1 = to_bytes(b'hello') 18 | >>> s2 = to_bytes('hello') 19 | >>> s3 = to_bytes(u'hello') 20 | >>> type(s1) is type(s2) is type(s3) is bytes 21 | True 22 | >>> s1 == s2 == s3 == b'hello' 23 | True 24 | 25 | >>> type(to_bytes(1234)) is bytes 26 | True 27 | >>> int(to_bytes(1234)) 28 | 1234 29 | """ 30 | cdef int convert 31 | for convert in range(2): 32 | if convert: 33 | s = str(s) 34 | if isinstance(s, bytes): 35 | return s 36 | elif isinstance(s, unicode): 37 | return ( s).encode(encoding) 38 | raise AssertionError(f"str() returned {type(s)}") 39 | 40 | 41 | cpdef unicode to_unicode(s): 42 | r""" 43 | Converts bytes and unicode ``s`` to unicode. 44 | 45 | Examples: 46 | 47 | >>> from cypari2.string_utils import to_unicode 48 | >>> s1 = to_unicode(b'hello') 49 | >>> s2 = to_unicode('hello') 50 | >>> s3 = to_unicode(u'hello') 51 | >>> type(s1) is type(s2) is type(s3) is type(u"") 52 | True 53 | >>> s1 == s2 == s3 == u'hello' 54 | True 55 | 56 | >>> print(to_unicode(1234)) 57 | 1234 58 | >>> type(to_unicode(1234)) is type(u"") 59 | True 60 | """ 61 | if isinstance(s, bytes): 62 | return ( s).decode(encoding) 63 | elif isinstance(s, unicode): 64 | return s 65 | return unicode(s) 66 | -------------------------------------------------------------------------------- /tests/rundoctest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | import sys 5 | import traceback 6 | 7 | # Autogen tests must be run in the root dir, and with the proper module path 8 | path = os.path.abspath(os.path.join(os.path.dirname(__file__), os.path.pardir)) 9 | os.chdir(path) 10 | sys.path.append(path) 11 | import doctest 12 | 13 | import cypari2 14 | 15 | # The doctests assume utf-8 encoding 16 | cypari2.string_utils.encoding = "utf-8" 17 | 18 | # For doctests, we want exceptions to look the same, 19 | # regardless of the Python version. Python 3 will put the 20 | # module name in the traceback, which we avoid by faking 21 | # the module to be __main__. 22 | cypari2.handle_error.PariError.__module__ = "__main__" 23 | 24 | # Disable stack size warning messages 25 | pari = cypari2.Pari() 26 | pari.default("debugmem", 0) 27 | 28 | modules = [cypari2.closure, cypari2.convert, cypari2.gen, 29 | cypari2.handle_error, cypari2.pari_instance, cypari2.stack, 30 | cypari2.string_utils] 31 | try: 32 | import autogen 33 | modules.extend([ 34 | autogen.doc, autogen.generator, autogen.parser]) 35 | except ImportError: 36 | print("Skipping autogen tests") 37 | 38 | failed = 0 39 | attempted = 0 40 | for mod in modules: 41 | print("="*80) 42 | print("Testing {}".format(mod.__name__)) 43 | try: 44 | test = doctest.testmod(mod, optionflags=doctest.ELLIPSIS|doctest.REPORT_NDIFF, verbose=False) 45 | failed += test.failed 46 | attempted += test.attempted 47 | except Exception: 48 | traceback.print_exc() 49 | failed += 1 50 | attempted += 1 51 | 52 | print("="*80) 53 | print("Summary result for cypari2:") 54 | print(" attempted = {}".format(attempted)) 55 | print(" failed = {}".format(failed)) 56 | 57 | sys.exit(1 if failed else 0) 58 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | ================== 2 | cypari2 change log 3 | ================== 4 | 5 | v2.2.2 6 | ------ 7 | 8 | - Port to Cython 3.1, various fixes 9 | 10 | v2.2.1 11 | ------ 12 | 13 | - Port to Pari 2.17, various fixes 14 | 15 | v2.2.0 16 | ------ 17 | 18 | - Require cysignals >= 1.11.3, use the signal hook 19 | [`https://github.com/sagemath/cypari2/pull/130`] by @kliem 20 | - Require Cython >= 3.0, use the trashcan for class Gen 21 | [`https://github.com/sagemath/cypari2/pull/77`] by @jdemeyer 22 | 23 | v2.1.5 24 | ------ 25 | 26 | - build wheels for PyPI [`https://github.com/sagemath/cypari2/pull/141`] by @mkoeppe 27 | - add missing `noexcept` clauses for Cython 3 [`https://github.com/sagemath/cypari2/pull/160`] 28 | by @tornaria 29 | - require Python >= 3.9 [`https://github.com/sagemath/cypari2/pull/155`] by @saraedum 30 | - modernize Python metadata [`https://github.com/sagemath/cypari2/pull/158`] by @mkoeppe 31 | - add input checking for matrix indices [`https://github.com/sagemath/cypari2/pull/149`] 32 | by @fchapoton 33 | - coding style improvements [`https://github.com/sagemath/cypari2/pull/148`, 34 | `https://github.com/sagemath/cypari2/pull/150`, 35 | `https://github.com/sagemath/cypari2/pull/151`, 36 | `https://github.com/sagemath/cypari2/pull/156`, 37 | `https://github.com/sagemath/cypari2/pull/157`, 38 | `https://github.com/sagemath/cypari2/pull/159`] by @fchapoton 39 | 40 | v2.1.4 41 | ------ 42 | 43 | - compatibility with Python 3.12 [`https://github.com/sagemath/cypari2/pull/138`] 44 | - compatibility with Cython 3 [`https://github.com/sagemath/cypari2/pull/139`] 45 | - declare build dependencies in `pyproject.toml` [`https://github.com/sagemath/cypari2/pull/139`] 46 | 47 | v2.1.3 48 | ------ 49 | 50 | - compatibility with PARI 2.15 51 | - compatibility with Python 3.11 [`https://github.com/sagemath/cypari2/pull/120`] 52 | -------------------------------------------------------------------------------- /tests/test_integers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #***************************************************************************** 3 | # Copyright (C) 2020 Vincent Delecroix 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 random 12 | import unittest 13 | 14 | import cypari2 15 | 16 | 17 | class TestPariInteger(unittest.TestCase): 18 | def randint(self): 19 | p = random.random() 20 | if p < 0.05: 21 | return random.randint(-2, 2) 22 | elif p < 0.5: 23 | return random.randint(-2**30, 2**30) 24 | else: 25 | return random.randint(-2**100, 2**100) 26 | 27 | def cmp(self, a, b): 28 | pari = cypari2.Pari() 29 | pa = pari(a) 30 | pb = pari(b) 31 | 32 | self.assertTrue(pa == pa and a == pa and pa == a) 33 | self.assertEqual(a == b, pa == pb) 34 | self.assertEqual(a != b, pa != pb) 35 | self.assertEqual(a < b, pa < pb) 36 | self.assertEqual(a <= b, pa <= pb) 37 | self.assertEqual(a > b, pa > pb) 38 | self.assertEqual(a >= b, pa >= pb) 39 | 40 | def test_cmp(self): 41 | for _ in range(100): 42 | a = self.randint() 43 | b = self.randint() 44 | self.cmp(a, a) 45 | self.cmp(a, b) 46 | 47 | def test_binop(self): 48 | pari = cypari2.Pari() 49 | 50 | for _ in range(100): 51 | a = self.randint() 52 | b = self.randint() 53 | 54 | self.assertEqual(a + b, pari(a) + pari(b)) 55 | self.assertEqual(a - b, pari(a) - pari(b)) 56 | self.assertEqual(a * b, pari(a) * pari(b)) 57 | 58 | if b > 0: 59 | self.assertEqual(a % b, pari(a) % pari(b)) 60 | 61 | def test_zero_division(self): 62 | pari = cypari2.Pari() 63 | with self.assertRaises(cypari2.PariError): 64 | pari(2) / pari(0) 65 | 66 | if __name__ == '__main__': 67 | unittest.main() 68 | -------------------------------------------------------------------------------- /cypari2/gen.pxd: -------------------------------------------------------------------------------- 1 | cimport cython 2 | from cpython.object cimport PyObject 3 | from .types cimport GEN, pari_sp 4 | 5 | 6 | cdef class Gen_base: 7 | # The actual PARI GEN 8 | cdef GEN g 9 | 10 | 11 | cdef class Gen(Gen_base): 12 | # There are 3 kinds of memory management for a GEN: 13 | # * stack: GEN on the PARI stack 14 | # * clone: refcounted clone on the PARI heap 15 | # * constant: universal constant such as gen_0 16 | # 17 | # A priori, it makes sense to have separate classes for these cases. 18 | # However, a GEN may be moved from the stack to the heap. This is 19 | # easier to support when there is just one class. Second, the 20 | # differences between the cases are really implementation details 21 | # which should not affect the user. 22 | 23 | # Base address of the GEN that we wrap. On the stack, this is the 24 | # value of avma when new_gen() was called. For clones, this is the 25 | # memory allocated by gclone(). For constants, this is NULL. 26 | cdef GEN address 27 | 28 | cdef inline pari_sp sp(self) noexcept: 29 | return self.address 30 | 31 | # The Gen objects on the PARI stack form a linked list, from the 32 | # bottom to the top of the stack. This makes sense since we can only 33 | # deallocate a Gen which is on the bottom of the PARI stack. If this 34 | # is the last object on the stack, then next = top_of_stack 35 | # (a singleton object). 36 | # 37 | # In the clone and constant cases, this is None. 38 | cdef Gen next 39 | 40 | # A cache for __getitem__. Initially, this is None but it will be 41 | # turned into a dict when needed. 42 | cdef dict itemcache 43 | 44 | cdef inline int cache(self, key, value) except -1: 45 | """ 46 | Add ``(key, value)`` to ``self.itemcache``. 47 | """ 48 | if self.itemcache is None: 49 | self.itemcache = {} 50 | self.itemcache[key] = value 51 | 52 | cdef Gen new_ref(self, GEN g) 53 | 54 | cdef GEN fixGEN(self) except NULL 55 | 56 | cdef GEN ref_target(self) except NULL 57 | 58 | 59 | cdef inline Gen Gen_new(GEN g, GEN addr): 60 | z = Gen.__new__(Gen) 61 | z.g = g 62 | z.address = addr 63 | return z 64 | 65 | 66 | cdef Gen list_of_Gens_to_Gen(list s) 67 | cpdef Gen objtogen(s) 68 | -------------------------------------------------------------------------------- /cypari2/convert.pxd: -------------------------------------------------------------------------------- 1 | from .paridecl cimport (GEN, t_COMPLEX, dbltor, real_0_bit, stoi, cgetg, 2 | set_gel, gen_0) 3 | from .gen cimport Gen 4 | from cpython.long cimport PyLong_AsLong 5 | from cpython.float cimport PyFloat_AS_DOUBLE 6 | from cpython.complex cimport PyComplex_RealAsDouble, PyComplex_ImagAsDouble 7 | from cpython.longintrepr cimport py_long 8 | 9 | 10 | # Conversion PARI -> Python 11 | 12 | cdef GEN gtoi(GEN g0) except NULL 13 | 14 | cdef PyObject_FromGEN(GEN g) 15 | 16 | cdef PyInt_FromGEN(GEN g) 17 | 18 | cpdef gen_to_python(Gen z) 19 | 20 | cpdef gen_to_integer(Gen x) 21 | 22 | 23 | # Conversion C -> PARI 24 | 25 | cdef inline GEN double_to_REAL(double x) noexcept: 26 | # PARI has an odd concept where it attempts to track the accuracy 27 | # of floating-point 0; a floating-point zero might be 0.0e-20 28 | # (meaning roughly that it might represent any number in the 29 | # range -1e-20 <= x <= 1e20). 30 | 31 | # PARI's dbltor converts a floating-point 0 into the PARI real 32 | # 0.0e-307; PARI treats this as an extremely precise 0. This 33 | # can cause problems; for instance, the PARI incgam() function can 34 | # be very slow if the first argument is very precise. 35 | 36 | # So we translate 0 into a floating-point 0 with 53 bits 37 | # of precision (that's the number of mantissa bits in an IEEE 38 | # double). 39 | if x == 0: 40 | return real_0_bit(-53) 41 | else: 42 | return dbltor(x) 43 | 44 | 45 | cdef inline GEN doubles_to_COMPLEX(double re, double im) noexcept: 46 | cdef GEN g = cgetg(3, t_COMPLEX) 47 | if re == 0: 48 | set_gel(g, 1, gen_0) 49 | else: 50 | set_gel(g, 1, dbltor(re)) 51 | if im == 0: 52 | set_gel(g, 2, gen_0) 53 | else: 54 | set_gel(g, 2, dbltor(im)) 55 | return g 56 | 57 | 58 | # Conversion Python -> PARI 59 | 60 | cdef inline GEN PyInt_AS_GEN(x) except? NULL: 61 | return stoi(PyLong_AsLong(x)) 62 | 63 | cdef GEN PyLong_AS_GEN(py_long x) noexcept 64 | 65 | cdef inline GEN PyFloat_AS_GEN(x) except? NULL: 66 | return double_to_REAL(PyFloat_AS_DOUBLE(x)) 67 | 68 | cdef inline GEN PyComplex_AS_GEN(x) except? NULL: 69 | return doubles_to_COMPLEX( 70 | PyComplex_RealAsDouble(x), PyComplex_ImagAsDouble(x)) 71 | 72 | cdef GEN PyObject_AsGEN(x) except? NULL 73 | 74 | 75 | # Deprecated functions still used by SageMath 76 | 77 | cdef Gen new_gen_from_double(double) 78 | cdef Gen new_t_COMPLEX_from_double(double re, double im) 79 | -------------------------------------------------------------------------------- /cypari2/pycore_long.h: -------------------------------------------------------------------------------- 1 | #include "Python.h" 2 | #include 3 | 4 | #if PY_VERSION_HEX >= 0x030C00A5 5 | #define ob_digit(o) (((PyLongObject*)o)->long_value.ob_digit) 6 | #else 7 | #define ob_digit(o) (((PyLongObject*)o)->ob_digit) 8 | #endif 9 | 10 | #if PY_VERSION_HEX >= 0x030C00A7 11 | // taken from cpython:Include/internal/pycore_long.h @ 3.12 12 | 13 | /* Long value tag bits: 14 | * 0-1: Sign bits value = (1-sign), ie. negative=2, positive=0, zero=1. 15 | * 2: Reserved for immortality bit 16 | * 3+ Unsigned digit count 17 | */ 18 | #define SIGN_MASK 3 19 | #define SIGN_ZERO 1 20 | #define SIGN_NEGATIVE 2 21 | #define NON_SIZE_BITS 3 22 | 23 | static inline bool 24 | _PyLong_IsZero(const PyLongObject *op) 25 | { 26 | return (op->long_value.lv_tag & SIGN_MASK) == SIGN_ZERO; 27 | } 28 | 29 | static inline bool 30 | _PyLong_IsNegative(const PyLongObject *op) 31 | { 32 | return (op->long_value.lv_tag & SIGN_MASK) == SIGN_NEGATIVE; 33 | } 34 | 35 | static inline bool 36 | _PyLong_IsPositive(const PyLongObject *op) 37 | { 38 | return (op->long_value.lv_tag & SIGN_MASK) == 0; 39 | } 40 | 41 | static inline Py_ssize_t 42 | _PyLong_DigitCount(const PyLongObject *op) 43 | { 44 | assert(PyLong_Check(op)); 45 | return op->long_value.lv_tag >> NON_SIZE_BITS; 46 | } 47 | 48 | #define TAG_FROM_SIGN_AND_SIZE(sign, size) ((1 - (sign)) | ((size) << NON_SIZE_BITS)) 49 | 50 | static inline void 51 | _PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) 52 | { 53 | assert(size >= 0); 54 | assert(-1 <= sign && sign <= 1); 55 | assert(sign != 0 || size == 0); 56 | op->long_value.lv_tag = TAG_FROM_SIGN_AND_SIZE(sign, (size_t)size); 57 | } 58 | 59 | #else 60 | // fallback for < 3.12 61 | 62 | static inline bool 63 | _PyLong_IsZero(const PyLongObject *op) 64 | { 65 | return Py_SIZE(op) == 0; 66 | } 67 | 68 | static inline bool 69 | _PyLong_IsNegative(const PyLongObject *op) 70 | { 71 | return Py_SIZE(op) < 0; 72 | } 73 | 74 | static inline bool 75 | _PyLong_IsPositive(const PyLongObject *op) 76 | { 77 | return Py_SIZE(op) > 0; 78 | } 79 | 80 | static inline Py_ssize_t 81 | _PyLong_DigitCount(const PyLongObject *op) 82 | { 83 | Py_ssize_t size = Py_SIZE(op); 84 | return size < 0 ? -size : size; 85 | } 86 | 87 | static inline void 88 | _PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) 89 | { 90 | #if (PY_MAJOR_VERSION == 3) && (PY_MINOR_VERSION < 9) 91 | // The function Py_SET_SIZE is defined starting with python 3.9. 92 | Py_SIZE(op) = size; 93 | #else 94 | Py_SET_SIZE(op, sign < 0 ? -size : size); 95 | #endif 96 | } 97 | 98 | #endif 99 | -------------------------------------------------------------------------------- /.github/workflows/dist.yml: -------------------------------------------------------------------------------- 1 | name: Distribution 2 | 3 | on: 4 | push: 5 | pull_request: 6 | workflow_dispatch: 7 | 8 | concurrency: 9 | # Cancel previous runs of this workflow for the same branch 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | make_sdist: 15 | name: Build sdist 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: astral-sh/setup-uv@v6 20 | - name: Install PARI 21 | run: ./.install-pari.sh 22 | - name: Build sdist 23 | run: uv build --sdist 24 | - uses: actions/upload-artifact@v4 25 | with: 26 | path: "dist/*.tar.*" 27 | name: release-sdist 28 | 29 | build_wheels: 30 | name: Build wheels on ${{ matrix.os }} 31 | runs-on: ${{ matrix.os }} 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | os: 36 | - ubuntu-latest 37 | - ubuntu-24.04-arm 38 | - macos-13 39 | - macos-latest 40 | steps: 41 | - uses: actions/checkout@v4 42 | - uses: astral-sh/setup-uv@v6 43 | - name: Build wheels 44 | uses: pypa/cibuildwheel@v3.2.1 45 | env: 46 | MACOSX_DEPLOYMENT_TARGET: ${{ matrix.os == 'macos-latest' && '15.0' || '13.0' }} 47 | - uses: actions/upload-artifact@v4 48 | with: 49 | name: release-wheels-${{ matrix.os }} 50 | path: wheelhouse/*.whl 51 | 52 | pypi-publish: 53 | # This needs to be a separate job because pypa/gh-action-pypi-publish cannot run on macOS 54 | # https://github.com/pypa/gh-action-pypi-publish 55 | name: Upload wheels to PyPI 56 | needs: [build_wheels, make_sdist] 57 | runs-on: ubuntu-latest 58 | env: 59 | CAN_DEPLOY: ${{ secrets.SAGEMATH_PYPI_API_TOKEN != '' }} 60 | permissions: 61 | id-token: write 62 | attestations: write 63 | contents: read 64 | steps: 65 | - uses: actions/download-artifact@v4 66 | with: 67 | pattern: release-* 68 | path: dist 69 | merge-multiple: true 70 | 71 | - name: List files 72 | run: ls -l -R dist 73 | 74 | - name: Generate artifact attestations 75 | uses: actions/attest-build-provenance@v2 76 | if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 77 | with: 78 | subject-path: "dist/*" 79 | 80 | - name: Publish package distributions to PyPI 81 | uses: pypa/gh-action-pypi-publish@release/v1 82 | with: 83 | user: __token__ 84 | password: ${{ secrets.SAGEMATH_PYPI_API_TOKEN }} 85 | verbose: true 86 | if: env.CAN_DEPLOY == 'true' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') 87 | -------------------------------------------------------------------------------- /autogen/ret.py: -------------------------------------------------------------------------------- 1 | """ 2 | Return types for PARI calls 3 | """ 4 | 5 | #***************************************************************************** 6 | # Copyright (C) 2015 Jeroen Demeyer 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 2 of the License, or 11 | # (at your option) any later version. 12 | # https://www.gnu.org/licenses/ 13 | #***************************************************************************** 14 | 15 | from __future__ import unicode_literals 16 | 17 | 18 | class PariReturn(object): 19 | """ 20 | This class represents the return value of a PARI call. 21 | """ 22 | def __init__(self): 23 | self.name = "_ret" 24 | 25 | def __repr__(self): 26 | return self.ctype() 27 | 28 | def ctype(self): 29 | """ 30 | Return the C type of the result of the PARI call. 31 | """ 32 | raise NotImplementedError 33 | 34 | def assign_code(self, value): 35 | """ 36 | Return code to assign the result of the PARI call in ``value`` 37 | to the variable named ``self.name``. 38 | """ 39 | s = " cdef {ctype} {name} = {value}\n" 40 | return s.format(ctype=self.ctype(), name=self.name, value=value) 41 | 42 | def return_code(self): 43 | """ 44 | Return code to return from the Cython wrapper. 45 | """ 46 | s = " clear_stack()\n" 47 | s += " return {name}\n" 48 | return s.format(name=self.name) 49 | 50 | 51 | class PariReturnGEN(PariReturn): 52 | def ctype(self): 53 | return "GEN" 54 | def return_code(self): 55 | s = " return new_gen({name})\n" 56 | return s.format(name=self.name) 57 | 58 | class PariReturnmGEN(PariReturn): 59 | def ctype(self): 60 | return "GEN" 61 | def return_code(self): 62 | s = " {name} = gcopy({name})\n" 63 | s += " return new_gen({name})\n" 64 | return s.format(name=self.name) 65 | 66 | class PariReturnInt(PariReturn): 67 | def ctype(self): 68 | return "int" 69 | 70 | class PariReturnLong(PariReturn): 71 | def ctype(self): 72 | return "long" 73 | 74 | class PariReturnULong(PariReturn): 75 | def ctype(self): 76 | return "unsigned long" 77 | 78 | class PariReturnVoid(PariReturn): 79 | def ctype(self): 80 | return "void" 81 | def assign_code(self, value): 82 | return " {value}\n".format(value=value) 83 | def return_code(self): 84 | s = " clear_stack()\n" 85 | return s 86 | 87 | 88 | pari_ret_types = { 89 | '': PariReturnGEN, 90 | 'm': PariReturnmGEN, 91 | 'i': PariReturnInt, 92 | 'l': PariReturnLong, 93 | 'u': PariReturnULong, 94 | 'v': PariReturnVoid, 95 | } 96 | -------------------------------------------------------------------------------- /cypari2/types.pxd: -------------------------------------------------------------------------------- 1 | """ 2 | Declarations for types used by PARI 3 | 4 | This includes both the C types as well as the PARI types (and a few 5 | macros for dealing with those). 6 | 7 | It is important that the functionality in this file does not call any 8 | PARI library functions. The reason is that we want to allow just using 9 | these types (for example, to define a Cython extension type) without 10 | linking to PARI. This file should consist only of typedefs and macros 11 | from PARI's include files. 12 | """ 13 | 14 | # **************************************************************************** 15 | # Distributed under the terms of the GNU General Public License (GPL) 16 | # as published by the Free Software Foundation; either version 2 of 17 | # the License, or (at your option) any later version. 18 | # https://www.gnu.org/licenses/ 19 | # **************************************************************************** 20 | 21 | cdef extern from "pari/pari.h": 22 | ctypedef unsigned long ulong "pari_ulong" 23 | 24 | ctypedef long* GEN 25 | ctypedef char* byteptr 26 | ctypedef unsigned long pari_sp 27 | ctypedef unsigned long pari_prime 28 | 29 | # PARI types 30 | enum: 31 | t_INT 32 | t_REAL 33 | t_INTMOD 34 | t_FRAC 35 | t_FFELT 36 | t_COMPLEX 37 | t_PADIC 38 | t_QUAD 39 | t_POLMOD 40 | t_POL 41 | t_SER 42 | t_RFRAC 43 | t_QFR 44 | t_QFI 45 | t_VEC 46 | t_COL 47 | t_MAT 48 | t_LIST 49 | t_STR 50 | t_VECSMALL 51 | t_CLOSURE 52 | t_ERROR 53 | t_INFINITY 54 | 55 | int BITS_IN_LONG 56 | int LOWDEFAULTPREC 57 | long DEFAULTPREC # 64 bits precision 58 | long MEDDEFAULTPREC # 128 bits precision 59 | long BIGDEFAULTPREC # 192 bits precision 60 | 61 | ulong CLONEBIT 62 | 63 | long typ(GEN x) 64 | long settyp(GEN x, long s) 65 | long isclone(GEN x) 66 | long setisclone(GEN x) 67 | long unsetisclone(GEN x) 68 | long lg(GEN x) 69 | long setlg(GEN x, long s) 70 | long signe(GEN x) 71 | long setsigne(GEN x, long s) 72 | long lgefint(GEN x) 73 | long setlgefint(GEN x, long s) 74 | long expo(GEN x) 75 | long setexpo(GEN x, long s) 76 | long valp(GEN x) 77 | long setvalp(GEN x, long s) 78 | long precp(GEN x) 79 | long setprecp(GEN x, long s) 80 | long varn(GEN x) 81 | long setvarn(GEN x, long s) 82 | long evaltyp(long x) 83 | long evallg(long x) 84 | long evalvarn(long x) 85 | long evalsigne(long x) 86 | long evalprecp(long x) 87 | long evalvalp(long x) 88 | long evalexpo(long x) 89 | long evallgefint(long x) 90 | 91 | ctypedef struct PARI_plot: 92 | long width 93 | long height 94 | long hunit 95 | long vunit 96 | long fwidth 97 | long fheight 98 | void (*draw)(PARI_plot *T, GEN w, GEN x, GEN y) 99 | 100 | ctypedef struct entree: 101 | const char *name 102 | ulong valence 103 | void *value 104 | long menu 105 | const char *code 106 | const char *help 107 | void *pvalue 108 | long arity 109 | ulong hash 110 | entree *next 111 | 112 | # Various structures that we don't interface but which need to be 113 | # declared, such that Cython understands the declarations of 114 | # functions using these types. 115 | struct bb_group 116 | struct bb_field 117 | struct bb_ring 118 | struct bb_algebra 119 | struct qfr_data 120 | struct nfmaxord_t 121 | struct forcomposite_t 122 | struct forpart_t 123 | struct forprime_t 124 | struct forvec_t 125 | struct gp_context 126 | struct nfbasic_t 127 | struct pariFILE 128 | struct pari_mt 129 | struct pari_stack 130 | struct pari_thread 131 | struct pari_timer 132 | struct GENbin 133 | struct hashentry 134 | struct hashtable 135 | 136 | # These are actually defined in cypari.h but we put them here to 137 | # prevent Cython from reordering the includes. 138 | GEN set_gel(GEN x, long n, GEN z) # gel(x, n) = z 139 | GEN set_gmael(GEN x, long i, long j, GEN z) # gmael(x, i, j) = z 140 | GEN set_gcoeff(GEN x, long i, long j, GEN z) # gcoeff(x, i, j) = z 141 | GEN set_uel(GEN x, long n, ulong z) # uel(x, n) = z 142 | 143 | 144 | # It is important that this gets included *after* all PARI includes 145 | cdef extern from "cypari.h": 146 | pass 147 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | pull_request: 5 | push: 6 | tags: 7 | - '*' 8 | workflow_dispatch: 9 | # Allow to run manually 10 | 11 | concurrency: 12 | # Cancel previous runs of this workflow for the same branch 13 | group: ${{ github.workflow }}-${{ github.ref }} 14 | cancel-in-progress: true 15 | 16 | jobs: 17 | build: 18 | name: Build and Test (${{ matrix.os }}, Python ${{ matrix.python-version }}, PARI ${{ matrix.pari-version }}) 19 | runs-on: ${{ matrix.os }} 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | os: [ubuntu-latest] 24 | python-version: ['3.12', '3.13', '3.14'] 25 | pari-version: ['2.15.4', '2.15.5', '2.17.2'] 26 | include: 27 | - os: macos-latest 28 | python-version: '3.12' 29 | pari-version: '2.17.2' # Whatever comes with homebrew 30 | - os: macos-latest 31 | python-version: '3.13' 32 | pari-version: '2.17.2' # Whatever comes with homebrew 33 | - os: macos-latest 34 | python-version: '3.14' 35 | pari-version: '2.17.2' # Whatever comes with homebrew 36 | env: 37 | LC_ALL: C 38 | PARI_VERSION: ${{ matrix.pari-version }} 39 | defaults: 40 | run: 41 | shell: ${{ matrix.os == 'windows-latest' && 'msys2 {0}' || 'bash' }} 42 | steps: 43 | - name: Set up the repository 44 | uses: actions/checkout@v4 45 | - name: Setup MSYS2 46 | if: runner.os == 'Windows' 47 | id: msys2 48 | uses: msys2/setup-msys2@v2 49 | with: 50 | msystem: UCRT64 51 | update: true 52 | install: >- 53 | base-devel 54 | m4 55 | bison 56 | make 57 | patch 58 | sed 59 | wget 60 | mingw-w64-ucrt-x86_64-toolchain 61 | mingw-w64-ucrt-x86_64-gmp 62 | mingw-w64-ucrt-x86_64-python 63 | mingw-w64-ucrt-x86_64-python-pip 64 | mingw-w64-ucrt-x86_64-meson-python 65 | mingw-w64-ucrt-x86_64-cython 66 | path-type: inherit 67 | - name: Set up Python ${{ matrix.python-version }} 68 | if: runner.os != 'Windows' 69 | uses: actions/setup-python@v4 70 | with: 71 | python-version: ${{ matrix.python-version }} 72 | - name: Install PARI 73 | if: matrix.os != 'macos-latest' 74 | run: | 75 | bash -x .install-pari.sh 76 | - name: Install PARI 77 | if: matrix.os == 'macos-latest' 78 | run: | 79 | brew install pari 80 | - name: Smoke test PARI 81 | run: | 82 | if [ ${{ runner.os }} = macOS ]; then 83 | HOMEBREW=`brew --prefix` 84 | clang -v tests/test.c -o test -I$HOMEBREW/include -L$HOMEBREW/lib -lpari -lgmp 85 | else 86 | gcc -v tests/test.c -o test -I/usr/local/include -L/usr/local/bin -lpari -lgmp 87 | fi 88 | expected="zeta(2) = 1.6449340668482264364 89 | p = x^3 + x^2 + x - 1 90 | modulus = y^3 + y^2 + y - 1 91 | centerlift(lift(fq)) = [x - y, 1; x + (y^2 + y - 1), 1; x + (-y^2 - 1), 1]" 92 | output="$(./test)" 93 | # Normalize newlines for comparison 94 | output="$(echo "$output" | tr -d '\r')" 95 | expected="$(echo "$expected" | tr -d '\r')" 96 | echo -e "Got:\n$output" 97 | if [ "$output" != "$expected" ]; then 98 | echo "Unexpected output from test.c" 99 | echo -e "Expected:\n$expected" 100 | exit 1 101 | fi 102 | 103 | - name: Setup uv 104 | uses: astral-sh/setup-uv@v6 105 | 106 | - name: Build 107 | run: | 108 | if [ ${{ runner.os }} = Windows ]; then 109 | export C_INCLUDE_PATH=$(cygpath -am "${{ steps.msys2.outputs.msys2-location }}")/usr/local/include 110 | export LIBRARY_PATH=$(cygpath -am "${{ steps.msys2.outputs.msys2-location }}")/usr/local/bin 111 | fi 112 | echo $PATH 113 | echo $C_INCLUDE_PATH 114 | echo $LIBRARY_PATH 115 | uv sync --frozen --inexact -v --no-install-project 116 | uv sync --frozen --inexact -v --no-build-isolation --no-editable --config-settings=builddir=builddir 117 | - name: Test 118 | run: | 119 | uv run make check 120 | - name: Build docs 121 | run: | 122 | uv sync --frozen --inexact -v --group doc 123 | (cd docs && uv run make html) 124 | -------------------------------------------------------------------------------- /meson.build: -------------------------------------------------------------------------------- 1 | project('cypari2', 2 | ['c', 'cython'], 3 | version: files('VERSION'), 4 | license: 'GPL v2+', 5 | default_options: ['c_std=c17', 'python.install_env=auto'], 6 | meson_version: '>=1.5.0', 7 | ) 8 | 9 | # Python module 10 | # https://mesonbuild.com/Python-module.html 11 | py = import('python').find_installation(pure: false) 12 | 13 | # Compilers 14 | cc = meson.get_compiler('c') 15 | cython = meson.get_compiler('cython') 16 | # Workaround as described in https://cython.readthedocs.io/en/latest/src/userguide/special_methods.html#arithmetic-methods 17 | add_project_arguments('-X c_api_binop_methods=True', language: 'cython') 18 | # Compiler debug output 19 | run_command(cc, '-v', check:true) 20 | 21 | # Dependencies 22 | inc_cysignals = run_command( 23 | py, 24 | [ 25 | '-c', 26 | ''' 27 | from os.path import relpath 28 | import cysignals 29 | path = cysignals.__file__.replace('__init__.py', '') 30 | try: 31 | print(relpath(path)) 32 | except Exception: 33 | print(path) 34 | '''.strip(), 35 | ], 36 | check: true, 37 | ).stdout().strip() 38 | cysignals = declare_dependency(include_directories: inc_cysignals) 39 | 40 | # Find PARI 41 | # GMP may be a dependency of Pari, but since Pari doesn't have a pkg-config file, 42 | # we have to find it ourselves. 43 | gmp = dependency('gmp', required: false) 44 | # Cannot be found via pkg-config 45 | # Also require shared library, since otherwise every Cython extension would include it's own copy of pari leading to hard-to-debug segmentation faults at runtime 46 | pari_header_include_dirs = [] # additional dirs to search for PARI headers 47 | pari_lib_dirs = [] # additional dirs to search for the PARI library 48 | if host_machine.system() == 'darwin' 49 | brew = find_program('brew', required: false) 50 | if brew.found() 51 | brew_prefix = run_command(brew, '--prefix', check: true).stdout().strip() 52 | message('Homebrew prefix: ' + brew_prefix) 53 | pari_header_include_dirs += include_directories(brew_prefix + '/include') 54 | pari_lib_dirs += [brew_prefix + '/lib'] 55 | endif 56 | endif 57 | 58 | # Explicitly check for header to give better error message (using -v to show search paths) 59 | cc.has_header( 60 | 'pari/pari.h', 61 | required: true, 62 | args: ['-v'], 63 | include_directories: pari_header_include_dirs, 64 | ) 65 | pari = cc.find_library( 66 | 'pari', 67 | has_headers: ['pari/pari.h'], 68 | required: true, 69 | dirs: pari_lib_dirs, 70 | header_include_directories: pari_header_include_dirs, 71 | ) 72 | pari = declare_dependency(include_directories: pari_header_include_dirs, 73 | dependencies: [pari, gmp]) 74 | 75 | # Get PARI version (mostly as smoke test) 76 | pari_version_code = ''' 77 | #include 78 | #include 79 | int main(void) { 80 | pari_init(1000000, 2); 81 | GEN v = pari_version(); 82 | const char *version = GENtostr(pari_version()); 83 | pari_printf("%s",version); 84 | pari_close(); 85 | return 0; 86 | } 87 | ''' 88 | pari_version = cc.run( 89 | pari_version_code, 90 | args: ['-v'], 91 | name: 'pari version', 92 | dependencies: [pari], 93 | required: true, 94 | ) 95 | message('PARI Version: ' + pari_version.stdout()) 96 | 97 | pari_datadir_code = ''' 98 | #include 99 | #include 100 | int main(void) { 101 | pari_init(1000000, 2); 102 | pari_printf("%s", pari_datadir); 103 | pari_close(); 104 | return 0; 105 | } 106 | ''' 107 | pari_datadir = cc.run( 108 | pari_datadir_code, 109 | name: 'pari datadir', 110 | dependencies: [pari], 111 | required: true, 112 | ).stdout() 113 | 114 | # In MSYS, convert to Windows path 115 | # (e.g /usr/share/pari -> C:\msys64\usr\share\pari) 116 | if host_machine.system() == 'windows' 117 | pari_datadir = run_command( 118 | 'cygpath', '-am', pari_datadir, 119 | check: true, 120 | ).stdout().strip() 121 | endif 122 | message('PARI Datadir: ' + pari_datadir) 123 | 124 | # Run code generation step 125 | code_gen_result = run_command( 126 | py.full_path(), '-c', 127 | ''' 128 | import sys 129 | sys.path.insert(0, ".") 130 | from autogen import rebuild 131 | rebuild(r"''' + pari_datadir + '''", force=True, output=r"''' + meson.current_build_dir() + '''/cypari2") 132 | print("Code generation successful") 133 | ''', 134 | check: true 135 | ) 136 | py.install_sources( 137 | meson.current_build_dir() + '/cypari2/auto_paridecl.pxd', 138 | meson.current_build_dir() + '/cypari2/auto_gen.pxi', 139 | meson.current_build_dir() + '/cypari2/auto_instance.pxi', 140 | subdir: 'cypari2' 141 | ) 142 | inc_root = include_directories('.') 143 | # Meson currently ignores include_directories for Cython modules, so we 144 | # have to add them manually. 145 | # https://github.com/mesonbuild/meson/issues/9562 146 | add_project_arguments('-I', meson.current_source_dir(), language: 'cython') 147 | add_project_arguments('-I', meson.current_build_dir(), language: 'cython') 148 | 149 | 150 | subdir('cypari2') 151 | subdir('tests') 152 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | CyPari 2 2 | ======== 3 | 4 | .. image:: https://readthedocs.org/projects/cypari2/badge/?version=latest 5 | :target: https://cypari2.readthedocs.io/en/latest/?badge=latest 6 | :alt: Documentation Status 7 | 8 | A Python interface to the number theory library `PARI/GP `_. 9 | 10 | Installation 11 | ------------ 12 | 13 | From a distribution package (GNU/Linux, conda-forge) 14 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 15 | 16 | A package might be available in your package manager, see 17 | https://repology.org/project/python:cypari2/versions or 18 | https://doc.sagemath.org/html/en/reference/spkg/cypari for 19 | installation instructions. 20 | 21 | 22 | From a pre-built wheel from PyPI 23 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 24 | 25 | Requirements: 26 | 27 | - Python >= 3.12 28 | - pip 29 | 30 | Install cypari2 via the Python Package Index (PyPI) via 31 | 32 | :: 33 | 34 | $ pip install cypari2 [--user] 35 | 36 | (the optional option *--user* allows to install cypari2 for a single user 37 | and avoids using pip with administrator rights). 38 | 39 | 40 | From source with pip 41 | ^^^^^^^^^^^^^^^^^^^^ 42 | 43 | Requirements: 44 | 45 | - PARI/GP >= 2.9.4 (header files and library); see 46 | https://doc.sagemath.org/html/en/reference/spkg/pari#spkg-pari 47 | for availability in distributions (GNU/Linux, conda-forge, Homebrew, FreeBSD), 48 | or install from source (e.g using the script ``.install-pari.sh`` provided in this 49 | repository). 50 | - gmp (if PARI/GP was built with gmp) 51 | - A C compiler (GCC) 52 | - pkg-config 53 | - Python >= 3.12 54 | - pip 55 | 56 | Install cypari2 via the Python Package Index (PyPI) via 57 | 58 | :: 59 | 60 | $ pip install --no-binary cypari2 cypari2 [--user] 61 | 62 | (the optional option *--user* allows to install cypari2 for a single user 63 | and avoids using pip with administrator rights). 64 | 65 | `pip` builds the package using build isolation. All Python build dependencies 66 | of the package, declared in pyproject.toml, are automatically installed in 67 | a temporary virtual environment. 68 | 69 | If you want to try the development version, use 70 | 71 | :: 72 | 73 | $ pip install git+https://github.com/sagemath/cypari2.git [--user] 74 | 75 | 76 | Usage 77 | ----- 78 | 79 | The interface as been kept as close as possible from PARI/GP. The following 80 | computation in GP 81 | 82 | :: 83 | 84 | ? zeta(2) 85 | %1 = 1.6449340668482264364724151666460251892 86 | 87 | ? p = x^3 + x^2 + x - 1; 88 | ? modulus = t^3 + t^2 + t - 1; 89 | ? fq = factorff(p, 3, modulus); 90 | ? centerlift(lift(fq)) 91 | %5 = 92 | [ x - t 1] 93 | 94 | [x + (t^2 + t - 1) 1] 95 | 96 | [ x + (-t^2 - 1) 1] 97 | 98 | translates into 99 | 100 | :: 101 | 102 | >>> import cypari2 103 | >>> pari = cypari2.Pari() 104 | 105 | >>> pari(2).zeta() 106 | 1.64493406684823 107 | 108 | >>> p = pari("x^3 + x^2 + x - 1") 109 | >>> modulus = pari("t^3 + t^2 + t - 1") 110 | >>> fq = p.factorff(3, modulus) 111 | >>> fq.lift().centerlift() 112 | [x - t, 1; x + (t^2 + t - 1), 1; x + (-t^2 - 1), 1] 113 | 114 | The object **pari** above is the object for the interface and acts as a 115 | constructor. It can be called with basic Python objects like integer 116 | or floating point. When called with a string as in the last example 117 | the corresponding string is interpreted as if it was executed in a GP shell. 118 | 119 | Beyond the interface object **pari** of type **Pari**, any object you get a 120 | handle on is of type **Gen** (that is a wrapper around the **GEN** type from 121 | libpari). All PARI/GP functions are then available in their original names as 122 | *methods* like **zeta**, **factorff**, **lift** or **centerlift** above. 123 | 124 | Alternatively, the pari functions are accessible as methods of **pari**. The 125 | same computations be done via 126 | 127 | :: 128 | 129 | >>> import cypari2 130 | >>> pari = cypari2.Pari() 131 | 132 | >>> pari.zeta(2) 133 | 1.64493406684823 134 | 135 | >>> p = pari("x^3 + x^2 + x - 1") 136 | >>> modulus = pari("t^3 + t^2 + t - 1") 137 | >>> fq = pari.factorff(p, 3, modulus) 138 | >>> pari.centerlift(pari.lift(fq)) 139 | [x - t, 1; x + (t^2 + t - 1), 1; x + (-t^2 - 1), 1] 140 | 141 | The complete documentation of cypari2 is available at https://cypari2.readthedocs.io and 142 | the PARI/GP documentation at https://pari.math.u-bordeaux.fr/doc.html 143 | 144 | Contributing & Development 145 | -------------------------- 146 | 147 | CyPari 2 is maintained by the SageMath community. 148 | 149 | Open issues or submit pull requests at https://github.com/sagemath/cypari2 150 | and join https://groups.google.com/group/sage-devel to discuss. 151 | 152 | To get started with development, you can set up an environment using Conda 153 | as follows: 154 | 155 | :: 156 | $ conda create -n cypari2-dev python cython pari=*=*_pthread ninja meson-python cysignals c-compiler 157 | $ conda activate cypari2-dev 158 | 159 | Afterwards, you can build and install the package in editable mode: 160 | 161 | :: 162 | $ pip install -e . --no-build-isolation 163 | -------------------------------------------------------------------------------- /.install-pari.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Helper script to install PARI (e.g. for CI builds). 4 | # On macOS: the default system fake gcc (clang) is used 5 | # On Linux: the default system gcc is used 6 | # On Windows: uses the ucrt64 toolchain in Msys2 7 | # On *BSD: should be clang; also make should be gmake 8 | 9 | # Exit on error 10 | set -e 11 | 12 | # Detect platform 13 | PLATFORM="unknown" 14 | case "$(uname -s)" in 15 | MSYS_NT*|MINGW*) 16 | PLATFORM="msys" 17 | ;; 18 | Linux) 19 | PLATFORM="linux" 20 | ;; 21 | Darwin) 22 | PLATFORM="macos" 23 | ;; 24 | FreeBSD) 25 | PLATFORM="freebsd" 26 | ;; 27 | OpenBSD) 28 | PLATFORM="openbsd" 29 | ;; 30 | *) 31 | echo "Unknown platform" 32 | exit 1 33 | ;; 34 | esac 35 | 36 | # Run the script again in UCRT64 system for msys 37 | if [ "$ucrt" != "0" ] && [ "$PLATFORM" = "msys" ]; then 38 | MSYSTEM=UCRT64 MSYS2_PATH_TYPE=inherit bash --login -c "cd $pwd ; $self" 39 | fi 40 | 41 | # Windows conda prefix is not added to path automatically 42 | # thus mingw compiler is not found later 43 | if [ -n "$CONDA_PREFIX" ]; then 44 | export PATH="$(cygpath "$CONDA_PREFIX")/Library/bin:$PATH" 45 | fi 46 | 47 | if [ "$PARI_VERSION" = "" ]; then 48 | PARI_VERSION=2.17.2 49 | fi 50 | 51 | PURE_VERSION=${PARI_VERSION/pari-} 52 | URLDIR=OLD/${PURE_VERSION%.*} 53 | 54 | PARI_URL="https://pari.math.u-bordeaux.fr/pub/pari/$URLDIR" 55 | PARI_URL1="https://pari.math.u-bordeaux.fr/pub/pari/unix" 56 | PARI_URL2="https://pari.math.u-bordeaux.fr/pub/pari/unstable" 57 | 58 | if [ -d build/pari-$PURE_VERSION ] ; then 59 | echo "Using existing pari-$PURE_VERSION build directory" 60 | cd "build/pari-$PURE_VERSION" 61 | else 62 | echo "Download PARI sources" 63 | if [ ! -d build ] ; then 64 | mkdir build 65 | fi 66 | cd build 67 | 68 | # install wget if not present 69 | if ! command -v wget &> /dev/null 70 | then 71 | if [ "$PLATFORM" = "msys" ]; then 72 | pacman -S --noconfirm mingw-w64-ucrt-x86_64-wget 73 | elif [ "$PLATFORM" = "linux" ]; then 74 | dnf install -y wget 75 | elif [ "$PLATFORM" = "macos" ]; then 76 | brew install wget 77 | fi 78 | fi 79 | 80 | wget --no-verbose "$PARI_URL/$PURE_VERSION.tar.gz" -O "pari-$PURE_VERSION.tgz" \ 81 | || wget --no-verbose "$PARI_URL1/pari-$PURE_VERSION.tar.gz" -O "pari-$PURE_VERSION.tgz" \ 82 | || wget --no-verbose "$PARI_URL2/pari-$PURE_VERSION.tar.gz" -O "pari-$PURE_VERSION.tgz" \ 83 | || wget --no-verbose "$PARI_URL/pari-$PURE_VERSION.tar.gz" -O "pari-$PURE_VERSION.tgz" 84 | tar xzf "pari-$PURE_VERSION.tgz" 85 | cd "pari-$PURE_VERSION" 86 | fi 87 | 88 | # Install gmp if not present 89 | if ! ldconfig -p | grep libgmp >/dev/null 2>&1; then 90 | echo "Installing GMP ..." 91 | if [ "$PLATFORM" = "msys" ]; then 92 | pacman -S --noconfirm mingw-w64-ucrt-x86_64-gmp 93 | MSYSTEM_PREFIX="/ucrt64" 94 | elif [ "$PLATFORM" = "linux" ]; then 95 | if command -v dnf >/dev/null 2>&1; then 96 | dnf install -y gmp-devel 97 | fi 98 | elif [ "$PLATFORM" = "macos" ]; then 99 | brew install gmp 100 | elif [ "$PLATFORM" = "freebsd" ]; then 101 | pkg install -y gmp 102 | elif [ "$PLATFORM" = "openbsd" ]; then 103 | pkg_add gmp 104 | fi 105 | fi 106 | 107 | echo "Building Pari ..." 108 | if [ "$PLATFORM" = "msys" ]; then 109 | # Remove "export_file='$(LIBPARI).def';" line from config/Makefile.SH" 110 | # Otherwise we get a Segmentation Fault during the resulting dlltool call 111 | sed -i.bak "/export_file='\\\$(LIBPARI).def';/d" config/Makefile.SH 112 | fi 113 | # For debugging: 114 | # export CFLAGS="-g" 115 | if [[ "$PLATFORM" = "msys" ]]; then 116 | # If one installs in a non-default location, then one needs to call os.add_dll_directory 117 | # in Python to find the DLLs. 118 | CONFIG_ARGS="--without-readline --prefix=$MSYSTEM_PREFIX" 119 | else 120 | CONFIG_ARGS="--prefix=/usr" 121 | fi 122 | chmod -R +x ./Configure ./config 123 | ./Configure $CONFIG_ARGS 124 | 125 | # On Windows, disable UNIX-specific code in language files 126 | # (not sure why UNIX is defined) 127 | lang_es="src/language/es.c" 128 | if [ -f "$lang_es" ] && [ "$PLATFORM" = "msys" ]; then 129 | sed -i.bak \ 130 | -e 's/#if[[:space:]]*defined(UNIX)/#if 0/' \ 131 | -e 's/#ifdef[[:space:]]*UNIX/#if 0/' \ 132 | "$lang_es" 133 | fi 134 | 135 | 136 | if [ "$PLATFORM" = "msys" ]; then 137 | # Windows 138 | cd Omingw-x86_64 139 | make install-lib-dyn 140 | make install-include 141 | make install-doc 142 | make install-cfg 143 | 144 | # Fix location of libpari.dll.a 145 | if [ -f "$MSYSTEM_PREFIX/bin/libpari.dll.a" ]; then 146 | cp "$MSYSTEM_PREFIX/bin/libpari.dll.a" "$MSYSTEM_PREFIX/lib/" 147 | fi 148 | else 149 | # Linux or macOS 150 | 151 | # Remove sudo provided by devtoolset since it doesn't work 152 | rm -f /opt/rh/gcc-toolset-14/root/usr/bin/sudo 153 | if ! command -v sudo >/dev/null 2>&1; then 154 | if command -v dnf >/dev/null 2>&1; then 155 | dnf install -y sudo 156 | elif command -v apt-get >/dev/null 2>&1; then 157 | apt-get update 158 | apt-get install -y sudo 159 | elif command -v apk >/dev/null 2>&1; then 160 | apk add sudo 161 | fi 162 | fi 163 | make gp 164 | sudo make install 165 | 166 | # Copy libpari to usr/lib64 (needed on RHEL/CentOS/Fedora) 167 | if [ -d /usr/lib64 ] && [ -f /usr/lib/libpari.so ]; then 168 | sudo cp /usr/lib/libpari.so* /usr/lib64/ 169 | sudo ldconfig 170 | fi 171 | 172 | # Diagnostic output 173 | ls -l /usr/lib | grep libpari || true 174 | ls -L /usr/lib | grep libpari || true 175 | ls -l /usr/local/lib | grep libpari || true 176 | ldconfig -p | grep libpari || true 177 | fi 178 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # CyPari2 documentation build configuration file, created by 4 | # sphinx-quickstart on Thu May 18 16:07:32 2017. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | # If extensions (or modules to document with autodoc) are in another directory, 16 | # add these directories to sys.path here. If the directory is relative to the 17 | # documentation root, use os.path.abspath to make it absolute, like shown here. 18 | # 19 | # import os 20 | # import sys 21 | # sys.path.insert(0, os.path.abspath('.')) 22 | 23 | 24 | # -- General configuration ------------------------------------------------ 25 | 26 | # If your documentation needs a minimal Sphinx version, state it here. 27 | # 28 | needs_sphinx = '1.6' 29 | 30 | # Add any Sphinx extension module names here, as strings. They can be 31 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 32 | # ones. 33 | extensions = [ 34 | 'sphinx.ext.autodoc', 35 | 'sphinx.ext.mathjax', 36 | 'sphinx.ext.extlinks', 37 | ] 38 | 39 | 40 | # Add any paths that contain templates here, relative to this directory. 41 | templates_path = ['_templates'] 42 | 43 | # The suffix(es) of source filenames. 44 | # You can specify multiple suffix as a list of string: 45 | # 46 | # source_suffix = ['.rst', '.md'] 47 | source_suffix = '.rst' 48 | 49 | # The master toctree document. 50 | master_doc = 'index' 51 | 52 | # General information about the project. 53 | project = 'CyPari2' 54 | copyright = '2017, Many people' 55 | author = 'Many people' 56 | 57 | # The version info for the project you're documenting, acts as replacement for 58 | # |version| and |release|, also used in various other places throughout the 59 | # built documents. 60 | # 61 | # The short X.Y version. 62 | version = open("../../VERSION").read().strip() 63 | # The full version, including alpha/beta/rc tags. 64 | release = version 65 | 66 | # The language for content autogenerated by Sphinx. Refer to documentation 67 | # for a list of supported languages. 68 | # 69 | # This is also used if you do content translation via gettext catalogs. 70 | # Usually you set "language" from the command line for these cases. 71 | #language = None 72 | 73 | # List of patterns, relative to source directory, that match files and 74 | # directories to ignore when looking for source files. 75 | # This patterns also effect to html_static_path and html_extra_path 76 | exclude_patterns = [] 77 | 78 | # The name of the Pygments (syntax highlighting) style to use. 79 | pygments_style = 'sphinx' 80 | 81 | # If true, `todo` and `todoList` produce output, else they produce nothing. 82 | todo_include_todos = False 83 | 84 | 85 | # -- Options for HTML output ---------------------------------------------- 86 | 87 | # The theme to use for HTML and HTML Help pages. See the documentation for 88 | # a list of builtin themes. 89 | # 90 | html_theme = 'alabaster' 91 | 92 | # Theme options are theme-specific and customize the look and feel of a theme 93 | # further. For a list of options available for each theme, see the 94 | # documentation. 95 | # 96 | # html_theme_options = {} 97 | 98 | # Add any paths that contain custom static files (such as style sheets) here, 99 | # relative to this directory. They are copied after the builtin static files, 100 | # so a file named "default.css" will overwrite the builtin "default.css". 101 | html_static_path = ['_static'] 102 | 103 | 104 | # -- Options for HTMLHelp output ------------------------------------------ 105 | 106 | # Output file base name for HTML help builder. 107 | htmlhelp_basename = 'CyPari2doc' 108 | 109 | 110 | # -- Options for LaTeX output --------------------------------------------- 111 | 112 | latex_elements = { 113 | # The paper size ('letterpaper' or 'a4paper'). 114 | # 115 | # 'papersize': 'letterpaper', 116 | 117 | # The font size ('10pt', '11pt' or '12pt'). 118 | # 119 | # 'pointsize': '10pt', 120 | 121 | # Additional stuff for the LaTeX preamble. 122 | # 123 | # 'preamble': '', 124 | 125 | # Latex figure (float) alignment 126 | # 127 | # 'figure_align': 'htbp', 128 | } 129 | 130 | # Grouping the document tree into LaTeX files. List of tuples 131 | # (source start file, target name, title, 132 | # author, documentclass [howto, manual, or own class]). 133 | latex_documents = [ 134 | (master_doc, 'CyPari2.tex', u'CyPari2 Documentation', 135 | u'Many People', 'manual'), 136 | ] 137 | 138 | 139 | # -- Options for manual page output --------------------------------------- 140 | 141 | # One entry per manual page. List of tuples 142 | # (source start file, name, description, authors, manual section). 143 | man_pages = [ 144 | (master_doc, 'cypari2', u'CyPari2 Documentation', 145 | [author], 1) 146 | ] 147 | 148 | 149 | # -- Options for Texinfo output ------------------------------------------- 150 | 151 | # Grouping the document tree into Texinfo files. List of tuples 152 | # (source start file, target name, title, author, 153 | # dir menu entry, description, category) 154 | texinfo_documents = [ 155 | (master_doc, 'CyPari2', u'CyPari2 Documentation', 156 | author, 'CyPari2', 'One line description of project.', 157 | 'Miscellaneous'), 158 | ] 159 | 160 | 161 | # Links to external resources (copied from Sage) 162 | extlinks = { 163 | 'trac': ('https://github.com/sagemath/sage/issues/%s', 'github issue #%s'), # support :trac: for backward compatibility 164 | 'issue': ('https://github.com/sagemath/sage/issues/%s', 'github issue #%s'), 165 | 'wikipedia': ('https://en.wikipedia.org/wiki/%s', 'Wikipedia article %s'), 166 | 'arxiv': ('https://arxiv.org/abs/%s', 'arXiv %s'), 167 | 'oeis': ('https://oeis.org/%s', 'OEIS sequence %s'), 168 | 'doi': ('https://dx.doi.org/%s', 'doi:%s'), 169 | 'pari': ('https://pari.math.u-bordeaux.fr/dochtml/help/%s', 'pari:%s'), 170 | 'mathscinet': ('https://www.ams.org/mathscinet-getitem?mr=%s', 'MathSciNet %s') 171 | } 172 | 173 | 174 | # Monkey-patch inspect with Cython support 175 | def isfunction(obj): 176 | return hasattr(type(obj), "__code__") 177 | 178 | import inspect 179 | inspect.isfunction = isfunction 180 | -------------------------------------------------------------------------------- /autogen/parser.py: -------------------------------------------------------------------------------- 1 | """ 2 | Read and parse the file pari.desc 3 | """ 4 | 5 | # *************************************************************************** 6 | # Copyright (C) 2015 Jeroen Demeyer 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 2 of the License, or 11 | # (at your option) any later version. 12 | # https://www.gnu.org/licenses/ 13 | # *************************************************************************** 14 | 15 | from __future__ import absolute_import, unicode_literals 16 | 17 | import io 18 | import re 19 | from pathlib import Path 20 | 21 | from .args import pari_arg_types 22 | from .ret import pari_ret_types 23 | 24 | paren_re = re.compile(r"[(](.*)[)]") 25 | argname_re = re.compile(r"[ {]*&?([A-Za-z_][A-Za-z0-9_]*)") 26 | 27 | 28 | def read_pari_desc(pari_datadir: Path) -> dict[str, dict[str, str]]: 29 | """ 30 | Read and parse the file ``pari.desc``. 31 | 32 | The output is a dictionary where the keys are GP function names 33 | and the corresponding values are dictionaries containing the 34 | ``(key, value)`` pairs from ``pari.desc``. 35 | 36 | EXAMPLES:: 37 | 38 | >>> from autogen.parser import read_pari_desc 39 | >>> D = read_pari_desc(Path("tests")) 40 | >>> Dcos = D["cos"] 41 | >>> if "description" in Dcos: _ = Dcos.pop("description") 42 | >>> Dcos.pop("doc").startswith('cosine of $x$.') 43 | True 44 | >>> Dcos == { 'class': 'basic', 45 | ... 'cname': 'gcos', 46 | ... 'function': 'cos', 47 | ... 'help': 'cos(x): cosine of x.', 48 | ... 'prototype': 'Gp', 49 | ... 'section': 'transcendental'} 50 | True 51 | """ 52 | pari_desc = pari_datadir / 'pari.desc' 53 | with io.open(pari_desc, encoding="utf-8") as f: 54 | lines = f.readlines() 55 | 56 | n = 0 57 | N = len(lines) 58 | 59 | functions: dict[str, dict[str, str]] = {} 60 | while n < N: 61 | fun: dict[str, str] = {} 62 | while True: 63 | L = lines[n] 64 | n += 1 65 | if L == "\n": 66 | break 67 | # As long as the next lines start with a space, append them 68 | while lines[n].startswith(" "): 69 | L += (lines[n])[1:] 70 | n += 1 71 | key, value = L.split(":", 1) 72 | # Change key to an allowed identifier name 73 | key = key.lower().replace("-", "") 74 | fun[key] = value.strip() 75 | 76 | name = fun["function"] 77 | functions[name] = fun 78 | 79 | return functions 80 | 81 | 82 | def parse_prototype(proto, help, initial_args=[]): 83 | """ 84 | Parse arguments and return type of a PARI function. 85 | 86 | INPUT: 87 | 88 | - ``proto`` -- a PARI prototype like ``"GD0,L,DGDGDG"`` 89 | 90 | - ``help`` -- a PARI help string like 91 | ``"qfbred(x,{flag=0},{d},{isd},{sd})"`` 92 | 93 | - ``initial_args`` -- other arguments to this function which come 94 | before the PARI arguments, for example a ``self`` argument. 95 | 96 | OUTPUT: a tuple ``(args, ret)`` where 97 | 98 | - ``args`` is a list consisting of ``initial_args`` followed by 99 | :class:`PariArgument` instances with all arguments of this 100 | function. 101 | 102 | - ``ret`` is a :class:`PariReturn` instance with the return type of 103 | this function. 104 | 105 | EXAMPLES:: 106 | 107 | >>> from autogen.parser import parse_prototype 108 | >>> proto = 'GD0,L,DGDGDG' 109 | >>> help = 'qfbred(x,{flag=0},{d},{isd},{sd})' 110 | >>> parse_prototype(proto, help) 111 | ([GEN x, long flag=0, GEN d=NULL, GEN isd=NULL, GEN sd=NULL], GEN) 112 | >>> proto = "GD&" 113 | >>> help = "sqrtint(x,{&r})" 114 | >>> parse_prototype(proto, help) 115 | ([GEN x, GEN* r=NULL], GEN) 116 | >>> parse_prototype("lp", "foo()", [str("TEST")]) 117 | (['TEST', prec precision=DEFAULT_BITPREC], long) 118 | """ 119 | # Use the help string just for the argument names. 120 | # "names" should be an iterator over the argument names. 121 | m = paren_re.search(help) 122 | if m is None: 123 | names = iter([]) 124 | else: 125 | s = m.groups()[0] 126 | matches = [argname_re.match(x) for x in s.split(",")] 127 | names = (m.groups()[0] for m in matches if m is not None) 128 | 129 | # First, handle the return type 130 | try: 131 | c = proto[0] 132 | t = pari_ret_types[c] 133 | n = 1 # index in proto 134 | except (IndexError, KeyError): 135 | t = pari_ret_types[""] 136 | n = 0 # index in proto 137 | ret = t() 138 | 139 | # Go over the prototype characters and build up the arguments 140 | args = list(initial_args) 141 | have_default = False # Have we seen any default argument? 142 | while n < len(proto): 143 | c = proto[n] 144 | n += 1 145 | 146 | # Parse default value 147 | if c == "D": 148 | default = "" 149 | if proto[n] not in pari_arg_types: 150 | while True: 151 | c = proto[n] 152 | n += 1 153 | if c == ",": 154 | break 155 | default += c 156 | c = proto[n] 157 | n += 1 158 | else: 159 | default = None 160 | 161 | try: 162 | t = pari_arg_types[c] 163 | if t is None: 164 | raise NotImplementedError('unsupported prototype character %r' % c) 165 | except KeyError: 166 | if c == ",": 167 | continue # Just skip additional commas 168 | else: 169 | raise ValueError('unknown prototype character %r' % c) 170 | 171 | arg = t(names, default, index=len(args)) 172 | if arg.default is not None: 173 | have_default = True 174 | elif have_default: 175 | # We have a non-default argument following a default 176 | # argument, which means trouble... 177 | # 178 | # A syntactical wart of Python is that it does not allow 179 | # that: something like def foo(x=None, y) is a SyntaxError 180 | # (at least with Python-2.7.13, Python-3.6.1 and Cython-0.25.2) 181 | # 182 | # A small number of GP functions (nfroots() for example) 183 | # wants to do this anyway. Luckily, this seems to occur only 184 | # for arguments of type GEN (prototype code "G") 185 | # 186 | # To work around this, we add a "fake" default value and 187 | # then raise an error if it was not given... 188 | if c != "G": 189 | raise NotImplementedError("non-default argument after default argument is only implemented for GEN arguments") 190 | arg.default = False 191 | args.append(arg) 192 | 193 | return (args, ret) 194 | -------------------------------------------------------------------------------- /cypari2/handle_error.pyx: -------------------------------------------------------------------------------- 1 | """ 2 | Handling PARI errors 3 | ******************** 4 | 5 | AUTHORS: 6 | 7 | - Peter Bruin (September 2013): initial version (:trac:`9640`) 8 | 9 | - Jeroen Demeyer (January 2015): use ``cb_pari_err_handle`` (:trac:`14894`) 10 | 11 | """ 12 | 13 | # **************************************************************************** 14 | # Copyright (C) 2013 Peter Bruin 15 | # Copyright (C) 2015 Jeroen Demeyer 16 | # 17 | # This program is free software: you can redistribute it and/or modify 18 | # it under the terms of the GNU General Public License as published by 19 | # the Free Software Foundation, either version 2 of the License, or 20 | # (at your option) any later version. 21 | # https://www.gnu.org/licenses/ 22 | # **************************************************************************** 23 | 24 | from cpython cimport PyErr_Occurred 25 | 26 | from cysignals.signals cimport sig_block, sig_unblock, sig_error 27 | 28 | from .paridecl cimport * 29 | from .paripriv cimport * 30 | from .stack cimport clone_gen_noclear, reset_avma, after_resize 31 | 32 | 33 | # We derive PariError from RuntimeError, for backward compatibility with 34 | # code that catches the latter. 35 | class PariError(RuntimeError): 36 | """ 37 | Error raised by PARI 38 | """ 39 | def errnum(self): 40 | r""" 41 | Return the PARI error number corresponding to this exception. 42 | 43 | EXAMPLES: 44 | 45 | >>> import cypari2 46 | >>> pari = cypari2.Pari() 47 | >>> try: 48 | ... pari('1/0') 49 | ... except PariError as err: 50 | ... print(err.errnum()) 51 | 31 52 | """ 53 | return self.args[0] 54 | 55 | def errtext(self): 56 | """ 57 | Return the message output by PARI when this error occurred. 58 | 59 | EXAMPLES: 60 | 61 | >>> import cypari2 62 | >>> pari = cypari2.Pari() 63 | >>> try: 64 | ... pari('pi()') 65 | ... except PariError as e: 66 | ... print(e.errtext()) 67 | not a function in function call 68 | """ 69 | return self.args[1] 70 | 71 | def errdata(self): 72 | """ 73 | Return the error data (a ``t_ERROR`` gen) corresponding to this 74 | error. 75 | 76 | EXAMPLES: 77 | 78 | >>> import cypari2 79 | >>> pari = cypari2.Pari() 80 | >>> try: 81 | ... pari('Mod(2,6)')**-1 82 | ... except PariError as e: 83 | ... E = e.errdata() 84 | >>> E 85 | error("impossible inverse in Fp_inv: Mod(2, 6).") 86 | >>> E.component(2) 87 | Mod(2, 6) 88 | """ 89 | return self.args[2] 90 | 91 | def __repr__(self): 92 | r""" 93 | TESTS: 94 | 95 | >>> import cypari2 96 | >>> pari = cypari2.Pari() 97 | >>> PariError(11) 98 | PariError(11) 99 | """ 100 | return "PariError(%d)" % self.errnum() 101 | 102 | def __str__(self): 103 | r""" 104 | Return a suitable message for displaying this exception. 105 | 106 | This is simply the error text with certain trailing characters 107 | stripped. 108 | 109 | EXAMPLES: 110 | 111 | >>> import cypari2 112 | >>> pari = cypari2.Pari() 113 | >>> try: 114 | ... pari('1/0') 115 | ... except PariError as err: 116 | ... print(err) 117 | _/_: impossible inverse in gdiv: 0 118 | 119 | A syntax error: 120 | 121 | >>> pari('!@#$%^&*()') 122 | Traceback (most recent call last): 123 | ... 124 | PariError: syntax error, unexpected ... 125 | """ 126 | return self.errtext().rstrip(" .:") 127 | 128 | 129 | cdef void _pari_init_error_handling() noexcept: 130 | """ 131 | Set up our code for handling PARI errors. 132 | 133 | TESTS: 134 | 135 | >>> import cypari2 136 | >>> pari = cypari2.Pari() 137 | >>> try: 138 | ... p = pari.polcyclo(-1) 139 | ... except PariError as e: 140 | ... print(e.errtext()) 141 | domain error in polcyclo: index <= 0 142 | 143 | Warnings still work just like in GP:: 144 | 145 | >>> pari('warning("test")') 146 | """ 147 | global cb_pari_err_handle 148 | global cb_pari_err_recover 149 | cb_pari_err_handle = _pari_err_handle 150 | cb_pari_err_recover = _pari_err_recover 151 | 152 | 153 | cdef int _pari_err_handle(GEN E) except 0: 154 | """ 155 | Convert a PARI error into a Python exception. 156 | 157 | This function is a callback from the PARI error handler. 158 | 159 | EXAMPLES: 160 | 161 | >>> import cypari2 162 | >>> pari = cypari2.Pari() 163 | >>> pari('error("test")') 164 | Traceback (most recent call last): 165 | ... 166 | PariError: error: user error: test 167 | >>> pari(1)/pari(0) 168 | Traceback (most recent call last): 169 | ... 170 | PariError: impossible inverse in gdiv: 0 171 | 172 | Test exceptions with a pointer to a PARI object: 173 | 174 | >>> from cypari2 import Pari 175 | >>> def exc(): 176 | ... K = Pari().nfinit("x^2 + 1") 177 | ... I = K.idealhnf(2) 178 | ... I[0] 179 | ... try: 180 | ... K.idealaddtoone(I, I) 181 | ... except RuntimeError as e: 182 | ... return e 183 | >>> L = [exc(), exc()] 184 | >>> print(L[0]) 185 | elements not coprime in idealaddtoone: 186 | [2, 0; 0, 2] 187 | [2, 0; 0, 2] 188 | """ 189 | cdef long errnum = E[1] 190 | cdef char* errstr 191 | cdef const char* s 192 | 193 | if errnum == e_STACK: 194 | # Custom error message for PARI stack overflow 195 | pari_error_string = "the PARI stack overflows (current size: {}; maximum size: {})\n" 196 | pari_error_string += "You can use pari.allocatemem() to change the stack size and try again" 197 | pari_error_string = pari_error_string.format(pari_mainstack.size, pari_mainstack.vsize) 198 | else: 199 | sig_block() 200 | try: 201 | errstr = pari_err2str(E) 202 | pari_error_string = errstr.decode('ascii') 203 | pari_free(errstr) 204 | finally: 205 | sig_unblock() 206 | 207 | s = closure_func_err() 208 | if s is not NULL: 209 | pari_error_string = s.decode('ascii') + ": " + pari_error_string 210 | 211 | raise PariError(errnum, pari_error_string, clone_gen_noclear(E)) 212 | 213 | 214 | cdef void _pari_err_recover(long errnum) noexcept: 215 | """ 216 | Reset the error string and jump back to ``sig_on()``, either to 217 | retry the code (in case of no error) or to make the already-raised 218 | exception known to Python. 219 | """ 220 | reset_avma() 221 | 222 | # Special case errnum == -1 corresponds to a reallocation of the 223 | # PARI stack. This is not an error, so call after_resize() and 224 | # proceed as if nothing happened. 225 | if errnum < 0: 226 | after_resize() 227 | return 228 | 229 | # An exception was raised. Jump to the signal-handling code 230 | # which will cause sig_on() to see the exception. 231 | sig_error() 232 | -------------------------------------------------------------------------------- /cypari2/closure.pyx: -------------------------------------------------------------------------------- 1 | """ 2 | Convert Python functions to PARI closures 3 | ***************************************** 4 | 5 | AUTHORS: 6 | 7 | - Jeroen Demeyer (2015-04-10): initial version, :trac:`18052`. 8 | 9 | Examples: 10 | 11 | >>> def the_answer(): 12 | ... return 42 13 | >>> import cypari2 14 | >>> pari = cypari2.Pari() 15 | >>> f = pari(the_answer) 16 | >>> f() 17 | 42 18 | 19 | >>> cube = pari(lambda i: i**3) 20 | >>> cube.apply(range(10)) 21 | [0, 1, 8, 27, 64, 125, 216, 343, 512, 729] 22 | """ 23 | 24 | # **************************************************************************** 25 | # Copyright (C) 2015 Jeroen Demeyer 26 | # 27 | # This program is free software: you can redistribute it and/or modify 28 | # it under the terms of the GNU General Public License as published by 29 | # the Free Software Foundation, either version 2 of the License, or 30 | # (at your option) any later version. 31 | # https://www.gnu.org/licenses/ 32 | # **************************************************************************** 33 | 34 | from cysignals.signals cimport sig_on, sig_off, sig_block, sig_unblock, sig_error 35 | 36 | from cpython.tuple cimport * 37 | from cpython.object cimport PyObject_Call 38 | from cpython.ref cimport Py_INCREF 39 | 40 | from .paridecl cimport * 41 | from .stack cimport new_gen, new_gen_noclear, clone_gen_noclear, DetachGen 42 | from .gen cimport objtogen 43 | 44 | try: 45 | from inspect import getfullargspec as getargspec 46 | except ImportError: 47 | from inspect import getargspec 48 | 49 | 50 | cdef inline GEN call_python_func_impl "call_python_func"(GEN* args, object py_func) except NULL: 51 | """ 52 | Call ``py_func(*args)`` where ``py_func`` is a Python function 53 | and ``args`` is an array of ``GEN``s terminated by ``NULL``. 54 | 55 | The arguments are converted from ``GEN`` to a cypari ``gen`` before 56 | calling ``py_func``. The result is converted back to a PARI ``GEN``. 57 | """ 58 | # We need to ensure that nothing above avma is touched 59 | avmaguard = new_gen_noclear(avma) 60 | 61 | # How many arguments are there? 62 | cdef Py_ssize_t n = 0 63 | while args[n] is not NULL: 64 | n += 1 65 | 66 | # Construct a Python tuple for args 67 | cdef tuple t = PyTuple_New(n) 68 | cdef Py_ssize_t i 69 | for i in range(n): 70 | a = clone_gen_noclear(args[i]) 71 | Py_INCREF(a) # Need to increase refcount because the tuple steals it 72 | PyTuple_SET_ITEM(t, i, a) 73 | 74 | # Call the Python function 75 | r = PyObject_Call(py_func, t, NULL) 76 | 77 | # Convert the result to a GEN and copy it to the PARI stack 78 | # (with a special case for None) 79 | if r is None: 80 | return gnil 81 | 82 | # Safely delete r and avmaguard 83 | d = DetachGen(objtogen(r)) 84 | del r 85 | res = d.detach() 86 | d = DetachGen(avmaguard) 87 | del avmaguard 88 | d.detach() 89 | 90 | return res 91 | 92 | 93 | # We rename this function to be able to call it with a different 94 | # signature. In particular, we want manual exception handling and we 95 | # implicitly convert py_func from a PyObject* to an object. 96 | cdef extern from *: 97 | GEN call_python_func(GEN* args, PyObject* py_func) 98 | 99 | 100 | cdef GEN call_python(GEN arg1, GEN arg2, GEN arg3, GEN arg4, GEN arg5, 101 | ulong nargs, ulong py_func) noexcept: 102 | """ 103 | This function, which will be installed in PARI, is a front-end for 104 | ``call_python_func_impl``. 105 | 106 | It has 5 optional ``GEN``s as argument, a ``nargs`` argument 107 | specifying how many arguments are valid and one ``ulong``, which is 108 | actually a Python callable object cast to ``ulong``. 109 | """ 110 | if nargs > 5: 111 | sig_error() 112 | 113 | # Convert arguments to a NULL-terminated array. 114 | cdef GEN args[6] 115 | args[0] = arg1 116 | args[1] = arg2 117 | args[2] = arg3 118 | args[3] = arg4 119 | args[4] = arg5 120 | args[nargs] = NULL 121 | 122 | sig_block() 123 | # Disallow interrupts during the Python code inside 124 | # call_python_func_impl(). We need to do this because this function 125 | # is very likely called within sig_on() and interrupting arbitrary 126 | # Python code is bad. 127 | cdef GEN r = call_python_func(args, py_func) 128 | sig_unblock() 129 | if not r: # An exception was raised 130 | sig_error() 131 | return r 132 | 133 | 134 | # Install the function "call_python" for use in the PARI library. 135 | cdef entree* ep_call_python 136 | 137 | cdef int _pari_init_closure() except -1: 138 | sig_on() 139 | global ep_call_python 140 | ep_call_python = install(call_python, "call_python", 'DGDGDGDGDGD5,U,U') 141 | sig_off() 142 | 143 | 144 | cpdef Gen objtoclosure(f): 145 | """ 146 | Convert a Python function (more generally, any callable) to a PARI 147 | ``t_CLOSURE``. 148 | 149 | .. NOTE:: 150 | 151 | With the current implementation, the function can be called 152 | with at most 5 arguments. 153 | 154 | .. WARNING:: 155 | 156 | The function ``f`` which is called through the closure cannot 157 | be interrupted. Therefore, it is advised to use this only for 158 | simple functions which do not take a long time. 159 | 160 | Examples: 161 | 162 | >>> from cypari2.closure import objtoclosure 163 | >>> def pymul(i,j): return i*j 164 | >>> mul = objtoclosure(pymul) 165 | >>> mul 166 | (v1,v2)->call_python(v1,v2,0,0,0,2,...) 167 | >>> mul(6,9) 168 | 54 169 | >>> mul.type() 170 | 't_CLOSURE' 171 | >>> mul.arity() 172 | 2 173 | >>> def printme(x): 174 | ... print(x) 175 | >>> objtoclosure(printme)('matid(2)') 176 | [1, 0; 0, 1] 177 | 178 | Construct the Riemann zeta function using a closure: 179 | 180 | >>> from cypari2 import Pari; pari = Pari() 181 | >>> def coeffs(n): 182 | ... return [1 for i in range(n)] 183 | >>> Z = pari.lfuncreate([coeffs, 0, [0], 1, 1, 1, 1]) 184 | >>> Z.lfun(2) 185 | 1.64493406684823 186 | 187 | A trivial closure: 188 | 189 | >>> f = pari(lambda x: x) 190 | >>> f(10) 191 | 10 192 | 193 | Test various kinds of errors: 194 | 195 | >>> mul(4) 196 | Traceback (most recent call last): 197 | ... 198 | TypeError: pymul() ... 199 | >>> mul(None, None) 200 | Traceback (most recent call last): 201 | ... 202 | ValueError: Cannot convert None to pari 203 | >>> mul(*range(100)) 204 | Traceback (most recent call last): 205 | ... 206 | PariError: call_python: too many parameters in user-defined function call 207 | >>> mul([1], [2]) 208 | Traceback (most recent call last): 209 | ... 210 | PariError: call_python: ... 211 | """ 212 | if not callable(f): 213 | raise TypeError("argument to objtoclosure() must be callable") 214 | 215 | # Determine number of arguments of f 216 | cdef Py_ssize_t i, nargs 217 | try: 218 | argspec = getargspec(f) 219 | except Exception: 220 | nargs = 5 221 | else: 222 | nargs = len(argspec.args) 223 | 224 | # Only 5 arguments are supported for now... 225 | if nargs > 5: 226 | nargs = 5 227 | 228 | # Fill in default arguments of PARI function 229 | sig_on() 230 | cdef GEN args = cgetg((5 - nargs) + 2 + 1, t_VEC) 231 | for i in range(5 - nargs): 232 | set_gel(args, i + 1, gnil) 233 | set_gel(args, (5 - nargs) + 1, stoi(nargs)) 234 | # Convert f to a t_INT containing the address of f 235 | set_gel(args, (5 - nargs) + 1 + 1, utoi(f)) 236 | 237 | # Create a t_CLOSURE which calls call_python() with py_func equal to f 238 | cdef Gen res = new_gen(snm_closure(ep_call_python, args)) 239 | 240 | # We need to keep a reference to f somewhere and there is no way to 241 | # have PARI handle this reference for us. So the only way out is to 242 | # force f to be never deallocated 243 | Py_INCREF(f) 244 | 245 | return res 246 | -------------------------------------------------------------------------------- /cypari2/stack.pyx: -------------------------------------------------------------------------------- 1 | """ 2 | Memory management for Gens on the PARI stack or the heap 3 | ******************************************************** 4 | """ 5 | 6 | # **************************************************************************** 7 | # Copyright (C) 2016 Luca De Feo 8 | # Copyright (C) 2018 Jeroen Demeyer 9 | # 10 | # This program is free software: you can redistribute it and/or modify 11 | # it under the terms of the GNU General Public License as published by 12 | # the Free Software Foundation, either version 2 of the License, or 13 | # (at your option) any later version. 14 | # https://www.gnu.org/licenses/ 15 | # **************************************************************************** 16 | 17 | cimport cython 18 | 19 | from cpython.ref cimport PyObject, _Py_REFCNT 20 | from cpython.exc cimport PyErr_SetString 21 | 22 | from cysignals.signals cimport (sig_on, sig_off, sig_block, sig_unblock, 23 | sig_error) 24 | 25 | from .gen cimport Gen, Gen_new 26 | from .paridecl cimport (avma, pari_mainstack, gnil, gcopy, 27 | is_universal_constant, is_on_stack, 28 | isclone, gclone, gclone_refc, 29 | paristack_setsize) 30 | 31 | from warnings import warn 32 | 33 | 34 | cdef extern from *: 35 | int sig_on_count "cysigs.sig_on_count" 36 | int block_sigint "cysigs.block_sigint" 37 | 38 | 39 | # Singleton object to denote the top of the PARI stack 40 | cdef Gen top_of_stack = Gen_new(gnil, NULL) 41 | 42 | # Pointer to the Gen on the bottom of the PARI stack. This is the first 43 | # element of the Gen linked list. If the linked list is empty, this 44 | # equals top_of_stack. This pointer is *not* refcounted, so it does not 45 | # prevent the stackbottom object from being deallocated. In that case, 46 | # we update stackbottom in Gen.__dealloc__ 47 | cdef PyObject* stackbottom = top_of_stack 48 | 49 | 50 | cdef void remove_from_pari_stack(Gen self) noexcept: 51 | global avma, stackbottom 52 | if self is not stackbottom: 53 | print("ERROR: removing wrong instance of Gen") 54 | print(f"Expected: {stackbottom}") 55 | print(f"Actual: {self}") 56 | if sig_on_count and not block_sigint: 57 | PyErr_SetString(SystemError, "calling remove_from_pari_stack() inside sig_on()") 58 | sig_error() 59 | if self.sp() != avma: 60 | if avma > self.sp(): 61 | print("ERROR: inconsistent avma when removing Gen from PARI stack") 62 | print(f"Expected: 0x{self.sp():x}") 63 | print(f"Actual: 0x{avma:x}") 64 | else: 65 | warn(f"cypari2 leaked {self.sp() - avma} bytes on the PARI stack", 66 | RuntimeWarning, stacklevel=2) 67 | n = self.next 68 | stackbottom = n 69 | self.next = None 70 | reset_avma() 71 | 72 | 73 | cdef inline Gen Gen_stack_new(GEN x): 74 | """ 75 | Allocate and initialize a new instance of ``Gen`` wrapping 76 | a GEN on the PARI stack. 77 | """ 78 | global stackbottom 79 | # n = stackbottom must be done BEFORE calling Gen_new() 80 | # since Gen_new may invoke gc.collect() which would mess up 81 | # the PARI stack. 82 | n = stackbottom 83 | z = Gen_new(x, avma) 84 | z.next = n 85 | stackbottom = z 86 | sz = z.sp() 87 | sn = n.sp() 88 | if sz > sn: 89 | raise SystemError(f"objects on PARI stack in invalid order (first: 0x{sz:x}; next: 0x{sn:x})") 90 | return z 91 | 92 | 93 | cdef void reset_avma() noexcept: 94 | """ 95 | Reset PARI stack pointer to remove unused stuff from the PARI stack. 96 | 97 | Note that the actual data remains on the stack. Therefore, it is 98 | safe to use as long as no further PARI functions are called. 99 | """ 100 | # NOTE: this can be called with an exception set (the error handler 101 | # does that)! 102 | global avma 103 | avma = (stackbottom).sp() 104 | 105 | 106 | cdef void clear_stack() noexcept: 107 | """ 108 | Call ``sig_off()`` and clean the PARI stack. 109 | """ 110 | sig_off() 111 | reset_avma() 112 | 113 | 114 | cdef int move_gens_to_heap(pari_sp lim) except -1: 115 | """ 116 | Move some/all Gens from the PARI stack to the heap. 117 | 118 | If lim == -1, move everything. Otherwise, keep moving as long as 119 | avma <= lim. 120 | """ 121 | while avma <= lim and stackbottom is not top_of_stack: 122 | current = stackbottom 123 | sig_on() 124 | current.g = gclone(current.g) 125 | sig_block() 126 | remove_from_pari_stack(current) 127 | sig_unblock() 128 | sig_off() 129 | # The .address attribute can only be updated now because it is 130 | # needed in remove_from_pari_stack(). This means that the object 131 | # is temporarily in an inconsistent state but this does not 132 | # matter since .address is normally not used. 133 | # 134 | # The more important .g attribute is updated correctly before 135 | # remove_from_pari_stack(). Therefore, the object can be used 136 | # normally regardless of what happens to the PARI stack. 137 | current.address = current.g 138 | 139 | 140 | cdef int before_resize() except -1: 141 | """ 142 | Prepare for resizing the PARI stack 143 | 144 | This must be called before reallocating the PARI stack 145 | """ 146 | move_gens_to_heap(-1) 147 | if top_of_stack.sp() != pari_mainstack.top: 148 | raise RuntimeError("cannot resize PARI stack here") 149 | 150 | 151 | cdef int set_pari_stack_size(size_t size, size_t sizemax) except -1: 152 | """ 153 | Safely set the PARI stack size 154 | """ 155 | before_resize() 156 | sig_on() 157 | paristack_setsize(size, sizemax) 158 | sig_off() 159 | after_resize() 160 | 161 | 162 | cdef void after_resize() noexcept: 163 | """ 164 | This must be called after reallocating the PARI stack 165 | """ 166 | top_of_stack.address = pari_mainstack.top 167 | 168 | 169 | cdef Gen new_gen(GEN x): 170 | """ 171 | Create a new ``Gen`` from a ``GEN``. Except if `x` is ``gnil``, then 172 | return ``None`` instead. 173 | 174 | Also call ``sig_off``() and clear the PARI stack. 175 | """ 176 | sig_off() 177 | if x is gnil: 178 | reset_avma() 179 | return None 180 | return new_gen_noclear(x) 181 | 182 | 183 | cdef new_gens2(GEN x, GEN y): 184 | """ 185 | Create a 2-tuple of new ``Gen``s from 2 ``GEN``s. 186 | 187 | Also call ``sig_off``() and clear the PARI stack. 188 | """ 189 | sig_off() 190 | global avma 191 | av = avma 192 | g1 = new_gen_noclear(x) 193 | # Restore avma in case that remove_from_pari_stack() was called 194 | avma = av 195 | g2 = new_gen_noclear(y) 196 | return (g1, g2) 197 | 198 | 199 | cdef Gen new_gen_noclear(GEN x): 200 | """ 201 | Create a new ``Gen`` from a ``GEN``. 202 | """ 203 | if not is_on_stack(x): 204 | reset_avma() 205 | if is_universal_constant(x): 206 | return Gen_new(x, NULL) 207 | elif isclone(x): 208 | gclone_refc(x) 209 | return Gen_new(x, x) 210 | raise SystemError("new_gen() argument not on PARI stack, not on PARI heap and not a universal constant") 211 | 212 | z = Gen_stack_new(x) 213 | 214 | # If we used over half of the PARI stack, move all Gens to the heap 215 | if (pari_mainstack.top - avma) >= pari_mainstack.size // 2: 216 | if sig_on_count == 0: 217 | try: 218 | move_gens_to_heap(-1) 219 | except MemoryError: 220 | pass 221 | 222 | return z 223 | 224 | 225 | cdef Gen clone_gen(GEN x): 226 | x = gclone(x) 227 | clear_stack() 228 | return Gen_new(x, x) 229 | 230 | 231 | cdef Gen clone_gen_noclear(GEN x): 232 | x = gclone(x) 233 | return Gen_new(x, x) 234 | 235 | 236 | @cython.no_gc 237 | cdef class DetachGen: 238 | """ 239 | Destroy a :class:`Gen` but keep the ``GEN`` which is inside it. 240 | 241 | The typical usage is as follows: 242 | 243 | 1. Creates the ``DetachGen`` object from a :class`Gen`. 244 | 245 | 2. Removes all other references to that :class:`Gen`. 246 | 247 | 3. Call the ``detach`` method to retrieve the ``GEN`` (or a copy of 248 | it if the original was not on the stack). 249 | """ 250 | def __init__(self, s): 251 | self.source = s 252 | 253 | cdef GEN detach(self) except NULL: 254 | src = self.source 255 | 256 | # Whatever happens, delete self.source 257 | self.source = None 258 | 259 | # Delete src safely, keeping it available as GEN 260 | cdef GEN res = src.g 261 | if is_on_stack(res): 262 | # Verify that we hold the only reference to src 263 | if _Py_REFCNT(src) != 1: 264 | raise SystemError("cannot detach a Gen which is still referenced") 265 | elif is_universal_constant(res): 266 | pass 267 | else: 268 | # Make a copy to the PARI stack 269 | res = gcopy(res) 270 | 271 | # delete src but do not change avma 272 | global avma 273 | cdef pari_sp av = avma 274 | avma = src.sp() # Avoid a warning when deallocating 275 | del src 276 | avma = av 277 | return res 278 | -------------------------------------------------------------------------------- /autogen/doc.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Handle PARI documentation 4 | """ 5 | 6 | from __future__ import unicode_literals 7 | 8 | import re 9 | import subprocess 10 | import sys 11 | 12 | leading_ws = re.compile("^( +)", re.MULTILINE) 13 | trailing_ws = re.compile("( +)$", re.MULTILINE) 14 | double_space = re.compile(" +") 15 | 16 | end_space = re.compile(r"(@\[end[a-z]*\])([A-Za-z])") 17 | end_paren = re.compile(r"(@\[end[a-z]*\])([(])") 18 | 19 | begin_verb = re.compile(r"@1") 20 | end_verb = re.compile(r"@[23] *@\[endcode\]") 21 | verb_loop = re.compile("^( .*)@\[[a-z]*\]", re.MULTILINE) 22 | 23 | dollars = re.compile(r"@\[dollar\]\s*(.*?)\s*@\[dollar\]", re.DOTALL) 24 | doubledollars = re.compile(r"@\[doubledollar\]\s*(.*?)\s*@\[doubledollar\] *", re.DOTALL) 25 | 26 | math_loop = re.compile(r"(@\[start[A-Z]*MATH\][^@]*)@\[[a-z]*\]") 27 | math_backslash = re.compile(r"(@\[start[A-Z]*MATH\][^@]*)=BACKSLASH=") 28 | 29 | prototype = re.compile("^[^\n]*\n\n") 30 | library_syntax = re.compile("The library syntax is.*", re.DOTALL) 31 | 32 | newlines = re.compile("\n\n\n\n*") 33 | 34 | bullet_loop = re.compile("(@BULLET( [^\n]*\n)*)([^ \n])") 35 | indent_math = re.compile("(@\\[startDISPLAYMATH\\].*\n(.+\n)*)(\\S)") 36 | 37 | escape_backslash = re.compile(r"^(\S.*)[\\]", re.MULTILINE) 38 | escape_mid = re.compile(r"^(\S.*)[|]", re.MULTILINE) 39 | escape_percent = re.compile(r"^(\S.*)[%]", re.MULTILINE) 40 | escape_hash = re.compile(r"^(\S.*)[#]", re.MULTILINE) 41 | 42 | label_define = re.compile(r"@\[label [a-zA-Z0-9:]*\]") 43 | label_ref = re.compile(r"(Section *)?@\[startref\](se:)?([^@]*)@\[endref\]") 44 | 45 | 46 | def sub_loop(regex, repl, text): 47 | """ 48 | In ``text``, substitute ``regex`` by ``repl`` recursively. As long 49 | as substitution is possible, ``regex`` is substituted. 50 | 51 | INPUT: 52 | 53 | - ``regex`` -- a compiled regular expression 54 | 55 | - ``repl`` -- replacement text 56 | 57 | - ``text`` -- input text 58 | 59 | OUTPUT: substituted text 60 | 61 | EXAMPLES: 62 | 63 | Ensure there a space between any 2 letters ``x``:: 64 | 65 | >>> from autogen.doc import sub_loop 66 | >>> import re 67 | >>> print(sub_loop(re.compile("xx"), "x x", "xxx_xx")) 68 | x x x_x x 69 | """ 70 | while True: 71 | text, n = regex.subn(repl, text) 72 | if not n: 73 | return text 74 | 75 | 76 | def raw_to_rest(doc): 77 | r""" 78 | Convert raw PARI documentation (with ``@``-codes) to reST syntax. 79 | 80 | INPUT: 81 | 82 | - ``doc`` -- the raw PARI documentation 83 | 84 | OUTPUT: a unicode string 85 | 86 | EXAMPLES:: 87 | 88 | >>> from autogen.doc import raw_to_rest 89 | >>> print(raw_to_rest(b"@[startbold]hello world@[endbold]")) 90 | :strong:`hello world` 91 | 92 | TESTS:: 93 | 94 | >>> raw_to_rest(b"@[invalid]") 95 | Traceback (most recent call last): 96 | ... 97 | SyntaxError: @ found: @[invalid] 98 | 99 | >>> s = b'@3@[startbold]*@[endbold] snip @[dollar]0@[dollar]\ndividing @[dollar]#E@[dollar].' 100 | >>> print(raw_to_rest(s)) 101 | - snip :math:`0` 102 | dividing :math:`\#E`. 103 | """ 104 | doc = doc.decode("utf-8") 105 | 106 | # Work around a specific problem with doc of "component" 107 | doc = doc.replace("[@[dollar]@[dollar]]", "[]") 108 | 109 | # Work around a specific problem with doc of "algdivl" 110 | doc = doc.replace(r"\y@", r"\backslash y@") 111 | 112 | # Special characters 113 | doc = doc.replace("@[lt]", "<") 114 | doc = doc.replace("@[gt]", ">") 115 | doc = doc.replace("@[pm]", "±") 116 | doc = doc.replace("@[nbrk]", "\xa0") 117 | doc = doc.replace("@[agrave]", "à") 118 | doc = doc.replace("@[aacute]", "á") 119 | doc = doc.replace("@[eacute]", "é") 120 | doc = doc.replace("@[ouml]", "ö") 121 | doc = doc.replace("@[uuml]", "ü") 122 | doc = doc.replace("\\'{a}", "á") 123 | 124 | # Remove leading and trailing whitespace from every line 125 | doc = leading_ws.sub("", doc) 126 | doc = trailing_ws.sub("", doc) 127 | 128 | # Remove multiple spaces 129 | doc = double_space.sub(" ", doc) 130 | 131 | # Sphinx dislikes inline markup immediately followed by a letter: 132 | # insert a non-breaking space 133 | doc = end_space.sub("\\1\xa0\\2", doc) 134 | 135 | # Similarly, for inline markup immediately followed by an open 136 | # parenthesis, insert a space 137 | doc = end_paren.sub("\\1 \\2", doc) 138 | 139 | # Fix labels and references 140 | doc = label_define.sub("", doc) 141 | doc = label_ref.sub("``\\3`` (in the PARI manual)", doc) 142 | 143 | # Bullet items 144 | doc = doc.replace("@3@[startbold]*@[endbold] ", "@BULLET ") 145 | doc = sub_loop(bullet_loop, "\\1 \\3", doc) 146 | doc = doc.replace("@BULLET ", "- ") 147 | 148 | # Add =VOID= in front of all leading whitespace (which was 149 | # intentionally added) to avoid confusion with verbatim blocks. 150 | doc = leading_ws.sub(r"=VOID=\1", doc) 151 | 152 | # Verbatim blocks 153 | doc = begin_verb.sub("::\n\n@0", doc) 154 | doc = end_verb.sub("", doc) 155 | doc = doc.replace("@0", " ") 156 | doc = doc.replace("@3", "") 157 | 158 | # Remove all further markup from within verbatim blocks 159 | doc = sub_loop(verb_loop, "\\1", doc) 160 | 161 | # Pair dollars -> beginmath/endmath 162 | doc = doc.replace("@[dollar]@[dollar]", "@[doubledollar]") 163 | doc = dollars.sub(r"@[startMATH]\1@[endMATH]", doc) 164 | doc = doubledollars.sub(r"@[startDISPLAYMATH]\1@[endDISPLAYMATH]", doc) 165 | 166 | # Replace special characters (except in verbatim blocks) 167 | # \ -> =BACKSLASH= 168 | # | -> =MID= 169 | # % -> =PERCENT= 170 | # # -> =HASH= 171 | doc = sub_loop(escape_backslash, "\\1=BACKSLASH=", doc) 172 | doc = sub_loop(escape_mid, "\\1=MID=", doc) 173 | doc = sub_loop(escape_percent, "\\1=PERCENT=", doc) 174 | doc = sub_loop(escape_hash, "\\1=HASH=", doc) 175 | 176 | # Math markup 177 | doc = doc.replace("@[obr]", "{") 178 | doc = doc.replace("@[cbr]", "}") 179 | doc = doc.replace("@[startword]", "\\") 180 | doc = doc.replace("@[endword]", "") 181 | # (special rules for Hom and Frob, see trac ticket 21005) 182 | doc = doc.replace("@[startlword]Hom@[endlword]", "\\text{Hom}") 183 | doc = doc.replace("@[startlword]Frob@[endlword]", "\\text{Frob}") 184 | doc = doc.replace("@[startlword]", "\\") 185 | doc = doc.replace("@[endlword]", "") 186 | doc = doc.replace("@[startbi]", "\\mathbb{") 187 | doc = doc.replace("@[endbi]", "}") 188 | 189 | # PARI TeX macros 190 | doc = doc.replace(r"\Cl", r"\mathrm{Cl}") 191 | doc = doc.replace(r"\Id", r"\mathrm{Id}") 192 | doc = doc.replace(r"\Norm", r"\mathrm{Norm}") 193 | doc = doc.replace(r"\disc", r"\mathrm{disc}") 194 | doc = doc.replace(r"\gcd", r"\mathrm{gcd}") 195 | doc = doc.replace(r"\lcm", r"\mathrm{lcm}") 196 | 197 | # Remove extra markup inside math blocks 198 | doc = sub_loop(math_loop, "\\1", doc) 199 | 200 | # Replace special characters by escape sequences 201 | # Note that =BACKSLASH= becomes an unescaped backslash in math mode 202 | # but an escaped backslash otherwise. 203 | doc = sub_loop(math_backslash, r"\1\\", doc) 204 | doc = doc.replace("=BACKSLASH=", r"\\") 205 | doc = doc.replace("=MID=", r"\|") 206 | doc = doc.replace("=PERCENT=", r"\%") 207 | doc = doc.replace("=HASH=", r"\#") 208 | doc = doc.replace("=VOID=", "") 209 | 210 | # Handle DISPLAYMATH 211 | doc = doc.replace("@[endDISPLAYMATH]", "\n\n") 212 | doc = sub_loop(indent_math, "\\1 \\3", doc) 213 | doc = doc.replace("@[startDISPLAYMATH]", "\n\n.. MATH::\n\n ") 214 | 215 | # Inline markup. We do use the more verbose :foo:`text` style since 216 | # those nest more easily. 217 | doc = doc.replace("@[startMATH]", ":math:`") 218 | doc = doc.replace("@[endMATH]", "`") 219 | doc = doc.replace("@[startpodcode]", "``") 220 | doc = doc.replace("@[endpodcode]", "``") 221 | doc = doc.replace("@[startcode]", ":literal:`") 222 | doc = doc.replace("@[endcode]", "`") 223 | doc = doc.replace("@[startit]", ":emphasis:`") 224 | doc = doc.replace("@[endit]", "`") 225 | doc = doc.replace("@[startbold]", ":strong:`") 226 | doc = doc.replace("@[endbold]", "`") 227 | 228 | # Remove prototype 229 | doc = prototype.sub("", doc) 230 | 231 | # Remove everything starting with "The library syntax is" 232 | # (this is not relevant for Python) 233 | doc = library_syntax.sub("", doc) 234 | 235 | # Allow at most 2 consecutive newlines 236 | doc = newlines.sub("\n\n", doc) 237 | 238 | # Strip result 239 | doc = doc.strip() 240 | 241 | # Ensure no more @ remains 242 | try: 243 | i = doc.index("@") 244 | except ValueError: 245 | return doc 246 | ilow = max(0, i-30) 247 | ihigh = min(len(doc), i+30) 248 | raise SyntaxError("@ found: " + doc[ilow:ihigh]) 249 | 250 | 251 | def get_raw_doc(function): 252 | r""" 253 | Get the raw documentation of PARI function ``function``. 254 | 255 | INPUT: 256 | 257 | - ``function`` -- name of a PARI function 258 | 259 | EXAMPLES:: 260 | 261 | >>> from autogen.doc import get_raw_doc 262 | >>> print(get_raw_doc("cos").decode()) 263 | @[startbold]cos@[dollar](x)@[dollar]:@[endbold] 264 | 265 | @[label se:cos] 266 | Cosine of @[dollar]x@[dollar]. 267 | ... 268 | >>> get_raw_doc("abcde") 269 | Traceback (most recent call last): 270 | ... 271 | RuntimeError: no help found for 'abcde' 272 | """ 273 | if sys.platform.startswith("win"): 274 | return b"" 275 | 276 | doc = subprocess.check_output(["gphelp", "-raw", function]) 277 | if doc.endswith(b"""' not found !\n"""): 278 | raise RuntimeError("no help found for '{}'".format(function)) 279 | return doc 280 | 281 | 282 | def get_rest_doc(function): 283 | r""" 284 | Get the documentation of the PARI function ``function`` in reST 285 | syntax. 286 | 287 | INPUT: 288 | 289 | - ``function`` -- name of a PARI function 290 | 291 | EXAMPLES:: 292 | 293 | >>> from autogen.doc import get_rest_doc 294 | >>> print(get_rest_doc("teichmuller")) 295 | Teichmüller character of the :math:`p`-adic number :math:`x`, i.e. the unique 296 | :math:`(p-1)`-th root of unity congruent to :math:`x / p^{v_...(x)}` modulo :math:`p`... 297 | 298 | :: 299 | 300 | >>> print(get_rest_doc("weber")) 301 | One of Weber's three :math:`f` functions. 302 | If :math:`flag = 0`, returns 303 | 304 | .. MATH:: 305 | 306 | f(x) = \exp (-i\pi/24).\eta ((x+1)/2)/\eta (x) {such that} 307 | j = (f^{24}-16)^.../f^{24}, 308 | 309 | where :math:`j` is the elliptic :math:`j`-invariant (see the function :literal:`ellj`). 310 | If :math:`flag = 1`, returns 311 | 312 | .. MATH:: 313 | 314 | f_...(x) = \eta (x/2)/\eta (x) {such that} 315 | j = (f_...^{24}+16)^.../f_...^{24}. 316 | 317 | Finally, if :math:`flag = 2`, returns 318 | 319 | .. MATH:: 320 | 321 | f_...(x) = \sqrt{2}\eta (2x)/\eta (x) {such that} 322 | j = (f_...^{24}+16)^.../f_...^{24}. 323 | 324 | Note the identities :math:`f^... = f_...^...+f_...^...` and :math:`ff_...f_... = \sqrt2`. 325 | 326 | 327 | :: 328 | 329 | >>> doc = get_rest_doc("ellap") # doc depends on PARI version 330 | 331 | :: 332 | 333 | >>> print(get_rest_doc("bitor")) 334 | bitwise (inclusive) 335 | :literal:`or` of two integers :math:`x` and :math:`y`, that is the integer 336 | 337 | .. MATH:: 338 | 339 | \sum 340 | (x_... or y_...) 2^... 341 | 342 | See ``bitand`` (in the PARI manual) for the behavior for negative arguments. 343 | """ 344 | raw = get_raw_doc(function) 345 | return raw_to_rest(raw) 346 | -------------------------------------------------------------------------------- /autogen/args.py: -------------------------------------------------------------------------------- 1 | """ 2 | Arguments for PARI calls 3 | """ 4 | 5 | #***************************************************************************** 6 | # Copyright (C) 2015 Jeroen Demeyer 7 | # 8 | # This program is free software: you can redistribute it and/or modify 9 | # it under the terms of the GNU General Public License as published by 10 | # the Free Software Foundation, either version 2 of the License, or 11 | # (at your option) any later version. 12 | # https://www.gnu.org/licenses/ 13 | #***************************************************************************** 14 | 15 | from __future__ import unicode_literals 16 | 17 | # Some replacements for reserved words 18 | replacements = {'char': 'character', 'return': 'return_value'} 19 | 20 | class PariArgument(object): 21 | """ 22 | This class represents one argument in a PARI call. 23 | """ 24 | def __init__(self, namesiter, default, index): 25 | """ 26 | Create a new argument for a PARI call. 27 | 28 | INPUT: 29 | 30 | - ``namesiter`` -- iterator over all names of the arguments. 31 | Usually, the next name from this iterator is used as argument 32 | name. 33 | 34 | - ``default`` -- default value for this argument (``None`` 35 | means that the argument is not optional). 36 | 37 | - ``index`` -- (integer >= 0). Index of this argument in the 38 | list of arguments. Index 0 means a ``"self"`` argument which 39 | is treated specially. For a function which is not a method, 40 | start counting at 1. 41 | """ 42 | self.index = index 43 | try: 44 | self.name = self.get_argument_name(namesiter) 45 | except StopIteration: 46 | # No more names available, use something default. 47 | # This is used in listcreate() and polsturm() for example 48 | # which have deprecated arguments which are not listed in 49 | # the help. 50 | self.name = "_arg%s" % index 51 | self.undocumented = True 52 | else: 53 | self.undocumented = False 54 | 55 | if self.index == 0: # "self" argument can never have a default 56 | self.default = None 57 | elif default is None: 58 | self.default = self.always_default() 59 | elif default == "": 60 | self.default = self.default_default() 61 | else: 62 | self.default = default 63 | 64 | # Name for a temporary variable. Only a few classes actually use this. 65 | self.tmpname = "_" + self.name 66 | 67 | def __repr__(self): 68 | s = self._typerepr() + " " + self.name 69 | if self.default is not None: 70 | s += "=" + self.default 71 | return s 72 | 73 | def _typerepr(self): 74 | """ 75 | Return a string representing the type of this argument. 76 | """ 77 | return "(generic)" 78 | 79 | def ctype(self): 80 | """ 81 | The corresponding C type. This is used for auto-generating 82 | the declarations of the C function. In some cases, this is also 83 | used for passing the argument from Python to Cython. 84 | """ 85 | raise NotImplementedError 86 | 87 | def always_default(self): 88 | """ 89 | If this returns not ``None``, it is a value which is always 90 | the default for this argument, which is then automatically 91 | optional. 92 | """ 93 | return None 94 | 95 | def default_default(self): 96 | """ 97 | The default value for an optional argument if no other default 98 | was specified in the prototype. 99 | """ 100 | return "NULL" 101 | 102 | def get_argument_name(self, namesiter): 103 | """ 104 | Return the name for this argument, given ``namesiter`` which is 105 | an iterator over the argument names given by the help string. 106 | """ 107 | n = next(namesiter) 108 | try: 109 | return replacements[n] 110 | except KeyError: 111 | return n 112 | 113 | def prototype_code(self): 114 | """ 115 | Return code to appear in the prototype of the Cython wrapper. 116 | """ 117 | raise NotImplementedError 118 | 119 | def deprecation_warning_code(self, function): 120 | """ 121 | Return code to appear in the function body to give a 122 | deprecation warning for this argument, if applicable. 123 | ``function`` is the function name to appear in the message. 124 | """ 125 | if not self.undocumented: 126 | return "" 127 | s = " if {name} is not None:\n" 128 | s += " from warnings import warn\n" 129 | s += " warn('argument {index} of the PARI/GP function {function} is undocumented and deprecated', DeprecationWarning)\n" 130 | return s.format(name=self.name, index=self.index, function=function) 131 | 132 | def convert_code(self): 133 | """ 134 | Return code to appear in the function body to convert this 135 | argument to something that PARI understand. This code can also 136 | contain extra checks. It will run outside of ``sig_on()``. 137 | """ 138 | return "" 139 | 140 | def c_convert_code(self): 141 | """ 142 | Return additional conversion code which will be run after 143 | ``convert_code`` and inside the ``sig_on()`` block. This must 144 | not involve any Python code (in particular, it should not raise 145 | exceptions). 146 | """ 147 | return "" 148 | 149 | def call_code(self): 150 | """ 151 | Return code to put this argument in a PARI function call. 152 | """ 153 | return self.name 154 | 155 | 156 | class PariArgumentObject(PariArgument): 157 | """ 158 | Class for arguments which are passed as generic Python ``object``. 159 | """ 160 | def prototype_code(self): 161 | """ 162 | Return code to appear in the prototype of the Cython wrapper. 163 | """ 164 | s = self.name 165 | if self.default is not None: 166 | # Default corresponds to None, actual default value should 167 | # be handled in convert_code() 168 | s += "=None" 169 | return s 170 | 171 | class PariArgumentClass(PariArgument): 172 | """ 173 | Class for arguments which are passed as a specific C/Cython class. 174 | 175 | The C/Cython type is given by ``self.ctype()``. 176 | """ 177 | def prototype_code(self): 178 | """ 179 | Return code to appear in the prototype of the Cython wrapper. 180 | """ 181 | s = self.ctype() + " " + self.name 182 | if self.default is not None: 183 | s += "=" + self.default 184 | return s 185 | 186 | 187 | class PariInstanceArgument(PariArgumentObject): 188 | """ 189 | ``self`` argument for ``Pari`` object. 190 | 191 | This argument is never actually used. 192 | """ 193 | def __init__(self): 194 | PariArgument.__init__(self, iter(["self"]), None, 0) 195 | def _typerepr(self): 196 | return "Pari" 197 | def ctype(self): 198 | return "GEN" 199 | 200 | 201 | class PariArgumentGEN(PariArgumentObject): 202 | def _typerepr(self): 203 | return "GEN" 204 | def ctype(self): 205 | return "GEN" 206 | def convert_code(self): 207 | """ 208 | Conversion to Gen 209 | """ 210 | if self.index == 0: 211 | # self argument 212 | s = "" 213 | elif self.default is None: 214 | s = " {name} = objtogen({name})\n" 215 | elif self.default is False: 216 | # This is actually a required argument 217 | # See parse_prototype() in parser.py why we need this 218 | s = " if {name} is None:\n" 219 | s += " raise TypeError(\"missing required argument: '{name}'\")\n" 220 | s += " {name} = objtogen({name})\n" 221 | else: 222 | s = " cdef bint _have_{name} = ({name} is not None)\n" 223 | s += " if _have_{name}:\n" 224 | s += " {name} = objtogen({name})\n" 225 | return s.format(name=self.name) 226 | def c_convert_code(self): 227 | """ 228 | Conversion Gen -> GEN 229 | """ 230 | if not self.default: 231 | # required argument 232 | s = " cdef GEN {tmp} = ({name}).g\n" 233 | elif self.default == "NULL": 234 | s = " cdef GEN {tmp} = NULL\n" 235 | s += " if _have_{name}:\n" 236 | s += " {tmp} = ({name}).g\n" 237 | elif self.default == "0": 238 | s = " cdef GEN {tmp} = gen_0\n" 239 | s += " if _have_{name}:\n" 240 | s += " {tmp} = ({name}).g\n" 241 | else: 242 | raise ValueError("default value %r for GEN argument %r is not supported" % (self.default, self.name)) 243 | return s.format(name=self.name, tmp=self.tmpname) 244 | def call_code(self): 245 | return self.tmpname 246 | 247 | class PariArgumentString(PariArgumentObject): 248 | def _typerepr(self): 249 | return "str" 250 | def ctype(self): 251 | return "char *" 252 | def convert_code(self): 253 | if self.default is None: 254 | s = " {name} = to_bytes({name})\n" 255 | s += " cdef char* {tmp} = {name}\n" 256 | else: 257 | s = " cdef char* {tmp}\n" 258 | s += " if {name} is None:\n" 259 | s += " {tmp} = {default}\n" 260 | s += " else:\n" 261 | s += " {name} = to_bytes({name})\n" 262 | s += " {tmp} = {name}\n" 263 | return s.format(name=self.name, tmp=self.tmpname, default=self.default) 264 | def call_code(self): 265 | return self.tmpname 266 | 267 | class PariArgumentVariable(PariArgumentObject): 268 | def _typerepr(self): 269 | return "var" 270 | def ctype(self): 271 | return "long" 272 | def default_default(self): 273 | return "-1" 274 | def convert_code(self): 275 | if self.default is None: 276 | s = " cdef long {tmp} = get_var({name})\n" 277 | else: 278 | s = " cdef long {tmp} = {default}\n" 279 | s += " if {name} is not None:\n" 280 | s += " {tmp} = get_var({name})\n" 281 | return s.format(name=self.name, tmp=self.tmpname, default=self.default) 282 | def call_code(self): 283 | return self.tmpname 284 | 285 | class PariArgumentLong(PariArgumentClass): 286 | def _typerepr(self): 287 | return "long" 288 | def ctype(self): 289 | return "long" 290 | def default_default(self): 291 | return "0" 292 | 293 | class PariArgumentULong(PariArgumentClass): 294 | def _typerepr(self): 295 | return "unsigned long" 296 | def ctype(self): 297 | return "unsigned long" 298 | def default_default(self): 299 | return "0" 300 | 301 | class PariArgumentPrec(PariArgumentClass): 302 | def _typerepr(self): 303 | return "prec" 304 | def ctype(self): 305 | return "long" 306 | def always_default(self): 307 | return "DEFAULT_BITPREC" 308 | def get_argument_name(self, namesiter): 309 | return "precision" 310 | def c_convert_code(self): 311 | s = " {name} = nbits2prec({name})\n" 312 | return s.format(name=self.name) 313 | 314 | class PariArgumentBitprec(PariArgumentClass): 315 | def _typerepr(self): 316 | return "bitprec" 317 | def ctype(self): 318 | return "long" 319 | def always_default(self): 320 | return "DEFAULT_BITPREC" 321 | def get_argument_name(self, namesiter): 322 | return "precision" 323 | 324 | class PariArgumentSeriesPrec(PariArgumentClass): 325 | def _typerepr(self): 326 | return "serprec" 327 | def ctype(self): 328 | return "long" 329 | def default_default(self): 330 | return "-1" 331 | def get_argument_name(self, namesiter): 332 | return "serprec" 333 | def c_convert_code(self): 334 | s = " if {name} < 0:\n" 335 | s += " {name} = precdl # Global PARI series precision\n" 336 | return s.format(name=self.name) 337 | 338 | class PariArgumentGENPointer(PariArgumentObject): 339 | default = "NULL" 340 | def _typerepr(self): 341 | return "GEN*" 342 | def ctype(self): 343 | return "GEN*" 344 | def convert_code(self): 345 | """ 346 | Conversion to NULL or Gen 347 | """ 348 | s = " cdef bint _have_{name} = ({name} is not None)\n" 349 | s += " if _have_{name}:\n" 350 | s += " raise NotImplementedError(\"optional argument {name} not available\")\n" 351 | return s.format(name=self.name) 352 | def c_convert_code(self): 353 | """ 354 | Conversion Gen -> GEN 355 | """ 356 | s = " cdef GEN * {tmp} = NULL\n" 357 | return s.format(name=self.name, tmp=self.tmpname) 358 | def call_code(self): 359 | return self.tmpname 360 | 361 | 362 | pari_arg_types = { 363 | 'G': PariArgumentGEN, 364 | 'W': PariArgumentGEN, 365 | 'r': PariArgumentString, 366 | 's': PariArgumentString, 367 | 'L': PariArgumentLong, 368 | 'U': PariArgumentULong, 369 | 'n': PariArgumentVariable, 370 | 'p': PariArgumentPrec, 371 | 'b': PariArgumentBitprec, 372 | 'P': PariArgumentSeriesPrec, 373 | '&': PariArgumentGENPointer, 374 | 375 | # Codes which are known but not actually supported yet 376 | 'V': None, 377 | 'I': None, 378 | 'E': None, 379 | 'J': None, 380 | 'C': None, 381 | '*': None, 382 | '=': None} 383 | -------------------------------------------------------------------------------- /autogen/generator.py: -------------------------------------------------------------------------------- 1 | """ 2 | Auto-generate methods for PARI functions. 3 | """ 4 | 5 | # **************************************************************************** 6 | # Copyright (C) 2015 Jeroen Demeyer 7 | # 2017 Vincent Delecroix 8 | # 9 | # This program is free software: you can redistribute it and/or modify 10 | # it under the terms of the GNU General Public License as published by 11 | # the Free Software Foundation, either version 2 of the License, or 12 | # (at your option) any later version. 13 | # https://www.gnu.org/licenses/ 14 | # **************************************************************************** 15 | 16 | from __future__ import absolute_import, print_function, unicode_literals 17 | 18 | import os 19 | import re 20 | import sys 21 | from pathlib import Path 22 | 23 | from .args import PariArgumentGEN, PariInstanceArgument 24 | from .doc import get_rest_doc 25 | from .parser import parse_prototype, read_pari_desc 26 | 27 | autogen_top = "# This file is auto-generated by {}\n".format( 28 | os.path.relpath(__file__)) 29 | 30 | gen_banner = autogen_top + ''' 31 | cdef class Gen_base: 32 | """ 33 | Wrapper for a PARI ``GEN`` containing auto-generated methods. 34 | 35 | This class does not manage the ``GEN`` inside in any way. It is just 36 | a dumb wrapper. In particular, it might be invalid if the ``GEN`` is 37 | on the PARI stack and the PARI stack has been garbage collected. 38 | 39 | You almost certainly want to use one of the derived class 40 | :class:`Gen` instead. That being said, :class:`Gen_base` can be 41 | used by itself to pass around a temporary ``GEN`` within Python 42 | where we cannot use C calls. 43 | """ 44 | ''' 45 | 46 | instance_banner = autogen_top + ''' 47 | cdef class Pari_auto: 48 | """ 49 | Part of the :class:`Pari` class containing auto-generated functions. 50 | 51 | You must never use this class directly (in fact, Python may crash 52 | if you do), use the derived class :class:`Pari` instead. 53 | """ 54 | ''' 55 | 56 | decl_banner = autogen_top + ''' 57 | from .types cimport * 58 | 59 | cdef extern from *: 60 | ''' 61 | 62 | 63 | function_re = re.compile(r"^[A-Za-z][A-Za-z0-9_]*$") 64 | function_blacklist = {"O", # O(p^e) needs special parser support 65 | "alias", # Not needed and difficult documentation 66 | "listcreate", # "redundant and obsolete" according to PARI 67 | "print", # Conflicts with Python builtin 68 | "allocatemem", # Dangerous; overridden in PariInstance 69 | "global", # Invalid in Python (and obsolete) 70 | "inline", # Total confusion 71 | "uninline", # idem 72 | "local", # idem 73 | "my", # idem 74 | } 75 | 76 | 77 | class PariFunctionGenerator(object): 78 | """ 79 | Class to auto-generate ``auto_gen.pxi`` and ``auto_instance.pxi``. 80 | 81 | The PARI file ``pari.desc`` is read and all suitable PARI functions 82 | are written as methods of either :class:`Gen` or 83 | :class:`Pari`. 84 | """ 85 | def __init__(self, pari_datadir: Path, output_dir: Path): 86 | self.gen_filename = output_dir / "auto_gen.pxi" 87 | self.instance_filename = output_dir / "auto_instance.pxi" 88 | self.decl_filename = output_dir / "auto_paridecl.pxd" 89 | self.pari_datadir = pari_datadir 90 | 91 | def can_handle_function(self, function, cname="", **kwds): 92 | """ 93 | Can we actually handle this function? 94 | 95 | EXAMPLES:: 96 | 97 | >>> from autogen.generator import PariFunctionGenerator 98 | >>> from pathlib import Path 99 | >>> G = PariFunctionGenerator(Path("test"), Path("dummy")) 100 | >>> G.can_handle_function("bnfinit", "bnfinit0", **{"class":"basic"}) 101 | True 102 | >>> G.can_handle_function("_bnfinit", "bnfinit0", **{"class":"basic"}) 103 | False 104 | >>> G.can_handle_function("bnfinit", "bnfinit0", **{"class":"hard"}) 105 | False 106 | """ 107 | if not cname: 108 | # No corresponding C function => must be specific to GP or GP2C 109 | return False 110 | if function in function_blacklist: 111 | # Blacklist specific troublesome functions 112 | return False 113 | if not function_re.match(function): 114 | # Not a legal function name, like "!_" 115 | return False 116 | cls = kwds.get("class", "unknown") 117 | sec = kwds.get("section", "unknown") 118 | if cls != "basic": 119 | # Different class: probably something technical or 120 | # specific to gp or gp2c 121 | return False 122 | if sec == "programming/control": 123 | # Skip if, return, break, ... 124 | return False 125 | return True 126 | 127 | def handle_pari_function(self, function, cname, prototype="", help="", 128 | obsolete=None, **kwds): 129 | r""" 130 | Handle one PARI function: decide whether or not to add the 131 | function, in which file (as method of :class:`Gen` or 132 | of :class:`Pari`?) and call :meth:`write_method` to 133 | actually write the code. 134 | 135 | EXAMPLES:: 136 | 137 | >>> from autogen.parser import read_pari_desc 138 | >>> from autogen.generator import PariFunctionGenerator 139 | >>> from pathlib import Path 140 | >>> G = PariFunctionGenerator(Path("test"), Path("dummy")) 141 | >>> G.gen_file = sys.stdout 142 | >>> G.instance_file = sys.stdout 143 | >>> G.decl_file = sys.stdout 144 | >>> G.handle_pari_function("bnfinit", 145 | ... cname="bnfinit0", prototype="GD0,L,DGp", 146 | ... help=r"bnfinit(P,{flag=0},{tech=[]}): compute...", 147 | ... **{"class":"basic", "section":"number_fields"}) 148 | GEN bnfinit0(GEN, long, GEN, long) 149 | def bnfinit(P, long flag=0, tech=None, long precision=DEFAULT_BITPREC): 150 | ... 151 | cdef bint _have_tech = (tech is not None) 152 | if _have_tech: 153 | tech = objtogen(tech) 154 | sig_on() 155 | cdef GEN _P = (P).g 156 | cdef GEN _tech = NULL 157 | if _have_tech: 158 | _tech = (tech).g 159 | precision = nbits2prec(precision) 160 | cdef GEN _ret = bnfinit0(_P, flag, _tech, precision) 161 | return new_gen(_ret) 162 | 163 | ... 164 | >>> G.handle_pari_function("ellmodulareqn", 165 | ... cname="ellmodulareqn", prototype="LDnDn", 166 | ... help=r"ellmodulareqn(N,{x},{y}): return...", 167 | ... **{"class":"basic", "section":"elliptic_curves"}) 168 | GEN ellmodulareqn(long, long, long) 169 | def ellmodulareqn(self, long N, x=None, y=None): 170 | ... 171 | cdef long _x = -1 172 | if x is not None: 173 | _x = get_var(x) 174 | cdef long _y = -1 175 | if y is not None: 176 | _y = get_var(y) 177 | sig_on() 178 | cdef GEN _ret = ellmodulareqn(N, _x, _y) 179 | return new_gen(_ret) 180 | 181 | >>> G.handle_pari_function("setrand", 182 | ... cname="setrand", prototype="vG", 183 | ... help=r"setrand(n): reset the seed...", 184 | ... doc=r"reseeds the random number generator...", 185 | ... **{"class":"basic", "section":"programming/specific"}) 186 | void setrand(GEN) 187 | def setrand(n): 188 | r''' 189 | Reseeds the random number generator... 190 | ''' 191 | sig_on() 192 | cdef GEN _n = (n).g 193 | setrand(_n) 194 | clear_stack() 195 | 196 | def setrand(self, n): 197 | r''' 198 | Reseeds the random number generator... 199 | ''' 200 | n = objtogen(n) 201 | sig_on() 202 | cdef GEN _n = (n).g 203 | setrand(_n) 204 | clear_stack() 205 | 206 | >>> G.handle_pari_function("polredord", 207 | ... cname="polredord", prototype="G", 208 | ... help="polredord(x): this function is obsolete, use polredbest.", 209 | ... obsolete="2008-07-20", 210 | ... **{"class":"basic", "section":"number_fields"}) 211 | GEN polredord(GEN) 212 | def polredord(x): 213 | r''' 214 | This function is obsolete, use polredbest. 215 | ''' 216 | from warnings import warn 217 | warn('the PARI/GP function polredord is obsolete (2008-07-20)', DeprecationWarning) 218 | sig_on() 219 | cdef GEN _x = (x).g 220 | cdef GEN _ret = polredord(_x) 221 | return new_gen(_ret) 222 | 223 | def polredord(self, x): 224 | r''' 225 | This function is obsolete, use polredbest. 226 | ''' 227 | from warnings import warn 228 | warn('the PARI/GP function polredord is obsolete (2008-07-20)', DeprecationWarning) 229 | x = objtogen(x) 230 | sig_on() 231 | cdef GEN _x = (x).g 232 | cdef GEN _ret = polredord(_x) 233 | return new_gen(_ret) 234 | 235 | """ 236 | try: 237 | args, ret = parse_prototype(prototype, help) 238 | except NotImplementedError: 239 | return # Skip unsupported prototype codes 240 | 241 | doc = get_rest_doc(function) 242 | 243 | self.write_declaration(cname, args, ret, self.decl_file) 244 | 245 | if len(args) > 0 and isinstance(args[0], PariArgumentGEN): 246 | # If the first argument is a GEN, write a method of the 247 | # Gen class. 248 | self.write_method(function, cname, args, ret, args, 249 | self.gen_file, doc, obsolete) 250 | 251 | # In any case, write a method of the Pari class. 252 | # Parse again with an extra "self" argument. 253 | args, ret = parse_prototype(prototype, help, [PariInstanceArgument()]) 254 | self.write_method(function, cname, args, ret, args[1:], 255 | self.instance_file, doc, obsolete) 256 | 257 | def write_declaration(self, cname, args, ret, file): 258 | """ 259 | Write a .pxd declaration of a PARI library function. 260 | 261 | INPUT: 262 | 263 | - ``cname`` -- name of the PARI C library call 264 | 265 | - ``args``, ``ret`` -- output from ``parse_prototype`` 266 | 267 | - ``file`` -- a file object where the declaration should be 268 | written to 269 | """ 270 | args = ", ".join(a.ctype() for a in args) 271 | s = ' {ret} {function}({args})'.format(ret=ret.ctype(), 272 | function=cname, args=args) 273 | print(s, file=file) 274 | 275 | def write_method(self, function, cname, args, ret, cargs, 276 | file, doc, obsolete): 277 | """ 278 | Write Cython code with a method to call one PARI function. 279 | 280 | INPUT: 281 | 282 | - ``function`` -- name for the method 283 | 284 | - ``cname`` -- name of the PARI C library call 285 | 286 | - ``args``, ``ret`` -- output from ``parse_prototype``, 287 | including the initial args like ``self`` 288 | 289 | - ``cargs`` -- like ``args`` but excluding the initial args 290 | 291 | - ``file`` -- a file object where the code should be written to 292 | 293 | - ``doc`` -- the docstring for the method 294 | 295 | - ``obsolete`` -- if ``True``, a deprecation warning will be 296 | given whenever this method is called 297 | """ 298 | doc = "\n".join(" " + line.strip() if line else line 299 | for line in doc.splitlines()) 300 | # Indent doc, no trailing whitespaces 301 | 302 | protoargs = ", ".join(a.prototype_code() for a in args) 303 | callargs = ", ".join(a.call_code() for a in cargs) 304 | 305 | s = " def {function}({protoargs}):\n" 306 | if doc: 307 | # Use triple single quotes to make it easier to doctest 308 | # this within triply double quoted docstrings. 309 | s += " r'''\n{doc}\n '''\n" 310 | # Warning for obsolete functions 311 | if obsolete: 312 | s += " from warnings import warn\n" 313 | s += " warn('the PARI/GP function {function} is obsolete ({obsolete})', DeprecationWarning)\n" 314 | # Warning for undocumented arguments 315 | for a in args: 316 | s += a.deprecation_warning_code(function) 317 | for a in args: 318 | s += a.convert_code() 319 | s += " sig_on()\n" 320 | for a in args: 321 | s += a.c_convert_code() 322 | s += ret.assign_code("{cname}({callargs})") 323 | s += ret.return_code() 324 | 325 | s = s.format(function=function, protoargs=protoargs, cname=cname, 326 | callargs=callargs, doc=doc, obsolete=obsolete) 327 | print(s, file=file) 328 | 329 | def __call__(self): 330 | """ 331 | Top-level function to generate the auto-generated files. 332 | """ 333 | gen_file_tmp = self.gen_filename.with_suffix('.tmp') 334 | instance_file_tmp = self.instance_filename.with_suffix('.tmp') 335 | decl_file_tmp = self.decl_filename.with_suffix('.tmp') 336 | 337 | D = read_pari_desc(self.pari_datadir) 338 | D = sorted(D.values(), key=lambda d: d['function']) 339 | sys.stdout.write("Generating PARI functions:") 340 | 341 | self.gen_file = gen_file_tmp.open('w', encoding='utf-8') 342 | self.gen_file.write(gen_banner) 343 | self.instance_file = instance_file_tmp.open('w', encoding='utf-8') 344 | self.instance_file.write(instance_banner) 345 | self.decl_file = decl_file_tmp.open('w', encoding='utf-8') 346 | self.decl_file.write(decl_banner) 347 | 348 | # Check for availability of hi-res SVG plotting. This requires 349 | # PARI-2.10 or later. 350 | have_plot_svg = False 351 | 352 | for v in D: 353 | func = v["function"] 354 | if self.can_handle_function(**v): 355 | sys.stdout.write(" %s" % func) 356 | sys.stdout.flush() 357 | self.handle_pari_function(**v) 358 | if func == "plothraw": 359 | have_plot_svg = True 360 | else: 361 | sys.stdout.write(" (%s)" % func) 362 | sys.stdout.write("\n") 363 | 364 | self.instance_file.write(f"DEF HAVE_PLOT_SVG = {have_plot_svg}") 365 | 366 | self.gen_file.close() 367 | self.instance_file.close() 368 | self.decl_file.close() 369 | 370 | # All done? Let's commit. 371 | gen_file_tmp.replace(self.gen_filename) 372 | instance_file_tmp.replace(self.instance_filename) 373 | decl_file_tmp.replace(self.decl_filename) 374 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /cypari2/convert.pyx: -------------------------------------------------------------------------------- 1 | # cython: cdivision = True 2 | """ 3 | Convert PARI objects to/from Python/C native types 4 | ************************************************** 5 | 6 | This modules contains the following conversion routines: 7 | 8 | - integers, long integers <-> PARI integers 9 | - list of integers -> PARI polynomials 10 | - doubles -> PARI reals 11 | - pairs of doubles -> PARI complex numbers 12 | 13 | PARI integers are stored as an array of limbs of type ``pari_ulong`` 14 | (which are 32-bit or 64-bit integers). Depending on the kernel 15 | (GMP or native), this array is stored little-endian or big-endian. 16 | This is encapsulated in macros like ``int_W()``: 17 | see section 4.5.1 of the 18 | `PARI library manual `_. 19 | 20 | Python integers of type ``int`` are just C longs. Python integers of 21 | type ``long`` are stored as a little-endian array of type ``digit`` 22 | with 15 or 30 bits used per digit. The internal format of a ``long`` is 23 | not documented, but there is some information in 24 | `longintrepr.h `_. 25 | 26 | Because of this difference in bit lengths, converting integers involves 27 | some bit shuffling. 28 | """ 29 | 30 | # **************************************************************************** 31 | # Copyright (C) 2016 Jeroen Demeyer 32 | # Copyright (C) 2016 Luca De Feo 33 | # Copyright (C) 2016 Vincent Delecroix 34 | # 35 | # This program is free software: you can redistribute it and/or modify 36 | # it under the terms of the GNU General Public License as published by 37 | # the Free Software Foundation, either version 2 of the License, or 38 | # (at your option) any later version. 39 | # https://www.gnu.org/licenses/ 40 | # **************************************************************************** 41 | 42 | from cysignals.signals cimport sig_on, sig_off, sig_error 43 | 44 | from cpython.version cimport PY_MAJOR_VERSION 45 | from cpython.long cimport PyLong_AsLong, PyLong_FromLong 46 | from cpython.longintrepr cimport (_PyLong_New, 47 | digit, PyLong_SHIFT, PyLong_MASK) 48 | from libc.limits cimport LONG_MIN, LONG_MAX 49 | from libc.math cimport INFINITY 50 | 51 | from .paridecl cimport * 52 | from .stack cimport new_gen, reset_avma 53 | from .string_utils cimport to_string, to_bytes 54 | from .pycore_long cimport (ob_digit, _PyLong_IsZero, _PyLong_IsPositive, 55 | _PyLong_DigitCount, _PyLong_SetSignAndDigitCount) 56 | 57 | ######################################################################## 58 | # Conversion PARI -> Python 59 | ######################################################################## 60 | 61 | cpdef gen_to_python(Gen z): 62 | r""" 63 | Convert the PARI element ``z`` to a Python object. 64 | 65 | OUTPUT: 66 | 67 | - a Python integer for integers (type ``t_INT``) 68 | 69 | - a ``Fraction`` (``fractions`` module) for rationals (type ``t_FRAC``) 70 | 71 | - a ``float`` for real numbers (type ``t_REAL``) 72 | 73 | - a ``complex`` for complex numbers (type ``t_COMPLEX``) 74 | 75 | - a ``list`` for vectors (type ``t_VEC`` or ``t_COL``). The function 76 | ``gen_to_python`` is then recursively applied on the entries. 77 | 78 | - a ``list`` of Python integers for small vectors (type ``t_VECSMALL``) 79 | 80 | - a ``list`` of ``list``s for matrices (type ``t_MAT``). The function 81 | ``gen_to_python`` is then recursively applied on the entries. 82 | 83 | - the floating point ``inf`` or ``-inf`` for infinities (type ``t_INFINITY``) 84 | 85 | - a string for strings (type ``t_STR``) 86 | 87 | - other PARI types are not supported and the function will raise a 88 | ``NotImplementedError`` 89 | 90 | Examples: 91 | 92 | >>> from cypari2 import Pari 93 | >>> from cypari2.convert import gen_to_python 94 | >>> pari = Pari() 95 | 96 | Converting integers: 97 | 98 | >>> z = pari('42'); z 99 | 42 100 | >>> a = gen_to_python(z); a 101 | 42 102 | >>> type(a) 103 | <... 'int'> 104 | 105 | >>> gen_to_python(pari('3^50')) == 3**50 106 | True 107 | >>> type(gen_to_python(pari('3^50'))) == type(3**50) 108 | True 109 | 110 | Converting rational numbers: 111 | 112 | >>> z = pari('2/3'); z 113 | 2/3 114 | >>> a = gen_to_python(z); a 115 | Fraction(2, 3) 116 | >>> type(a) 117 | 118 | 119 | Converting real numbers (and infinities): 120 | 121 | >>> z = pari('1.2'); z 122 | 1.20000000000000 123 | >>> a = gen_to_python(z); a 124 | 1.2 125 | >>> type(a) 126 | <... 'float'> 127 | 128 | >>> z = pari('oo'); z 129 | +oo 130 | >>> a = gen_to_python(z); a 131 | inf 132 | >>> type(a) 133 | <... 'float'> 134 | 135 | >>> z = pari('-oo'); z 136 | -oo 137 | >>> a = gen_to_python(z); a 138 | -inf 139 | >>> type(a) 140 | <... 'float'> 141 | 142 | Converting complex numbers: 143 | 144 | >>> z = pari('1 + I'); z 145 | 1 + I 146 | >>> a = gen_to_python(z); a 147 | (1+1j) 148 | >>> type(a) 149 | <... 'complex'> 150 | 151 | >>> z = pari('2.1 + 3.03*I'); z 152 | 2.10000000000000 + 3.03000000000000*I 153 | >>> a = gen_to_python(z); a 154 | (2.1+3.03j) 155 | 156 | Converting vectors: 157 | 158 | >>> z1 = pari('Vecsmall([1,2,3])'); z1 159 | Vecsmall([1, 2, 3]) 160 | >>> z2 = pari('[1, 3.4, [-5, 2], oo]'); z2 161 | [1, 3.40000000000000, [-5, 2], +oo] 162 | >>> z3 = pari('[1, 5.2]~'); z3 163 | [1, 5.20000000000000]~ 164 | >>> z1.type(), z2.type(), z3.type() 165 | ('t_VECSMALL', 't_VEC', 't_COL') 166 | 167 | >>> a1 = gen_to_python(z1); a1 168 | [1, 2, 3] 169 | >>> type(a1) 170 | <... 'list'> 171 | >>> [type(x) for x in a1] 172 | [<... 'int'>, <... 'int'>, <... 'int'>] 173 | 174 | >>> a2 = gen_to_python(z2); a2 175 | [1, 3.4, [-5, 2], inf] 176 | >>> type(a2) 177 | <... 'list'> 178 | >>> [type(x) for x in a2] 179 | [<... 'int'>, <... 'float'>, <... 'list'>, <... 'float'>] 180 | 181 | >>> a3 = gen_to_python(z3); a3 182 | [1, 5.2] 183 | >>> type(a3) 184 | <... 'list'> 185 | >>> [type(x) for x in a3] 186 | [<... 'int'>, <... 'float'>] 187 | 188 | Converting matrices: 189 | 190 | >>> z = pari('[1,2;3,4]') 191 | >>> gen_to_python(z) 192 | [[1, 2], [3, 4]] 193 | 194 | >>> z = pari('[[1, 3], [[2]]; 3, [4, [5, 6]]]') 195 | >>> gen_to_python(z) 196 | [[[1, 3], [[2]]], [3, [4, [5, 6]]]] 197 | 198 | Converting strings: 199 | 200 | >>> z = pari('"Hello"') 201 | >>> a = gen_to_python(z); a 202 | 'Hello' 203 | >>> type(a) 204 | <... 'str'> 205 | 206 | Some currently unsupported types: 207 | 208 | >>> z = pari('x') 209 | >>> z.type() 210 | 't_POL' 211 | >>> gen_to_python(z) 212 | Traceback (most recent call last): 213 | ... 214 | NotImplementedError: conversion not implemented for t_POL 215 | 216 | >>> z = pari('12 + O(2^13)') 217 | >>> z.type() 218 | 't_PADIC' 219 | >>> gen_to_python(z) 220 | Traceback (most recent call last): 221 | ... 222 | NotImplementedError: conversion not implemented for t_PADIC 223 | """ 224 | return PyObject_FromGEN(z.g) 225 | 226 | 227 | cpdef gen_to_integer(Gen x): 228 | """ 229 | Convert a PARI ``gen`` to a Python ``int`` or ``long``. 230 | 231 | INPUT: 232 | 233 | - ``x`` -- a PARI ``t_INT``, ``t_FRAC``, ``t_REAL``, a purely 234 | real ``t_COMPLEX``, a ``t_INTMOD`` or ``t_PADIC`` (which are 235 | lifted). 236 | 237 | Examples: 238 | 239 | >>> from cypari2.convert import gen_to_integer 240 | >>> from cypari2 import Pari 241 | >>> pari = Pari() 242 | >>> a = gen_to_integer(pari("12345")); a; type(a) 243 | 12345 244 | <... 'int'> 245 | >>> gen_to_integer(pari("10^30")) == 10**30 246 | True 247 | >>> gen_to_integer(pari("19/5")) 248 | 3 249 | >>> gen_to_integer(pari("1 + 0.0*I")) 250 | 1 251 | >>> gen_to_integer(pari("3/2 + 0.0*I")) 252 | 1 253 | >>> gen_to_integer(pari("Mod(-1, 11)")) 254 | 10 255 | >>> gen_to_integer(pari("5 + O(5^10)")) 256 | 5 257 | >>> gen_to_integer(pari("Pol(42)")) 258 | 42 259 | >>> gen_to_integer(pari("u")) 260 | Traceback (most recent call last): 261 | ... 262 | TypeError: unable to convert PARI object u of type t_POL to an integer 263 | >>> s = pari("x + O(x^2)") 264 | >>> s 265 | x + O(x^2) 266 | >>> gen_to_integer(s) 267 | Traceback (most recent call last): 268 | ... 269 | TypeError: unable to convert PARI object x + O(x^2) of type t_SER to an integer 270 | >>> gen_to_integer(pari("1 + I")) 271 | Traceback (most recent call last): 272 | ... 273 | TypeError: unable to convert PARI object 1 + I of type t_COMPLEX to an integer 274 | 275 | Tests: 276 | 277 | >>> gen_to_integer(pari("1.0 - 2^64")) == -18446744073709551615 278 | True 279 | >>> gen_to_integer(pari("1 - 2^64")) == -18446744073709551615 280 | True 281 | >>> import sys 282 | >>> if sys.version_info.major == 3: 283 | ... long = int 284 | >>> for i in range(10000): 285 | ... x = 3**i 286 | ... if long(pari(x)) != long(x) or int(pari(x)) != x: 287 | ... print(x) 288 | 289 | Check some corner cases: 290 | 291 | >>> for s in [1, -1]: 292 | ... for a in [1, 2**31, 2**32, 2**63, 2**64]: 293 | ... for b in [-1, 0, 1]: 294 | ... Nstr = str(s * (a + b)) 295 | ... N1 = gen_to_integer(pari(Nstr)) # Convert via PARI 296 | ... N2 = int(Nstr) # Convert via Python 297 | ... if N1 != N2: 298 | ... print(Nstr, N1, N2) 299 | ... if type(N1) is not type(N2): 300 | ... print(N1, type(N1), N2, type(N2)) 301 | """ 302 | return PyInt_FromGEN(x.g) 303 | 304 | 305 | cdef PyObject_FromGEN(GEN g): 306 | cdef long t = typ(g) 307 | cdef Py_ssize_t i, j 308 | cdef Py_ssize_t lr, lc 309 | 310 | if t == t_INT: 311 | return PyInt_FromGEN(g) 312 | elif t == t_FRAC: 313 | from fractions import Fraction 314 | num = PyInt_FromGEN(gel(g, 1)) 315 | den = PyInt_FromGEN(gel(g, 2)) 316 | return Fraction(num, den) 317 | elif t == t_REAL: 318 | return rtodbl(g) 319 | elif t == t_COMPLEX: 320 | re = PyObject_FromGEN(gel(g, 1)) 321 | im = PyObject_FromGEN(gel(g, 2)) 322 | return complex(re, im) 323 | elif t == t_VEC or t == t_COL: 324 | return [PyObject_FromGEN(gel(g, i)) for i in range(1, lg(g))] 325 | elif t == t_VECSMALL: 326 | return [g[i] for i in range(1, lg(g))] 327 | elif t == t_MAT: 328 | lc = lg(g) 329 | if lc <= 1: 330 | return [[]] 331 | lr = lg(gel(g, 1)) 332 | return [[PyObject_FromGEN(gcoeff(g, i, j)) for j in range(1, lc)] 333 | for i in range(1, lr)] 334 | elif t == t_INFINITY: 335 | if inf_get_sign(g) >= 0: 336 | return INFINITY 337 | else: 338 | return -INFINITY 339 | elif t == t_STR: 340 | return to_string(GSTR(g)) 341 | else: 342 | tname = to_string(type_name(t)) 343 | raise NotImplementedError(f"conversion not implemented for {tname}") 344 | 345 | 346 | cdef PyInt_FromGEN(GEN g): 347 | # First convert the input to a t_INT 348 | try: 349 | g = gtoi(g) 350 | finally: 351 | # Reset avma now. This is OK as long as we are not calling further 352 | # PARI functions before this function returns. 353 | reset_avma() 354 | 355 | if not signe(g): 356 | return PyLong_FromLong(0) 357 | 358 | cdef ulong u 359 | if PY_MAJOR_VERSION == 2: 360 | # Try converting to a Python 2 "int" first. Note that we cannot 361 | # use itos() from PARI since that does not deal with LONG_MIN 362 | # correctly. 363 | if lgefint(g) == 3: # abs(x) fits in a ulong 364 | u = g[2] # u = abs(x) 365 | # Check that (u) or (-u) does not overflow 366 | if signe(g) >= 0: 367 | if u <= LONG_MAX: 368 | return PyLong_FromLong(u) 369 | else: 370 | if u <= -LONG_MIN: 371 | return PyLong_FromLong(-u) 372 | 373 | # Result does not fit in a C long 374 | res = PyLong_FromINT(g) 375 | return res 376 | 377 | 378 | cdef GEN gtoi(GEN g0) except NULL: 379 | """ 380 | Convert a PARI object to a PARI integer. 381 | 382 | This function is shallow and not stack-clean. 383 | """ 384 | if typ(g0) == t_INT: 385 | return g0 386 | cdef GEN g 387 | try: 388 | sig_on() 389 | g = simplify_shallow(g0) 390 | if typ(g) == t_COMPLEX: 391 | if gequal0(gel(g, 2)): 392 | g = gel(g, 1) 393 | if typ(g) == t_INTMOD: 394 | g = gel(g, 2) 395 | g = trunc_safe(g) 396 | if typ(g) != t_INT: 397 | sig_error() 398 | sig_off() 399 | except RuntimeError: 400 | s = to_string(stack_sprintf( 401 | "unable to convert PARI object %Ps of type %s to an integer", 402 | g0, type_name(typ(g0)))) 403 | raise TypeError(s) 404 | return g 405 | 406 | 407 | cdef PyLong_FromINT(GEN g): 408 | # Size of input in words, bits and Python digits. The size in 409 | # digits might be a small over-estimation, but that is not a 410 | # problem. 411 | cdef size_t sizewords = (lgefint(g) - 2) 412 | cdef size_t sizebits = sizewords * BITS_IN_LONG 413 | cdef size_t sizedigits = (sizebits + PyLong_SHIFT - 1) // PyLong_SHIFT 414 | 415 | # Actual correct computed size 416 | cdef Py_ssize_t sizedigits_final = 0 417 | 418 | cdef py_long x = _PyLong_New(sizedigits) 419 | cdef digit* D = ob_digit(x) 420 | 421 | cdef digit d 422 | cdef ulong w 423 | cdef size_t i, j, bit 424 | for i in range(sizedigits): 425 | # The least significant bit of digit number i of the output 426 | # integer is bit number "bit" of word "j". 427 | bit = i * PyLong_SHIFT 428 | j = bit // BITS_IN_LONG 429 | bit = bit % BITS_IN_LONG 430 | 431 | w = int_W(g, j)[0] 432 | d = w >> bit 433 | 434 | # Do we need bits from the next word too? 435 | if BITS_IN_LONG - bit < PyLong_SHIFT and j+1 < sizewords: 436 | w = int_W(g, j+1)[0] 437 | d += w << (BITS_IN_LONG - bit) 438 | 439 | d = d & PyLong_MASK 440 | D[i] = d 441 | 442 | # Keep track of last non-zero digit 443 | if d: 444 | sizedigits_final = i+1 445 | 446 | # Set correct size 447 | _PyLong_SetSignAndDigitCount(x, signe(g), sizedigits_final) 448 | 449 | return x 450 | 451 | 452 | ######################################################################## 453 | # Conversion Python -> PARI 454 | ######################################################################## 455 | 456 | cdef GEN PyLong_AS_GEN(py_long x) noexcept: 457 | cdef const digit* D = ob_digit(x) 458 | 459 | # Size of the input 460 | cdef size_t sizedigits 461 | cdef long sgn 462 | if _PyLong_IsZero(x): 463 | return gen_0 464 | elif _PyLong_IsPositive(x): 465 | sizedigits = _PyLong_DigitCount(x) 466 | sgn = evalsigne(1) 467 | else: 468 | sizedigits = _PyLong_DigitCount(x) 469 | sgn = evalsigne(-1) 470 | 471 | # Size of the output, in bits and in words 472 | cdef size_t sizebits = sizedigits * PyLong_SHIFT 473 | cdef size_t sizewords = (sizebits + BITS_IN_LONG - 1) // BITS_IN_LONG 474 | 475 | # Compute the most significant word of the output. 476 | # This is a special case because we need to be careful not to 477 | # overflow the ob_digit array. We also need to check for zero, 478 | # in which case we need to decrease sizewords. 479 | # See the loop below for an explanation of this code. 480 | cdef size_t bit = (sizewords - 1) * BITS_IN_LONG 481 | cdef size_t dgt = bit // PyLong_SHIFT 482 | bit = bit % PyLong_SHIFT 483 | 484 | cdef ulong w = (D[dgt]) >> bit 485 | if 1*PyLong_SHIFT - bit < BITS_IN_LONG and dgt+1 < sizedigits: 486 | w += (D[dgt+1]) << (1*PyLong_SHIFT - bit) 487 | if 2*PyLong_SHIFT - bit < BITS_IN_LONG and dgt+2 < sizedigits: 488 | w += (D[dgt+2]) << (2*PyLong_SHIFT - bit) 489 | if 3*PyLong_SHIFT - bit < BITS_IN_LONG and dgt+3 < sizedigits: 490 | w += (D[dgt+3]) << (3*PyLong_SHIFT - bit) 491 | if 4*PyLong_SHIFT - bit < BITS_IN_LONG and dgt+4 < sizedigits: 492 | w += (D[dgt+4]) << (4*PyLong_SHIFT - bit) 493 | if 5*PyLong_SHIFT - bit < BITS_IN_LONG and dgt+5 < sizedigits: 494 | w += (D[dgt+5]) << (5*PyLong_SHIFT - bit) 495 | 496 | # Effective size in words plus 2 special codewords 497 | cdef size_t pariwords = sizewords+2 if w else sizewords+1 498 | cdef GEN g = cgeti(pariwords) 499 | g[1] = sgn + evallgefint(pariwords) 500 | 501 | if w: 502 | int_MSW(g)[0] = w 503 | 504 | # Fill all words 505 | cdef GEN ptr = int_LSW(g) 506 | cdef size_t i 507 | for i in range(sizewords - 1): 508 | # The least significant bit of word number i of the output 509 | # integer is bit number "bit" of Python digit "dgt". 510 | bit = i * BITS_IN_LONG 511 | dgt = bit // PyLong_SHIFT 512 | bit = bit % PyLong_SHIFT 513 | 514 | # Now construct the output word from the Python digits: we need 515 | # to check that we shift less than the number of bits in the 516 | # type. 6 digits are enough assuming that PyLong_SHIFT >= 15 and 517 | # BITS_IN_LONG <= 76. A clever compiler should optimize away all 518 | # but one of the "if" statements below. 519 | w = (D[dgt]) >> bit 520 | if 1*PyLong_SHIFT - bit < BITS_IN_LONG: 521 | w += (D[dgt+1]) << (1*PyLong_SHIFT - bit) 522 | if 2*PyLong_SHIFT - bit < BITS_IN_LONG: 523 | w += (D[dgt+2]) << (2*PyLong_SHIFT - bit) 524 | if 3*PyLong_SHIFT - bit < BITS_IN_LONG: 525 | w += (D[dgt+3]) << (3*PyLong_SHIFT - bit) 526 | if 4*PyLong_SHIFT - bit < BITS_IN_LONG: 527 | w += (D[dgt+4]) << (4*PyLong_SHIFT - bit) 528 | if 5*PyLong_SHIFT - bit < BITS_IN_LONG: 529 | w += (D[dgt+5]) << (5*PyLong_SHIFT - bit) 530 | 531 | ptr[0] = w 532 | ptr = int_nextW(ptr) 533 | 534 | return g 535 | 536 | 537 | cdef GEN PyObject_AsGEN(x) except? NULL: 538 | """ 539 | Convert basic Python types to a PARI GEN. 540 | """ 541 | cdef GEN g = NULL 542 | if isinstance(x, unicode): 543 | x = to_bytes(x) 544 | if isinstance(x, bytes): 545 | sig_on() 546 | g = gp_read_str(x) 547 | sig_off() 548 | elif isinstance(x, int): 549 | sig_on() 550 | g = PyLong_AS_GEN(x) 551 | sig_off() 552 | elif isinstance(x, float): 553 | sig_on() 554 | g = PyFloat_AS_GEN(x) 555 | sig_off() 556 | elif isinstance(x, complex): 557 | sig_on() 558 | g = PyComplex_AS_GEN(x) 559 | sig_off() 560 | return g 561 | 562 | 563 | #################################### 564 | # Deprecated functions 565 | #################################### 566 | 567 | cdef Gen new_gen_from_double(double x): 568 | sig_on() 569 | return new_gen(double_to_REAL(x)) 570 | 571 | 572 | cdef Gen new_t_COMPLEX_from_double(double re, double im): 573 | sig_on() 574 | return new_gen(doubles_to_COMPLEX(re, im)) 575 | 576 | 577 | def integer_to_gen(x): 578 | """ 579 | Convert a Python ``int`` or ``long`` to a PARI ``gen`` of type 580 | ``t_INT``. 581 | 582 | Examples: 583 | 584 | >>> from cypari2.convert import integer_to_gen 585 | >>> from cypari2 import Pari 586 | >>> pari = Pari() 587 | >>> a = integer_to_gen(int(12345)); a; type(a) 588 | 12345 589 | <... 'cypari2.gen.Gen'> 590 | >>> integer_to_gen(float(12345)) 591 | Traceback (most recent call last): 592 | ... 593 | TypeError: integer_to_gen() needs an int or long argument, not float 594 | >>> integer_to_gen(2**100) 595 | 1267650600228229401496703205376 596 | 597 | Tests: 598 | 599 | >>> import sys 600 | >>> if sys.version_info.major == 3: 601 | ... long = int 602 | >>> assert integer_to_gen(long(12345)) == 12345 603 | >>> for i in range(10000): 604 | ... x = 3**i 605 | ... if pari(long(x)) != pari(x) or pari(int(x)) != pari(x): 606 | ... print(x) 607 | """ 608 | if isinstance(x, int): 609 | sig_on() 610 | return new_gen(PyLong_AS_GEN(x)) 611 | raise TypeError("integer_to_gen() needs an int or long " 612 | "argument, not {}".format(type(x).__name__)) 613 | --------------------------------------------------------------------------------