我对使用SymPy扩展我的工程模型感兴趣。我不想定义一组僵化的输入和输出,而是希望用户简单地提供他们对系统的了解,然后将该数据应用于计算未知数的代数模型(如果有足够的数据)。
例如,假设我有一些stuff
,其中有一些mass
,volume
和density
。我想定义这些参数之间的关系(density = mass / volume
),以便当用户提供足够的信息(任何两个变量)时,将自动计算第三个变量。最后,如果以后更新任何值,则其他值之一应更改以保留关系。该系统的挑战之一是,当存在多个自变量时,将需要一种方法来指定应更改哪个自变量以满足要求。
这是我目前拥有的一些工作代码:
from sympy import *
class Stuff(object):
def __init__(self, *args, **kwargs):
#String of variables
varString = 'm v rho'
#Initialize Symbolic variables
m, v, rho = symbols(varString)
#Define density equation
# "rho = m / v" becomes "rho - m / v = 0" which means the eqn. is rho - m / v
# This is because solve() assumes equation = 0
density_eq = rho - m / v
#Store the equation and the variable string for later use
self._eqn = density_eq
self._varString = varString
#Get a list of variable names
variables = varString.split()
#Initialize parameters dictionary
self._params = {name: None for name in variables}
@property
def mass(self):
return self._params['m']
@mass.setter
def mass(self, value):
param = 'm'
self._params[param] = value
self.balance(param)
@property
def volume(self):
return self._params['v']
@volume.setter
def volume(self, value):
param = 'v'
self._params[param] = value
self.balance(param)
@property
def density(self):
return self._params['rho']
@density.setter
def density(self, value):
param = 'rho'
self._params[param] = value
self.balance(param)
def balance(self, param):
#Get the list of all variable names
variables = self._varString.split()
#Get a copy of the list except for the recently changed parameter
others = [name for name in variables if name != param]
#Loop through the less recently changed variables
for name in others:
try:
#Symbolically solve for the current variable
eq = solve(self._eqn, [symbols(name)])[0]
#Get a dictionary of variables and values to substitute for a numerical solution (will contain None for unset values)
indvars = {symbols(n): self._params[n] for n in variables if n is not name}
#Set the parameter with the new numeric solution
self._params[name] = eq.evalf(subs=indvars)
except Exception, e:
pass
if __name__ == "__main__":
#Run some examples - need to turn these into actual tests
stuff = Stuff()
stuff.density = 0.1
stuff.mass = 10.0
print stuff.volume
stuff = Water()
stuff.mass = 10.0
stuff.volume = 100.0
print stuff.density
stuff = Water()
stuff.density = 0.1
stuff.volume = 100.0
print stuff.mass
#increase volume
print "Setting Volume to 200"
stuff.volume = 200.0
print "Mass changes"
print "Mass {0}".format(stuff.mass)
print "Density {0}".format(stuff.density)
#Setting Mass to 15
print "Setting Mass to 15.0"
stuff.mass = 15.0
print "Volume changes"
print "Volume {0}".format(stuff.volume)
print "Density {0}".format(stuff.density)
#Setting Density to 0.5
print "Setting Density to 0.5"
stuff.density = 0.5
print "Mass changes"
print "Mass {0}".format(stuff.mass)
print "Volume {0}".format(stuff.volume)
print "It is impossible to let mass and volume drive density with this setup since either mass or volume will " \
"always be recalculated first."
我试图在类的整体方法和布局上尽可能地优雅,但是我不禁想知道我是否做错了-是否使用错误的方式使用SymPy来完成此任务任务。我对开发具有数十/数百个相关属性的复杂航空航天模型感兴趣。我想找到一种优雅且可扩展的方式来使用SymPy来控制整个车辆的属性关系,然后再根据这个相当简单的示例进行扩展。
我还担心如何/何时重新平衡方程式。我熟悉PyQt Signals和Slots,这是我首先想到的方法,即如何将相关的事物链接在一起以触发模型更新(值更新时发出信号,重新平衡依赖于每个方程系统的函数会收到该信号)该参数?)。是的,我真的不知道使用SymPy做到这一点的最佳方法。可能需要一个更大的例子来探索方程组。
这是我对本项目前进方向的一些思考。仅以质量为例,我想将整个车辆质量定义为子系统质量的总和,而将所有子系统质量定义为部件质量的总和。此外,某些子系统和组件之间将存在质量关系。这些关系将驱动模型,直到提供更多具体数据为止。因此,如果默认的燃油质量与车辆总质量之比为50%,则指定100lb的燃油将使车辆的大小为200lb。但是,如果以后我指定车辆实际为210磅,我希望重新计算该关系(由于燃料质量和车辆质量的设置是最近的,或者因为我指定它们是自变量或锁定的或类似的,所以它变得依赖。下一个问题是迭代。当模型中存在循环或冲突关系时,必须对模型进行迭代以希望收敛于解决方案。对于上述车辆质量模型通常是这种情况。如果车辆变得更重,则需要更多的燃料来满足要求,这会导致车辆变得更重,等等。我不确定在这些情况下如何利用SymPy。
有什么建议么?
聚苯乙烯
Good explanation of the challenges associated with space launch vehicle design.
编辑:根据 goncalopp的建议更改代码结构...
class Balanced(object):
def __init__(self, variables, equationStr):
self._variables = variables
self._equation = sympify(equationStr)
#Initialize parameters dictionary
self._params = {name: None for name in self._variables}
def var_getter(varname, self):
return self._params[varname]
def var_setter(varname, self, value):
self._params[varname] = value
self.balance(varname)
for varname in self._variables:
setattr(Balanced, varname, property(fget=partial(var_getter, varname),
fset=partial(var_setter, varname)))
def balance(self, recentlyChanged):
#Get a copy of the list except for the recently changed parameter
others = [name for name in self._variables if name != recentlyChanged]
#Loop through the less recently changed variables
for name in others:
try:
eq = solve(self._equation, [symbols(name)])[0]
indvars = {symbols(n): self._params[n] for n in self._variables if n != name}
self._params[name] = eq.evalf(subs=indvars)
except Exception, e:
pass
class HasMass(Balanced):
def __init__(self):
super(HasMass, self).__init__(variables=['mass', 'volume', 'density'],
equationStr='density - mass / volume')
class Prop(HasMass):
def __init__(self):
super(Prop, self).__init__()
if __name__ == "__main__":
prop = Prop()
prop.density = 0.1
prop.mass = 10.0
print prop.volume
prop = Prop()
prop.mass = 10.0
prop.volume = 100.0
print prop.density
prop = Prop()
prop.density = 0.1
prop.volume = 100.0
print prop.mass
这使我想要做的是使用多重继承或修饰器自动为事物分配物理上相互关联的属性。因此,我可以使用另一个名为“Cylindrical”的类来定义半径,直径和长度属性,然后可以使用
class DowelRod(HasMass, Cylindrical)
。这里最棘手的部分是,我想定义圆柱体的体积(体积=长度* pi *半径^ 2),并让该体积与质量平衡方程中定义的体积相互作用...这样,也许质量会做出响应改变长度等等。不仅多重继承很棘手,而且自动组合关系会变得更糟。这将很快变得棘手。我还不知道如何处理方程组,并且很明显,由于存在许多参数关系,因此必须进行参数锁定或指定自变量/因变量。
请您参考如下方法:
虽然我没有使用这种模型的经验,也没有使用SymPy的经验,但是这里有一些提示:
@property
def mass(self):
return self._params['m']
@mass.setter
def mass(self, value):
param = 'm'
self._params[param] = value
self.balance(param)
@property
def volume(self):
return self._params['v']
@volume.setter
def volume(self, value):
param = 'v'
self._params[param] = value
self.balance(param)
正如您所注意到的,您为每个变量重复了很多代码。这是不必要的,并且由于您将有很多变量,因此最终会导致代码维护的噩梦。您的变量整齐地排列在
varString = 'm v rho'
上,我的建议是继续做下去,并定义一个字典:
my_vars= {"m":"mass", "v":"volume", "rho":"density"}
然后 add the properties and setters dynamically to the class(而不是显式):
from functools import partial
def var_getter(varname, self):
return self._params[varname]
def var_setter(varname, self, value):
self._params[varname] = value
self.balance(varname)
for k,v in my_vars.items():
setattr(Stuff, k, property(fget=partial(var_getter, v), fset=partial(var_setter, v)))
这样,您只需要编写一次getter和setter即可。
如果您要使用几个不同的 setter/getter ,则仍然可以使用此技术。存储每个变量的getter或每个getter的变量-以更方便的方式存储。
一旦方程变得复杂,另一件事可能有用的是,您可以在源中将方程保留为字符串:
density_eq = sympy.sympify( "rho - m / v" )
使用这两个“技巧”,您甚至可能希望将变量和方程式定义在外部文本文件或CSV中。