将Python嵌入Fortran 90

11
我正在研究将Python嵌入到Fortran90中,以添加Python功能到我的现有Fortran90代码中。我知道可以使用来自numpy的f2py通过将Fortran90扩展为Python实现相反的方式。但是,我想要保留Fortran中的超级优化主循环,并添加Python来执行一些其他任务/评估进一步开发,然后才能用Fortran完成它,并且使代码维护更轻松。我正在寻找以下问题的答案:
1)是否存在一个库,可以从中将Python嵌入到Fortran中?(我知道f2py可以实现相反的方式)
2)我们如何处理从Fortran到Python和返回的数据传输?
3)如何实现回调功能?(让我描述一下情景....我在Fortran中拥有my main_fortran程序,它调用Python中的Func1_Python模块。现在,我希望从这个Func1_Python中调用另一个函数...例如,在Fortran中的Func2_Fortran)
4)将Python解释器嵌入到Fortran中,对性能有什么影响....例如,加载时间、运行时间、发送数据(一个大型双精度数组)等。
谢谢你的帮助!!

如果您计划使用sympy,请查看此链接:https://gist.github.com/pv/5506308 - Saullo G. P. Castro
除非你在进行字符串操作,否则Fortran可能比Python更快。 - Kyle Kanos
7个回答

9
我开发了库Forpy,它允许你在Fortran中使用Python(嵌入)。它使用Fortran C互操作性来调用Python C API函数。
虽然我同意扩展(在Python中使用Fortran)通常是更可取的,但嵌入也有其用途:
  • 大型、现有的Fortran代码可能需要大量重构才能从Python中使用——在这种情况下,嵌入可以节省开发时间
  • 用Python实现替换现有代码的一部分
  • 临时嵌入Python以尝试给定的Fortran代码:例如测试替代算法或提取中间结果
除了嵌入之外,Forpy还支持扩展Python。使用Forpy,您可以完全使用Fortran编写Python扩展模块。与现有工具(如f2py)相比,一个优点是您可以使用Python数据类型(例如编写一个以Python列表为参数的函数或返回Python字典的函数)。
与现有的、可能是遗留的Fortran代码一起工作通常非常具有挑战性,我认为开发人员应该有嵌入和扩展Python的工具。

如果你必须从Fortran访问Python,并且无法切换到从Python调用Fortran,那么这就是答案。Forpy非常棒。 - John

9

1. 不要这样做

我知道你想把Python代码放在Fortran程序里,而不是使用带有Fortran扩展的Python程序。我的第一条建议是不要这样做。Fortran在数组运算方面比Python更快,但Python比Fortran更容易编写,使用OOP技术扩展Python代码更容易,并且Python可能可以访问对你很重要的库。你提到在Fortran中有一个超级优化的主循环;Fortran非常适合超级优化的内部循环。在Python程序中使用Numpy传递Fortran数组的逻辑比在Fortran中正确处理Python对象所需做的事情要简单得多。

当我从头开始启动科学计算项目时,我总是先用Python编写,找出性能瓶颈,然后将其转换为Fortran。能够针对经过验证的Python代码测试更快的Fortran代码,使得更容易展示代码是否正确工作。

由于您已经有现有的代码,将Python代码扩展为用Fortran制作的模块需要重构,但这个过程应该很简单。将初始化代码与主循环分离,将循环分解成逻辑部分,将每个例程包装在Python函数中,然后您的主Python代码可以调用Fortran子例程,并根据需要与Python函数交错使用。在此过程中,您可能能够保留Fortran主循环中大量的优化。F2PY是一个相当标准的工具,因此不难找到可以帮助您解决任何问题的人。
2. 系统调用
如果您一定要让Fortran代码调用Python代码,而不是相反,最简单的方法就是让Fortran代码将一些数据写入磁盘,并使用SYSTEMEXECUTE_COMMAND_LINE运行Python代码。如果您使用EXECUTE_COMMAND_LINE,则可以让Python代码将其结果输出到标准输出,然后Fortran代码可以将其作为字符数据读取;如果输出很多(例如,一个大矩阵),则更合理的做法是让Python代码输出一个文件,然后Fortran代码再读取它。但是,磁盘读写开销可能会变得非常高,这可能会成为限制因素。此外,您还需要编写Fortran代码来输出数据,Python代码来读取数据,Python代码再次输出数据,以及Fortran代码重新输入数据。编写和测试这些代码应该很简单,但是在编辑代码时保持这四个部分同步可能会变成一个头疼的问题。
(这种方法已经在this Stack Overflow question中尝试过)

