Время на прочтение
9 мин
Количество просмотров 25K
Аппарат линейной алгебры применяют в самых разных областях — в линейном программировании, эконометрике, в естественных науках. Отдельно отмечу, что этот раздел математики востребован в машинном обучении. Если, например, вам нужно поработать с матрицами и векторами, то, вполне возможно, на каком-то шаге вам придётся решать систему линейных алгебраических уравнений (СЛАУ).
СЛАУ — мощный инструмент моделирования процессов. Если некая хорошо изученная модель на основе СЛАУ годится для формализации задачи, то с высокой вероятностью её можно будет решить. А вот насколько быстро — это зависит от вашего инструментария и вычислительных мощностей.
Я расскажу про один из таких инструментов — Python-пакет scipy.linalg из библиотеки SciPy, который позволяет быстро и эффективно решать многие задачи с использованием аппарата линейной алгебры.
В этом туториале вы узнаете:
- как установить scipy.linalg и подготовить среду выполнения кода;
- как работать с векторами и матрицами с помощью NumPy;
- почему scipy.linalg лучше, чем numpy.linalg;
- как формализовать задачи с использованием систем линейных алгебраических уравнений;
- как решать СЛАУ с помощью scipy.linalg (на реальном примере).
Если можно — сделай тут habraCUT! Важно, чтобы этот ^^ список люди прочитали и заинтересовались
Когда речь идёт о математике, изложение материала должно быть последовательным — таким, чтобы одно следовало из другого. Эта статья не исключение: сначала будет много подготовительной информации и только потом мы перейдём непосредственно к делу.
Если готовы к этому — приглашаю под кат. Хотя, честно говоря, некоторые разделы можно пропускать — например, основы работы с векторами и матрицами в NumPy (если вы хорошо знакомы с ним).
Установка scipy.linalg
SciPy — это библиотека Python с открытым исходным кодом для научных вычислений: решение СЛАУ, оптимизация, интеграция, интерполяция и обработка сигналов. Помимо linalg, она содержит несколько других пакетов — например, NumPy, Matplotlib, SymPy, IPython и pandas.
Среди прочего, scipy.linalg содержит функции для с работы с матрицами — вычисление определителя, инверсия, вычисление собственных значений и векторов, а также сингулярное разложение.
Чтобы использовать scipy.linalg, вам необходимо установить и настроить библиотеку SciPy. Это можно сделать с помощью дистрибутива Anaconda, а также системы управления пакетами и инсталлятора Conda.
Для начала подготовьте среду выполнения для библиотеки:
$ conda create --name linalg
$ conda activate linalg
Установите необходимые пакеты:
(linalg) $ conda install scipy jupyter
Эта команда может работать долго. Не пугайтесь!
Я предлагаю использовать Jupyter Notebook для запуска кода в интерактивной среде. Это не обязательно, но лично мне он облегчает работу.
Перед открытием Jupyter Notebook вам необходимо зарегистрировать экземпляр conda linalg, чтобы использовать его в качестве ядра при создании ноутбука. Для этого выполните следующую команду:
(linalg) $ python -m ipykernel install --user --name linalg
Теперь можно открыть Jupyter Notebook:
$ jupyter notebook
Когда он загрузится в вашем браузере, создайте новый notebook, нажав New → linalg:
Чтобы убедиться, что установка библиотеки SciPy прошла успешно, введите в ноутбуке:
>>>
In [1]: import scipy
NumPy для работы с векторами и матрицами (куда же без него)
Сложно переоценить роль векторов и матриц при решении технических задач и, в том числе, задач машинного обучения.
NumPy — это наиболее популярный пакет для работы с матрицами и векторами в Python. Часто его применяют в сочетании с scipy.linalg. Чтобы начать работу с матрицами и векторами, нужно импортировать пакет NumPy:
>>>
In [2]: import numpy as np
Для представления матриц и векторов NumPy использует специальный тип, называемый ndarray. Чтобы создать объект ndarray, вы можете использовать array ().
Например, вам нужно создать следующую матрицу:
Создадим матрицу как набор вложенных списков (векторов-строк):
>>>
In [3]: A = np.array([[1, 2], [3, 4], [5, 6]])
...: A
Out[3]:
array([[1, 2],
[3, 4],
[5, 6]])
Заметьте, что приведённый выше вывод (Outp[3]) достаточно наглядно показывает получившуюся матрицу.
И ещё: все элементы матрицы должны и будут иметь один тип. Это можно проверить с помощью dtype.
>>>
In [4]: A.dtype
Out[4]:
dtype('int64')
Здесь элементы являются целыми числами, поэтому их общий тип по умолчанию — int64. Если бы среди них было хотя бы одно число с плавающей точкой, все элементы получили бы тип float64:
>>>
In [5]: A = np.array([[1.0, 2], [3, 4], [5, 6]])
...: A
Out[5]:
array([[1., 2.],
[3., 4.],
[5., 6.]])
In [6]: A.dtype
Out[6]:
dtype('float64')
Чтобы вывести на экран размерность матрицы, можно использовать метод shape:
>>>
In [7]: A.shape
Out[7]:
(3, 2)
Как и ожидалось, размерность матрицы A 3×2, то есть A имеет три строки и два столбца.
При работе с матрицами часто приходится использовать операцию транспонирования, которая столбцы превращает в строки и наоборот. Чтобы транспонировать вектор или матрицу (представленную объектом типа ndarray), вы можете использовать .transpose () или .T. Например:
>>>
In [8]: A.T
Out[8]:
array([[1., 3., 5.],
[2., 4., 6.]])
Чтобы создать вектор, также можно использовать.array(), передав туда список значений в качестве аргумента:
>>>
In [9]: v = np.array([1, 2, 3])
...: v
Out[9]:
array([1, 2, 3])
По аналогии с матрицами, используем .shape(), чтобы вывести на экран размерность вектора:
>>>
In [10]: v.shape
Out[10]:
(3,)
Заметьте, что она выглядит как (3,), а не как (3, 1) или (1, 3). Разработчики NumPy решили сделать отображение размерности векторов так же, как в MATLAB.
Чтобы получить на выходе размерность (1, 3), нужно было бы создать вот такой массив:
>>>
In [11]: v = np.array([[1, 2, 3]])
...: v.shape
Out[11]:
(1, 3)
Для (3, 1) — вот такой:
>>>
In [12]: v = np.array([[1], [2], [3]])
...: v.shape
Out[12]:
(3, 1)
Как видите, они не идентичны.
Часто возникает задача из вектора-строки сделать вектор-столбец. Как вариант, можно сначала создать вектор-строку, а потом использовать .reshape() для его преобразования в столбец:
>>>
In [13]: v = np.array([1, 2, 3]).reshape(3, 1)
...: v.shape
Out[13]:
(3, 1)
В приведённом выше примере мы использовали .reshape() для получения вектора-столбца с размерностью (3, 1) из вектора с размерностью(3,). Стоит отметить, что .reshape() делает преобразование с учётом того, что количество элементов (100% заполненных мест) в массиве с новой размерностью должно быть равно количеству элементов в исходном массиве.
В практических задачах часто требуется создавать матрицы, полностью состоящие из нулей, единиц или случайных чисел. Для этого NumPy предлагает несколько удобных функций, о которых я расскажу в следующем разделе.
Заполнение массивов данными
NumPy позволяет быстро создавать и заполнять массивы. Например, чтобы создать массив, заполненный нулями, можно использовать .zeros():
>>>
In [15]: A = np.zeros((3, 2))
...: A
Out[15]:
array([[0., 0.],
[0., 0.],
[0., 0.]])
В качестве аргумента .zeros() нужно указать размерность массива, упакованную в кортеж (перечислить значения через запятую и обернуть это в круглые скобки). Элементы созданного массива получат тип float64.
Точно так же, для создания массивов из единиц можно использовать .ones ():
>>>
In [16]: A = np.ones((2, 3))
...: A
Out[16]:
array([[1., 1., 1.],
[1., 1., 1.]])
Элементы созданного массива также получат тип float64.
Создать массив, заполненный случайными числами, поможет .random.rand():
>>>
In [17]: A = np.random.rand(3, 2)
...: A
Out[17]:
array([[0.8206045 , 0.54470809],
[0.9490381 , 0.05677859],
[0.71148476, 0.4709059 ]])
Говоря точнее, метод .random.rand() возвращает массив с псевдослучайными значениями (от 0 до 1) из множества, сгенерированного по закону равномерного распределения. Обратите внимание, что в отличие от .zeros() и .ones(), .random.rand () на вход принимает не кортеж, а просто два значения через запятую.
Чтобы получить массив с псевдослучайными значениями, взятыми из множества, сгенерированного по закону нормального распределения с нулевым средним и единичной дисперсией, вы можете использовать .random.randn():
>>>
In [18]: A = np.random.randn(3, 2)
...: A
Out[18]:
array([[-1.20019512, -1.78337814],
[-0.22135221, -0.38805899],
[ 0.17620202, -2.05176764]])
Почему scipy.linalg лучше, чем numpy.linalg
NumPy имеет встроенный модуль numpy.linalg для решения некоторых задач, связанных с аппаратом линейной алгебры. Обычно scipy.linalg рекомендуют использовать по следующим причинам:
- В официальной документации сказано, что scipy.linalg содержит все функции numpy.linalg, а также дополнительные функции, не входящие в numpy.linalg.
- В пакет scipy.linalg включена поддержка стандартов и библиотек BLAS (Basic Linear Algebra Subprograms) и LAPACK (Linear Algebra PACKage). Они позволяют выполнять алгебраические операции с оптимизацией вычислений. В numpy.linalg BLAS и LAPACK по умолчанию не включены. Поэтому, чаще всего, из коробки scipy.linalg работает быстрее, чем numpy.linalg.
Несмотря на то, что SciPy добавит зависимости в ваш проект, по возможности, используйте scipy.linalg вместо numpy.linalg.
В следующем разделе мы применим scipy.linalg для работы с системами линейных алгебраических уравнений. Наконец-то практика!
Формализация и решение задач с scipy.linalg
Пакет scipy.linalg может стать полезным инструментом для решения транспортной задачи, балансировки химических уравнений и электрических нагрузок, полиномиальной интерполяции и так далее.
В этом разделе вы узнаете, как использовать scipy.linalg.solve() для решения СЛАУ. Но прежде чем приступить к работе с кодом, займёмся формализацией задачи и далее рассмотрим простой пример.
Система линейных алгебраических уравнений — это набор из m уравнений, n переменных и вектора свободных членов. Прилагательное «линейных» означает, что все переменные имеют первую степень. Для простоты рассмотрим СЛАУ, где m и n равны 3:
Есть ещё одно требование к «линейности»: коэффициенты K₁ … K₉ и вектор b₁ … b₃ должны быть константами (в математическом смысле этого слова).
В реальных задачах СЛАУ обычно содержат большое количество переменных, что делает невозможным решение систем вручную. К счастью, такие инструменты, как scipy.linalg, могут выполнить эту тяжелую работу.
Задача 1
Мы сначала разберёмся с основами scipy.linalg.solve() на простом примере, а в следующем разделе возьмём задачу посложнее.
Итак:
Перейдём к матричной записи нашей системы и введём соответствующие обозначения — A, x и b:
Заметьте: левая часть в исходной записи системы — это обычное произведение матрицы A на вектор x.
Всё, теперь можно переходить к программированию.
Пишем код, используя scipy.linalg.solve()
Входными данными для scipy.linalg.solve() будут матрица A и вектор b. Их нужно представить в виде двух массивов: A — массив 2х2 и b — массив 2х1. В этом нам как раз поможет NumPy. Таким образом, мы можем решить систему так:
>>>
1In [1]: import numpy as np
2 ...: from scipy.linalg import solve
3
4In [2]: A = np.array(
5 ...: [
6 ...: [3, 2],
7 ...: [2, -1],
8 ...: ]
9 ...: )
10
11In [3]: b = np.array([12, 1]).reshape((2, 1))
12
13In [4]: x = solve(A, b)
14 ...: x
15 Out[4]:
16 array([[2.],
17 [3.]])
Разберём приведённый выше код:
- Строки 1 и 2: импортируем NumPy и функцию solve() из scipy.linalg.
- Строки с 4 по 9: создаём матрицу коэффициентов как двумерный массив с именем A.
- Строка 11: создаём вектор-строку b как массив с именем b. Чтобы сделать его вектор-столбцом, вызываем .reshape ((2, 1)).
- Строки 13 и 14: вызываем .solve() для нашей системы, результат сохраняем в х и выводим его. Обратите внимание, что .solve() возвращает вектор из чисел с плавающей запятой, даже если все элементы исходных массивов являются целыми числами.
Если вы подставите x1 = 2 и x2 = 3 в исходные уравнения, будет ясно, что это действительно решение нашей системы.
Далее возьмём более сложный пример из реальной практики.
Задача 2: составление плана питания
Это одна из типовых задач, встречающихся на практике: найти пропорции компонентов для получения определенной смеси.
Ниже мы сформируем план питания, смешивая разные продукты, чтобы получить сбалансированную диету.
Нам даны нормы содержания витаминов в пище:
- 170 единиц витамина А;
- 180 единиц витамина B;
- 140 единиц витамина С;
- 180 единиц витамина D;
- 350 единиц витамина Е.
В следующей таблице представлены результаты анализа продуктов. В одном грамме каждого продукта содержится некоторое количество витаминов:
Необходимо скомбинировать продукты питания так, чтобы их концентрация была оптимальной и соответствовала нормам содержания витаминов в пище.
Обозначим оптимальную концентрацию (количество единиц) для продукта 1 как x1, для продукта 2 — как x2 и так далее. Так как мы будем смешивать продукты, то для каждого витамина (столбца таблицы) можно просто просуммировать значения, по всем продуктам. Учитывая, что сбалансированная диета должна включать 170 единиц витамина А, то, используя данные из столбца «Витамин А», составим уравнение:
Аналогичные уравнения можно составить и для витаминов B, C, D, E, объединив всё в систему:
Запишем полученную СЛАУ в матричной форме:
Теперь для решения системы можно использовать scipy.linalg.solve():
>>>
In [1]: import numpy as np
...: from scipy.linalg import solve
In [2]: A = np.array(
...: [
...: [1, 9, 2, 1, 1],
...: [10, 1, 2, 1, 1],
...: [1, 0, 5, 1, 1],
...: [2, 1, 1, 2, 9],
...: [2, 1, 2, 13, 2],
...: ]
...: )
In [3]: b = np.array([170, 180, 140, 180, 350]).reshape((5, 1))
In [4]: x = solve(A, b)
...: x
Out[4]:
array([[10.],
[10.],
[20.],
[20.],
[10.]])
Мы получили результат. Давайте его расшифруем. Сбалансированная диета должна включать:
- 10 единиц продукта 1;
- 10 единиц продукта 2;
- 20 единиц продукта 3;
- 20 единиц продукта 4;
- 10 единиц продукта 5.
Питайтесь правильно! Используйте scipy.linalg и будьте здоровы!
Облачные VPS-серверы с быстрыми NVMе-дисками и посуточной оплатой. Загрузка своего ISO.
Use SymPy to algebraically solve a system of equations, whether linear or
nonlinear. For example, solving (x^2 + y = 2z, y = -4z) for x and y (assuming z
is a constant or parameter) yields ({(x = -sqrt{6z}, y = -4z),) ({(x =
sqrt{6z}, y = -4z)}}).
Alternatives to Consider#
-
Some systems of equations cannot be solved algebraically (either at all or by
SymPy), so you may have to solve your system of equations
numerically usingnsolve()
instead.
Examples of Solving a System of Equations Algebraically#
Whether your equations are linear or nonlinear, you can use solve()
:
Solve a System of Linear Equations Algebraically#
>>> from sympy import solve >>> from sympy.abc import x, y, z >>> solve([x + y - 2*z, y + 4*z], [x, y], dict=True) [{x: 6*z, y: -4*z}]
Solve a System of Nonlinear Equations Algebraically#
>>> from sympy import solve >>> from sympy.abc import x, y, z >>> solve([x**2 + y - 2*z, y + 4*z], x, y, dict=True) [{x: -sqrt(6)*sqrt(z), y: -4*z}, {x: sqrt(6)*sqrt(z), y: -4*z}]
Guidance#
Refer to
Include the Variable to be Solved for in the Function Call
and Ensure Consistent Formatting From solve().
There are two methods below for containing solution results:
dictionary or
set. A dictionary is easier to interrogate
programmatically, so if you need to extract solutions using code, we recommend
the dictionary approach.
Solve and Use Results in a Dictionary#
Solve Into a Solution Given as a Dictionary#
You can solve a system of equations for some variables (for example, (x) and
(y)) leaving another symbol as a constant or parameter (for example, (z)). You
can specify the variables to solve for as multiple separate arguments, or as a
list (or tuple):
>>> from sympy import solve >>> from sympy.abc import x, y, z >>> equations = [x**2 + y - 2*z, y + 4*z] >>> solutions = solve(equations, x, y, dict=True) >>> solutions [{x: -sqrt(6)*sqrt(z), y: -4*z}, {x: sqrt(6)*sqrt(z), y: -4*z}]
Use a Solution Given as a Dictionary#
You can then extract solutions by indexing (specifying in brackets) the solution
number, and then the symbol. For example solutions[0][x]
gives the result for
x
in the first solution:
>>> solutions[0][x] -sqrt(6)*sqrt(z) >>> solutions[0][y] -4*z
Solve Results in a Set#
To get a list of symbols and set of solutions, use set=True
instead of
dict=True
:
from sympy import solve from sympy.abc import x, y, z solve([x**2 + y - 2*z, y + 4*z], [x, y], set=True) ([x, y], {(-sqrt(6)*sqrt(z), -4*z), (sqrt(6)*sqrt(z), -4*z)})
Options That Can Speed up solve()
#
Refer to Options That Can Speed up solve().
Not All Systems of Equations Can be Solved#
Systems of Equations With no Solution#
Some systems of equations have no solution. For example, the following two
systems have no solution because they reduce to 1 == 0
, so SymPy returns an
empty list:
>>> from sympy import solve >>> from sympy.abc import x, y >>> solve([x + y - 1, x + y], [x, y], dict=True) []
from sympy import solve from sympy.abc import x, y, z solve([x + y - (z + 1), x + y - z)], [x, y], dict=True) []
The following system reduces to (z = 2z), so it has no general solution, but it
could be satisfied if (z=0). Note that solve()
will not assume that
(z=0), even though that is the only value of (z) that makes the system of
equations consistent, because (z) is a parameter rather than an unknown. That
is, solve()
does not treat (z) as an unknown because it is not in the
list of symbols specified as unknowns ([x, y]
) and all such symbols are
treated like parameters with arbitrary value. Whether a symbol is treated as a
variable or a parameter is determined only by whether it is specified as a
symbol to solve for in solve()
. There is no such distinction made when
creating the symbol using symbols()
(or importing from abc
).
>>> from sympy import solve >>> from sympy.abc import x, y, z >>> solve([x + y - z, x + y - 2*z], [x, y], dict=True) []
The following system is
overconstrained, meaning
there are more equations (three) than unknowns to be solved for (two, namely (x)
and (y)). It has no solution:
>>> from sympy import solve >>> from sympy.abc import x, y, z >>> solve([x + y - z, x - (z + 1), 2*x - y], [x, y], dict=True) []
Note that some overconstrained systems do have solutions (for example, if an
equation is a linear combination of the others), in which case SymPy can solve
the overconstrained system.
Systems of Equations With no Closed-Form Solution#
Some systems of equations cannot be solved algebraically, for example those
containing transcendental
equations:
>>> from sympy import cos, solve >>> from sympy.abc import x, y, z >>> solve([x - y, cos(x) - y], [x, y], dict=True) Traceback (most recent call last): ... NotImplementedError: could not solve -y + cos(y)
So you can use nsolve()
to find a numerical
solution:
>>> from sympy import cos, nsolve >>> from sympy.abc import x, y, z >>> nsolve([x - y, cos(x) - y], [x, y], [1,1]) Matrix([ [0.739085133215161], [0.739085133215161]])
Equations Which Have a Closed-Form Solution, and SymPy Cannot Solve#
It is also possible that there is an algebraic solution to your equation, and
SymPy has not implemented an appropriate algorithm. If SymPy returns an empty
set or list when you know there is a closed-form solution (indicating a bug in
SymPy), please post it on the mailing list,
or open an issue on SymPy’s GitHub
page. Until the issue is resolved, you
can use a different method listed in Alternatives to Consider.
Report a Bug#
If you find a bug with solve()
, please post the problem on the SymPy
mailing list. Until the issue is resolved,
you can use a different method listed in Alternatives to Consider.
Решение систем линейных уравнений в Python¶
Пример 1. Система 2 уравнений¶
Рассмотрим простую систему из 2 линейных уравнений с 2 неизвестными:
begin{matrix}
2x+5y=1 &(1)
\
x-10y=3 &(2)
end{matrix}
Аналитическое решение¶
Система легко решается аналитически. Для этого достаточно выразить из уравнения (2) переменную x:
begin{matrix}
x=3 + 10y &(3)
end{matrix}
После чего подставить её в уравнение (1):
begin{matrix}
2cdot(3 + 10y)+5y=1,
end{matrix}
и решить получившееся линейное уравнение относительно переменной y:
begin{matrix}
25y+6=1
end{matrix}
begin{matrix}
25y=-5 \
end{matrix}begin{matrix}
y=-0.2
end{matrix}
Полученное значение y можно подставить в выражение для x в уравнение (3):
begin{matrix}
x=3 + 10cdot (-0.2)
end{matrix}
и получить значение переменной x:
begin{matrix}
x=1
end{matrix}
Таким образом решением системы будет: (1; -0,2)
(1-е значение в ответе — x, 2-е — y)
Решение матричным методом (python numpy)¶
Запишем исходную систему уравнений в виде матрицы (левая часть) и вектора (правая часть):
begin{matrix}
2x+5y=1
\
x-10y=3
end{matrix}
Для этого выпишем по порядку все коэффициенты перед неизвестными в матрицу.
Коэффициент перед переменной х 1й строки на место в матрице с координатами 0,0. (2)
Коэффициент перед переменной y 1й строки на место в матрице с координатами 0,1. (5)
Коэффициент перед переменной х 2й строки на место в матрице с координатами 1,0. (1)
Коэффициент перед переменной y 2й строки на место в матрице с координатами 1,1. (-10)
begin{pmatrix}
2& 5
\
1 & -10
end{pmatrix}
Значение свободного члена (число, не умноженное ни на одну переменную системы) после знака равенства 1й строки на место 0 в векторе.
Значение свободного члена 2й строки на место 1 в векторе.
begin{pmatrix}
1
\
3
end{pmatrix}
Для этого воспользуемся numpy массивами:
In [1]:
import numpy # импортируем библиотеку M1 = numpy.array([[2., 5.], [1., -10.]]) # Матрица (левая часть системы) v1 = numpy.array([1., 3.]) # Вектор (правая часть системы) #Запишем все числа с точкой, т.к. иначе в Python 2 они будут участвовать в целочисленных операциях (остатки от деления будут отбрасываться)
In [2]:
numpy.linalg.solve(M1, v1)
Обратим внимание, что ответом так же является numpy массив!
при этом порядок следования ответов в массиве соответствует порядку столбцов исходной матрицы. Т.е. на 0 месте находится x=1, т.к. мы в матрице внесли в 0 столбец коэффициенты перед переменной х!
Ответ: (1; -0,2)
Пример 2. Система 4 уравнений¶
Рассмотрим простую систему из 2 линейных уравнений с 2 неизвестными:
begin{matrix}
A + C = 2 &(4)
\
-A + B — 2C + D = -2 &(5)
\
4A + C — 2D = 0 &(6)
\
-4A + 4B + D = 5 &(7)
end{matrix}
Для наглядности решения данной системы матричным методом запишем её в таком виде, чтобы каждое уравнение содержало все 4 переменных, и чтобы они занимали в каждой строке одно и то же порядковое место (А — 0, B — 1, C — 2, D — 3):
begin{matrix}
A + 0B + C + 0D = 2 &(8)
\
-A + B — 2C + D = -2 &(9)
\
4A + 0B + C — 2D = 0 &(10)
\
-4A + 4B + 0C + D = 5 &(11)
end{matrix}
теперь аналогично примеру 1 выпишем матрицу (коэффициенты перед переменными из левой части системы построчно в порядке следования переменных) и вектор (свободные члены из правой части системы):
begin{pmatrix}
1 & 0 & 1 & 0
\
-1 & 1 & -2 & 1
\
4 & 0 & 1 & -2
\
-4 & 4 & 0 & 1
end{pmatrix}begin{pmatrix}
2
\
-2
\
0
\
5
end{pmatrix}
In [3]:
import numpy # импортируем библиотеку M2 = numpy.array([[1., 0., 1., 0.], [-1., 1., -2., 1.], [4., 0., 1., -2.], [-4., 4., 0., 1.]]) # Матрица (левая часть системы) v2 = numpy.array([2., -2., 0., 5.]) # Вектор (правая часть системы) numpy.linalg.solve(M2, v2)
Ответ: (0; 1; 2; 1)
A = 0, B = 1, C =2, D = 1
Пример 3. Система 3 уравнений (с приведением к линейному виду)¶
Матричный метод применим только для решения линейных уравнений. Однако иногда можно встретить нелинейные уравнения, легко приводимые к линейной форме, например:
begin{matrix}
2x_{1}+x_{2}^2+x_{3} = 2 &(12)
\
x_{1}-x_{2}^2 = -2 &(13)
\
3x_{1}-x_{2}^2+2x_{3} = 2 &(14)
end{matrix}
Можно заметить, что переменная x2 входит во все 3 уравнения только в квадратичной форме. Это означает, что мы можем осуществить замену:
begin{matrix}
x_{2}^2 = a &(15)
end{matrix}
С учётом этой подстановки запишем систему, аналогично примеру 2 с вхождением всех 3 переменных:
begin{matrix}
2x_{1}+a+x_{3} = 2 &(16)
\
x_{1}-a+0x_{3} = -2 &(17)
\
3x_{1}-a+2x_{3} = 2 &(18)
end{matrix}
Для новой системы мы уже умеем записывать матрицу (коэффициенты перед переменными из левой части системы) и вектор (свободные члены из правой части):
begin{pmatrix}
2 & 1 & 1
\
1 & -1 & 0
\
3 & -1 & 2
end{pmatrix}begin{pmatrix}
2
\
-2
\
2
end{pmatrix}
In [4]:
import numpy # импортируем библиотеку M3 = numpy.array([[2., 1., 1.], [1., -1., 0.], [3., -1., 2.]]) # Матрица (левая часть системы) v3 = numpy.array([2., -2., 2.]) # Вектор (правая часть системы) numpy.linalg.solve(M3, v3)
Мы получили промежуточный результат:
$x_{1}= -1, a= 1, x_{3}= 3,$
или
$x_{1}= -1, x_{2}^2= 1, x_{3}= 3,$
$x_{2}^2=1$ соответствует 2 значениям $x_{2}$: 1 и -1.
Таким образом мы получаем 2 решения нашей системы: $x_{1}= -1, x_{2}= 1, x_{3}= 3,$ и $x_{1}= -1, x_{2}= -1, x_{3}= 3,$
Ответ: (-1; 1; 3) и (-1; -1; 3)
Пример 4. Решение простой задачи с помощью системы линейных уравнений.¶
Навстречу друг другу из одного города в другой, расстояние между которыми составляет 30 км, едут два велосипедиста. Предположим, что если велосипедист 1 выедет на 2 ч раньше своего товарища, то они встретятся через 2,5 часа после отъезда велосипедиста 2; если же велосипедист 2 выедет 2мя часами ранее велосипедиста 1, то встреча произойдет через 3 часа после отъезда первого. С какой скоростью движется каждый велосипедист?
Обозначим за неизвестные x и y скорости велосипедистов.
Путь = скорость * время
Расстояние между велосипедистами = путь 1 велосипедиста + путь 2 велосипедиста
На основании этих простых рассуждений и данных задачи можно записать уравнения:
begin{matrix}
(2.5 + 2)x+2.5y=30 &(19)
\
3x + (3+2)y=30 &(20)
end{matrix}
или
begin{matrix}
4.5x+2.5y=30 &(21)
\
3x + 5y=30 &(22)
end{matrix}
Аналогично примеру 1 легко составим матрицу коэффициентов перед неизвестными (левая часть системы) и вектор со свободными членами (права часть системы):
begin{pmatrix}
4.5& 2.5
\
3 & 5
end{pmatrix}begin{pmatrix}
30
\
30
end{pmatrix}
In [5]:
import numpy # импортируем библиотеку M4 = numpy.array([[4.5, 2.5], [3., 5.]]) # Матрица (левая часть системы) v4 = numpy.array([30., 30.]) # Вектор (правая часть системы) numpy.linalg.solve(M4, v4)
Пример 5. Нахождение уравнения плоскости по точкам, через которые она проходит.¶
Уравнение плоскости в 3-х мерном пространстве задаётся уравнением:
begin{matrix}
z = ax + by + c & (23)
end{matrix}
Уравнение плоскости однозначно задаётся 3 точками через которые она проходит.
Таким образом легко понять, что если мы знаем координаты точек, через которые проходит плоскость, то в уравнении (23) у вас 3 переменных: a, b, c. А значения x, y, z нам известны для 3 точек.
Если плоскость проходит через точки (1;-6;1), (0;-3;2) и (-3;0;-1), то мы легко можем найти коэффициенты, подставив значения соответствующих координат для всех 3 точек в уравнение (23) и получив систему из 3 уравнений.
Для точки x = 1, y = -6, z = 1:
begin{matrix}
acdot 1 + bcdot (-6) + c = 1 &(24)
end{matrix}
Для точки x = 0, y = -3, z = 2:
begin{matrix}
acdot 0 + bcdot (-3) + c = 2 &(25)
end{matrix}
Для точки x = -3, y = 0, z = -1:
begin{matrix}
acdot (-3) + bcdot 0 + c = -1 &(26)
end{matrix}
На основании системы уравнений (24), (25), (26) можно записать матрицу коэффициентов перед неизвестными (левая часть матрицы):
begin{pmatrix}
1& -6 & 1
\
0 & -3 & 1
\
-3 & 0 & 1
end{pmatrix}
И вектор свободных членов (правая часть):
begin{pmatrix}
1
2
-1
end{pmatrix}
In [6]:
import numpy # импортируем библиотеку M5 = numpy.array([[1., -6., 1.], [0., -3., 1], [-3, 0, 1]]) # Матрица (левая часть системы) v5 = numpy.array([1., 2., -1.]) # Вектор (правая часть системы) numpy.linalg.solve(M5, v5)
Ответ: уравнение искомой плоскости в пространстве задаётся уравнением $z = 2x + y + 5$
Пример 6. Нахождение уравнения параболы по 2 точкам и касательной¶
Найти уравнение параболы ($f(x) = ax^2 + bx + x & (27)$), проходящей через точки (1,1) и (-1,1) и касающейся биссектрисы 1й координатной четверти.
Как и в предыдущем примере неизвестными для нас являются коэффициенты a, b, c.
Подставив в уравнение параболы (27) значения аргумента (x) и функции (f(x)) получим 2 уравнения:
begin{matrix}
acdot 1^2 + bcdot 1 + c = 1 &(28)
\
acdot (-1)^2 + bcdot (-1) + c = 1 &(29)
end{matrix}
Однако для нахождения 3 неизвестных 2 уравнений мало. Необходимо найти ещё одно из оставшихся условий.
Касание биссектрисы 1й координатной четверти означает, что наша парабола имеет касательную $y = x$. Если посмотреть на условие задачи, то мы увидим, что одна из точек (1, 1) лежит на этой прямой. Это означает, что мы знаем точку касания.
Уравнение прямой, делящей 1-ю координатную четверть пополам (биссектрисы) имеет вид $y = kx quad (30)$
При этом мы знаем, что угол уравнения касательной (коэффициент k уравнения (30)) равен производной от функции (27) в точке касания.
begin{matrix}
f'(x) = 2ax + bx & (31)
end{matrix}
Подставив значение аргумента (x = 1) в точке касания и коэффициента (k = 1 в качестве производной f'(x))
begin{matrix}
1 = 2acdot1 + bcdot1 & (32)
end{matrix}
Используя уравнения (28), (29) и (32), запишем полную систему уравнений, которую нам необходимо решить:
begin{matrix}
acdot 1 + bcdot 1 + c cdot 1 = 1 &(33)
\
acdot 1 + bcdot (-1) + c cdot 1 = 1 &(34)
\
acdot 2 + bcdot 1 + c cdot 0 = 1 &(35)
end{matrix}
По привычной уже схеме запишем коэффициенты перед переменными (левую часть системы) в матрицу, а свободные члены (правую часть) в вектор:
begin{pmatrix}
1& 1 & 1
\
1 & -1 & 1
\
2 & 1 & 0
end{pmatrix}begin{pmatrix}
1
\
1
\
1
end{pmatrix}
In [7]:
import numpy # импортируем библиотеку M6 = numpy.array([[1., 1., 1.], [1., -1., 1], [2, 1, 0]]) # Матрица (левая часть системы) v6 = numpy.array([1., 1., 1.]) # Вектор (правая часть системы) numpy.linalg.solve(M6, v6)
Ответ: уравнение искомой параболы задаётся функцией $f(x) = 0.5x^2 + 0.5$
Примечание к примеру 6¶
На иллюстрации ниже изображены графики параболы и биссектрисы, которой она касается. А так же 2 точки, через которых проходит парабола.
In [8]:
import numpy import matplotlib as mpl import matplotlib.pyplot as plt % matplotlib inline mpl.rc('font', family='Verdana', size= 16) w = numpy.linalg.solve(M6, v6) # запишем найденные коэффициенты в переменную def f(x): return w[0]*x**2 + w[1]*x + w[2] # уравнение параболы fig, ax = plt.subplots(figsize=(10,5)) x = numpy.linspace(-2,2,200) ax.axis([-2., 2., 0., 2.]) ax.grid() ax.plot(x, f(x), label = 'Парабола') ax.plot(x, x, label = 'Биссектриса 1йnкоординатной четверти') ax.set_xlabel(u'x',{'fontname':'Arial', 'size': 24}) ax.set_ylabel(u'f(x)',{'fontname':'Arial', 'size': 24}) plt.plot([-1, 1], [1, 1], 'ro', label = 'Точки дляnпостроения графика') ax.annotate('Точкаnкасания', xy=(1., 1.), xytext=(1.5, 0.5), arrowprops=dict(facecolor='black', shrink=0.05), ) ax.legend(bbox_to_anchor=(1.6, 1.)) plt.show()![]()
Возврат к списку
17 авг. 2022 г.
читать 2 мин
Чтобы решить систему уравнений в Python, мы можем использовать функции из библиотеки NumPy .
В следующих примерах показано, как использовать NumPy для решения нескольких различных систем уравнений в Python.
Пример 1. Решение системы уравнений с двумя переменными
Предположим, у нас есть следующая система уравнений, и мы хотели бы найти значения x и y:
5х + 4у = 35
2х + 6у = 36
В следующем коде показано, как использовать NumPy для поиска значений x и y:
import numpy as np
#define left-hand side of equation
left_side = np.array([[5, 4], [2, 6]])
#define right-hand side of equation
right_side = np.array([35, 36])
#solve for x and y
np.linalg.inv (left_side). dot (right_side)
array([3., 5.])
Это говорит нам о том, что значение x равно 3 , а значение y равно 5 .
Пример 2. Решение системы уравнений с тремя переменными
Предположим, у нас есть следующая система уравнений, и мы хотели бы найти значения x, y и z:
4х + 2у + 1з = 34
3x + 5y – 2z = 41
2х + 2у + 4з = 30
В следующем коде показано, как использовать NumPy для определения значений x, y и z:
import numpy as np
#define left-hand side of equation
left_side = np.array([[4, 2, 1], [3, 5, -2], [2, 2, 4]])
#define right-hand side of equation
right_side = np.array([34, 41, 30])
#solve for x, y, and z
np.linalg.inv (left_side). dot (right_side)
array([5., 6., 2.])
Это говорит нам о том, что значение x равно 5 , значение y равно 6 , а значение z равно 2 .
Пример 3. Решение системы уравнений с четырьмя переменными
Предположим, у нас есть следующая система уравнений, и мы хотели бы найти значения w, x, y и z:
6ш + 2х + 2у + 1з = 37
2ш + 1х + 1у + 0з = 14
3ш + 2х + 2у + 4з = 28
2ш + 0х + 5у + 5з = 28
В следующем коде показано, как использовать NumPy для определения значений w, x, y и z:
import numpy as np
#define left-hand side of equation
left_side = np.array([[6, 2, 2, 1], [2, 1, 1, 0], [3, 2, 2, 4], [2, 0, 5, 5]])
#define right-hand side of equation
right_side = np.array([37, 14, 28, 28])
#solve for w, x, y, and z
np.linalg.inv (left_side). dot (right_side)
array([4., 3., 3., 1.])
Это говорит нам о том, что значение w равно 4 , x равно 3 , y равно 3 и z равно 1 .
Дополнительные ресурсы
В следующих руководствах объясняется, как решить систему уравнений с помощью другого статистического программного обеспечения:
Как решить систему уравнений в R
Как решить систему уравнений в Excel
Решение системы уравнений методом Гаусса является одним из фундаментальных заданий линейной алгебры. Этот метод основан на элементарных преобразованиях строк матрицы системы уравнений и позволяет свести ее к эквивалентной треугольной форме. В этой статье мы покажем Вам, как реализовать алгоритм метода Гаусса на Python.
Алгоритм метода Гаусса
Метод Гаусса заключается в последовательном применении трех типов элементарных преобразований над строками расширенной матрицы системы уравнений, где в последнем столбце записаны свободные члены уравнений.
Эти преобразования включают в себя:
- Перестановка двух строк матрицы местами;
- Умножение строки на ненулевое число;
- Прибавление к одной строке другой строке, умноженной на число.
Цель алгоритма Гаусса – привести матрицу системы к ступенчатому виду, то есть к матрице, в которой первый ненулевой элемент каждой строки находится правее первого ненулевого элемента предыдущей строки. Затем полученную матрицу можно привести к диагональному виду методом обратных ходов.
Когда матрица системы находится в диагональном виде, можно легко найти решение системы линейных уравнений, обратившись к коэффициентам при неизвестных, записанным на главной диагонали матрицы.
- Для решения СЛАУ методом Гаусса мы будем использовать библиотеку numpy, которая позволит сделать код чище и читаемее за счёт вынесения некоторой логики на уровень абстракции.
- Далее мы зададим начальные значения для матрицы системы уравнений и столбца свободных членов. Далее, мы вынесем это в функцию с принимаемыми аргументами.
A = np.array([[2.7, 3.3, 1.3], [3.5, -1.7, 2.8], [4.1, 5.8, -1.7]])
B = np.array([2.1, 1.7, 0.8])
- Согласно формуле метода Гаусса, по нему необходимо пройтись циклом. Длина цикла будет равна длине массива свободных членов (B).
n = len(B)
for i in range(n):
- В цикле в первую очередь нам нужно найти максимальный элемент в каждом столбце. Т.е. при каждой итерации цикла мы будем искать в i-том столбце.
maxEl = abs(A[i][i])
maxRow = i
for k in range(i + 1, n):
if abs(A[k][i]) > maxEl:
maxEl = abs(A[k][i])
maxRow = k
- Далее мы производим обмен строками, всё также согласно методу Гаусса.
for k in range(i, n):
tmp = A[maxRow][k]
A[maxRow][k] = A[i][k]
A[i][k] = tmp
tmp = B[maxRow]
B[maxRow] = B[i]
B[i] = tmp
- После того, как мы поставили строки в нужном порядке, нам необходимо привести систему линейных уравнений к треугольному виду. Т.е., сделать так, чтобы все нули образовывали треугольник, а значения были вторым треугольником.
for k in range(i + 1, n):
c = -A[k][i] / A[i][i]
for j in range(i, n):
if i == j:
A[k][j] = 0
else:
A[k][j] += c * A[i][j]
B[k] += c * B[i]
- Далее мы делаем обратный ход, который является последней операцией над СЛАУ для решения её методом Гаусса.
x = np.zeros(n)
for i in range(n - 1, -1, -1):
x[i] = B[i]
for j in range(i + 1, n):
x[i] -= A[i][j] * x[j]
x[i] /= A[i][i]
Полный код решения СЛАУ методом Гаусса на Python
import numpy as np
# Определяем матрицу системы уравнений
A = np.array([[2.7, 3.3, 1.3], [3.5, -1.7, 2.8], [4.1, 5.8, -1.7]])
# Определяем столбец свободных членов
B = np.array([2.1, 1.7, 0.8])
# Прямой ход метода Гаусса
n = len(B)
for i in range(n):
# Поиск максимального элемента в столбце i
maxEl = abs(A[i][i])
maxRow = i
for k in range(i + 1, n):
if abs(A[k][i]) > maxEl:
maxEl = abs(A[k][i])
maxRow = k
# Обмен строками
for k in range(i, n):
tmp = A[maxRow][k]
A[maxRow][k] = A[i][k]
A[i][k] = tmp
tmp = B[maxRow]
B[maxRow] = B[i]
B[i] = tmp
# Приведение к верхнетреугольному виду
for k in range(i + 1, n):
c = -A[k][i] / A[i][i]
for j in range(i, n):
if i == j:
A[k][j] = 0
else:
A[k][j] += c * A[i][j]
B[k] += c * B[i]
# Обратный ход метода Гаусса
x = np.zeros(n)
for i in range(n - 1, -1, -1):
x[i] = B[i]
for j in range(i + 1, n):
x[i] -= A[i][j] * x[j]
x[i] /= A[i][i]
# Вывод результата
print("Result:")
print(x)
Результат выполнения кода:
Выделим код в отдельную функцию
Для того, чтобы алгоритм был более универсальным и вы могли его скопировать к себе в проект, я выделю функцию, которая будет принимать на вход все нужные аргументы и выводить конечный результат. Всё, что вам останется, это скопировать функцию и передать ей правильные аргументы. Итак, готовый код:
import numpy as np
def gauss(A, B):
# Прямой ход метода Гаусса
n = len(B)
for i in range(n):
# Поиск максимального элемента в столбце i
maxEl = abs(A[i][i])
maxRow = i
for k in range(i + 1, n):
if abs(A[k][i]) > maxEl:
maxEl = abs(A[k][i])
maxRow = k
# Обмен строками
for k in range(i, n):
tmp = A[maxRow][k]
A[maxRow][k] = A[i][k]
A[i][k] = tmp
tmp = B[maxRow]
B[maxRow] = B[i]
B[i] = tmp
# Приведение к верхнетреугольному виду
for k in range(i + 1, n):
c = -A[k][i] / A[i][i]
for j in range(i, n):
if i == j:
A[k][j] = 0
else:
A[k][j] += c * A[i][j]
B[k] += c * B[i]
# Обратный ход метода Гаусса
x = np.zeros(n)
for i in range(n - 1, -1, -1):
x[i] = B[i]
for j in range(i + 1, n):
x[i] -= A[i][j] * x[j]
x[i] /= A[i][i]
return x
# Определяем матрицу системы уравнений
A = np.array([[2.7, 3.3, 1.3], [3.5, -1.7, 2.8], [4.1, 5.8, -1.7]])
# Определяем столбец свободных членов
B = np.array([2.1, 1.7, 0.8])
x = gauss(A, B)
# Вывод результата
print("Result:")
print(x)