11

我想将一个字符串拆分3cm/µs² + 4e-4 sqmiles/km/h**2为它的SI 单位(在本例中为m/s**2)和它的大小(以该单位的倍数表示)。

由于 sympy 提供了解析模块许多物理单位和 SI 前缀,我想使用 sympy 是个好主意。但是,实现这一目标的好方法是什么?我会写一个类似下面的算法,但我想避免重新发明一个方轮:

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

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

4

2 回答 2

4

单位

一个解决方案是从 SymPyunits模块中收集所有单元,并使用它们来替换由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 支持做出任何承诺。

如果你实现 new Units,请考虑在 github 上向他们提出 pull request。要编辑的文件应该是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) 

安全

sympifyeval如果您要将其用于面向 Web 的应用程序,则可利用的用途!

于 2013-04-09T09:01:04.437 回答
3

我发现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

以下函数将scipyCODATA 常量作为数量添加到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

yt解决了与您的问题类似的问题。查看测试文件以了解它是如何使用的。

于 2013-12-18T16:57:10.837 回答