【问题标题】:What exactly is the `subs` argument of the `evalf` method of SymPy Expression for? Like `s.evalf(subs={...})`?SymPy 表达式的 `evalf` 方法的 `subs` 参数究竟是什么?像`s.evalf(subs={...})`?
【发布时间】:2018-05-20 20:22:45
【问题描述】:

我偶然发现了这一点,因为它的行为不像我预期的那样。 s.evalf(subs={t: 0}) 到底是干什么用的?

它似乎既不是s.evalf().subs({t: 0}) 的“捷径”,也不是s.evalf().subs({t: 0}) 的“捷径”

import sympy

omega, t = sympy.symbols("omega, t")
s = sympy.pi * sympy.cos(omega*t)

# both result in 3.14159265358979
print(s.evalf().subs({t: 0}))
print(s.subs({t: 0}).evalf())
# results in 3.14159265358979*cos(omega*t)
print(s.evalf(subs={t: 0}))

结果

3.14159265358979
3.14159265358979
3.14159265358979*cos(omega*t)

在这方面找不到好的文档。也许有人知道答案?否则我可能需要查看源代码以了解更多信息...

【问题讨论】:

  • 你比较的两个不同的东西似乎是相同的。
  • 替代 :)
  • @percusse 如果可以替换,正如您评论的那样,为什么在表达式中根本不替换 t

标签: python numpy sympy


【解决方案1】:

evalf(subs=...) 试图避免因简单替换而导致的重要性丢失。

例如

>>> (x + y - z).subs({x: 1e100, y: 1, z: 1e100})
0
>>> (x + y - z).evalf(subs={x: 1e100, y: 1, z: 1e100})
1.00000000000000

朴素替换评估 1e100 + 1 - 1e100,它会丢失 1,因为默认精度 15 位不足以保留该信息。使用evalf(subs=...),表达式通过 evalf 算法运行,该算法对此类可能丢失重要性的问题非常小心。 subs 字典告诉 evalf 算法遇到哪些符号应该用数字替换。如果您对细节感兴趣,可以查看the source of evalf.py

【讨论】:

  • 非常感谢您的回答。但是,我很抱歉,我仍然看不出精确度是我上面的示例也不适用于evalf(subs=...) 的原因。为什么t 根本没有被替换?
  • @Jayjayyy 啊我没注意到。那是 SymPy 中的 known bugevalf(subs=...) 目前不起作用,除非您替换表达式中的每个符号。
【解决方案2】:

The documentation 只是说:

有关选项的信息,另请参阅 .evalf() 的文档字符串。

您可以在交互式会话中执行help(sympy.evalf),或者您可以在N 上单击source 并向上滚动到文档字符串。无论哪种方式:

   subs=<dict>
       Substitute numerical values for symbols, e.g.
       subs={x:3, y:1+pi}. The substitutions must be given as a
       dictionary.

如果您查看the source for evalf itself,它最终的作用是:

x = x.subs(evalf_subs(prec, options['subs']))

evalf_subs 所做的是:

def evalf_subs(prec, subs):
    """ Change all Float entries in `subs` to have precision prec. """
    newsubs = {}
    for a, b in subs.items():
        b = S(b)
        if b.is_Float:
            b = b._eval_evalf(prec)
        newsubs[a] = b
    return newsubs

所以,你可以看到,这与在调用eval 之前调用subs 并不完全相同。 (当然,它们都与调用subs 之后 eval 完全不同,尽管在一些简单的示例中它们最终会做同样的事情。)

意图似乎是subs= 可以在替换发生时应用其他evalf 选项。从源头上看,“其他evalf 选项”目前似乎只是prec,但可以想象,这在未来可能会发生变化。

无论如何,如果您尝试以给定的精度进行数值近似,则将prec 应用于所有替换是一个好主意。

但如果您尝试继续使用符号计算,这可能不是一个好主意。我怀疑这可能就是您在这里看到的,尽管这部分只是一个猜测。当然,无论t 是什么,cos(omega*0) 都将是 1。但是cos(omega*t) 其中t 在某个特定精度下在0 的1 ulp 内可能是1 加或减几个ulp,具体取决于omega 的值。所以,不能减少。