3. 在Fortran中嵌入C中的Python

我不知道有什么直接将Python对象传递到Fortran的方法。然而,Fortran代码可以调用C代码,而C代码可以嵌入Python。(请参见扩展和嵌入Python教程。)通常情况下,扩展Python(如我在第1点中推荐的)比将其嵌入C/C++更可取。(请参见扩展与嵌入:只有一个正确的决定。)让这个工作起来将是一场噩梦,因为Python和Fortran之间的任何通信问题可能发生在Python和C之间,或者在C和Fortran之间。我不知道是否有人实际上在Fortran中嵌入了Python,因此获得帮助将会很困难。

1
你好Alex,非常感谢你详尽的回答。我也在考虑Fortran-> C -> Python选项...我将在几天内发布使用Fortran扩展Python与将Python嵌入Fortran的扩展结果。 - Ravi
有大量的C/C++程序嵌入了Python解释器。当然,Lua或Guile更容易嵌入,但社区中使用Python的人更多。使用适当的C/Fortran互操作性,无论您是在C还是Fortran内嵌入,差别不会太大。 - Vladimir F Героям слава
2
虽然我知道Alex的心意是好的,但他本可以省略掉第1和第2点。Greenfield Fortran编程很少见,当人们询问如何在Fortran中嵌入语言时,这是因为核心代码必须出于实用原因(例如质量保证、速度、缺乏资源以从头开始使用另一种语言编写、开发人员的核心能力)。没有人会将语言嵌入作为一种随意的练习。同样,触发system()调用可能是不安全和不可移植的,因此有很多理由不要这样做。 - arclight

6
如果您想在Fortran中嵌入Python,您将需要通过Fortran的C接口来实现;这就是ISO_C_BINDING的作用。我建议不要嵌入Python,不是因为技术上的困难,而是因为Python(语言或社区)似乎坚决反对Python被用作下级语言。普遍的观点是,您当前编写代码的任何非Python语言都应该分解成库并用于扩展Python,而不是相反。因此,您会看到(如此处)更多的回复试图说服您,让您真正不想做您实际想做的事情,而不是提供实际的技术帮助。
这不是在挑衅或发表评论或做出道德判断;这只是一个简单的事实陈述。如果您尝试嵌入Python,您将无法从Python社区获得帮助。
如果您需要超出Fortran语言本身支持的功能(例如文件系统操作)并且您不需要特定的Python 并且您想要比C更具表现力的语言,则可能需要考虑嵌入Lua。与Python不同,Lua专门用于嵌入,因此您可能会面临更少的社交和技术阻力。
有许多项目将Fortran和Lua集成,迄今为止我见过的最完整的项目是Aotus。作者非常负责,并且集成过程很简单。
诚然,这并没有回答原始问题(如何在Fortran 90应用程序中嵌入Python解释器),但公平地说,其他回复也没有回答。这些天我使用Python作为我的便携式通用语言,并且当扩展我们的主要产品时(用Fortran编写)我真的更喜欢坚持使用它。出于上述原因,我放弃了嵌入Python的尝试,转而嵌入Lua;出于社交原因,我认为Lua是一个更好的技术选择。这不是我的首选,但至少在我的情况下可以工作。
如果我冒犯了任何人,我很抱歉;我不是在挑起争端,只是在讲述我研究这个特定主题时的经验。

谢谢你的答复。我同意你所说的。Python不是为了嵌入到其他语言中而设计的,因此几乎没有支持这样做的最小/无支持。但是,从科学计算的角度来看,Python非常有趣,特别是由于计算包(如NumPy和SciPy)在所有其他优势方面。我确实想出了一种方法,即通过制作一种嵌入和扩展之间的混合体来充分发挥两全其美的优点。你可以看一下我的上面的回答。 - Ravi
Python是我在工作环境(工程分析)中进行一般计算和低性能数字计算的选择。在这里,软件是由主题专家(通常是核工程师、化学工程师和机械工程师)编写的,他们的主要语言是Fortran,而不是计算机科学家。出于坚实的技术和组织原因,将我们的主要编程语言从Fortran切换到Python是不可行的。我宁愿避免引入第三种语言,但对于我们的需求来说,Lua是一个更好的选择。你的情况可能有所不同。 - arclight

5

