Символьные вычисления средствами Python. Часть1. Основы

image

Реализация алгоритмов на языке 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)

>>> var('u,v')
(u, v)
>>> f=sin(u)**2+tan(v) # переменная f автоматически становится символьной
>>> type(f)




Главное отличие между функциями 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)

>>> type(S(10)) # символьная константа десять



Разница между постоянной 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)

>>> 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>> 1/oo
0


Символ бесконечности в основном используется функциями limit () и integrate () при задании пределов интегрирования, о чем мы поговорим в одной из следующих статей.

Вывод


Рассмотренные в статье символьные вычисления отличаются от числовых методов тем, что результаты можно и дальше исследовать, например, определять экстремумы функций, решать уравнения со вложенными переменными и так далее.

Надеюсь, моя статья будет полезна всем интересующимся программированием на языке Python, студентам и тем, кто занимается научными исследованиями.

© Habrahabr.ru