GSL Python 化教程

GSL Python 化教程#

本教程介绍了 pythonizations 及其如何解决底层问题的使用方式。

设定:想象您想使用 numpy,但被提供了一个基于 GNU 科学库(GNU Scientific Library,简称 GSL)的 C 或 C++ 库。如何让它们友好地协同工作?

GSL 是用 C 编写的,但结构非常严谨:它有一致的命名约定,为其主要结构提供分配器/解除分配器,并且具有明确的所有权规则。由于这种结构,可以根据反射信息编写 pythonizations,最终变得非常简单,因此易于维护。

import cppyy
---------------------------------------------------------------------------
ModuleNotFoundError                       Traceback (most recent call last)
Cell In[1], line 1
----> 1 import cppyy

ModuleNotFoundError: No module named 'cppyy'

为了简化,我们将使用 gsl_blas_dgemm 作为“基于GSL的C/C++库”的替代。为了让我们的生活更轻松,我们将把绑定到 GSL 和 GSLBLAS 的内容包装成单个反射字典。对于简单项目来说,这有些过头了,但如果我们想访问所有的 GSL(而不是单独的 GSL 和 GSLBLAS),这种方法是可行的。

# first, pull in all headers from the GSL installation directory (/usr/include on my system).
import glob, os
GSL_HOME = '/usr/include'
gsl_headers = [os.path.relpath(x, GSL_HOME) for x in glob.glob(GSL_HOME+'/gsl/*.h')]

接下来,我们编写一个选择文件,从 GSL 中提取所有有趣的部分。由于适当的命名约定,这个文件可以保持简单(如果 GSL 是一个 C++ 库,并且位于一个命名空间中,它可能会更简单)。然后我们运行 genreflex 命令生成字典文件并编译它,链接到 GSL 和 GSLBLAS。最后,我们准备将字典反射文件加载到 cppyy 中。

!mkdir -p .temp
%%file .temp/gsl_selection.xml
<lcgdict>
   <struct pattern="gsl_*" />
   <function pattern="gsl_*" />
   <enum pattern="GSL*" />
   <enum pattern="CBLAS*" />
</lcgdict>
Writing .temp/gsl_selection.xml
# conventional name for generated output
rfldct = 'GSLDict'

if not os.path.exists(f'{rfldct}_rflx.cpp'):
    import subprocess, sys

    # generate the reflection dictionary
    try:
        subprocess.check_output(
            ['genreflex',                  # utility installed by pip when installing cppyy
             '-s', '.temp/gsl_selection.xml',    # selection file (see above)
             '-o', f'.temp/{rfldct}_rflx.cpp',   # intermediate output file
             '-I'+GSL_HOME]+               # include search path for GSL headers
             gsl_headers)                  # headers themselves
    except subprocess.CalledProcessError as e:
        print(f"genreflex failed ({e.returncode:d}): {e.output}")
    else:
        print("genreflex done")
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[5], line 9
      7 # generate the reflection dictionary
      8 try:
----> 9     subprocess.check_output(
     10         ['genreflex',                  # utility installed by pip when installing cppyy
     11          '-s', '.temp/gsl_selection.xml',    # selection file (see above)
     12          '-o', f'.temp/{rfldct}_rflx.cpp',   # intermediate output file
     13          '-I'+GSL_HOME]+               # include search path for GSL headers
     14          gsl_headers)                  # headers themselves
     15 except subprocess.CalledProcessError as e:
     16     print(f"genreflex failed ({e.returncode:d}): {e.output}")

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:466, in check_output(timeout, *popenargs, **kwargs)
    463         empty = b''
    464     kwargs['input'] = empty
--> 466 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    467            **kwargs).stdout

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:548, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    545     kwargs['stdout'] = PIPE
    546     kwargs['stderr'] = PIPE
--> 548 with Popen(*popenargs, **kwargs) as process:
    549     try:
    550         stdout, stderr = process.communicate(input, timeout=timeout)

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1026, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1022         if self.text_mode:
   1023             self.stderr = io.TextIOWrapper(self.stderr,
   1024                     encoding=encoding, errors=errors)
-> 1026     self._execute_child(args, executable, preexec_fn, close_fds,
   1027                         pass_fds, cwd, env,
   1028                         startupinfo, creationflags, shell,
   1029                         p2cread, p2cwrite,
   1030                         c2pread, c2pwrite,
   1031                         errread, errwrite,
   1032                         restore_signals,
   1033                         gid, gids, uid, umask,
   1034                         start_new_session, process_group)
   1035 except:
   1036     # Cleanup if the child failed starting.
   1037     for f in filter(None, (self.stdin, self.stdout, self.stderr)):

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1955, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1953     err_msg = os.strerror(errno_num)
   1954 if err_filename is not None:
