
При решении задач математического моделирования процессов и объектов часто очень практично использовать алгоритмы на языке Python с использованием символьных вычислений. Основываясь на библиотеке SymPy, Python с успехом справляется с решением уравнений и систем, интегрированием и дифференцированием, вычислением пределов, разложением в ряд и суммированием рядов, упрощением выражений, выполняет поиск решения дифференциальных уравнений и систем.
При использовании символьных вычислений пользователю предоставляется возможность управлять работой программы в процессе ее исполнения путём ввода любых допустимых функций с заданным количеством переменных.
Как преподаватель дисциплины «Компьютерная техника и программирование», в модуле, посвященном программированию на языке Python, я знакомлю студентов с возможностями этого языка для научных исследований. Вашему вниманию представляется цикл статей, в которых можно ознакомиться с символьными вычислениями на Python. Хочу сразу предупредить, что данные статьи не претендуют на абсолютную уникальность, так как собраны на основании материалов из различных источников, их цель – обучить студентов основам символьных вычислений.
Самым первым шагом на пути к символьным вычислениям является импортирование функций модуля SymPy с помощью pip, системы управления пакетами Python. Если вы с этим справились, сразу перейдем к объявлению переменных.
Примечание. Для сокращения записи во всех следующих примерах не приводится первая строка: from sympy import *
Явное объявление символьных переменных
Для символьных вычислений с помощью модуля SymPy символьные переменные и функции должны быть объявлены как таковые. В программах для математических вычислений, таких как Mathematica или Maple, переменные сразу рассматриваются как символьные. В Python же их необходимо принудительно объявить символьными, и сделать это можно несколькими путями. Самым простым будет использование функций symbols() или var(). Первая функция возвращает ссылку на символьный объект в виде какой-либо переменной. Вторая, без присваивания создает символьную переменную.
Пример кода
>>> x,y,a,b = symbols('x y a b') # созданы четыре символьные переменные, предыдущие же значения переменных затираются >>> f=a**3*x + 3*a**2*x**2/2 + a*x**3 + x**4/4 # переменная f становится автоматически символьной >>> type(f) <class 'sympy.core.add.Add'> >>> var('u,v') (u, v) >>> f=sin(u)**2+tan(v) # переменная f автоматически становится символьной >>> type(f) <class 'sympy.core.add.Add'>
Главное отличие между функциями symbols() и var() состоит в том, первая функция возвращает ссылку на символьный объект. Для использования в дальнейшем, ее нужно присвоить какой-либо переменной. Вторая, без присваивания, создает символьную переменную.
В функциях symbols() и var() можно объявлять символьные переменные с индексом:
Пример кода
>>> x=symbols('x:9'); x # диапазон индексов от 0 до 9 (x0, x1, x2, x3, x4, x5, x6, x7, x8) >>> x=symbols('x5:10'); x # диапазон индексов от 5 до 9 (x5, x6, x7, x8, x9) >>> x=var('x:9'); x # диапазон индексов от 0 до 9 (x0, x1, x2, x3, x4, x5, x6, x7, x8) >>> x=var('x5:10'); x # диапазон индексов от 5 до 9 (x5, x6, x7, x8, x9)
Также можно назначить тип и накладывать ограничения на символьные переменные прямо в функциях symbols() и var(). Иногда без таких ограничений очевидные преобразования не работают, например, сравните:
Пример кода
>>> x = symbols('x', integer=True) #назначаем целый тип >>> sqrt(x**2) Abs(x) >>> x = symbols('x', positive = True, integer=True) >>> sqrt(x**2) x >>> x = symbols('x') >>> sqrt(x**2) # это x, если x≥0 sqrt(x**2) >>> x = var('x', integer=True) >>> sqrt(x**2) Abs(x) >>> x = var('x', positive = True, integer=True) >>> sqrt(x**2) x >>> x = var('x') >>> sqrt(x**2) # это x, если x≥0 sqrt(x**2)
Чтобы создать контейнер для одиночного символа, используем аргумент seq=True:
>>> symbols('x',seq=True) (x,)
Определение действительных значений для символьных переменных:
>>> x, y, z = symbols('x,y,z', real=True) >>> x.is_real and y.is_real and z.is_real True
Функция S()
Иногда символьные выражения могут быть проинтерпретированы как числовые константы Python, а не SymPy. Поэтому для объявления символьных переменных, а также для преобразования числовых констант в символьные, применяют функцию S(), например, сравним:
>>> expr = x**2 + sin(y) + S(10)/2; expr x**2 + sin(y) + 5 >>> type(10) <class 'int'> >>> type(S(10)) # символьная константа десять <class 'sympy.core.numbers.Integer'>
Разница между постоянной Python и символьной состоит в том, что символьная константа может быть ��ычислена с заданной степенью точности, как показано в следующем примере в сравнении со стандартной функцией round():
z=1/7; z # вычисляет переменную z с процессорной точностью 0.14285714285714285 z1=S(1)/7; z1 1/7 z2=z1.n(30); z2 # вычисляет переменную z2 с точностью до 30 значащих цифр 0.142857142857142857142857142857 z3=round(z1,30); z3 0.14285714285714285
Cимвольные имена
Если в текущей сессии необходимо использовать символьную математику постоянно, то можно импортировать общепринятые символьные имена из модуля sympy.abc:
Пример кода
>>> import sympy.abc >>> dir(sympy.abc) ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', '_clash', '_clash1', '_clash2', 'a', 'alpha', 'b', 'beta', 'c', 'chi', 'd', 'delta', 'division', 'e', 'epsilon', 'eta', 'exec_', 'f', 'g', 'gamma', 'greeks', 'h', 'i', 'iota', 'j', 'k', 'kappa', 'l', 'lamda', 'm', 'mu', 'n', 'nu', 'o', 'omega', 'omicron', 'p', 'phi', 'pi', 'print_function', 'psi', 'q', 'r', 'rho', 's', 'sigma', 'string', 'symbols', 't', 'tau', 'theta', 'u', 'upsilon', 'v', 'w', 'x', 'xi', 'y', 'z', 'zeta']
Имя переменной из пространства имен можно удалить командой del имя1, имя2,..:
>>> type(x) <class 'sympy.core.symbol.Symbol'> >>> del x,y >>> x NameError: name 'x' is not defined
Для восстановления значений стандартных констант, а также имен некоторых функций, нужно повторно загрузить модуль sympy.
>>> from sympy import *
Метод subs(...)
Следует помнить, что при записи символьного выражения может автоматически выполняться его упрощение, например:
>>> a,b,c,d,x,y,z,u,v,w = symbols('a b c d x y z u v w') >>> x - z + 20 -z- 15 + 3*sin(pi/2)+2*z x + 8
Метод subs(...) используется для вычисления символьного выражения при заданных значениях переменных, например:
>>> a, x = symbols('a x') >>> f= a**3*x + 3*a**2*x**2/2 + a*x**3 + x**4/4 >>> f.subs(a,1) # в выражение f вместо переменной a была подставлена единица x**4/4 + x**3 + 3*x**2/2 + x
Если в методе subs использовать два аргумента, то они интерпретируются как subs(old,new), т.е. старый идентификатор old заменяется новым new. Аргумент метода subs() может быть последовательностью, которая должна содержать пары (old,new), а может быть символьным выражением, например:
>>> a,b,c,d,x,y,z = symbols('a b c d x y z') >>> f=a*x**3 +b*y**2 + c*z+d >>> f.subs([(a,1),(b,2),(c,3),(d,4)]) # выполнена подстановка a=1, b=2, c=3, d=4 x**3 + 2*y**2 + 3*z + 4 >>> pr= x**3+4*x**2+6*x+10 >>> pr.subs(x,1/x) # выполнена подстановка символьного выражения 10 + 6/x + 4/x**2 + x**(-3)
Обратим ваше внимание на следующую особенность работы с переменными (символьными и обычными переменными Python). Выполним следующий код:
>>> x='Hello' >>> pr=x+'world' >>> pr 'Helloworld' >>> x='AAA' #присвоили символьной переменной x новое значение >>> pr 'Helloworld'
Здесь действует правило: если переменная изменилась, то созданное ранее выражение, содержащее эту переменную, не пересчитывается автоматически. Это правило срабатывает и для обычных переменных Python.
Операции с дробями
Модуль SymPy может проводить вычисления с дробями и приводить их к общему знаменателю, например, сравните:
>>> S(1)/3+S(2)/5 11/15 >>> 1/3+2/5 0.7333333333333334
Функции Rational(числитель, знаменатель) и Integer(...) используются для создания рациональных дробей без десятичного округления:
>>> z=Rational(1, 7)+Rational(2, 5); z 19/35 >>> Integer(1)/Integer(5) 1/5 >>> 1/5 0.2 >>> z=Integer(1)/Integer(5)+Rational(2, 7); z 17/35
Округления вычислений
В символьных вычислениях работает правило – если ничего не сказано, не делать никаких округлений. Посмотрите, как в первом случае Python преобразует выражение, но оставит в записи ответа квадратный корень и не выполнит никаких округлений, а во втором, так как одно из чисел задано с десятичной точкой, результат будет приближенным:
>>> sqrt(20) 2*sqrt(5) >>> sqrt(20.0) # в выражении используется число с десятичной точкой 4.47213595499958
Для любого символьного объекта существует метод evalf(...)(evaluate float), который возвращает его десятичное представление:
>>> sqrt(20).evalf() # функция sqrt() модуля sympy 4.47213595499958 >>> E.evalf() 2.71828182845905
В методе evalf([n,...]) можно использовать аргумент, задающий точность результата (n = количество значащих цифр)
>>> sqrt(20).evalf(30) 4.47213595499957939281834733746 >>> pi.evalf(20) 3.1415926535897932385
Также всегда нужно помнить, что вещественная арифметика не возвращает точный результат, сравните:
>>> from sympy import * >>> one=S('one') >>> one = cos(1)**2 + sin(1)**2 >>> one.evalf() # равно 1 1.00000000000000 >>> (one-1).evalf() # должно быть равно 0 -0.e-124
Если известно, что результат содержит погрешность вычислений, то с помощью опции chop=True метода evalf() ее можно удалить. Очень маленькое значение вещественной или мнимой части результата в этом случае заменяется нулем. Возьмем предыдущий пример:
>>> (one-1).evalf() # должно быть равно 0 -0.e-124 >>> (one - 1).evalf(chop=True) 0
Бесконечность
После выполнения первой строки from sympy import * становится доступен символ бесконечности – oo (две буквы „o‟), с которым тоже можно выполнять определенные операции:
>>> oo+1 oo >>> 1000000<oo True >>> 1/oo 0
Символ бесконечности в основном используется функциями limit() и integrate() при задании пределов интегрирования, о чем мы поговорим в одной из следующих статей.
Вывод
Рассмотренные в статье символьные вычисления отличаются от числовых методов тем, что результаты можно и дальше исследовать, например, опр��делять экстремумы функций, решать уравнения со вложенными переменными и так далее.
Надеюсь, моя статья будет полезна всем интересующимся программированием на языке Python, студентам и тем, кто занимается научными исследованиями.
