为求解器重排方程

1 投票
2 回答
4695 浏览
提问于 2025-04-15 13:05

我想找一种通用的Python方法,把文本处理成可以解的方程。

举个例子:

可能需要先初始化一些常量

e1,e2=0.58,0.62
ma1,ma2=0.85,1.15
mw=0.8
Cpa,Cpw=1.023,4.193
dba,dbr=0.0,25.0

然后有一组方程(这里写出来是为了更好阅读,而不是为了求解器使用)

Q=e1*ma1*Cpa*(tw1-dba)
Q=ma1*Cpa*(dbs-dba)
Q=mw*Cpw*(tw1-tw2)
Q=e2*ma2*Cpa*(dbr-tw2)
Q=ma2*Cpa*(dbr-dbo)

这就留下了5个未知数,所以理论上这个系统是可以解的。

Q, dbo, dbr, tw1, tw2

实际上,系统是非线性的,而且复杂得多。

我已经用scipy、Delphi、Sage等解决了这个简单的例子……所以我并不想要求解的部分。

这些方程是直接在文本编辑器里输入的,我想要一个Python程序,给我一个未知数的数组和一个误差函数的数组。

y = mysolver.fsolve(f, x)

所以,对于上面的例子

x=[Q,dbo,dbr,tw1,tw2]

f=[Q-e1*ma1*Cpa*(tw1-dba), Q-ma1*Cpa*(dbs-dba), Q-mw*Cpw*(tw1-tw2),
   Q-e2*ma2*Cpa*(dbr-tw2), Q-ma2*Cpa*(dbr-dbo)]

我就是不知道怎么提取未知数并创建误差函数。

我试过compile.parse()这个函数,它似乎能给出一个结构化的分解。

有没有人能给我一些关于最佳方法的建议。

2 个回答

1

如果你不想为自己的表达式语言写一个解析器,其实可以尝试使用Python的语法。不要使用编译器模块,而是用某种抽象语法。从Python 2.5版本开始,你可以使用 _ast 模块:

py> import _ast                                                                     
py> tree = compile("e1,e2=0.58,0.62", "<string>", "exec", _ast.PyCF_ONLY_AST)
py> tree
<_ast.Module object at 0xb7cd5fac>                                    
py> tree.body[0]
<_ast.Assign object at 0xb7cd5fcc>
py> tree.body[0].targets[0]
<_ast.Tuple object at 0xb7cd5fec>
py> tree.body[0].targets[0].elts
[<_ast.Name object at 0xb7cd5e4c>, <_ast.Name object at 0xb7cd5f6c>]
py> tree.body[0].targets[0].elts[0].id
'e1'
py> tree.body[0].targets[0].elts[1].id
'e2'

在早期版本中,你需要使用 parser.suite,这样会得到一个具体的语法树,处理起来会更麻烦。

2

其实,我在Python中实现了完全一样的东西。我也对你提到的Eureka和其他程序很熟悉。你可以在xyzsolve.appspot.com上看到我的实现(抱歉,稍微自我宣传一下)。这个实现完全是用Python写的。我会列出代码经历的几个步骤:

步骤 #0:对方程中的每个变量进行简单的搜索和替换,把变量替换成它的值。例如,如果x和y的值分别是1.1和2.2,那么x * y就会变成1.1 * 2.2。得到转换后的字符串后,你可以直接用eval函数来计算它的值,并把结果放入残差(或者在你那叫f向量)。SciPy的fsolve/fmin函数允许你把额外的参数传递给残差函数,所以可以好好利用这一点。也就是说,传递一个包含每个命名变量索引的字典。你的字典应该像这样:{'x': 0, 'y': 1},然后你就可以对每个方程进行搜索和替换。这种方法有效,但速度很慢,因为每次调用残差函数时都要进行搜索和替换。

步骤 #1:和步骤 #0一样,只是直接用x数组的元素替换变量,比如'y'就变成'x[1]'。实际上,你可以通过这种方式生成一个函数字符串;比如看起来像“def f(x): return x[0]+x[1], x[0] - x[1]”。然后你可以用Python的exec函数来创建这个函数,以便传递给fsolve/fmin。如果你的方程是有效的Python语法,这样做不会影响速度。如果你想支持更复杂的方程输入格式,这种方法就不够用了。

步骤 #2:实现一个自定义的词法分析器和解析器。这听起来可能有点难,但其实并不复杂。我使用了这个链接中的词法分析器。我创建了一个递归下降解析器(这并不难,大约100行代码),用来解析每个方程。这让你在方程格式上有了完全的灵活性。我会分别记录方程两边出现的变量和常量。解析器在解析方程时,会构建一个像'var_000 + var_001 * var_002'这样的方程字符串。最后,我只需把'var_000'替换成x向量中的相应索引。所以'var_000'就变成'x[0]',依此类推。如果你愿意,可以构建一个抽象语法树(AST),进行更多复杂的转换,但我在这里就停下来了。

最后,你可能还需要考虑输入方程的类型。有一些看似无害的非线性方程是无法用fsolve解决的(它使用的是MINPACK hybrdj)。你可能还需要一种输入初始猜测的方法。

我很想知道还有没有其他替代的方法可以做到这一点。

撰写回答