使用f2py可以非常轻松地实现这一点。编写您的Python方法并将其添加为Fortran子例程的输入。在cf2py钩子和类型声明中都声明它为EXTERNAL,并声明其返回值类型,例如REAL*8。然后,您的Fortran代码将具有指向存储Python方法的地址的指针。速度会很慢,但对于测试算法非常有用。我经常这样做(我将很多古老的Fortran代码移植到Python模块中……)。这也是在遗留的Fortran代码中使用优化的Scipy调用的绝佳方式。


4
我已经尝试了几种方法来解决问题,并找到了一种可能最优的方式。我将简要列出这些方法和结果。
1)通过系统调用进行嵌入:每次我们想要从Fortran访问Python时,我们使用系统调用来执行一些Python脚本并在它们之间交换数据。这种方法的速度受到磁盘读写的限制(在代码缓存级别优化的时代,去磁盘是致命的罪过)。此外,每次我们想要执行脚本时都需要初始化解释器,这是一个相当大的开销。一个简单的四阶龙格-库塔方法运行300个时间步骤需要长达59秒的执行时间。
2)通过C从Fortran到Python:我们使用ISO_C绑定在Fortran和C之间通信;并将Python解释器嵌入C中。我已经使其中的某些部分工作,但在此期间我发现了更好的方法并放弃了这个想法。不过为了完整起见,我仍然希望评估这种方法。

3)使用f2py将Fortran子程序导入Python(扩展): 在这里,我们将主循环从Fortran中取出并在Python中编码(这种方法称为使用Fortran扩展Python),并使用f2py将所有Fortran子程序导入Python(http://cens.ioc.ee/projects/f2py2e/usersguide/)。 我们具有在任何科学应用中具有最重要数据的灵活性,即Python中的最外层循环(通常是时间循环),以便我们可以将其与其他应用程序耦合。但是,我们也需要在Fortran和Python之间交换可能不必要的数据。相同的Runge Kutta 4阶方法示例需要0.372秒才能执行。

4) 通过扩展模拟嵌入式: 到目前为止,我们已经看到了两种纯粹的嵌入式方法(主循环保留在Fortran中,我们根据需要调用Python和主循环保留在Python中,我们根据需要调用Fortran)。还有另一种方法,我发现它是最优化的。将主循环的部分转移到Python中会导致开销,这可能并不总是必要的。为了摆脱这种开销,我们可以将主循环保留在Fortran中,将其转换为子例程而不进行任何更改,在Python中有一个伪主循环,它只是调用Fortran中的主循环,程序执行起来就像是我们未改动过的Fortran程序一样。每当需要时,我们可以使用回调函数将所需数据带回Python,执行脚本,然后再返回Fortran。在这种方法中,Runge-Kutta 4阶方法花费了0.083秒。我对代码进行了剖析,并发现初始化Python解释器和加载需要0.075秒,而程序仅需要0.008秒(其中包括300个回调函数到Python)。原始的Fortran代码需要0.007秒。因此,使用这种方法,我们几乎可以获得类似Fortran的性能,同时具备Python的灵活性。


这种方法(第四种)对你有用吗?你有可以分享的示例存储库吗? - ximiki

2
我刚刚使用 cffi 成功地将 Python 嵌入到我们公司约 500 KLOC 的 Fortran 程序中。一个重要的方面是不要触及现有的代码。该程序是用 Fortran 95 编写的。我使用 iso_c_binding 模块编写了一个薄的 2003 包装器,它只是从各个模块导入数据,获取指向这些数据的 C 指针和/或将 Fortran 类型包装成 C 结构体,把所有内容放入单个类型/结构体中,并将其发送到一个 C 函数。这个 C 函数恰好是用 cffi 包装的 Python 函数。它将 C 结构体解包成更加用户友好的 Python 对象,将 Fortran 数组包装为 Numpy 数组(无需复制),然后根据用户配置,进入交互式 Python 控制台或运行 Python 脚本。除了一个头文件之外,没有必要编写 C 代码。显然有一些开销,但这个功能是为了可扩展性而设计的,而不是为了性能。
我建议不要使用 f2py。它没有得到很好的维护,并且严重限制了您的 Fortran 代码的公共接口。

2
我为此编写了一个库,forcallpy,使用一个C层嵌入Python表达式解释函数,并专门处理Fortran和Python之间的参数传递,以使脚本调用尽可能简单(使用嵌入numpy将Fortran数组直接映射到ndarrays中,在C/Python端使用参数名称来确定它们的类型)。

您可以在readthedocs文档中看到一些示例。

Laurent.


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接