-> 1955     raise child_exception_type(errno_num, err_msg, err_filename)
   1956 else:
   1957     raise child_exception_type(errno_num, err_msg)

FileNotFoundError: [Errno 2] No such file or directory: 'genreflex'
if not os.path.exists('%s.so' % rfldct):
    # get command line arguments for compiler from cling
    try:
        clingflags = subprocess.check_output(
            ['cling-config',               # utility installed by pip when installing cppyy
             '--cppflags'])
    except subprocess.CalledProcessError as e:
        print('cling-config failed (%d):' % e.returncode, e.output)
        raise
    else:
        print('cling-config done')

    # compile generated file
    try:
        subprocess.check_output(
            ['g++',                        # C++ compiler
             '-fPIC',                      # require position independent code
             '-shared',                    # generate shared library
             '-o', '%s.so'%rfldct,         # output file
             '-I'+GSL_HOME,                # include search path for GSL headers
             '%s_rflx.cpp'%rfldct]+        # intermediate file to compile
             clingflags.split()+           # extra flags provided by cling
             ['-lgsl', '-lgslcblas'])      # link in GSL and GSLBLAS
    except subprocess.CalledProcessError as e:
        print('compilation failed (%d):' % e.returncode, e.output)
    else:
        print('compilation done')
---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
Cell In[6], line 4
      1 if not os.path.exists('%s.so' % rfldct):
      2     # get command line arguments for compiler from cling
      3     try:
----> 4         clingflags = subprocess.check_output(
      5             ['cling-config',               # utility installed by pip when installing cppyy
      6              '--cppflags'])
      7     except subprocess.CalledProcessError as e:
      8         print('cling-config failed (%d):' % e.returncode, e.output)

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:466, in check_output(timeout, *popenargs, **kwargs)
    463         empty = b''
    464     kwargs['input'] = empty
--> 466 return run(*popenargs, stdout=PIPE, timeout=timeout, check=True,
    467            **kwargs).stdout

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:548, in run(input, capture_output, timeout, check, *popenargs, **kwargs)
    545     kwargs['stdout'] = PIPE
    546     kwargs['stderr'] = PIPE
--> 548 with Popen(*popenargs, **kwargs) as process:
    549     try:
    550         stdout, stderr = process.communicate(input, timeout=timeout)

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1026, in Popen.__init__(self, args, bufsize, executable, stdin, stdout, stderr, preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags, restore_signals, start_new_session, pass_fds, user, group, extra_groups, encoding, errors, text, umask, pipesize, process_group)
   1022         if self.text_mode:
   1023             self.stderr = io.TextIOWrapper(self.stderr,
   1024                     encoding=encoding, errors=errors)
-> 1026     self._execute_child(args, executable, preexec_fn, close_fds,
   1027                         pass_fds, cwd, env,
   1028                         startupinfo, creationflags, shell,
   1029                         p2cread, p2cwrite,
   1030                         c2pread, c2pwrite,
   1031                         errread, errwrite,
   1032                         restore_signals,
   1033                         gid, gids, uid, umask,
   1034                         start_new_session, process_group)
   1035 except:
   1036     # Cleanup if the child failed starting.
   1037     for f in filter(None, (self.stdin, self.stdout, self.stderr)):

File /opt/hostedtoolcache/Python/3.12.7/x64/lib/python3.12/subprocess.py:1955, in Popen._execute_child(self, args, executable, preexec_fn, close_fds, pass_fds, cwd, env, startupinfo, creationflags, shell, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, restore_signals, gid, gids, uid, umask, start_new_session, process_group)
   1953     err_msg = os.strerror(errno_num)
   1954 if err_filename is not None:
-> 1955     raise child_exception_type(errno_num, err_msg, err_filename)
   1956 else:
   1957     raise child_exception_type(errno_num, err_msg)

FileNotFoundError: [Errno 2] No such file or directory: 'cling-config'
# load the generated dictionary
cppyy.load_reflection_info(rfldct)
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
Cell In[7], line 2
      1 # load the generated dictionary
----> 2 cppyy.load_reflection_info(rfldct)

NameError: name 'cppyy' is not defined