SymPy中的尺寸分析

时间:2019-01-11 19:40:18

标签: python sympy pint

我正在尝试使用SymPy完成以下任务:

  1. 简化一些物理量和物理常数的代数方程。
  2. 进行尺寸分析以确保我的方程式正确
  3. 通过插入物理量的值来评估方程式。

下面是我的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)

m = \frac{E}{c^2}

太好了!现在该执行尺寸分析了。我将以焦耳为单位定义能量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)

Quantity(m,m)=\frac{Quantity(E,E)}{Quantity(speed_{oflight},c)^2}

它看起来不漂亮,但是表达是正确的。现在,我想看看m的值是什么,m的单位是什么。首先,我将在m中保存m_solve的解决方案:

m_solve = solve(eq_E,m)[0]
display(m_solve)

\frac{Quantity(E,E)}{Quantity(speed_{oflight},c)^2}

现在,我可以看到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,但似乎缺乏符号支持。)

1 个答案:

答案 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)

enter image description here

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)

enter image description here

这就是我想要的!您也可以做疯狂的事情,例如:

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)

enter image description here

免责声明:

  1. 有些零件很脏
  2. 有时候,一旦执行了equals()方法,就无法重用变量,而需要重新定义它。
  3. 它不会隐式转换单位(不能将米加到英寸等,如Pint可以)

如果对此感兴趣,请发表评论,我将尝试在其中添加更多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)