Символы и выражения: строим формулы

Прежде чем дифференцировать и решать, надо научиться создавать символы и собирать из них выражения — это азбука SymPy.

Символ (Symbol) — математическая переменная (например, x), с которой SymPy работает буквенно; из символов и операций строятся выражения.

Создание символов

Главная функция — symbols(). Она создаёт один или несколько символов сразу:

import sympy as sp

x = sp.symbols("x")              # один символ
a, b, c = sp.symbols("a b c")    # три сразу
i, j = sp.symbols("i j", integer=True)   # с предположением: целые

expr = a * x**2 + b * x + c      # обычное квадратное выражение
print(expr)                      # a*x**2 + b*x + c

Важный момент: имя переменной Python и имя символа — это разные вещи. x = symbols("igrek") создаст символ, который печатается как «igrek», но в коде вы зовёте его x. По привычке их делают одинаковыми, чтобы не путаться.

Предположения (assumptions)

SymPy можно подсказать свойства символа — это влияет на упрощения. Например, positive=True позволит упростить sqrt(x²) до x (без модуля):

ПредположениеЧто задаёт
positive=Trueсимвол строго больше нуля
real=Trueвещественное число
integer=Trueцелое число
nonzero=Trueне равно нулю (можно делить)

Подстановка значений

Метод .subs() заменяет символ на число или другое выражение — это мост от формулы к конкретному значению:

import sympy as sp
x = sp.symbols("x")
expr = x**2 + 2*x + 1

print(expr.subs(x, 3))      # 16 — подставили x=3, получили точно
print(expr.subs(x, sp.Rational(1, 2)))   # 9/4 — точная дробь

«Дерево выражения» руками на stdlib

Чтобы понять, что SymPy хранит выражения деревом, построим крошечный «символьный калькулятор» на чистом Python: представим выражение вложенными кортежами и вычислим его, подставив значение x:

# Выражение x^2 + 2*x + 1 как дерево из кортежей
# ('+', левое, правое), ('*', ...), ('^', ...), ('x',), число
expr = ('+',
            ('+', ('^', ('x',), 2),
                  ('*', 2, ('x',))),
            1)

def evaluate(node, x):
    if isinstance(node, (int, float)):
        return node
    op = node[0]
    if op == 'x':
        return x
    if op == '+':
        return evaluate(node[1], x) + evaluate(node[2], x)
    if op == '*':
        return evaluate(node[1], x) * evaluate(node[2], x)
    if op == '^':
        return evaluate(node[1], x) ** node[2]

print("При x=3 :", evaluate(expr, 3))    # 9 + 6 + 1 = 16
print("При x=0 :", evaluate(expr, 0))    # 0 + 0 + 1 = 1

Вывод:

При x=3 : 16
При x=0 : 1

Это ровно то, что .subs() делает внутри SymPy: обходит дерево и подставляет значение в листья-символы. Только настоящий SymPy умеет ещё и не вычислять, а преобразовывать такое дерево.

Как работает под капотом

Когда вы пишете a*x**2 + b*x + c, Python вызывает перегруженные операторы SymPy-объектов: ** создаёт узел Pow, *Mul, +Add. Результат — иммутабельное (неизменяемое) дерево: SymPy никогда не меняет выражение на месте, а строит новое. Это делает выражения безопасными для повторного использования и кэширования. Ещё SymPy автоматически делает мелкие упрощения при построении (например, x + x сразу станет 2*x), но крупные оставляет вам через simplify.

Частые ошибки

  • Забыть создать символ. Использование x без x = symbols("x") даст обычную Python-ошибку «имя не определено».
  • Думать, что .subs() меняет выражение. Оно возвращает новое выражение; старое не трогается (иммутабельность).
  • Игнорировать предположения. Без positive=True SymPy не упростит sqrt(x**2) — он не знает, что x ≥ 0.

Итог

  • Символы создаются через symbols(); из них и операций строятся выражения.
  • Предположения (positive, real, integer) управляют упрощениями.
  • .subs() подставляет значение и возвращает новое (точное) выражение.
  • Под капотом — иммутабельные деревья; .subs() — это обход дерева.
Проверьте себя
1. Что делает функция symbols() в SymPy?
AРешает уравнение
BСоздаёт один или несколько математических символов (переменных)
CУпрощает выражение
DПодставляет числа
2. Зачем символу задают предположение вроде positive=True?
AДля ускорения
BЧтобы SymPy мог делать корректные упрощения (например, sqrt(x**2) → x только если x ≥ 0)
CЭто обязательно для всех символов
DЧтобы запретить подстановку
3. Что возвращает метод expr.subs(x, 3)?
AИзменяет expr на месте
BНовое выражение с подставленным значением; исходное expr не меняется (иммутабельность)
CСписок значений
DПроизводную