You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python调用Fortran复杂模型:实现内存驻留式优化器交互问询

问题

我有一个独立的Fortran复杂模型,可读取输入文件、分配内存、初始化、求解非线性方程、输出结果并释放内存。目前已通过以下Python代码实现单次调用该模型:

import sys
import ctypes as ct
lib = ct.CDLL('prog.dll')
f = getattr(lib,'M_EXFUN_mp_EXFUN')
f.argtypes = [ct.c_char_p,ct.c_int]
f.restype = None
x = b'x ' + sys.argv[1].encode('UTF-8') + b' debug=yes'
f(x,len(x))

现在需要用Python优化器将该模型作为目标函数反复调用,预期Python代码逻辑如下:

def fun(x):
    return f(x)
x0 = [2.0, 0.0]
res = minimize(fun, x0)

其中minimize为Python库提供的优化函数。由于每次调用完整模型会重复初始化并释放内存,效率极低,我希望先运行Fortran模型完成内存分配与参数初始化后暂停,返回控制权给Python,再由Python反复调用Fortran内的目标函数计算逻辑。

附简化示例代码:
Fortran主模块main2_mod(负责内存分配与参数初始化):

module main2_mod
  contains
    !dec$ attributes dllexport :: main2
    subroutine main2(cc)
    use objfun_mod
    implicit none
    character(*), intent(in) :: cc
    integer :: i, n
    real(8) :: f
    real(8), allocatable :: x(:), par(:)

    n = 5
    allocate(x(n),par(n))
    x(:)   = (/(i,i=1,n)/)
    par(:) = (/(i**0.5,i=1,n)/)

    call objfun(n,x,par,f)

    write(6,'(<n>f6.1,f6.1)') (x(i),i=1,n), f

    deallocate(x)
    deallocate(par)

    return
    end
end module

目标函数模块objfun_mod:

module objfun_mod
  contains
    !dec$ attributes dllexport :: objfun
    subroutine objfun(n,x,par,f)
    implicit none
    integer, intent(in)  :: n
    real(8), intent(in)  :: x(n)
    real(8), intent(in)  :: par(n)
    real(8), intent(out) :: f
    integer :: i
    real(8) :: ff

    ff = 0.0
    do i = 1, n
      ff = ff + x(i)*par(i)
    enddo
    f = ff

    return
    end
end module

当前Python调用main2的代码:

# for ctypes
import ctypes as ct

# load the DLL
lib = ct.CDLL('main2.dll')

# find entry point for function to be called
f = getattr(lib,'MAIN2_MOD_mp_MAIN2')

# specify its argument types
f.argtypes = [ct.c_char_p,ct.c_int]

# specify return type
f.restype = None

# set up the argument to be sent to the function
x = b'x '

# run the function with commandline (x) as an argument
f(x,len(x))

现需调整实现:先调用main2完成初始化但不释放内存,再让Python优化器反复调用objfun计算目标函数值,询问该方案是否可行,是否需要回调机制及具体实现方式。


解决方案

方案可行性

完全可行,不需要复杂的回调机制。核心是利用Fortran模块变量的持久化特性——模块内定义的变量在DLL加载后会一直存在,直到DLL卸载或显式释放,刚好可以存储初始化后的内存和参数。

具体实现步骤

1. 修改Fortran代码

调整模块结构,把需要保留的初始化变量放到模块全局作用域,新增三个导出接口:

  • initialize: 完成内存分配、参数初始化(替代原main2的初始化逻辑,不释放内存)
  • compute_objfun: 接收Python传入的x,利用已初始化的par计算目标函数值
  • cleanup: 显式释放内存,避免泄漏

修改后的main2_mod:

module main2_mod
    ! 持久化变量:模块级变量会在DLL生命周期内保留
    integer, save :: n
    real(8), allocatable, save :: par(:)
contains
    !dec$ attributes dllexport :: initialize
    subroutine initialize(cc)
        implicit none
        character(*), intent(in) :: cc
        integer :: i
        
        n = 5
        allocate(par(n))
        par(:) = (/(i**0.5,i=1,n)/)
        ! 这里可保留原有的输入文件读取、其他初始化逻辑
        return
    end subroutine initialize

    !dec$ attributes dllexport :: compute_objfun
    subroutine compute_objfun(x, f)
        use objfun_mod
        implicit none
        real(8), intent(in) :: x(n)  ! 直接使用模块全局的n
        real(8), intent(out) :: f
        
        call objfun(n, x, par, f)
        return
    end subroutine compute_objfun

    !dec$ attributes dllexport :: cleanup
    subroutine cleanup()
        implicit none
        if(allocated(par)) deallocate(par)
        n = 0
        return
    end subroutine cleanup
end module main2_mod

objfun_mod可保持不变,也可以合并到main2_mod中。

2. 修改Python调用代码

先调用initialize完成初始化,在优化器目标函数里反复调用compute_objfun,最后调用cleanup释放内存:

import ctypes as ct
from scipy.optimize import minimize

# 加载DLL
lib = ct.CDLL('main2.dll')

# 初始化接口配置
initialize = getattr(lib, 'MAIN2_MOD_mp_INITIALIZE')
initialize.argtypes = [ct.c_char_p, ct.c_int]
initialize.restype = None

# 计算目标函数接口配置
compute_objfun = getattr(lib, 'MAIN2_MOD_mp_COMPUTE_OBJFUN')
compute_objfun.argtypes = [ct.POINTER(ct.c_double), ct.POINTER(ct.c_double)]
compute_objfun.restype = None

# 清理接口配置
cleanup = getattr(lib, 'MAIN2_MOD_mp_CLEANUP')
cleanup.argtypes = []
cleanup.restype = None

# 第一步:执行初始化
init_args = b'x '  # 原有的输入参数
initialize(init_args, len(init_args))

# 定义优化器的目标函数
def fun(x):
    x_arr = (ct.c_double * len(x))(*x)
    f = ct.c_double(0.0)
    compute_objfun(x_arr, ct.byref(f))
    return f.value

# 第二步:执行优化(x0维度需和Fortran的n一致)
x0 = [2.0, 0.0, 3.0, 4.0, 5.0]
res = minimize(fun, x0, method='nelder-mead')
print(res)

# 第三步:显式清理内存
cleanup()

关键注意事项

  • Fortran模块变量线程不安全,多线程优化场景需额外处理
  • 必须显式调用cleanup,否则DLL卸载时可能导致内存泄漏
  • 确保Python传入的x维度与Fortran初始化的n完全一致,避免越界访问
  • 若需动态调整n,可在initialize接口中接收参数并设置全局n

内容的提问来源于stack exchange,提问作者Adrian

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 02:36:03