【讨论】:

  • 嗯,好的,所以中间有evalf_subs。但我似乎很难理解这如何使我上面的示例不再起作用。另外或替代地,我何时使用subs 参数以及何时在evalf 之前调用subs 方法?
  • @Jayjayyy 另外,更一般地说,您可能无法使用您在 SymPy 中发现的所有内容。如果subs(…).evalf(…) 始终是您想要的,您可以使用它。 (另一方面,执行evalf(…).subs(…) 可能与两者都非常不同;您更有可能出于不同目的需要两者,即使在这个特定示例中它是相同的。)
  • @Jayjayyy 在深入研究之后,我可以看到通过evalf_subs 可以 有所作为(请参阅更新的答案),但我仍然不能证明这就是它确实在这里有所作为的原因。有人可能需要更深入地调试(或通过源代码挖掘)才能确定。这足够了吗,还是您需要更多?
  • 这个答案不准确。您看到的subs(evalf_subs(...)) 代码只有一个后备。在普通操作下,替换字典被传递给 evalf 例程,该例程将表达式评估为给定的精度(避免失去重要性),并在变量进行时替换变量(here)。看我的回答。
【解决方案3】:

我的猜测是这类似于MathematicaReplace method。另外this answer 对类似问题可能会有所帮助。

基本上,它将 symbol t 替换为值 0。在您的示例中,这不是很重要:

print(s.evalf().subs({t: 0}))
print(s.subs({t: 0}).evalf())

两个语句的最终值相同。但是,在计算(例如,导数)时,顺序很重要。例如,

In [76]: x=sympy.Symbol('x')

In [77]: sympy.diff(x**2, x)
Out[77]: 2*x

In [78]: sympy.diff((x**2).subs({'x':5}), x)
Out[78]: 0

In [79]: sympy.diff(x**2, x).subs({'x':5})
Out[79]: 10

在第一个示例中,x 被替换为 5 before 微分(并且 25 对 x 的导数为 0),在第二个示例中,首先计算导数 (2*x ) 然后在这个派生表达式中,符号 x 被替换为 5,所以答案是 2*5=10


@abarnet 评论后编辑:

In [85]: import sympy
    ...: 
    ...: omega, t = sympy.symbols("omega, t")
    ...: s = sympy.pi * sympy.cos(omega*t)
    ...: 
    ...: # both result in 3.14159265358979
    ...: print(s.evalf().subs({t: 0}))
    ...: print(s.subs({t: 0}).evalf())
    ...: # results in 3.14159265358979*cos(omega*t)
    ...: print(s.evalf(subs={t: 0, omega: 1}))
    ...: 
    ...: 
3.14159265358979
3.14159265358979
3.14159265358979

因此,提供tsympy 来说是不够的——它无法在不知道omega 是什么的情况下评估什么是omega*0


编辑 2:

第二个示例有效,因为评估是在 替换之后执行的,而第三个示例尝试执行 numerical 评估 使用给定的替换,但它失败了以数字方式计算表达式(没有omega),因此它以符号形式保留表达式。第一个示例也有效,因为它只是将 t 替换为 0 并简化了表达式。然而,

In [86]: s.evalf()
Out[86]: 3.14159265358979*cos(omega*t)

编辑 3:

调查代码,很明显evalf 代码(实际上是evalf_* 函数之一)尝试使用给定选项评估each argument in args 独立subs),然后再对表达式进行求值。所以,在第一种情况下,s.evalf().subs({t: 0}),第一个eval() 不做任何事情(嗯,几乎...... - 它评估pi3.1415...)和subs({t: 0}) 用0 替换t 然后表达式被重新评估/简化为3.14...。在第二种情况下,首先执行替换,然后将pi 评估为3.1415...。在第三种情况下,s.args 中的每个参数都会被评估。代码在这里失败(因为它无法评估 omega 给定提供的 subs 选项)并返回输入表达式不变。

即第三个选项s.eval(subs={t: 0}) 要求定义s的所有参数。对此的检查是在here for the mul function 执行的(显然sympy master 在我安装的版本之前)。

要看到这一点,请尝试一个简单的表达式:

In [1]: import sympy
    ...: from sympy.core import evalf
    ...: t, x = sympy.symbols("t, x")
    ...: 
    ...: 

