我正在尝试使用SymPy完成以下任务:
下面是我的MCVE问题,我想在其中使用mass-energy equivalence查找与给定能量相等的质量。 (我在Jupyter中运行了它,因为它可以使方程看起来很漂亮。)
盯着E=m*c**2
我想解决m
:
from sympy import *
E, m, c = symbols('E m c') # symbols, for energy, mass, and speed of light
eq_E = Eq(E, m*c**2) # define equation for E in terms of m and c
eq_m = Eq(m,solve(eq_E,m)[0]) # solve equation for m
display(eq_m)
太好了!现在该执行尺寸分析了。我将以焦耳为单位定义能量E
,而不必定义c
的单位,因为它是一个物理常数,并且我使用的是SI单位(这似乎是默认值)对于SymPy)。 我想找出1焦耳能量中有多少质量,并且我想知道该质量的单位。我将使用Quantity()
定义变量,然后将其设置为再次求等式,以便SymPy可以解决它:
from sympy.physics.units import Quantity, energy, joule, speed_of_light
from sympy import Eq, solve
m = Quantity('m') # Define unknown mass 'm' as a quantity
E = Quantity('E') # Define known energy 'E' as a quantity
E.set_dimension(energy) # E is a quantity of energy
E.set_scale_factor(1.0*joule, 'SI') # set energy to 1.0 Joules
eq_E = Eq(E,m*speed_of_light**2) # define E = mc^2
eq_m = Eq(m,solve(eq_E,m)[0]) # Solve E = mc^2 for m
display(eq_m)
它看起来不漂亮,但是表达是正确的。现在,我想看看m
的值是什么,m
的单位是什么。首先,我将在m
中保存m_solve
的解决方案:
m_solve = solve(eq_E,m)[0]
display(m_solve)
现在,我可以看到m_solve
有哪些单位吗?
print(m_solve.dimension)
AttributeError: 'Mul' object has no attribute 'dimension'
m_solve
的值如何?
print(m_solve.scale_factor)
AttributeError: 'Mul' object has no attribute 'scale_factor'
什么是Mul
?如何获得Quantity
?更一般而言,SymPy中是否有良好的工作流程来处理符号方程式并检查整个过程中的单位,然后最终评估这些方程式?如果不是SymPy,是否有好的选择? (我尝试了几种替代方法,最有希望的是pint,但似乎缺乏符号支持。)
答案 0 :(得分:1)
通过创建一个结合了 SymPy 和 Pint 的新类,我将其称为“ Sympint”,我可以做自己想要的事情。我使用属性symbol
(字符串),expression
(SymPy),unit
(品脱)和value
(浮点数)创建此类。下面是我原始问题的用例。 滚动到类定义的最后一个代码块。
第一个用例:我知道光速,但是我还不知道需要多少能量,只想看看m
处于什么单位:
# Sympint class defined at end of post
import pint; ureg = pint.UnitRegistry()
E = Sympint(symbol='E', unit=ureg.joule) # energy
c = Sympint(symbol='c', unit=ureg.meter/ureg.second, value=3.0e8) # speed of light
m = Sympint(symbol='m') # I don't know the units yet!
E.equals(m*c**2)
m = E.solve_for(m)
display(m)
m
以千克为单位!现在我想看看有多少质量等于9000焦耳的能量:
E = Sympint(symbol='E', unit=ureg.joule, value=9000) # energy
m = Sympint(symbol='m') # I don't know the units or the value
E.equals(m*c**2)
m = E.solve_for(m)
display(m)
这就是我想要的!您也可以做疯狂的事情,例如:
S = Sympint
x = S('x', unit=ureg.meter, value=5.0)
L = S('L', unit=ureg.meter, value=3.0)
Zw = S('Z_w')
T0 = S('T_0', unit=ureg.kelvin,value=300.0)
T0.equals(Zw**(x/L-S(1)/S(2))) # enclose '1' and '2' in Sympint so python doesn't evaluate them 1/2 as 0.5
display(T0)
# now solve for Zw, whatever that is
Zw = T0.solve_for(Zw)
display(Zw)
免责声明:
equals()
方法,就无法重用变量,而需要重新定义它。如果对此感兴趣,请发表评论,我将尝试在其中添加更多SymPy / Pint功能并将其放在GitHub上。
import sympy
import pint
ureg = pint.UnitRegistry()
# Class that inherits from sympy.Symbol but also gets the 'parent' attribute so we can find the Sympint instance from the symbol
class Symbol(sympy.Symbol):
def __init__(self,name,parent=None):
super().__new__(name=name,cls=sympy.Symbol) # call sympy.symbol __new__ method
self.parent = parent # assign parent
# Main Sympint class
class Sympint:
def __init__(self,
symbol=None,
expression=None,
unit=None,
value=None):
# If first input is a float or int, assign it to Value and make it dimensionless
# example: Sympint(42)
if isinstance(symbol,float) or isinstance(symbol,int):
self.value = symbol
self.unit = ureg.dimensionless
self.symbol = None
self.expression = sympy.Number(symbol)
return
# else, symbol should be a string or another sympy symbol
# Take input symbol as a string or a
# sympy.Symbol and store it as a sympy.Symbol
elif isinstance(symbol,str):
self.symbol = Symbol(symbol,parent=self)
else:
self.symbol = symbol
# If Sympint object has no expression,
# set its expression to its own symbol
# If it doesn't have a symbol, set it
# to an empty string
if expression is None:
if self.symbol is None:
self.expression = None
else:
self.expression = self.symbol
else:
self.expression = expression
# unit must be of type
# pint.unit.build_unit_class.<locals>.Unit
# (no error handling for this yet)
self.unit = unit
# Value must be float, won't work if some Sympints are floats and some Sympints are ints
if value is not None:
self.value = float(value)
else:
self.value = None
# Set the symbol of sympy part of the object
def set_symbol(self,string):
self.symbol = symbol(string,parent=self)
# Set the symbol equal to an expression.
def equals(self,other):
if other.expression != self.symbol:
self.expression = other.expression
if (self.unit is None or self.unit == ureg.dimensionless) and (other.unit is not None and other.unit != ureg.dimensionless):
self.unit = other.unit
if self.value is None and other.value is not None:
self.value = other.value
# solve the expression for other, and return a new Sympint for the solution
# currently just returns the first expression in the list. If you wanted
# to return multiple solutions, remove the [0] and iterate through them.
def solve_for(self,other):
eq = sympy.Eq(self.symbol,self.expression)
expression = sympy.solve(eq,other.symbol)[0]
output = Sympint(symbol=other.symbol,expression=expression)
output.evaluate()
return output
# Recursively evaluate self.expression to get the units and value of the expression
def evaluate(self):
if self.expression.is_Symbol:
self.symbol = self.expression.parent.symbol
self.value = self.expression.parent.value
self.unit = self.expression.parent.unit
else:
try:
self.value = float(self.expression)
self.unit = ureg.dimensionless
except:
output = Sympint()
if self.expression.is_Mul or self.expression.is_Add:
for i,arg_sympy in enumerate(self.expression.args):
arg = Sympint(expression=arg_sympy)
arg.evaluate()
if i == 0:
output = arg
else:
if self.expression.is_Mul:
output = output*arg
elif self.expression.is_Add:
output = output + arg
elif self.expression.is_Pow:
arg = Sympint(expression=self.expression.as_base_exp()[0])
exponent = Sympint(expression=self.expression.as_base_exp()[1])
arg.evaluate()
exponent.evaluate()
output = arg**exponent
self.unit = output.unit
self.value = output.value
# override the add, subtract, multiply, divide, and power operations
def __add__(self,other):
return self.override_operator(other,'__add__')
def __sub__(self,other):
return self.override_operator(other,'__sub__')
def __mul__(self,other):
return self.override_operator(other,'__mul__')
__rmul__ = __mul__
def __truediv__ (self,other):
return self.override_operator(other,'__truediv__')
def __intdiv__ (self,other):
return self.override_operator(other,'__intdiv__')
def __pow__(self,other):
return self.override_operator(other,'__pow__')
# helper function for overriding add, subtract, multiply, divide, and power operations
def override_operator(self,other,operator):
# if the other is not a Sympint, then it is either a number or a sympy number, so we make a Sympint out of it
if not isinstance(other,Sympint):
try:
expression = sympy.Number(other)
except:
#try expression = sympy.Symbol(other)
expression = other
try:
value = float(other)
unit = ureg.dimensionless
except:
value = None
unit = None
raise ValueError('cannot convert {} to value'.format(other))
other=Sympint(expression=expression,value=value,unit=unit)
# Now other is definitely a Sympint
# Apply operator to expressions
expression = getattr(self.expression,operator)(other.expression)
try:
unit = getattr(self.unit, operator)(other.unit)
except:
if self.unit is None or other.unit is None:
unit = None
if self.unit is None or other.unit is None: # if units are unknown
unit = None
elif operator == '__pow__' and other.unit == ureg.dimensionless:
unit = self.unit**other.value
else:
try: # try applying the operator to the units to see if we get a usable result
unit = getattr(self.unit, operator)(other.unit)
except:
if self.unit == other.unit:
unit = self.unit
else:
raise ValueError('ValueError: units "{}" and "{}" do not match for operation method "{}"'.format(self.unit,other.unit,operator))
# set the value of the result
if self.value is not None and other.value is not None:
value = getattr(self.value, operator)(other.value)
#elif self.value is not None:
# value = self.value
else:
value = None
return Sympint(symbol=None,expression=expression,unit=unit,value=value)
# override __repr__ geared specifically for Jupyter notebooks
def __repr__(self):
# first display the sympy expression
if self.symbol is not None and self.expression == self.symbol:
display(self.symbol)
elif self.symbol is not None and self.expression is not None:
display(sympy.Eq(self.symbol,self.expression))
elif self.expression is not None:
display(self.expression)
# then return the symbol, value, and unit for printing
if self.symbol is None:
symbol = 'unnamed'
else:
symbol = self.symbol
if self.unit is None:
unit = 'Unknown units'
else:
# can only convert to base units after multiplying by one!
unit = str((1.0*self.unit).to_base_units())[4:]
if self.value is None:
value = '\b' # delete space so we don't have two spaces
else:
value = self.value
# kludge alert: printing base units puts a '1.0 ' in front,
# so we trim off the first four characters.
return '{} = {} [{}]'.format(symbol,value,unit)