Мне сложно инкапсулировать большую часть логики с возможностью повторного использования при написании sympy
кода.
Я хотел бы написать функцию следующим образом:
def compute_integral(fn):
s = symbols_new() # do not apply lexicographic identity, this symbol identity is unique
return integrate( fn(s), (s, 0, 1))
Если я просто использую обычный symbols()
для создания экземпляра s
, я рискую fn
иметь тот же символ где-то внутри его определяющего выражения, например, может произойти следующее:
def compute_integral(fn):
s = symbols("s")
return integrate(fn(s), (s, 0, 1))
...
import sympy as sp
from sympy.abc import *
t = sp.sin(3 + s)
f = sp.lambdify([x], x**2 + t * s * x)
compute_integral(f) #< ---- symbol s is already present in the function, now the integral will produce the wrong result
То, что я делаю сейчас, чтобы свести к минимуму эту проблему, далеко не идеально:
compute_integral_local_var = 0
def compute_integral(fn):
global compute_integral_local_var
compute_integral_local_var += 1
s = symbols("local_{}".format(compute_integral_local_var))
return integrate(fn(s), (s, 0, 1))
Я хочу избежать подобных коллизий без добавления специальной глобальной переменной. Есть ли лучшие подходы для достижения такого типа инкапсуляции в API sympy
?
Возможно, это не имеет отношения к делу, но почему вы используете функцию Sympy integrate
для результата лямбдификации? Вам следует использовать методы интеграции Sympy для выражений, которые все еще являются полностью символическими.
@jared, потому что, в отличие от выражения, lambdify
позволяет применить аргумент, вместо этого выражение само по себе не может интерпретироваться как интегрируемая функция, если только оно не является членом пары (expression, symbol)
@jared, когда вы применяете аргумент к lambdify
, он возвращает инкапсулированное (полностью символическое) выражение перед передачей integrate
Нет, lambdify
преобразует символическое выражение Sympy в числовую функцию, которую могут использовать библиотеки, ориентированные на числовые значения, такие как numpy, scipy и mpmath.
Я использую последнюю версию Sympy, версию 1.13.2, и предоставленный вами блок кода не запускается: возникает ошибка.
Кажется, существуют некоторые заблуждения, которые я хотел бы прояснить. Рассмотрим этот блок кода:
import sympy as sp
from sympy.abc import s, x
t = sp.sin(3 + s)
print("t:", t)
e = x**2 + t * s * x
print("e:", e)
# t: sin(s + 3)
# e: s*x*sin(s + 3) + x**2
Здесь я создаю два символических выражения: t
и e
. t
содержит символьную функцию sin
, а e
состоит из t
. Вы можете рассматривать e
как функцию, но в терминологии Sympy e
— это символическое выражение.
Далее в вашем примере вы выполнили что-то вроде этого:
f = sp.lambdify([x], e)
который создает числовую функцию, которая будет оцениваться NumPy. Затем вы попытались символически проинтегрировать эту числовую функцию, что абсолютно неверно. С помощью SymPy вы можете выполнять только символическую интеграцию символических выражений.
Если все, что вы пытаетесь выполнить, — это символическое интегрирование, то этой команды достаточно:
sp.integrate(e, (x, 0, 1))
# s*sin(s + 3)/2 + 1/3
Если вы хотите объединить несколько символических выражений в одном диапазоне, вы можете создать собственную функцию:
def compute_integral(expr, sym):
return sp.integrate(expr, (sym, 0, 1))
print(compute_integral(e, x))
# s*sin(s + 3)/2 + 1/3
print(compute_integral(t, x))
# sin(s + 3)
Обновлено:
Судя по небольшим деталям, которыми вы поделились, лямбда-функция Python и символы Dummy
Sympy могут дать то, что вы хотите. Символ Dummy
гарантированно уникален: он используется, когда необходим временный символ или когда имя символа не важно.
def compute_integral(func):
s = sp.Dummy()
return sp.integrate(func(s), (s, 0, 1))
t = sp.sin(3 + s)
f = lambda x: x**2 + t * s * x
compute_integral(f)
# s*sin(s + 3)/2 + 1/3
Пожалуйста, пожалуйста, пожалуйста: не используйте функции, созданные с помощью lambdify
, в качестве аргументов функций Sympy: обычно это не работает. Вы привели следующий пример:
from sympy.abc import r, s
sp.lambdify([r], r**2)(s).diff(s)
# out: 2*s
Вам повезло: числовая функция, сгенерированная lambdify
, содержит только простые операции Python (в данном случае умножение). Если исходное символическое выражение содержит другие функции, такие как sin, cos, exp и т. д., вы получите ошибку:
sp.lambdify([r], sin(r))(s).diff(s)
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
AttributeError: 'Symbol' object has no attribute 'sin'
The above exception was the direct cause of the following exception:
TypeError Traceback (most recent call last)
Cell In[50], line 1
----> 1 sp.lambdify([r], sin(r))(s).diff(s)
File <lambdifygenerated-4>:2, in _lambdifygenerated(r)
1 def _lambdifygenerated(r):
----> 2 return sin(r)
TypeError: loop of ufunc does not support argument 0 of type Symbol which has no callable sin method
действительно, пару (expression, symbol)
можно интерпретировать как функцию (хотя семантика менее ясна). В этом случае, конечно, его можно заставить работать, однако проблема остается, если 1) мы хотим использовать новый символ и 2) хотим быть уверены, что новый символ отсутствует на других объектах, которыми манипулируют, тогда у вас есть прибегнуть к вышеупомянутым уловкам (и из вашего ответа я делаю вывод, что ничего подобного gensym
на sympy
не существует)
не следуя вашему комментарию о том, что lambdify
становится «числовой функцией». На самом деле очень легко продемонстрировать контрпример: sp.lambdify([r], r**2)(s).diff(s)
возвращает 2*s
Мать драконов, что я только что увидел... В SymPy вы многого не понимаете. Дайте мне полчаса на редактирование ответа.
@UnkemptPanda обновил ответ.
Если вы не против, чтобы в названиях ваших символов было немного тарабарщины, вы можете посмотреть uuid
.
import uuid
def compute_integral(fn):
s = symbols(f"s_{uuid.uudi4()}") # will get something like 's_5a324a9c-510d-42d1-8fa8-7a613e3e093f'
return integrate(fn(s), (s, 0, 1))
Теоретически это не предотвращает конфликт имен на 100%, но практически это довольно близко к 100%.
да, uuid на самом деле решает проблему риска столкновений. Это не самый элегантный подход, но его можно преобразовать в анонимный фабричный метод символов.
Основная проблема заключается в том, что будет трудно разобраться, когда интеграл не может быть вычислен (что происходит в большинстве случаев).
Это похоже на «gensym» в Лиспе… может быть, это еще одно ключевое слово для поиска?