In [2]: (t*x).evalf(subs={t:0})
Out[2]: t*x

In [3]: evalf.evalf(t*x, 10, {t: 0})
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
~/.../lib/python3.5/site-packages/sympy/core/evalf.py in evalf(x, prec, options)
    1285         rf = evalf_table[x.func]
-> 1286         r = rf(x, prec, options)
    1287     except KeyError:

~/.../lib/python3.5/site-packages/sympy/core/evalf.py in evalf_symbol(x, prec, options)
    1203 def evalf_symbol(x, prec, options):
-> 1204     val = options['subs'][x]
    1205     if isinstance(val, mpf):

KeyError: 'subs'

During handling of the above exception, another exception occurred:

AttributeError                            Traceback (most recent call last)
~/.../lib/python3.5/site-packages/sympy/core/evalf.py in evalf(x, prec, options)
    1292             xe = x._eval_evalf(prec)
-> 1293             re, im = xe.as_real_imag()
    1294             if re.has(re_) or im.has(im_):

AttributeError: 'NoneType' object has no attribute 'as_real_imag'

During handling of the above exception, another exception occurred:

NotImplementedError                       Traceback (most recent call last)
<ipython-input-3-e075388b139d> in <module>()
----> 1 evalf.evalf(t*x, 10, {t: 0})

~/.../lib/python3.5/site-packages/sympy/core/evalf.py in evalf(x, prec, options)
    1284     try:
    1285         rf = evalf_table[x.func]
-> 1286         r = rf(x, prec, options)
    1287     except KeyError:
    1288         try:

~/.../lib/python3.5/site-packages/sympy/core/evalf.py in evalf_mul(v, prec, options)
    536     from sympy.core.numbers import Float
    537     for arg in args:
--> 538         arg = evalf(arg, prec, options)
    539         if arg[0] is None:
    540             continue

~/.../lib/python3.5/site-packages/sympy/core/evalf.py in evalf(x, prec, options)
    1308             r = re, im, reprec, imprec
    1309         except AttributeError:
-> 1310             raise NotImplementedError
    1311     if options.get("verbose"):
    1312         print("### input", x)

NotImplementedError: 

In [4]: 

【讨论】:

  • 这对于subs(…).evalf(…)evalf(…).subs(…) 之间的区别都是正确的,但我认为OP 已经理解了这部分,并且只是想弄清楚这两个evalf(…, subs=…) 中的哪一个像,以及它与哪个不同。
  • @abarnert 谢谢!我没有仔细阅读这个问题。我正在编辑答案。
  • 是的,同时提供tomega 将为您提供与所有三个相同的结果。但这仍然不能解释问题。毕竟,第二个示例 能够在不知道omega 的情况下评估omega*0 是什么(或cos(omega*0) 是什么),但第三个示例却不是。您的回答中没有任何内容甚至暗示可能是什么原因。这都是有价值的背景,但我认为这都是 OP 可能已经拥有的背景。唯一的问题是其他人是否可能有类似的问题,但没有有这种背景,在这种情况下,它可能对那个目的很有价值。
  • @abarnert 我不同意你的观点。第二个示例有效,因为评估是在 替换后执行的,而第三个示例尝试执行 numerical 评估 使用给定的替换,但它无法评估数字表达式(不带omega),因此它以符号形式保留表达式。
  • 但是如果给定了subs 参数,仍然会从evalf 内部调用subs 方法...为什么不能用0 代替t,因此cos(...)1 在数值评估之前?
【解决方案4】:

您可以将s.evalf(subs={t: 0}) 视为“在 t = 0 的值处评估 s”的一种方式。 evalf 机器试图在计算期间处理中间精度的同时为您提供所需精度的答案。 (Aaron 已经给出了一个很好的例子,如果这些值被简单地替换并按照 Python 规定的顺序进行评估,求和将给出错误的答案。)

【讨论】:

    【解决方案5】:

    我也在寻找解决此问题的方法。在asmeurer's code sample 的情况下,以下似乎可以解决问题:

    from sympy import symbols, Float
    x, y, z = symbols('x y z')
    
    (x + y - z).subs({x: Float(1e100, 100), y: 1, z: Float(1e100, 100)})
    

    正确计算为 1.0。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-09-08
      • 2012-04-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多