在 sympy 中自定义 Function 子类的 subs() 功能

Customising the subs() functionality of Function subclasses in sympy

我想定义一个 class 函数,遵循这个数学定义:

定义 给定函数 q(t) 和两个值 a 和 b,我们将以下函数定义为变体

         /
         | q(t) if s=0 or t=a or t=b
v(t,s) = |
         | v(t,s) else
         \ 

我正在尝试使用 Function subclass 来模拟此行为定义:

from sympy import Function
class Variation(Function):
    # Initialize the function with the desired properties
    def __init__(self, path, st, en, name='\vartheta'):
        self.path = path
        self.st = st
        self.en= en
        self.ends = [self.st, self.en]
        self.name = name

    # here I define the behaviour when called
    def __call__(self, tt, ss):
        if tt in self.ends:
            return self.path(tt)
        elif ss == 0:
            return self.path(tt)
        else:
            return Function(self.name)(tt,ss)  # This is the part that fails to behave 

该函数在调用时表现良好:

from sympy import *
s,t,a,b = symbols('s t a b')
c = Function('c')

Var = Variation(c, a, b)
Var(t,s), Var(a,s), Var(t,0) 

> \vartheta(t,s), q(a), q(t)

但正如预期的那样,如果我们这样做:

Var(t,s).subs(t,0)

> \vartheta(t,0)

有没有办法修改 .subs() 的方法行为?因为据我所知,integrate() 函数使用了 subs()

我也尝试将 Function(self.name)(tt,ss) 更改为 self(tt,ss) 但这给了我一个无限循环(也是预期的)。

同理,是否有在 python 上构建任意数学函数的良好指南?

编辑:已尝试

def Var(t,s):
    return Piecewise((c(t), s==0), (c(t), t==a), (c(t), t==b), (Function('v')(t,s), True ))

Var(t,s).subs(t,0)

但它有同样的问题。

这还不是问题的答案。但据我了解,这个问题需要一些翻新。

使用post中定义的Variation class,我将c修改为q只是为了符合提供的函数定义,只是为了检查我是否正确理解了问题:

from sympy import *
s,t,a,b = symbols('s t a b')
q = Function('q')

Var = Variation(q, a, b)
Var(t,s), Var(a,s), Var(t,0) 

(\vartheta(t, s), q(a), q(t))

符合预期:

Var(t,s).subs(t,0)

\vartheta(0, s)

Var(0,s),因为 tab 不同,并且 s 不为零。

我认为您需要稍微调整一下处理方式。

正在创建您的 Var 函数。

您确实希望示例中的 Var 成为 Function class。 sympy 是围绕 classes 函数设计的,并使用 eval() class 方法来计算它们。覆盖 Function subclass 的 __call__ 似乎非常不标准,我还没有看到任何使用它的 sympy 内置函数,所以我不不认为这是正确的方法。一种方法是创建一个工厂函数来为您创建 class:

def Variation(path_, st_, en_, v_):

    class Variation(Function):

        nargs = 2

        path = path_
        st = st_
        en = en_
        ends = [st, en]
        v = v_

        @classmethod
        def eval(cls, tt, ss):
            if tt in cls.ends:
                return cls.path(tt)

            if ss == 0:
                return cls.path(tt)

            return cls.v(tt, ss)

    return Variation

Var = Variation(c, a, b, Function(r'\vartheta'))
  • 我已经用实际函数替换了你的 'name' 变量,这看起来更明智。

现在您可以使用标准标志创建变体并防止立即求值(如果需要):

# by default the function is immediately evaluated...
Var(a, t)
>>> c(a)
# ...but that can be overridden
Var(a, t, evaluate=False)
>>> Variation(a, t)

您也可以通过展平 Var 函数并将 st enpath 参数直接传递给 eval() 来解决这个问题工厂功能层:

class Variation(Function):

    @classmethod
    def eval(cls, path, st, en, v, tt, ss):
        if tt in [st, en]:
            return path(tt)
        elif ss == 0:
            return path(tt)
        else:
            return v(tt, ss)

Variation(c, a, b, Function(r'\vartheta'), a, t)
>>> Variation(c, a, b, \vartheta, a, t)

请注意,由于您可以覆盖 .eval(),因此您可以对其进行修改,使其不会根据需要自动简化,而只是返回 cls:

的新实例
class Variation(Function):
    no_eval = True

    @classmethod
    def eval(cls, tt, ss):

        if cls.no_eval:
            return cls(tt, ss, evaluate=False)
        # etc.

自定义 .subs()

默认情况下,每当您执行 subs() 时,sympy 也会执行 eval()(根据 the .subs() docs)。因此,默认情况下,当您使用您的特殊值之一执行 .subs() 时,将调用 .eval() 并简化函数。

但是,您现在可以覆盖 ._eval_subs() 并执行您自己的操作,如果您愿意:

class Variation(Function):
    ...
    def _eval_subs(self, old, new):
        # return self to do no substitutions at all
        return self
        # return None to continue normally by next calling _subs on the arguments to this Function
        # return some other Expression to return that instead.

请注意,._eval_subs() 返回的任何内容随后也会被 .eval() 编辑。如果你想解决这个问题,你可以按照上面的解释覆盖 .eval()

所以我认为这回答了关于如何修改 .subs() 行为的问题...

不太明白你想用什么:

is there a good guide to construct arbitrary mathematical functions on python?

我认为 sympy 非常好,并且在其代码库中有合理的文档和许多内置示例,很容易从中借鉴。无论如何,在 Whosebug 上寻求指导是 off-topic (see point 4) ;-).