Pythonic方式管理任意数量的变量,用于方程求解。

时间:2015-03-25 10:43:07

标签: python 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

虽然这很麻烦,而且容易出错(我必须添加检查以确定其中一个参数设置为“无”)。

有更好,更清洁的方式吗?

我发现这种“问题”经常以各种各样的方式发生,特别是一旦变量数量增加(增加密度,雷诺数,混合粘度),不同if语句的数量就会快速增长。 (IE,如果我有8个变量,任何5个使系统唯一,我需要8 nCr 5 = 56 if语句)。

6 个答案:

答案 0 :(得分:4)

使用sympy,您可以为每个方程创建一个类。使用ω, π = 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,则会引发错误,因此您也可以这样测试。)

或者,您只需检查None中的values_dct值是否大于1。


要合并多个方程式,例如PV=nRTP=2m,您可以像前面的符号一样创建额外的符号m,并将2m指定给新的等式名{ {1}},然后将其插入函数的MY_EQ_2

eq_lst

答案 1 :(得分:3)

使用sympykwargs检查用户提供的信息的基本解决方案:

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

如果要存储和操作系统中的不同值,可以将其转换为类方法,绘制类属性而不是关键字参数。

以上也可以改写为:

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)

答案 2 :(得分:1)

一种解决方案可能是使用字典来存储变量名称及其值。这使您可以随时轻松添加其他变量。此外,您可以检查一个变量是否具有值&#34;无&#34;通过计算&#34;无&#34;的数量你词典中的项目。

答案 3 :(得分:1)

我的方法很简单:

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的属性。

答案 4 :(得分:0)

此方法允许您设置对象的属性:

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中的所有值。

答案 5 :(得分:0)

数值方法

您可能希望在没有同情的情况下执行此操作,例如,使用数字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方法