管理任意数量变量的 Pythonic 方式,用于方程求解。
Pythonic way to manage arbitrary amount of variables, used for equation solving.
如果没有直接的例子,这有点难以解释。因此,让我们以非常简单的 ideal-gas law 为例。对于正常情况下的理想气体,以下等式成立:
PV = RT
这意味着如果我们知道 4 个变量中的 3 个(压力、体积、特定气体常数和温度),我们就可以求解另一个。
如何将其放入对象中?我想要一个对象,我可以在其中插入 3 个变量,然后计算第 4 个。请问是否可以通过属性实现?
我目前最好的猜测是像这样插入它:
class gasProperties(object):
__init__(self, P=None, V=None, R=None, T=None)
self.setFlowParams(P, V, R, T)
def setFlowParams(self, P=None, V=None, R=None, T=None)
if P is None:
self._P = R*T/V
self._V = V
self._R = R
self._T = T
elif V is None:
self._V = R*T/P
self._P = P
self._R = R
self._T = T
#etc
虽然这很麻烦,而且容易出错(我必须添加检查以确保恰好有一个参数设置为 "None")。
有没有更好、更简洁的方法?
我看到这种情况 "problem" 以各种不同的方式经常发生,尤其是一旦变量的数量增加(增加密度、雷诺数、混合粘度),不同的 if-语句增长很快。 (即,如果我有 8 个变量并且任何 5 个使系统唯一,我将需要 8 nCr 5 = 56 if 语句)。
一种解决方案是使用字典来存储变量名称及其值。这使您可以随时轻松添加其他变量。此外,您可以通过计算字典中 "None" 项的数量来检查是否只有一个变量具有值 "None"。
我的方法相当简单:
class GasProperties(object):
def __init__(self, P=None, V=None, R=None, T=None):
self.setFlowParams(P, V, R, T)
def setFlowParams(self, P=None, V=None, R=None, T=None):
if sum(1 for arg in (P, V, R, T) if arg is None) != 1:
raise ValueError("Expected 3 out of 4 arguments.")
self._P = P
self._V = V
self._R = R
self._T = T
@property
def P(self):
return self._P is self._P is not None else self._R*self._T/self._V
您同样定义 V、R 和 T 的属性。
此方法允许您设置对象的属性:
def setFlowParams(self, P=None, V=None, R=None, T=None):
params = self.setFlowParams.func_code.co_varnames[1:5]
if sum([locals()[param] is None for param in params]) > 1:
raise ValueError("3 arguments required")
for param in params:
setattr(self, '_'+param, locals()[param])
此外,您需要为带有公式的属性定义getter。像这样:
@property
def P(self):
if self._P is None:
self._P = self._R*self._T/self._V
return self._P
或者计算setFlowParams中的所有值。
使用 sympy
和 kwargs
检查用户提供的信息的基本解决方案:
from sympy.solvers import solve
from sympy import Symbol
def solve_gas_properties(**kwargs):
properties = []
missing = None
for letter in 'PVRT':
if letter in kwargs:
properties.append(kwargs[letter])
elif missing is not None:
raise ValueError("Expected 3 out of 4 arguments.")
else:
missing = Symbol(letter)
properties.append(missing)
if missing is None:
raise ValueError("Expected 3 out of 4 arguments.")
P, V, R, T = properties
return solve(P * V - R * T, missing)
print solve_gas_properties(P=3, V=2, R=1) # returns [6], the solution for T
如果您想在系统中存储和操作不同的值,则可以将其转换为 class 方法,利用 class 属性而不是关键字参数。
上面也可以改写为:
def gas_properties(**kwargs):
missing = [Symbol(letter) for letter in 'PVRT' if letter not in kwargs]
if len(missing) != 1:
raise ValueError("Expected 3 out of 4 arguments.")
missing = missing[0]
P, V, R, T = [kwargs.get(letter, missing) for letter in 'PVRT']
return solve(P * V - R * T, missing)
使用sympy
,您可以为每个方程创建一个class。用 ω, π = sp.symbols('ω π')
等创建等式的符号,等式本身,然后使用函数 f()
完成其余的工作:
import sympy as sp
# Create all symbols.
P, V, n, R, T = sp.symbols('P V n R T')
# Create all equations
IDEAL_GAS_EQUATION = P*V - n*R*T
def f(x, values_dct, eq_lst):
"""
Solves equations in eq_lst for x, substitutes values from values_dct,
and returns value of x.
:param x: Sympy symbol
:param values_dct: Dict with sympy symbols as keys, and numbers as values.
"""
lst = []
lst += eq_lst
for i, j in values_dct.items():
lst.append(sp.Eq(i, j))
try:
return sp.solve(lst)[0][x]
except IndexError:
print('This equation has no solutions.')
试试这个...:
vals = {P: 2, n: 3, R: 1, T:4}
r = f(V, values_dct=vals, eq_lst=[IDEAL_GAS_EQUATION, ])
print(r) # Prints 6
如果您没有通过 values_dct
提供足够的参数,您将得到类似 3*T/2
的结果,检查其 type()
您会得到 <class 'sympy.core.mul.Mul'>
。
如果您确实提供了所有参数,您得到的结果是 6
并且它的类型是 <class 'sympy.core.numbers.Integer'>
,因此您可以引发异常或任何您需要的。您也可以使用 int()
将其转换为 int (如果您使用 3*T/2
而不是 6 会引发错误,因此您也可以那样测试它)。
或者,您可以简单地检查 values_dct
中的 None
值是否大于 1。
要组合多个方程式,例如 PV=nRT
和 P=2m
,您可以像以前的符号一样创建额外的符号 m
并将 2m
分配给新方程式name MY_EQ_2
,然后将其插入函数的 eq_lst
中:
m = sp.symbols('m')
MY_EQ_2 = P - 2 * m
vals = {n: 3, R: 1, T:4}
r = f(V, values_dct=vals, eq_lst=[IDEAL_GAS_EQUATION, MY_EQ_2])
print(r) # Prints 6/m
数值方法
您可能想要在没有 sympy 的情况下执行此操作,例如,使用数字 root finding 进行练习。这种方法的美妙之处在于它适用于范围极广的方程式,即使是有同情心的人也会遇到麻烦。我认识的每个人都在大学学士数学课程中教过这个*,不幸的是,没有多少人可以在实践中应用它。
因此,首先我们获取 rootfinder,您可以在维基百科和整个网络上找到代码示例,这是众所周知的东西。许多数学包都内置了这些,例如 scipy.optimize 以获得好的根查找器。我将使用正割法来简化实现(在这种情况下我真的不需要迭代,但如果您碰巧想使用其他一些公式,我还是会使用通用版本)。
"""Equation solving with numeric root finding using vanilla python 2.7"""
def secant_rootfind(f, a, incr=0.1, accuracy=1e-15):
""" secant root finding method """
b=a+incr;
while abs(f(b)) > accuracy :
a, b = ( b, b - f(b) * (b - a)/(f(b) - f(a)) )
class gasProperties(object):
def __init__(self, P=None,V=None,n=None,T=None):
self.vars = [P, V, n, 8.314, T]
unknowns = 0
for i,v in enumerate(self.vars):
if v is None :
self._unknown_=i
unknowns += 1
if unknowns > 1:
raise ValueError("too many unknowns")
def equation(self, a):
self.vars[self._unknown_] = a
P, V, n, R, T = self.vars
return P*V - n*R*T # = 0
def __str__(self):
return str((
"P = %f\nV = %f\nn = %f\n"+
"R = %f\nT = %f ")%tuple(self.vars))
def solve(self):
secant_rootfind(self.equation, 0.2)
print str(self)
if __name__=="__main__": # run tests
gasProperties(P=1013.25, V=1., T=273.15).solve()
print "--- test2---"
gasProperties( V=1,n = 0.446175, T=273.15).solve()
求根的好处是,即使您的公式不是那么简单,它仍然可以工作,因此只需编写公式就可以完成任意数量的公式。这通常是一项非常有用的技能。 SYMPY 很好,但符号数学并不总是很容易解决
根求解器很容易扩展到向量和多方程的情况,甚至是矩阵求解。为优化而构建的现成 scipy 函数默认情况下已完成此操作。
这里还有一些资源:
* 大多数至少引入了 Newton–Raphson 方法
如果没有直接的例子,这有点难以解释。因此,让我们以非常简单的 ideal-gas law 为例。对于正常情况下的理想气体,以下等式成立:
PV = RT
这意味着如果我们知道 4 个变量中的 3 个(压力、体积、特定气体常数和温度),我们就可以求解另一个。
如何将其放入对象中?我想要一个对象,我可以在其中插入 3 个变量,然后计算第 4 个。请问是否可以通过属性实现?
我目前最好的猜测是像这样插入它:
class gasProperties(object):
__init__(self, P=None, V=None, R=None, T=None)
self.setFlowParams(P, V, R, T)
def setFlowParams(self, P=None, V=None, R=None, T=None)
if P is None:
self._P = R*T/V
self._V = V
self._R = R
self._T = T
elif V is None:
self._V = R*T/P
self._P = P
self._R = R
self._T = T
#etc
虽然这很麻烦,而且容易出错(我必须添加检查以确保恰好有一个参数设置为 "None")。
有没有更好、更简洁的方法?
我看到这种情况 "problem" 以各种不同的方式经常发生,尤其是一旦变量的数量增加(增加密度、雷诺数、混合粘度),不同的 if-语句增长很快。 (即,如果我有 8 个变量并且任何 5 个使系统唯一,我将需要 8 nCr 5 = 56 if 语句)。
一种解决方案是使用字典来存储变量名称及其值。这使您可以随时轻松添加其他变量。此外,您可以通过计算字典中 "None" 项的数量来检查是否只有一个变量具有值 "None"。
我的方法相当简单:
class GasProperties(object):
def __init__(self, P=None, V=None, R=None, T=None):
self.setFlowParams(P, V, R, T)
def setFlowParams(self, P=None, V=None, R=None, T=None):
if sum(1 for arg in (P, V, R, T) if arg is None) != 1:
raise ValueError("Expected 3 out of 4 arguments.")
self._P = P
self._V = V
self._R = R
self._T = T
@property
def P(self):
return self._P is self._P is not None else self._R*self._T/self._V
您同样定义 V、R 和 T 的属性。
此方法允许您设置对象的属性:
def setFlowParams(self, P=None, V=None, R=None, T=None):
params = self.setFlowParams.func_code.co_varnames[1:5]
if sum([locals()[param] is None for param in params]) > 1:
raise ValueError("3 arguments required")
for param in params:
setattr(self, '_'+param, locals()[param])
此外,您需要为带有公式的属性定义getter。像这样:
@property
def P(self):
if self._P is None:
self._P = self._R*self._T/self._V
return self._P
或者计算setFlowParams中的所有值。
使用 sympy
和 kwargs
检查用户提供的信息的基本解决方案:
from sympy.solvers import solve
from sympy import Symbol
def solve_gas_properties(**kwargs):
properties = []
missing = None
for letter in 'PVRT':
if letter in kwargs:
properties.append(kwargs[letter])
elif missing is not None:
raise ValueError("Expected 3 out of 4 arguments.")
else:
missing = Symbol(letter)
properties.append(missing)
if missing is None:
raise ValueError("Expected 3 out of 4 arguments.")
P, V, R, T = properties
return solve(P * V - R * T, missing)
print solve_gas_properties(P=3, V=2, R=1) # returns [6], the solution for T
如果您想在系统中存储和操作不同的值,则可以将其转换为 class 方法,利用 class 属性而不是关键字参数。
上面也可以改写为:
def gas_properties(**kwargs):
missing = [Symbol(letter) for letter in 'PVRT' if letter not in kwargs]
if len(missing) != 1:
raise ValueError("Expected 3 out of 4 arguments.")
missing = missing[0]
P, V, R, T = [kwargs.get(letter, missing) for letter in 'PVRT']
return solve(P * V - R * T, missing)
使用sympy
,您可以为每个方程创建一个class。用 ω, π = sp.symbols('ω π')
等创建等式的符号,等式本身,然后使用函数 f()
完成其余的工作:
import sympy as sp
# Create all symbols.
P, V, n, R, T = sp.symbols('P V n R T')
# Create all equations
IDEAL_GAS_EQUATION = P*V - n*R*T
def f(x, values_dct, eq_lst):
"""
Solves equations in eq_lst for x, substitutes values from values_dct,
and returns value of x.
:param x: Sympy symbol
:param values_dct: Dict with sympy symbols as keys, and numbers as values.
"""
lst = []
lst += eq_lst
for i, j in values_dct.items():
lst.append(sp.Eq(i, j))
try:
return sp.solve(lst)[0][x]
except IndexError:
print('This equation has no solutions.')
试试这个...:
vals = {P: 2, n: 3, R: 1, T:4}
r = f(V, values_dct=vals, eq_lst=[IDEAL_GAS_EQUATION, ])
print(r) # Prints 6
如果您没有通过 values_dct
提供足够的参数,您将得到类似 3*T/2
的结果,检查其 type()
您会得到 <class 'sympy.core.mul.Mul'>
。
如果您确实提供了所有参数,您得到的结果是 6
并且它的类型是 <class 'sympy.core.numbers.Integer'>
,因此您可以引发异常或任何您需要的。您也可以使用 int()
将其转换为 int (如果您使用 3*T/2
而不是 6 会引发错误,因此您也可以那样测试它)。
或者,您可以简单地检查 values_dct
中的 None
值是否大于 1。
要组合多个方程式,例如 PV=nRT
和 P=2m
,您可以像以前的符号一样创建额外的符号 m
并将 2m
分配给新方程式name MY_EQ_2
,然后将其插入函数的 eq_lst
中:
m = sp.symbols('m')
MY_EQ_2 = P - 2 * m
vals = {n: 3, R: 1, T:4}
r = f(V, values_dct=vals, eq_lst=[IDEAL_GAS_EQUATION, MY_EQ_2])
print(r) # Prints 6/m
数值方法
您可能想要在没有 sympy 的情况下执行此操作,例如,使用数字 root finding 进行练习。这种方法的美妙之处在于它适用于范围极广的方程式,即使是有同情心的人也会遇到麻烦。我认识的每个人都在大学学士数学课程中教过这个*,不幸的是,没有多少人可以在实践中应用它。
因此,首先我们获取 rootfinder,您可以在维基百科和整个网络上找到代码示例,这是众所周知的东西。许多数学包都内置了这些,例如 scipy.optimize 以获得好的根查找器。我将使用正割法来简化实现(在这种情况下我真的不需要迭代,但如果您碰巧想使用其他一些公式,我还是会使用通用版本)。
"""Equation solving with numeric root finding using vanilla python 2.7"""
def secant_rootfind(f, a, incr=0.1, accuracy=1e-15):
""" secant root finding method """
b=a+incr;
while abs(f(b)) > accuracy :
a, b = ( b, b - f(b) * (b - a)/(f(b) - f(a)) )
class gasProperties(object):
def __init__(self, P=None,V=None,n=None,T=None):
self.vars = [P, V, n, 8.314, T]
unknowns = 0
for i,v in enumerate(self.vars):
if v is None :
self._unknown_=i
unknowns += 1
if unknowns > 1:
raise ValueError("too many unknowns")
def equation(self, a):
self.vars[self._unknown_] = a
P, V, n, R, T = self.vars
return P*V - n*R*T # = 0
def __str__(self):
return str((
"P = %f\nV = %f\nn = %f\n"+
"R = %f\nT = %f ")%tuple(self.vars))
def solve(self):
secant_rootfind(self.equation, 0.2)
print str(self)
if __name__=="__main__": # run tests
gasProperties(P=1013.25, V=1., T=273.15).solve()
print "--- test2---"
gasProperties( V=1,n = 0.446175, T=273.15).solve()
求根的好处是,即使您的公式不是那么简单,它仍然可以工作,因此只需编写公式就可以完成任意数量的公式。这通常是一项非常有用的技能。 SYMPY 很好,但符号数学并不总是很容易解决
根求解器很容易扩展到向量和多方程的情况,甚至是矩阵求解。为优化而构建的现成 scipy 函数默认情况下已完成此操作。
这里还有一些资源:
* 大多数至少引入了 Newton–Raphson 方法