代码生成¶
此模块提供了从 SymPy 表达式直接生成可编译代码的功能。codegen 函数是 SymPy 中代码生成功能的用户接口。以下为高级用户可能希望直接使用该框架的一些实现细节。
备注
codegen 可调用对象不会自动出现在 sympy 命名空间中,要使用它,你必须首先执行
>>> from sympy.utilities.codegen import codegen
实现细节¶
这里我们展示了内部结构中最重要的一部分,因为高级用户可能希望直接使用它,例如通过子类化一个代码生成器以用于专门的程序。你很可能会更喜欢使用上面文档中提到的codegen()函数。
基本假设:
一个通用的例程数据结构描述了必须被翻译成C/Fortran/…代码的例程。该数据结构涵盖了在一个或多个支持的语言中存在的所有特性。
从 CodeGen 类派生的类将多个 Routine 实例转换为可编译的代码。每个派生类翻译成特定的语言。
在许多情况下,人们希望有一个简单的流程。最后一部分中的友好函数是在 Routine/CodeGen 内容之上的一个简单 API。它们更易于使用,但功能较弱。
常规¶
Routine 类是 codegen 模块中非常重要的部分。将 codegen 工具视为将数学表达式翻译成编程语言中的一组语句的翻译器,Routine 实例负责提取和存储关于如何在函数调用中封装数学信息的信息。因此,正是 Routine 构造函数决定了例程将需要哪些参数以及是否应该有返回值。
API 参考¶
用于生成评估 SymPy 表达式的 C、C++、Fortran77、Fortran90、Julia、Rust 和 Octave/Matlab 例程的模块。此模块正在开发中。以下列表中带有 ‘+’ 字符的里程碑已完成。
— sympy.utilities.codegen 与 sympy.printing.ccode 有何不同? —
我们考虑过扩展 SymPy 函数的打印例程,使其打印出完整的可编译代码,但这会导致一些无法克服的问题,这些问题只能通过专门的代码生成器来解决:
对于C语言,需要一个代码文件和一个头文件,而打印例程只生成一个字符串。此代码生成器可以扩展以支持f2py的.pyf文件。
SymPy 函数不涉及编程技术问题,如输入、输出和输入输出参数。其他例子包括连续或非连续数组,包括 gsl 或其他库的头文件。
在一个C例程中评估多个SymPy函数是非常有趣的,最终可以通过cse例程的帮助共享常见的中间结果。这不仅仅是打印。
从编程的角度来看,带有常量的表达式应在代码生成器中尽可能地进行求值。这与打印不同。
— 基本假设 —
一个通用的例程数据结构描述了必须被翻译成C/Fortran/…代码的例程。该数据结构涵盖了在一个或多个支持的语言中存在的所有特性。
从 CodeGen 类派生的类将多个 Routine 实例转换为可编译的代码。每个派生类翻译成特定的语言。
在许多情况下,人们希望有一个简单的流程。最后一部分中的友好函数是在 Routine/CodeGen 内容之上的一个简单 API。它们更易于使用,但功能较弱。
— 里程碑 —
第一个带有标量输入参数的工作版本,生成C代码,测试
友好函数,比严格的Routine/CodeGen工作流程更易于使用。
输入和输出为整数和实数
输出参数
输入输出参数
正确排序输入/输出参数
连续数组参数(numpy矩阵)
同时为 f2py 生成 .pyf 代码(在 autowrap 模块中)
将常量隔离并在双精度下提前评估它们
Fortran 90
Octave/Matlab
公共子表达式消除
生成的代码中的用户定义注释
可选的额外包含行,用于可以评估特殊函数的库/对象
测试其他C编译器和库:gcc, tcc, libtcc, gcc+gsl, …
连续数组参数(SymPy 矩阵)
非连续数组参数(SymPy 矩阵)
当 ccode 遇到无法翻译成 C 语言的内容时,必须引发错误。ccode(integrate(sin(x)/x, x)) 没有意义。
复数作为输入和输出
默认的复杂数据类型
在头部包含额外信息:日期、用户、主机名、sha1 哈希值等。
Fortran 77
C++
Python
Julia
Rust
…
- class sympy.utilities.codegen.Argument(
- name,
- datatype=None,
- dimensions=None,
- precision=None,
抽象的参数数据结构:一个名称和一个数据类型。
此结构在下面的后代中得到了改进。
- 属性:
- 名称
方法
get_datatype(language)返回请求语言的数据类型字符串。
- class sympy.utilities.codegen.CCodeGen(
- project='project',
- printer=None,
- preprocessor_statements=None,
- cse=False,
C代码生成器。
从 CodeGen 继承的 .write() 方法将输出一个代码文件和一个接口文件,分别是 <prefix>.c 和 <prefix>.h。
- 属性:
- 打印机
方法
dump_c(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_code(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_h(routines, f, prefix[, header, empty])写入C头文件。
get_prototype(routine)返回该例程的函数原型字符串。
routine(name, expr[, argument_sequence, ...])创建一个适合该语言的 Routine 对象。
write(routines, prefix[, to_files, header, ...])为给定的例程编写所有源代码文件。
- dump_c(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
通过调用特定语言的方法来编写代码。
生成的文件包含了低级代码中所有例程的定义,并在适当的情况下引用了头文件。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀,用于引用正确的头文件。仅使用前缀的基本名称。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- dump_h(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
写入C头文件。
此文件包含所有函数声明。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀,用于构建包含防护。仅使用前缀的基本名称。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- class sympy.utilities.codegen.CodeGen(project='project', cse=False)[源代码][源代码]¶
代码生成器的抽象类。
- 属性:
- 打印机
方法
dump_code(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
routine(name, expr[, argument_sequence, ...])创建一个适合该语言的 Routine 对象。
write(routines, prefix[, to_files, header, ...])为给定的例程编写所有源代码文件。
- dump_code(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
通过调用特定语言的方法来编写代码。
生成的文件包含了低级代码中所有例程的定义,并在适当的情况下引用了头文件。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀,用于引用正确的头文件。仅使用前缀的基本名称。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- routine(
- name,
- expr,
- argument_sequence=None,
- global_vars=None,
创建一个适合该语言的 Routine 对象。
此实现至少适用于 C/Fortran。如有必要,子类可以重写此方法。
在这里,我们假设最多有一个返回值(左值),该值必须是标量。额外的输出是 OutputArguments(例如,右侧的指针或按引用传递)。矩阵总是通过 OutputArguments 返回。如果
argument_sequence为 None,参数将按字母顺序排列,但所有 InputArguments 优先,然后是 OutputArgument 和 InOutArguments。
- write(
- routines,
- prefix,
- to_files=False,
- header=True,
- empty=True,
为给定的例程编写所有源代码文件。
生成的源代码以 (文件名, 内容) 元组列表的形式返回,或者写入文件(见下文)。每个文件名由给定的前缀组成,并附加适当的扩展名。
- 参数:
- 例程列表
要写入的例程实例列表
- 前缀字符串
输出文件的前缀
- to_filesbool, 可选
当为 True 时,输出会被写入文件。否则,返回一个 (文件名, 内容) 元组的列表。 [默认值: False]
- 标题bool, 可选
当为 True 时,每个源文件的顶部会包含一个标题注释。[默认值: True]
- 空bool, 可选
当为 True 时,空行会被包含在内以结构化源文件。[默认值: True]
- class sympy.utilities.codegen.DataType(
- cname,
- fname,
- pyname,
- jlname,
- octname,
- rsname,
在不同语言中保存某种数据类型的字符串。
- class sympy.utilities.codegen.FCodeGen(project='project', printer=None)[源代码][源代码]¶
Fortran 95 代码生成器
从 CodeGen 继承的 .write() 方法将输出一个代码文件和一个接口文件,分别是 <prefix>.f90 和 <prefix>.h。
- 属性:
- 打印机
方法
dump_code(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_f95(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_h(routines, f, prefix[, header, empty])将接口写入头文件。
get_interface(routine)返回函数接口的字符串。
routine(name, expr[, argument_sequence, ...])创建一个适合该语言的 Routine 对象。
write(routines, prefix[, to_files, header, ...])为给定的例程编写所有源代码文件。
- dump_f95(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
通过调用特定语言的方法来编写代码。
生成的文件包含了低级代码中所有例程的定义,并在适当的情况下引用了头文件。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀,用于引用正确的头文件。仅使用前缀的基本名称。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- dump_h(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
将接口写入头文件。
此文件包含所有函数声明。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- class sympy.utilities.codegen.JuliaCodeGen(project='project', printer=None)[源代码][源代码]¶
Julia 代码生成器。
从 CodeGen 继承的 .write() 方法将输出一个代码文件 <prefix>.jl。
- 属性:
- 打印机
方法
dump_code(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_jl(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
routine(name, expr, argument_sequence, ...)Julia 的专用例程创建。
write(routines, prefix[, to_files, header, ...])为给定的例程编写所有源代码文件。
- class sympy.utilities.codegen.OctaveCodeGen(project='project', printer=None)[源代码][源代码]¶
用于生成 Octave 代码的生成器。
从 CodeGen 继承的 .write() 方法将输出一个代码文件 <prefix>.m。
Octave .m 文件通常包含一个函数。该函数名应与文件名 (
prefix) 匹配。如果你传递多个name_expr对,后面的被认为是主函数访问的私有函数。你只应将输入传递给
argument_sequence:输出根据它们在name_expr中的顺序进行排序。- 属性:
- 打印机
方法
dump_code(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_m(routines, f, prefix[, header, empty, ...])通过调用特定语言的方法来编写代码。
routine(name, expr, argument_sequence, ...)为 Octave 创建专用例程。
write(routines, prefix[, to_files, header, ...])为给定的例程编写所有源代码文件。
- dump_m(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
- inline=True,
通过调用特定语言的方法来编写代码。
生成的文件包含了低级代码中所有例程的定义,并在适当的情况下引用了头文件。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀,用于引用正确的头文件。仅使用前缀的基本名称。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- class sympy.utilities.codegen.OutputArgument(
- name,
- result_var,
- expr,
- datatype=None,
- dimensions=None,
- precision=None,
OutputArgument 总是在例程中初始化。
- 属性:
- 名称
方法
get_datatype(language)返回请求语言的数据类型字符串。
- class sympy.utilities.codegen.Result(
- expr,
- name=None,
- result_var=None,
- datatype=None,
- dimensions=None,
- precision=None,
一个返回值的表达式。
名称 result 用于避免与 Python 语言中的保留字 “return” 发生冲突。它也比 ReturnValue 更短。
这些可能在目标位置不需要名称(例如,“return(x*y)”可能返回一个值而不需要命名它)。
- 属性:
- 名称
方法
get_datatype(language)返回请求语言的数据类型字符串。
- class sympy.utilities.codegen.Routine(
- name,
- arguments,
- results,
- local_vars,
- global_vars,
一组表达式的评估例程的通用描述。
一个 CodeGen 类可以将此类实例翻译成特定语言的代码。例程规范涵盖了这些语言中的所有特性。当目标语言中不存在某些特性时,CodeGen 部分必须引发异常。例如,Python 中可以有多个返回值,但 C 或 Fortran 中不行。另一个例子:Fortran 和 Python 支持复数,而 C 不支持。
- 属性:
result_variables返回一个包含 OutputArgument、InOutArgument 和 Result 的列表。
variables返回一组可能在该例程中使用的所有变量。
- property result_variables¶
返回一个包含 OutputArgument、InOutArgument 和 Result 的列表。
如果有返回值,它们位于列表的末尾。
- property variables¶
返回一组可能在该例程中使用的所有变量。
对于具有未命名返回值的例程,可能会使用也可能不会使用的虚拟变量将包含在集合中。
- class sympy.utilities.codegen.RustCodeGen(project='project', printer=None)[源代码][源代码]¶
Rust 代码生成器。
从 CodeGen 继承的 .write() 方法将输出一个代码文件 <prefix>.rs
- 属性:
- 打印机
方法
dump_code(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
dump_rs(routines, f, prefix[, header, empty])通过调用特定语言的方法来编写代码。
get_prototype(routine)返回该例程的函数原型字符串。
routine(name, expr, argument_sequence, ...)为 Rust 创建专用例程。
write(routines, prefix[, to_files, header, ...])为给定的例程编写所有源代码文件。
- dump_rs(
- routines,
- f,
- prefix,
- header=True,
- empty=True,
通过调用特定语言的方法来编写代码。
生成的文件包含了低级代码中所有例程的定义,并在适当的情况下引用了头文件。
- 参数:
- 例程列表
一系列的 Routine 实例。
- f类文件
文件应写入的位置。
- 前缀字符串
文件名前缀,用于引用正确的头文件。仅使用前缀的基本名称。
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会包含一个头部注释。 [默认值 : True]
- 空bool, 可选
当为 True 时,空行会被包含进来以结构化源文件。 [默认值 : True]
- sympy.utilities.codegen.codegen(
- name_expr,
- language=None,
- prefix=None,
- project='project',
- to_files=False,
- header=True,
- empty=True,
- argument_sequence=None,
- global_vars=None,
- standard=None,
- code_gen=None,
- printer=None,
为指定语言中的表达式生成源代码。
- 参数:
- name_expr元组,或元组列表
一个 (名称, 表达式) 元组或 (名称, 表达式) 元组列表。每个元组对应一个例程。如果表达式是一个等式(类 Equality 的实例),则左侧被视为输出参数。如果表达式是可迭代的,则该例程将有多个输出。
- 语言字符串,
指示源代码语言的字符串。这是不区分大小写的。目前,支持 ‘C’、’F95’ 和 ‘Octave’。’Octave’ 生成的代码与 Octave 和 Matlab 兼容。
- 前缀字符串,可选
包含源代码文件名称的前缀。将附加与语言相关的后缀。如果省略,则使用第一个 name_expr 元组的名称。
- 项目字符串,可选
项目名称,用于生成唯一的预处理器指令。[默认: “project”]
- to_filesbool, 可选
当为 True 时,代码将被写入一个或多个具有给定前缀的文件中,否则将返回包含这些文件名称和内容的字符串。[默认值: False]
- 标题bool, 可选
当设置为True时,每个源文件的顶部都会写入一个标题。[默认值: True]
- 空bool, 可选
当为 True 时,空行用于结构化代码。[默认值: True]
- 参数序列可迭代对象, 可选
例程参数的顺序,按优先顺序排列。如果缺少必需的参数,则会引发 CodeGenError。冗余参数会无警告地使用。如果省略,参数将按字母顺序排列,但所有输入参数优先,然后是输出或输入输出参数。
- global_vars可迭代对象, 可选
该例程使用的全局变量序列。此处列出的变量不会显示为函数参数。
- 标准字符串,可选
- 代码生成CodeGen 实例,可选
一个 CodeGen 子类的实例。覆盖
language。- 打印机打印机实例,可选
一个 Printer 子类的实例。
示例
>>> from sympy.utilities.codegen import codegen >>> from sympy.abc import x, y, z >>> [(c_name, c_code), (h_name, c_header)] = codegen( ... ("f", x+y*z), "C89", "test", header=False, empty=False) >>> print(c_name) test.c >>> print(c_code) #include "test.h" #include <math.h> double f(double x, double y, double z) { double f_result; f_result = x + y*z; return f_result; } >>> print(h_name) test.h >>> print(c_header) #ifndef PROJECT__TEST__H #define PROJECT__TEST__H double f(double x, double y, double z); #endif
另一个使用 Equality 对象来提供命名输出的示例。这里文件名(前缀)取自第一个(名称,表达式)对。
>>> from sympy.abc import f, g >>> from sympy import Eq >>> [(c_name, c_code), (h_name, c_header)] = codegen( ... [("myfcn", x + y), ("fcn2", [Eq(f, 2*x), Eq(g, y)])], ... "C99", header=False, empty=False) >>> print(c_name) myfcn.c >>> print(c_code) #include "myfcn.h" #include <math.h> double myfcn(double x, double y) { double myfcn_result; myfcn_result = x + y; return myfcn_result; } void fcn2(double x, double y, double *f, double *g) { (*f) = 2*x; (*g) = y; }
如果生成的函数将成为一个包含各种已定义全局变量的更大项目的一部分,可以使用 ‘global_vars’ 选项从函数签名中移除指定的变量。
>>> from sympy.utilities.codegen import codegen >>> from sympy.abc import x, y, z >>> [(f_name, f_code), header] = codegen( ... ("f", x+y*z), "F95", header=False, empty=False, ... argument_sequence=(x, y), global_vars=(z,)) >>> print(f_code) REAL*8 function f(x, y) implicit none REAL*8, intent(in) :: x REAL*8, intent(in) :: y f = x + y*z end function
- sympy.utilities.codegen.make_routine(
- name,
- expr,
- argument_sequence=None,
- global_vars=None,
- language='F95',
一个根据表达式生成适当例程的工厂。
- 参数:
- 名称字符串
生成的代码中此例程的名称。
- 表达式表达式或表达式列表/元组
Routine 实例将表示的 SymPy 表达式。如果给定一个表达式列表或元组,则该例程将被视为具有多个返回值和/或输出参数。
- 参数序列列表或元组,可选
以首选顺序列出例程的参数。如果省略,结果取决于语言,例如,按字母顺序或与给定表达式相同的顺序。
- global_vars可迭代对象, 可选
该例程使用的全局变量序列。此处列出的变量不会显示为函数参数。
- 语言字符串,可选
指定目标语言。 该例程本身应与语言无关,但创建的具体方式、错误检查等取决于语言。 [默认: “F95”]。
注释
是否使用输出参数或返回值的决定取决于语言和特定的数学表达式。对于类型为 Equality 的表达式,通常将左侧设为 OutputArgument(如果合适,可能是 InOutArgument)。否则,通常将计算的表达式设为例程的返回值。
示例
>>> from sympy.utilities.codegen import make_routine >>> from sympy.abc import x, y, f, g >>> from sympy import Eq >>> r = make_routine('test', [Eq(f, 2*x), Eq(g, x + y)]) >>> [arg.result_var for arg in r.results] [] >>> [arg.name for arg in r.arguments] [x, y, f, g] >>> [arg.name for arg in r.result_variables] [f, g] >>> r.local_vars set()
另一个更复杂的例子,混合了指定和自动分配的名称。还包括矩阵输出。
>>> from sympy import Matrix >>> r = make_routine('fcn', [x*y, Eq(f, 1), Eq(g, x + g), Matrix([[x, 2]])]) >>> [arg.result_var for arg in r.results] [result_5397460570204848505] >>> [arg.expr for arg in r.results] [x*y] >>> [arg.name for arg in r.arguments] [x, y, f, g, out_8598435338387848786]
我们可以更仔细地检查各种参数:
>>> from sympy.utilities.codegen import (InputArgument, OutputArgument, ... InOutArgument) >>> [a.name for a in r.arguments if isinstance(a, InputArgument)] [x, y]
>>> [a.name for a in r.arguments if isinstance(a, OutputArgument)] [f, out_8598435338387848786] >>> [a.expr for a in r.arguments if isinstance(a, OutputArgument)] [1, Matrix([[x, 2]])]
>>> [a.name for a in r.arguments if isinstance(a, InOutArgument)] [g] >>> [a.expr for a in r.arguments if isinstance(a, InOutArgument)] [g + x]