English (unofficial) translations of posts at kexue.fm
Source

First Attempt at Using PARI/GP in Python

Translated by DeepSeek V4 Pro. Translations can be inaccurate, please refer to the original post for important stuff.

BoJone likes Python very much and also loves number theory, so he enjoys playing with number theory using Python. Usually, I like to write some number theory functions myself; after all, Python supports large integer high-precision arithmetic, which is excellent. However, in many practical applications, I still hope to have a ready-made number theory function library to call. Previously, I tried the HugeCalc library from the Mathematics R&D Network, but due to various unfamiliarities, it came to nothing. Later, a user named Wuxin on the forum recommended PARI/GP. After a small trial, I successfully called it in Python. Now I no longer have to worry about number theory calculation problems in Python, haha 

First, here is an official introduction to PARI/GP:

PARI/GP is a widely used computer algebra system designed for fast computations in number theory (factorizations, algebraic number theory, elliptic curves...), but also contains a large number of other useful functions to compute with mathematical entities such as matrices, polynomials, power series, algebraic numbers, and many transcendental functions. PARI is also available as a C library to allow for even faster computations.

Originally developed by Henri Cohen and his co-workers (Université Bordeaux I, France), PARI is now under the GPL and maintained by Karim Belabas with the help of many volunteers.

  • PARI is a C library, allowing for fast computations.

  • gp is an easy-to-use interactive shell giving access to the PARI functions.

  • GP is the name of the gp scripting language.

  • gp2c, the GP-to-C compiler, combines the best of both worlds by compiling GP scripts into the C language and transparently loading the resulting functions into gp. (gp2c-compiled scripts will typically run 3 to 4 times faster.) gp2c currently only understands a part of the GP language.

PARI/GP provides source code packages that can be compiled for use, and it also provides executable installation packages for Windows, which can be installed and used directly. This article only discusses calling PARI using Python on the Windows platform. It is estimated that it can be called similarly on Linux, but I have not personally tested it before writing this article. Calling it under Windows only requires the libpari.dll file in the PARI/GP installation directory. Readers can extract it after installing the package from the official website or download it from the attachment at the end of this article.

First, it should be noted that the Python version used to call PARI must be 32-bit. Both Python 2 and Python 3 are acceptable, but 64-bit will cause errors (you can install 32-bit Python on a 64-bit operating system). Of course, you can also use PyPy to call it. PyPy is based on 32-bit Python, and the latest version already supports Python 3; readers can choose their favorite. Let’s first look at the method in Python 2.x; the details in 3.x are slightly different and will be explained later.

A DLL is a Dynamic Link Library. After loading the DLL, you can use the functions inside it. This is somewhat similar to loading a module. The difference is that DLLs are generally written in C, so their execution efficiency is relatively high. Loading C/C++ functions in Python allows you to leverage the execution efficiency of C/C++ and the development efficiency of Python. To load a DLL in Python, you need to import the ctypes module. After importing, use the ctypes.cdll.LoadLibrary() function to load libpari.dll (there are similar link libraries in Linux with the .so suffix). The code is as follows:

import ctypes
from ctypes import * 
pari = ctypes.cdll.LoadLibrary('libpari.dll')

Before calling PARI’s functions, you must first initialize it using its pari_init() function, for example, pari.pari_init(4000000, 2). The first parameter represents the number of bytes provided for PARI to use, which is the maximum memory usage of PARI; generally, it should not be less than 500,000. The second parameter is a pre-generated prime table. Pre-generating part of the prime table can slightly reduce the calculation load when dealing with prime-related problems later, but it is not mandatory; you can also set it to 0, and it can still complete prime-related operations.

Below is an example using PARI’s primes() function. The primes(n) function outputs the first n prime numbers. The code is as follows:

import ctypes
from ctypes import * 
pari = ctypes.cdll.LoadLibrary('libpari.dll') # Import libpari.dll

pari.pari_init(4000000, 2) # Initialization
pari.primes.restype = ctypes.POINTER(ctypes.c_long) # Purpose of these two lines is not entirely clear
pari.GENtostr.restype = ctypes.POINTER(ctypes.c_char) # Purpose of these two lines is not entirely clear

n = pari.primes(10) # Generate
y = pari.GENtostr(n) # Convert
print(ctypes.string_at(y)) # Output

Since I am just starting to try this, I don’t quite understand the role of restype. Taking it literally, it should correspond the function’s return type to a pointer. This code is based on some code found online. My tests also found that even without these two restype lines, the program can work normally; I’m not sure what the difference is. The process of the program is to first use the primes() function to generate the result. The result is of the GEN type, which cannot be recognized in Python. Use the GENtostr() function to convert it to a string, but this function returns an address, so string_at() must be used to read the output. In Python 2.7, the running result is (the output is a string):

[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

The above code can also run normally in Python 3, but the output result is slightly different. This will be explained below.

The second example is the isprime() function, which is a primality testing function. This type of function is somewhat special; it does not use the C/C++ int type, so you cannot simply input a Python integer. It is estimated that PARI defines its own large integer type or stores large integers in other ways. We need to read the number from a string. The code is as follows:

import ctypes
from ctypes import * 
pari = ctypes.cdll.LoadLibrary('libpari.dll') # Import libpari.dll

pari.pari_init(4000000, 2) # Initialization
pari.gp_read_str.restype = ctypes.POINTER(ctypes.c_int) # Purpose not entirely clear

n = pari.isprime(pari.gp_read_str(str(127))) # Test primality of 127
print(n)

The core code is the second to last line. First, convert 127 to a string, then use the gp_read_str() function to read from the string, and finally use the isprime() function to judge. The output result of this example is 1.

However, if you run this example in Python 3, an error will occur. The reason is that the string str in Python 3 is no longer the same as the str in Python 2.x. In Python 3, a new type bytes is defined. In fact, the str in 2.x is more like bytes in 3. Therefore, in Python 3, the code should be changed to:

pari.isprime(pari.gp_read_str(b'127'))

Or (using the encode() function of str to convert to bytes type):

pari.isprime(pari.gp_read_str(str(127).encode()))

Similarly, in Python 3, the object returned by ctypes.string_at(y) is not str but bytes. Readers may notice that the output result has an extra b prefix for this reason.

That’s all for the introduction for now. If there are new discoveries, I will continue to share them with everyone   I hope experts passing by will provide more guidance.

Download: libpari.zip

When reposting, please include the address of this article: https://kexue.fm/archives/2775

For more detailed reposting matters, please refer to: Scientific Space FAQ