如何解析和简化像'3cm /μs²+ 4e-4 sqmiles / km / h ** 2'的字符串正确处理物理单位?

时间:2013-04-09 07:25:55

标签: python parsing sympy

我想将3cm/µs² + 4e-4 sqmiles/km/h**2之类的字符串拆分为SI unit(在本例中为m/s**2)及其大小(以该单位的倍数)。

由于sympy同时提供parsing modulemany physical units and SI prefixes,我猜使用sympy会是一个好主意。但实现这一目标的好方法是什么?我会写一个类似下面的算法,但我想避免重新发明方形轮:

  • 将数字和字母(除4e-4之类的语法除外)和空格(除非它在显式运算符旁边)之间的转换视为乘法,然后标记化
  • 用SI表示替换每个非数字标记(也检查SI前缀)
  • 将新表达式简化为Magnitude * some SI units(在不一致的单位上提供有意义的错误消息,例如Cannot add m**2 to s

这可以通过现有手段轻松实现吗?或者如何最好地实施?

2 个答案:

答案 0 :(得分:3)

单元

解决方案是从SymPy units模块收集所有单元并使用它们替换sympify

创建的符号
>>> import sympy.physics.units as u 
... subs = {} 
... for k, v in u.__dict__.items(): 
...     if isinstance(v, Expr) and v.has(u.Unit): 
...         subs[Symbol(k)] = v # Map the `Symbol` for a unit to the unit

>>> # sympify returns `Symbol`s, `subs` maps them to `Unit`s
>>> print sympify('yard*millimeter/ly').subs(subs)
127*m/1313990343414000000000

如果符号不在units中,则只会将其打印为未知符号(例如barn

>>> print sympify('barn/meter**2').subs(subs)
barn/m**2 

但您可以随时在subs字典中添加内容。

>>> subs[Symbol('almost_meter')] = 0.9*u.meter
... sympify('almost_meter').subs(subs)
0.9*m

SI前缀不能完全像您想要的那样工作。您需要添加一个乘法符号(或者希望它是一个明确实现的km之类的公共单位)。此外,由于它们不是Unit个实例,而是Integer个实例,因此您必须将它们添加到subs

>>> import sympy.physics.units as u
... subs = {} 
... for k, v in u.__dict__.items(): 
...     if (isinstance(v, Expr) and v.has(u.Unit)) or isinstance(v, Integer): 
...         subs[Symbol(k)] = v 

>>> print sympify('mega*m').subs(subs)
1000000*m 

对于unicode,您可能需要一些预处理。我不认为SymPy对unicode支持作出任何承诺。

如果您实施新的Unit,请考虑在github上向他们发送拉取请求。要编辑的文件应为sympy/physics/units.py

空格和隐式乘法

在SymPy的开发版本中,您可以找到用于假定隐式乘法的代码,其中写入了适当的空格:

>>> from sympy.parsing.sympy_parser import (parse_expr,
... standard_transformations, implicit_multiplication_application)

>>> parse_expr("10sin**2 x**2 + 3xyz + tan theta",
...            transformations=(standard_transformations + 
...                             (implicit_multiplication_application,)))
3*x*y*z + 10*sin(x**2)**2 + tan(theta) 

安全

sympify使用eval,如果您打算将其用于面向网络的应用程序,则可以利用它!

答案 1 :(得分:1)

我发现astropy有一个好的单位模块。经过一些准备,你可以做到

import astropy.units as u
from functools import reduce
u.Unit('MeV/fm').si #160.218 N
eval('1*MeV/fm+3*N',u.__dict__).si #163.21765649999998 N

from astropy.units import imperial
u.__dict__.update(imperial.__dict__)
u.sqmiles = u.mile**2
eval('3*cm/Ys**2 + 4e-4*sqmiles/km/h**2',u.__dict__).si #7.993790464000001e-08 m / s2

以下函数将scipy CODATA常量作为astropy单位的数量添加

def units_and_constants():
    """
    >>> u = units_and_constants()
    >>> u.hartree_joule_relationship
    <Quantity 4.35974434e-18 J>

    >>> eval('1*MeV/fm+3*N',u.__dict__).si
    <Quantity 163.21765649999998 N>

    """
    import astropy.units as u
    from astropy.units import imperial
    u.__dict__.update(imperial.__dict__)
    from scipy.constants import physical_constants, value, unit
    import string
    def qntty(x): 
        un = unit(x)
        va = value(x)
        if un:
            return va*eval(un.strip().replace(' ','*').replace('^','**'),u.__dict__)
        else:
            return va
    u.sr = u.radian**2
    u.E_h = qntty('hartree-joule relationship')
    u.c = qntty('speed of light in vacuum')
    u.C_90 = (1+4.6e-8)*u.C 
    codata = {}
    for n, t in physical_constants.items():
        v = qntty(n)
        for x in string.punctuation+' ':
            n = n.replace(x,'_')
        codata[n] = v
    u.__dict__.update(codata)
    return u

yttackles a problem similar to yours。 查看Test file以查看其使用方式。