Я хочу определить класс функций, которые следуют этому математическому определению:

< Сильный > Определение Для функции q (t) и двух значений a и b мы определяем как вариацию следующую функцию

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

Я пытаюсь эмулировать это определение поведения с помощью подкласса Function:

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)

Но у него были те же проблемы.

2
iiqof 4 Апр 2017 в 06:26

2 ответа

Лучший ответ

Я думаю, вам нужно немного изменить то, как вы это делаете.

Создание вашей Var функции.

Вы действительно хотите, чтобы Var в вашем примере был классом Function. sympy разработан вокруг функций, являющихся классами, и использует метод класса eval() для их оценки. Переопределение __call__ подкласса Function кажется очень нестандартным, и я не видел никаких встроенных функций sympy, которые его используют, поэтому я не думаю, что это правильно подходить. Один из способов сделать это - создать фабричную функцию для создания класса:

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 en и path прямо в 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() (согласно .subs() документы). Поэтому по умолчанию, когда вы делаете .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() ...

Я не совсем понимаю, что вы хотите с:

Есть ли хорошее руководство для построения произвольных математических функций на Python?

Я думаю, что sympy довольно хорош и имеет разумные документы и множество встроенных примеров в своей кодовой базе, которые легко взломать. В любом случае, запрос руководств не по теме (см. Пункт 4) в stackoverflow ;-).

1
Community 23 Май 2017 в 11:54

Это еще не ответ на проблему. Но, насколько я понимаю, вопрос нуждается в некотором обновлении.

Используя класс Variation, определенный в посте, я изменил 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, с)

Это Var(0,s), учитывая, что t отличается от a и b, а s не равен нулю.

1
rll 8 Апр 2017 в 13:42