2025, Oct 17 10:17
Как подставлять значения в параметрическое решение linsolve (SymPy)
Как подставлять значения в параметрические решения SymPy linsolve: оборачиваем в Tuple, используем free_symbols и subs, задаём нули или точные дроби.
Когда linsolve в SymPy возвращает параметрическое решение, в нём часто присутствуют свободные переменные. Чтобы получить конкретный вариант, обычно подставляют значения вместо этих параметров. Задача — сделать это аккуратно и единообразно, особенно если набор свободных переменных меняется от системы к системе.
Постановка задачи
Предположим, у вас есть вектор решения из linsolve, в котором несколько символов свободны. Для примера рассмотрим такую структуру:
from sympy import symbols
y2, y4, y5, y6, y7, y8 = symbols('y2 y4 y5 y6 y7 y8')
ans_vec = (
    -2.0*y2 - 2.0,
    4.0*y4 + 4.0*y7 + 2.0,
    y2,
    -2.0*y4 - 1.0*y5 - 2.0*y7 - 2.0,
    y4,
    y5,
    y6,
    y7,
    y8,
)
Здесь y2, y4, y5, y6, y7, y8 — свободные переменные. Если нужен конкретный экземпляр, можно подставить вместо них нули и получить числовой вектор вроде (-2.0, 2.0, 0, -2.0, 0, 0, 0, 0, 0). Чтобы выполнять такую подстановку надёжно, без ручной фильтрации, нужен контейнер, поддерживающий subs, и устойчивый способ собрать задействованные символы.
Что происходит на самом деле
В SymPy подстановки делают методом subs, но обычные кортежи Python не поддерживают механику подстановок SymPy. Ключевой приём — обернуть элементы решения в контейнер, понятный SymPy, чтобы subs мог проходить по структуре. Ещё один практический момент: набор свободных переменных меняется, поэтому выбирать имена вручную рискованно. Использование свойства free_symbols у выражения решает это, запрашивая напрямую у SymPy, какие символы встречаются.
Аккуратные схемы подстановки
Короткий путь — поместить последовательность в Tuple и вызвать subs с словарём. Если вы уже знаете, какие переменные зафиксировать, сопоставьте их явно:
from sympy import Tuple as TPack
# Подставить только известное подмножество символов
TPack(*ans_vec).subs({s: 0 for s in (y2, y4, y5, y6, y7, y8)})
# Результат: (-2.0, 2.0, 0, -2.0, 0, 0, 0, 0, 0)
Если состав свободных переменных плавающий и вы хотите занулить всё, что встречается в выражении, используйте free_symbols у обёрнутого объекта:
wrapped = TPack(*ans_vec)
wrapped.subs({t: 0 for t in wrapped.free_symbols})
# Результат: (-2.0, 2.0, 0, -2.0, 0, 0, 0, 0, 0)
Подставляемое значение необязательно должно быть нулём. Это может быть любой допустимый в выражении объект — например, целое число 1 или другой символ. Если нужна точная дробь вроде одной трети, избегайте записи 1/3, чтобы не получить десятичную аппроксимацию; используйте S(1)/3 для точного Rational.
from sympy import Symbol, S
z = Symbol('z')
wrapped.subs({t: z for t in wrapped.free_symbols})
wrapped.subs({t: S(1)/3 for t in wrapped.free_symbols})
Зачем это важно
В параметрических решениях список свободных переменных не фиксирован. Собирать словари подстановок вручную неудобно и чревато ошибками. Оборачивая решение в контейнер, который понимает subs, и опираясь на free_symbols, вы сохраняете процесс компактным и устойчивым. К тому же легко переключаться между занулением параметров, подстановкой целых чисел или точных дробей там, где это требуется.
Итоги
Для последующей обработки результатов linsolve воспринимайте ответ как выражение SymPy: оберните его в Tuple и применяйте subs. Можно нацелиться на конкретный поднабор символов или воспользоваться free_symbols, если хотите заменить всё, что встречается. Нужны точные дроби — предпочитайте S(1)/3 вместо 1/3, чтобы избежать непреднамеренных десятичных приближений.
Статья основана на вопросе на StackOverflow от пользователя some1fromhell и ответе пользователя smichr.