From cc89b8119066492982739e57a4adfe7ff4390f6b Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Fri, 18 Aug 2023 10:50:55 +0300 Subject: [PATCH 01/41] Add Newton method to theory --- main.tex | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/main.tex b/main.tex index ee5ad5e..4c1d0c6 100644 --- a/main.tex +++ b/main.tex @@ -94,6 +94,7 @@ После применение метода на заданных входных данных получим множество решений уравнения \(Ans = \{x, x \in \textbf{R}\}, |Ans| \geq 1\). + \subsubsection{Реализации метода в библиотеках numpy, scipy} Библиотека scipy содержит функцию \textbf{bisect} из модуля \textbf{scipy.optimize} \cite{links:scipy_doc}, которая реализует @@ -147,10 +148,83 @@ \end{enumerate} \subsection{Метод Ньютона (метод касательных)} +Данный итерационный метод позволит найти единственное решение +уравнения (если оно существует) для каждого из выбранных начальных +приближений. +\subsubsection{Описание метода} +На итерации \(i, i = 1\dots k\), строится касательная к кривой +\(y = F(x)\) в точке \((x_i;F(x_i))\), затем находится \(x_{i+1}\) +--- пересечение данной касательной с осью \(Ox\). Процесс продолжается +пока не будет достигнута заданная точность (\(| F ( x_i ) |< \varepsilon\) +или \(| x_{i+1} - x_i | < \varepsilon\)). + +\subsubsection{Реализации метода в библиотеках numpy, scipy} +Библиотека scipy содержит функцию \textbf{newton} из модуля +\textbf{scipy.optimize} \cite{links:scipy_doc}, которая реализует +данный метод. + +Функция имеет следующие параметры (задаются в порядке перечисления): +\begin{enumerate} + \item \(f\) --- function + + Функция, корень которой требуется. Это должна быть функция + одной переменной вида \(f(x,a,b,c \dots)\), где \(a,b,c \dots\) + --- дополнительные аргументы, которые можно передать в параметре + \(args\). + \item \(x0\) --- float, sequence, или ndarray + + Начальная оценка корня, которая должна быть где-то рядом с + фактическим корнем. Если \(f\) не скалярная, то \(f\) должна + быть векторизована и возвращать последовательность или массив + той же формы, что и ее первый аргумент. + \item \(fprime\) --- callable, необязательный + + Производная функции, когда она доступна и удобна. Если это + \verb|None| (по умолчанию), то используется метод секущей. + \item \(args\) --- tuple, необязательный + + Дополнительные аргументы для использования при вызове функции. + \item \(tol\) --- float, необязательный + + Допустимая погрешность значения корня. Если + \(y=f(x),y \in \textbf{Z}\), рекомендуется большее значение + \(tol\), так как и действительная, и мнимая части \(x\) + вносят вклад в \(|x - x0|\). + \item \(maxiter\) --- int, необязательный + + Максимальное количество итераций. + \item \(fprime2\) --- callable, необязательный + + Производная функции второго порядка, если она доступна и удобна. + Если это \verb|None| (по умолчанию), то используется обычный + метод Ньютона или метод секущих. Если не \verb|None|, то + используется метод Галлея. + \item \(x1\) float, необязательный + + Еще одна оценка корня, которая должна быть где-то рядом с фактическим корнем. Используется, если \(fprime\) не указан. + \item \(rtol\) --- float, необязательный + + Допустимое отклонение (относительное) для прерывания работы. + \item \(full\_output\) --- bool, необязательный + + Если \(full\_output\) имеет значение \verb|False| (по умолчанию), + возвращается корень. Если \verb|True| и \(x0\) --- скаляр, + возвращаемое значение равно \verb|(x, r)|, где \(x\) --- это + корень, а \(r\) --- объект \verb|RootResults|. Если \verb|True| + и \(x0\) --- не скаляр, возвращаемое значение равно + \verb|(x, converged, zero_der)|, где: + \begin{itemize} + \item converged --- ndarray из значений bool. Указывает, какие элементы сошлись успешно. + \item zero\_der --- ndarray из значений bool. Указывает, какие элементы имеют нулевую производную. + \end{itemize} + \item \(disp\) --- bool, необязательный + + Если \verb|True| и алгоритм не сошелся, будет сгенерировано исключение \verb|RuntimeError|, с сообщением, содержащим количество итераций и текущее значение функции. В противном случае статус сходимости записывается в возвращаемый объект \verb|RootResults|. Игнорируется, если \verb|x0| не является скалярным. Примечание: это не имеет ничего общего с отображением, однако ключевое слово \verb|disp| нельзя переименовать для сохранения обратной совместимости. +\end{enumerate} + \subsection{Метод простой итерации} \chapter{Экспериментальное исследование возможностей библиотек} - \chapter*{Заключение} \addcontentsline{toc}{chapter}{Заключение} From f1a13873249f8907c71431d455adf0be349d82db Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Fri, 18 Aug 2023 10:51:21 +0300 Subject: [PATCH 02/41] Fix TOC, itemize list style --- config.tex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/config.tex b/config.tex index ec4f7b4..f4a4964 100644 --- a/config.tex +++ b/config.tex @@ -57,6 +57,12 @@ {\hspace*{-2.3em}} {\titlerule*[2mm]{.}\contentspage} +\titlecontents{subsection} +[3.5em] % 1.5em (chapter) + 2.3em +{} +{\thecontentslabel. } +{\hspace*{-2.3em}} +{\titlerule*[2mm]{.}\contentspage} % ОФОРМЛЕНИЕ ЗАГОЛОВКОВ ----------------- % В т.ч. и содержания @@ -79,6 +85,7 @@ % ОФОРМЛЕНИЕ СПИСКОВ -------------------- \setlist{noitemsep,align=left,left=\parindent,topsep=0pt} +\setlist[itemize]{label=--} % ОФОРМЛЕНИЕ ЛИСТИНГОВ КОДА ----------------- \definecolor{dkgreen}{rgb}{0,0.6,0} From 4f0cdcee0e4466412c7e931bbe2535182765bf8c Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Fri, 18 Aug 2023 15:00:05 +0300 Subject: [PATCH 03/41] Add structure sceleton --- main.tex | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) diff --git a/main.tex b/main.tex index 4c1d0c6..5832145 100644 --- a/main.tex +++ b/main.tex @@ -223,6 +223,126 @@ \end{enumerate} \subsection{Метод простой итерации} +\subsubsection{Описание метода} +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\section{Методы решения систем линейных алгебраических уравнений} +\subsection{Метод Гаусса} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод обратной матрицы} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод прогонки} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод простой итерации (метод Якоби)} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод Зейделя} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\section{Численные методы решения систем нелинейных уравнений} +TODO +\subsection{Метод простой итерации (метод Якоби) для систем +нелинейных уравнений} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод Зейделя для систем нелинейных уравнений} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод Ньютона решения систем нелинейных уравнений} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\section{Аппроксимация функций} +\subsection{Интерполяционный полином в форме Лагранжа} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Интерполяционный полином в форме Ньютона} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Сплайн-интерполяция} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Сглаживание. Метод наименьших квадратов} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\section{Численное интегрирование} +\subsection{Метод прямоугольников} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод трапеций} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод парабол (Симпсона)} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + + +\section{Численное решение обыкновенных дифференциальных уравнений} +\subsection{Метод Эйлера} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Модифицированный метод Эйлера} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + +\subsection{Метод Рунге-Кутта} +TODO +\subsubsection{Описание метода} +TODO +\subsubsection{Реализации метода в библиотеках numpy, scipy} + + \chapter{Экспериментальное исследование возможностей библиотек} \chapter*{Заключение} From a8e713565439780313800ec7f2f46b09e7feff92 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Fri, 18 Aug 2023 18:08:53 +0300 Subject: [PATCH 04/41] [theory] Add simple iteration method --- main.tex | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/main.tex b/main.tex index 5832145..046af9d 100644 --- a/main.tex +++ b/main.tex @@ -223,8 +223,23 @@ \end{enumerate} \subsection{Метод простой итерации} +Данный метод, как и предыдущий, не позволяет найти несколько решений +уравнения за исполнение алгоритма на единственном начальном приближении. \subsubsection{Описание метода} +Уравнение \(F(x)=0\) приводим к виду \(x = \varphi(x)\), например +\(x-F(x)/M\), где \(M\) --- константа. + +Условие сходимости алгоритма: \(0<|\varphi'(x)|<1\). Исходя из него, \(M\) определяется как \(M=1.01*F'(x_0)\), где \(x_0\) --- +начальное приближение. + +Таким образом, для итерации \(i, i = 1\dots k,\) +\(x_i = \varphi(x_{i-1})\). + +Процесс продолжается, пока не будет достигнута заданная точность, в +данном случае достаточно будет выполнения условия: +\(|x_{i-1}-x_i|<\varepsilon\). \subsubsection{Реализации метода в библиотеках numpy, scipy} +В библиотеках numpy, scipy не найдено реализаций данного метода. \section{Методы решения систем линейных алгебраических уравнений} \subsection{Метод Гаусса} @@ -260,7 +275,7 @@ TODO \section{Численные методы решения систем нелинейных уравнений} TODO \subsection{Метод простой итерации (метод Якоби) для систем -нелинейных уравнений} + нелинейных уравнений} TODO \subsubsection{Описание метода} TODO From 587dd77d25127bf8673601ff87423d6bab4d143c Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 19 Aug 2023 19:33:48 +0300 Subject: [PATCH 05/41] [content] Replace quotation marks symbols to <<, >> --- config.tex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config.tex b/config.tex index f4a4964..a5016ff 100644 --- a/config.tex +++ b/config.tex @@ -20,6 +20,7 @@ \usepackage{xstring} \usepackage{tabularx} \usepackage{enumitem} +\usepackage[strict=true]{csquotes} \usepackage[abspath]{currfile} \usepackage[hidelinks,linktoc=all]{hyperref} @@ -179,3 +180,4 @@ % ОФОРМЛЕНИЕ ТЕКСТА \renewcommand{\baselinestretch}{1.5} +\MakeOuterQuote{"} From 267497e79c8017071cf9ebc65b8092709b1e1963 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 19 Aug 2023 19:34:28 +0300 Subject: [PATCH 06/41] [sources] Add book, 1 link. Refactor command --- sources.tex | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/sources.tex b/sources.tex index 066532e..a46d792 100644 --- a/sources.tex +++ b/sources.tex @@ -1,9 +1,11 @@ \newcommand{\LiteratureAccessDate}[1][1]{% - дата~обращения: \ifcase#1\or 03.08.2023% - \or 07.08.2023% - \or 09.08.2023% + дата~обращения: \ifcase#1% + \or 03.08.2023% 1 (default) + \or 07.08.2023% 2 + \or 09.08.2023% 3 + \or 19.08.2023% 4 \else\@ctrerr\fi - } +} \renewcommand\bibname{Библиографический список} \begin{thebibliography}{00} \addcontentsline{toc}{chapter}{Библиографический список} @@ -12,12 +14,14 @@ \bibitem{book:bahvalov} Бахвалов~Н.~С., Жидков~Н.~П., Кобельков~Г.~М. Численные методы. -- 7-е изд. -- М.: БИНОМ. Лаборатория знаний, 2011. -- 636 с., c илл. -- (Классический университетский учебник). + \bibitem{book:levitin} Левитин~A.~В. Алгоритмы: введение в разработку и анализ. -- Пер.~с~англ. -- М.:Издательский~дом~"Вильяме", 2006. -- 576 с., с ил. \bibitem{book:lectures} Письменный~Д.~Т. Конспект лекций по высшей математике. 2 часть. -- М.: Рольф, 2000. -- 256 с., с илл. \bibitem{links:numpy} Numpy. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://numpy.org/} (\LiteratureAccessDate[2]). \bibitem{links:numpy_doc} Numpy API Reference [Электронный ресурс] -- URL:~\url{https://numpy.org/doc/stable/reference/index.html} (\LiteratureAccessDate[3]). + \bibitem{links:PEP465} PEP 465 -- A dedicated infix operator for matrix multiplication [Электронный ресурс] -- URL:~\url{A dedicated infix operator for matrix multiplication} (\LiteratureAccessDate[4]). \bibitem{links:python} Python. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://www.python.org/} (\LiteratureAccessDate). \bibitem{links:scipy} Scipy. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://scipy.org/} (\LiteratureAccessDate[2]). \bibitem{links:scipy_doc} Scipy API Reference [Электронный ресурс] -- URL:~\url{https://docs.scipy.org/doc/scipy/reference/index.html} (\LiteratureAccessDate[3]). - \bibitem{links:tiobe_index} TIOBE. Официальный сайт проекта + \bibitem{links:tiobe_index} TIOBE. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://www.tiobe.com/tiobe-index/} (\LiteratureAccessDate). \end{thebibliography} From eef44ba60da781d48e39bb917e8017124f675385 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 19 Aug 2023 19:38:13 +0300 Subject: [PATCH 07/41] [content] Add 2 SLE methods. Refactor --- main.tex | 154 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 148 insertions(+), 6 deletions(-) diff --git a/main.tex b/main.tex index 046af9d..d3945ae 100644 --- a/main.tex +++ b/main.tex @@ -58,6 +58,7 @@ непрерывна, то обязательно найдется такое \(x_k \in (a,b)\), что \(F(x_k) = 0\) либо \(|F(x_k)| < \varepsilon\), где \(\varepsilon\) --- погрешность искомого решения. + \subsection{Метод деления отрезка пополам} Данный метод использует технику поиска решения, похожую на бинарный поиск. @@ -219,7 +220,14 @@ \end{itemize} \item \(disp\) --- bool, необязательный - Если \verb|True| и алгоритм не сошелся, будет сгенерировано исключение \verb|RuntimeError|, с сообщением, содержащим количество итераций и текущее значение функции. В противном случае статус сходимости записывается в возвращаемый объект \verb|RootResults|. Игнорируется, если \verb|x0| не является скалярным. Примечание: это не имеет ничего общего с отображением, однако ключевое слово \verb|disp| нельзя переименовать для сохранения обратной совместимости. + Если \verb|True| и алгоритм не сошелся, будет сгенерировано + исключение \verb|RuntimeError|, с сообщением, содержащим + количество итераций и текущее значение функции. В противном + случае статус сходимости записывается в возвращаемый объект + \verb|RootResults|. Игнорируется, если \verb|x0| не является + скалярным. Примечание: это не имеет ничего общего с + отображением, однако ключевое слово \verb|disp| нельзя + переименовать для сохранения обратной совместимости. \end{enumerate} \subsection{Метод простой итерации} @@ -229,7 +237,8 @@ Уравнение \(F(x)=0\) приводим к виду \(x = \varphi(x)\), например \(x-F(x)/M\), где \(M\) --- константа. -Условие сходимости алгоритма: \(0<|\varphi'(x)|<1\). Исходя из него, \(M\) определяется как \(M=1.01*F'(x_0)\), где \(x_0\) --- +Условие сходимости алгоритма: \(0<|\varphi'(x)|<1\). Исходя из него, +\(M\) определяется как \(M=1.01 \cdot F'(x_0)\), где \(x_0\) --- начальное приближение. Таким образом, для итерации \(i, i = 1\dots k,\) @@ -242,17 +251,150 @@ В библиотеках numpy, scipy не найдено реализаций данного метода. \section{Методы решения систем линейных алгебраических уравнений} +Система линейных алгебраических (далее, СЛУ) уравнений имеет вид +\begin{eqnarray} + \left\{ + \begin{aligned} + & a_{11}x_1 + a_{12}x_2 + a_{1n}x_n = b_1 \\ + & a_{21}x_1 + a_{22}x_2 + a_{2n}x_n = b_2 \\ + & ........................................ \\ + & a_{n1}x_1 + a_{n2}x_2 + a_{nn}x_n = b_n \\ + \end{aligned} + \right. + \label{formula:eqn_system} +\end{eqnarray} + +Для решения таких систем существуют прямые и итерационные методы. +Прямые методы (к ним относятся "метод Гаусса", "метод обратной матрицы" +и "метод прогонки") позволяют получить решение за конечное количество +операций, точность которого ограничивается лишь погрешностью округления. +Итерационные методы (среди которых есть методы, такие как +"метод простой итерации" и "метод Зейделя") позволяют получить +приближенное решение с помощью последовательного приближения к точному. + \subsection{Метод Гаусса} -TODO \subsubsection{Описание метода} -TODO +Для решения СЛУ система (\ref{formula:eqn_system}) приводится к +треугольному виду (\ref{formula:triag_eqn_system}) с помощью цепочки элементарных преобразований. +\begin{eqnarray} + \left\{ + \begin{aligned} + & a'_{11}x_1 + a'_{12}x_2 + a'_{1n}x_n = b'_1 \\ + & 0x_1 + a'_{22}x_2 + a'_{2n}x_n = b'_2 \\ + & ........................................ \\ + & 0x_1 + 0x_2 + a'_{nn}x_n = b'_n \\ + \end{aligned} + \right. + \label{formula:triag_eqn_system} +\end{eqnarray} +Данный процесс называется прямым ходом, а нахождение неизвестных +\(x_n, x_{n-1}, \dots,x_1 \) --- обратным. \subsubsection{Реализации метода в библиотеках numpy, scipy} +В библиотеке scipy реализован частный случай метода Гаусса --- +LU-разложение \cite[с. 259]{book:levitin}. Для получения решения +СЛУ необходимо задействовать две функции из модуля \textbf{scipy.linalg} \cite{links:scipy_doc}: +\begin{enumerate} + \item Для получения разложения используется функция + \textbf{lu\_factor}. + \item Для совершения обратного хода алгоритма используется + \textbf{lu\_solve}, которая принимает на вход разложение с + предыдущего этапа. +\end{enumerate} + +Функция \textbf{lu\_factor} имеет следующие параметры (задаются в порядке перечисления): +\begin{enumerate} + \item \(a\) --- (M, N) array\_like + + Матрица для разложения + \item \(overwrite\_a\) bool, необязательный + + Следует ли перезаписывать данные в A (может повысить + производительность). По умолчанию \verb|False|. + \item \(check\_finite\) bool, необязательный + + Проверять, содержит ли входная матрица только конечные числа. + Отключение может дать прирост производительности, но может + привести к проблемам (сбоям, незавершению), если входные данные + содержат бесконечности или NaN. По умолчанию \verb|True|. +\end{enumerate} + +Функция \textbf{lu\_solve} имеет следующие параметры (задаются в порядке перечисления): +\begin{enumerate} + \item \((lu, piv)\) --- tuple + + Факторизация матрицы коэффициентов a, полученная из + \textbf{lu\_factor}. + \item \(b\) --- array + + Правая сторона + \item \(trans\) --- {0, 1, 2}, необязательный + + Тип системы, которую необходимо решить: + + \begin{tabularx}{0.8\textwidth}{|X|X|} + \hline \(trans\) & вид системы \\ + \hline 0 & \(ax = b\) \\ + \hline 1 & \(a^T x = b\) \\ + \hline 2 & \(a^H x = b\) \\ + \hline + \end{tabularx}\\ + + По умолчанию \verb|0|. + \item \(overwrite\_b\) --- bool, необязательный + + Следует ли перезаписывать данные в \(b\) (может повысить производительность). По умолчанию \verb|False|. + \item \(check\_finite\) --- bool, необязательный + + Проверять, содержат ли входные матрицы только конечные числа. + Отключение может дать прирост производительности, но может + привести к проблемам (сбоям, незавершению), если входные данные + содержат бесконечности или NaN. По умолчанию \verb|True|. +\end{enumerate} \subsection{Метод обратной матрицы} -TODO \subsubsection{Описание метода} -TODO +Исходная система (\ref{formula:eqn_system}) представляется в форме +\(AX=B\), тогда вектор неизвестных переменных \(X\) определяется по +формуле (\ref{formula:inv_m_method}). +\begin{equation} + X=A^{-1}B + \label{formula:inv_m_method} +\end{equation} \subsubsection{Реализации метода в библиотеках numpy, scipy} +Отдельной функции для решения СЛУ не существует, вектор \(X\) можно +найти по формуле (\ref{formula:inv_m_method}). Для получения \(A^{-1}\) +существует функция \textbf{inv} в модуле \textbf{scipy.linalg} +\cite{links:scipy_doc} библиотеки scipy, и функция \textbf{inv} в модуле +\textbf{numpy.linalg} библиотеки numpy. Для перемножения \(A^{-1}\) +и \(B\) в языке Python есть оператор \verb|@|, начиная с версии \(3.5\) +\cite{links:numpy_doc}\cite{links:PEP465}. + +В результате для получения решения СЛУ необходимо выполнить выражение +\verb|inv(A) @ B|, используя одну из вышеописанных функций. + +Функция \textbf{inv} модуля \textbf{scipy.linalg} имеет следующие +параметры (задаются в порядке перечисления): +\begin{enumerate} + \item \(a\) --- array\_like + + Квадратная матрица, которую необходимо инвертировать. + \item \(overwrite\_a\) --- bool, необязательный + + Не запоминать состояние \(a\) (может улучшить + производительность). По умолчанию \verb|False|. + \item \(check\_finite\) --- bool, необязательный + + Проверять, содержат ли входные матрицы только конечные числа. + Отключение может дать прирост производительности, но может + привести к проблемам (сбоям, незавершению), если входные данные + содержат бесконечности или NaN. По умолчанию \verb|True|. +\end{enumerate} + +Функция \textbf{inv} модуля \textbf{scipy.linalg} имеет следующие +параметры (задаются в порядке перечисления): +\begin{enumerate} + \item \(a\) --- аналогичен параметру \(a\) функции из модуля \textbf{scipy.linalg}. +\end{enumerate} \subsection{Метод прогонки} TODO From da1e6ef1dbf21b4585f8afe4b5cd36ea20bf3084 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Mon, 21 Aug 2023 23:37:57 +0300 Subject: [PATCH 08/41] [theory] Add new method; Fix styling --- main.tex | 190 +++++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 155 insertions(+), 35 deletions(-) diff --git a/main.tex b/main.tex index d3945ae..e00641b 100644 --- a/main.tex +++ b/main.tex @@ -257,7 +257,7 @@ \begin{aligned} & a_{11}x_1 + a_{12}x_2 + a_{1n}x_n = b_1 \\ & a_{21}x_1 + a_{22}x_2 + a_{2n}x_n = b_2 \\ - & ........................................ \\ + & \dots\dots\dots\dots\dots\dots\dots\dots \\ & a_{n1}x_1 + a_{n2}x_2 + a_{nn}x_n = b_n \\ \end{aligned} \right. @@ -281,7 +281,7 @@ \begin{aligned} & a'_{11}x_1 + a'_{12}x_2 + a'_{1n}x_n = b'_1 \\ & 0x_1 + a'_{22}x_2 + a'_{2n}x_n = b'_2 \\ - & ........................................ \\ + & \dots\dots\dots\dots\dots\dots\dots. \\ & 0x_1 + 0x_2 + a'_{nn}x_n = b'_n \\ \end{aligned} \right. @@ -389,6 +389,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения привести к проблемам (сбоям, незавершению), если входные данные содержат бесконечности или NaN. По умолчанию \verb|True|. \end{enumerate} +\vspace{\baselineskip} Функция \textbf{inv} модуля \textbf{scipy.linalg} имеет следующие параметры (задаются в порядке перечисления): @@ -397,106 +398,225 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \end{enumerate} \subsection{Метод прогонки} -TODO +Данный метод применяется для решения трехдиагональных матриц \hspace{1mm} вида +\begin{equation} + \left\{ + \begin{aligned} + & a_1 x_0 + b_1 x_1 + c_1 x_2 = d_1 \\ + & a_2 x_1 + b_2 x_2 + c_2 x_3 = d_2 \\ + & \dots\dots\dots\dots\dots\dots\dots \\ + & a_n x_{n-1} + b_n x_n + c_n x_{n+1} = d_n \\ + \end{aligned} + \right. + \label{formula:eqn_banded_system} +\end{equation} + +Является частным случаем метода Гаусса. \subsubsection{Описание метода} -TODO +После исключения переменных ниже главной диагонали с помощью +элементарных преобразований, в каждом уравнении СЛУ остается +\(\leq 2\) неизвестных. В таком случае, формула обратного хода будет +следующей: \(x_i = U_i x_{i+1}+V_i, i = n,n-1,\dots,1\). После замены +\(i\) на \(i-1\) и подстановке выражения в общий вид уравнения из СЛУ +(\ref{formula:eqn_banded_system}) \(a_i x_{i-1} + b_i x_i + c_i x_{i+1}\) +получим следующее выражение: +\begin{equation} + x_i = - \frac{c_i}{a_i U_{i-1} + b_i} x_{i+1} + + \frac{d_i - a_i V_{i-1}}{a_i U_{i-1} + b_i} +\end{equation} +Из которого получим: +\begin{equation} + U_i = - \frac{c_i}{a_i U_{i-1} + b_i}, + V_i = \frac{d_i - a_i V_{i-1}}{a_i U_{i-1} + b_i} + \hspace{1cm} i = 1,2,3,\dots,n +\end{equation} +При этом \( c_n = 0; a_1 = 0\). + +Таким образом, сначала вычисляем \(U_i, V_i\), затем +\(x_i, i =n,n-1,\dots,1\). + +Данный метод в общем случае не устойчив, за исключением случаев, +когда матрица СЛУ обладает свойством диагонального преобладания +(условие \ref{formula:eqn_diag_dominant}) или она положительно +определенная \cite{links:bhatia}. +\begin{equation} + \sum_{i \ne j} |a_{ij}| < |a_{ii}| + \label{formula:eqn_diag_dominant} +\end{equation} + \subsubsection{Реализации метода в библиотеках numpy, scipy} +В scipy для решения СЛУ вида (\ref{formula:eqn_banded_system}) +существует две функции в модуле \textbf{scipy.linalg}: +\textbf{solve\_banded} \cite{links:scipy_doc} и +\textbf{solveh\_banded} \cite{links:scipy_doc}. + +Различие между ними заключается в том, что \textbf{solve\_banded} +не использует метод прогонки, из-за низкой устойчивости метода в общем +случае, что позволяет найти решение даже если матрица не положительно +определенная или не обладает свойством диагонального преобладания. + +\textbf{solveh\_banded} реализует метод прогонки, но авторы библиотеки +указывают, что вводимая матрица должна быть положительно определенной +\cite{links:scipy_doc}. + +Функция \textbf{solve\_banded} имеет следующие параметры (задаются в +порядке перечисления): +\begin{enumerate} + \item \((l, u)\) --- (integer, integer) tuple + + Количество ненулевых нижних и верхних диагоналей. + \item \(ab\) --- (l + u + 1, M) array\_like + + Ленточная матрица. + \item \(b\) --- (M,) or (M, K) array\_like + + Правая сторона. + \item \(overwrite\_ab\) --- bool, необязательный + + Разрешить изменять данные в \(ab\) (может повысить + производительность). По умолчанию \verb|False|. + \item \(overwrite\_b\) --- bool, необязательный + + Разрешить изменять данные в \(b\) (может повысить + производительность). По умолчанию \verb|False|. + \item \(check\_finite\) --- bool, необязательный + + Проверять, содержат ли входные матрицы только конечные числа. + Отключение может дать прирост производительности, но может + привести к проблемам (сбоям, незавершению), если входные данные + содержат бесконечности или NaN. По умолчанию \verb|True|. +\end{enumerate} + +Функция \textbf{solveh\_banded} имеет несколько иной набор параметров +(задаются в порядке перечисления): +\begin{enumerate} + \item \(ab\) --- (u + 1, M) array\_like + + Ленточная матрица, \(u\) --- число верхних диагоналей. + \item \(b\) --- (M,) or (M, K) array\_like + + Правая сторона. + \item \(overwrite\_ab\) --- bool, необязательный + + Разрешить изменять данные в \(ab\) (может повысить + производительность). По умолчанию \verb|False|. + \item \(overwrite\_b\) --- bool, необязательный + + Разрешить изменять данные в \(b\) (может повысить + производительность). По умолчанию \verb|False|. + \item \(lower\) --- bool, необязательный + + Является ли матрица в нижней форме. (По умолчанию используется + верхняя форма), то есть \verb|False|. + \item \(check\_finite\) --- bool, необязательный + + Совпадает с параметром \(check\_finite\) функции + \textbf{solve\_banded}. +\end{enumerate} + +Обе функции принимают матрицу \(ab\) либо в верхней (\textbf{solve\_banded}), либо в нижней форме (\textbf{solveh\_banded} при +включенной опции \(lower\)). Например, для матрицы + +\begin{tabular}[htpb]{ccccc} + 5 & 2 & -1 & 0 & 0 \\ + 1 & 4 & 2 & -1 & 0 \\ + 0 & 1 & 3 & 2 & -1 \\ + 0 & 0 & 1 & 2 & 2 \\ + 0 & 0 & 0 & 1 & 1 \\ +\end{tabular}\\ +верхняя форма будет следующей: + +\begin{tabular}[htpb]{ccccc} + 0 & 0 & -1 & -1 & -1 \\ + 0 & 2 & 2 & 2 & 2 \\ + 5 & 4 & 3 & 2 & 1 \\ + 1 & 1 & 1 & 1 & 0 \\ +\end{tabular} + +Так как данная матрица не эрмитова, и, следовательно, не положительно +определенна, описание нижней формы для нее неуместно. Если взять эрмитову +положительно определенную матрицу + +\begin{tabular}[htpb]{cccccc} + 4 & 2 & -1 & 0 & 0 & 0 \\ + 2 & 5 & 2 & -1 & 0 & 0 \\ + -1 & 2 & 6 & 2 & -1 & 0 \\ + 0 & -1 & 2 & 7 & 2 & -1 \\ + 0 & 0 & -1 & 2 & 8 & 2 \\ + 0 & 0 & 0 & -1 & 2 & 9 \\ +\end{tabular}\\ +то ее нижняя форма будет следующая: + +\begin{tabular}[htpb]{cccccc} + 4 & 5 & 6 & 7 & 8 & 9 \\ + 2 & 2 & 2 & 2 & 2 & 0 \\ + -1 & -1 & -1 & -1 & 0 & 0 \\ +\end{tabular} \subsection{Метод простой итерации (метод Якоби)} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Метод Зейделя} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \section{Численные методы решения систем нелинейных уравнений} -TODO \subsection{Метод простой итерации (метод Якоби) для систем нелинейных уравнений} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Метод Зейделя для систем нелинейных уравнений} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Метод Ньютона решения систем нелинейных уравнений} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \section{Аппроксимация функций} \subsection{Интерполяционный полином в форме Лагранжа} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Интерполяционный полином в форме Ньютона} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Сплайн-интерполяция} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Сглаживание. Метод наименьших квадратов} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \section{Численное интегрирование} \subsection{Метод прямоугольников} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Метод трапеций} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Метод парабол (Симпсона)} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \section{Численное решение обыкновенных дифференциальных уравнений} \subsection{Метод Эйлера} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Модифицированный метод Эйлера} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} \subsection{Метод Рунге-Кутта} -TODO \subsubsection{Описание метода} -TODO \subsubsection{Реализации метода в библиотеках numpy, scipy} From e668810916178b22be95e1edc1b893a1ca7dfd4d Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Mon, 21 Aug 2023 23:38:16 +0300 Subject: [PATCH 09/41] [sources] Add new item --- sources.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/sources.tex b/sources.tex index a46d792..5799292 100644 --- a/sources.tex +++ b/sources.tex @@ -20,6 +20,7 @@ \bibitem{links:numpy_doc} Numpy API Reference [Электронный ресурс] -- URL:~\url{https://numpy.org/doc/stable/reference/index.html} (\LiteratureAccessDate[3]). \bibitem{links:PEP465} PEP 465 -- A dedicated infix operator for matrix multiplication [Электронный ресурс] -- URL:~\url{A dedicated infix operator for matrix multiplication} (\LiteratureAccessDate[4]). \bibitem{links:python} Python. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://www.python.org/} (\LiteratureAccessDate). + \bibitem{links:bhatia} R. Bhatia Positive definite matrices -- Princeton Series in Applied Mathematics -- 2007. \bibitem{links:scipy} Scipy. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://scipy.org/} (\LiteratureAccessDate[2]). \bibitem{links:scipy_doc} Scipy API Reference [Электронный ресурс] -- URL:~\url{https://docs.scipy.org/doc/scipy/reference/index.html} (\LiteratureAccessDate[3]). \bibitem{links:tiobe_index} TIOBE. Официальный сайт проекта From 36c728cddd7c0af0d2990dc787e5a161bdf8260b Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Tue, 22 Aug 2023 14:20:41 +0300 Subject: [PATCH 10/41] [content] Fix formulas, style; Add 2 methods - Add matrix representation of SLE --- main.tex | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 72 insertions(+), 7 deletions(-) diff --git a/main.tex b/main.tex index e00641b..d631b50 100644 --- a/main.tex +++ b/main.tex @@ -263,6 +263,33 @@ \right. \label{formula:eqn_system} \end{eqnarray} +СЛУ также представима в матричной форме \(AX=B\), где \(A,X,B\) имеют +следующий вид: +\begin{eqnarray} + A = \left( + \begin{aligned} + & a_{11} \quad a_{12} \quad \dots \quad a_{1n} \\ + & a_{21} \quad a_{22} \quad \dots \quad a_{2n} \\ + & \ \vdots \ \qquad \vdots \quad \ \ddots \quad \ \vdots \\ + & a_{n1} \quad a_{n2} \quad \dots \quad a_{nn} \\ + \end{aligned} + \right), B = \left( + \begin{aligned} + & b_1 \\ + & b_2 \\ + & \ \vdots \\ + & b_n \\ + \end{aligned} + \right), X = \left( + \begin{aligned} + & x_1 \\ + & x_2 \\ + & \ \vdots \\ + & x_n \\ + \end{aligned} + \right) + \label{formula:eqn_matrix_system} +\end{eqnarray} Для решения таких систем существуют прямые и итерационные методы. Прямые методы (к ним относятся "метод Гаусса", "метод обратной матрицы" @@ -272,6 +299,8 @@ "метод простой итерации" и "метод Зейделя") позволяют получить приближенное решение с помощью последовательного приближения к точному. +Для итерационных методов необходимо начальное приближение, которое +будет обозначено как \(x^{(0)}\). \subsection{Метод Гаусса} \subsubsection{Описание метода} Для решения СЛУ система (\ref{formula:eqn_system}) приводится к @@ -389,7 +418,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения привести к проблемам (сбоям, незавершению), если входные данные содержат бесконечности или NaN. По умолчанию \verb|True|. \end{enumerate} -\vspace{\baselineskip} +\pagebreak Функция \textbf{inv} модуля \textbf{scipy.linalg} имеет следующие параметры (задаются в порядке перечисления): @@ -430,17 +459,16 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения V_i = \frac{d_i - a_i V_{i-1}}{a_i U_{i-1} + b_i} \hspace{1cm} i = 1,2,3,\dots,n \end{equation} -При этом \( c_n = 0; a_1 = 0\). - -Таким образом, сначала вычисляем \(U_i, V_i\), затем -\(x_i, i =n,n-1,\dots,1\). +При этом \( c_n = 0; a_1 = 0\), в ходе выполнения алгоритма сначала +вычисляем \(U_i, V_i\), затем \(x_i, i =n,n-1,\dots,1\). +\pagebreak Данный метод в общем случае не устойчив, за исключением случаев, когда матрица СЛУ обладает свойством диагонального преобладания (условие \ref{formula:eqn_diag_dominant}) или она положительно определенная \cite{links:bhatia}. \begin{equation} - \sum_{i \ne j} |a_{ij}| < |a_{ii}| + \sum_{i \ne j} |a_{ij}| < |a_{ii}|; \qquad i=1,2,3,\dots,n \label{formula:eqn_diag_dominant} \end{equation} @@ -523,7 +551,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения 0 & 1 & 3 & 2 & -1 \\ 0 & 0 & 1 & 2 & 2 \\ 0 & 0 & 0 & 1 & 1 \\ -\end{tabular}\\ +\end{tabular}\\ верхняя форма будет следующей: \begin{tabular}[htpb]{ccccc} @@ -555,11 +583,48 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsection{Метод простой итерации (метод Якоби)} \subsubsection{Описание метода} +Для матрицы СЛУ (\ref{formula:eqn_matrix_system}) размеров +\(n \times n\), и начального приближения \(x^{(0)}\) приближенное +решение на итерации \(p, p = 1,2,\dots,k,k+1\) вычисляется по +следующей формуле: +\begin{equation*} + x^{(p+1)}_i = \frac{1}{a_{ii}} + (b_i - \sum_{j=1}^{i-1}a_{ij} x^{(p)}_j + - \sum_{j=i+1}^{n}a_{ij} x^{(p)}_j); + \quad i = 1,2,3,\dots,n +\end{equation*} + +Метод сходится при \(p \to \infty\), если матрица СЛУ обладает свойством +диагонального преобладания (выполняется условие +\ref{formula:eqn_diag_dominant}). Заданная точность достигается +при выполнении условия: +\begin{equation} + \max_i |x^{(p+1)}_i-x^{(p)_i}| < \varepsilon + \label{formula:precision_iter_sle} +\end{equation} \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного метода в библиотеках numpy, scipy не найдено. \subsection{Метод Зейделя} \subsubsection{Описание метода} +Для матрицы СЛУ (\ref{formula:eqn_matrix_system}) размеров +\(n \times n\), и начального приближения \(x^{(0)}\) приближенное +решение на итерации \(p, p = 1,2,\dots,k,k+1\) вычисляется по +следующей формуле: +\begin{equation*} + x^{(p+1)}_i = \frac{1}{a_{ii}} + (b_i - \sum_{j=1}^{i-1}a_{ij} x^{(p+1)}_j + - \sum_{j=i+1}^{n}a_{ij} x^{(p)}_j); + \quad i = 1,2,3,\dots,n +\end{equation*} + +Данный метод, в отличие от предыдущего, использует уже найденные +компоненты этой же итерации с м\'eньшим индексом. + +Сходимость и точность задаются условиями +(\ref{formula:eqn_diag_dominant}) и (\ref{formula:precision_iter_sle}). \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного алгоритма в библиотеках numpy, scipy не найдено. \section{Численные методы решения систем нелинейных уравнений} \subsection{Метод простой итерации (метод Якоби) для систем From f1a6257b45751f9933bc0fd06f683942ec9b481c Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Wed, 23 Aug 2023 22:27:53 +0300 Subject: [PATCH 11/41] [content] Rename refs. Fix formulas. Add SnLE method --- main.tex | 62 +++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/main.tex b/main.tex index d631b50..42d98cb 100644 --- a/main.tex +++ b/main.tex @@ -235,10 +235,15 @@ уравнения за исполнение алгоритма на единственном начальном приближении. \subsubsection{Описание метода} Уравнение \(F(x)=0\) приводим к виду \(x = \varphi(x)\), например -\(x-F(x)/M\), где \(M\) --- константа. +\(x = x-F(x)/M\), где \(M\) --- константа. Условие сходимости алгоритма: \(0<|\varphi'(x)|<1\). Исходя из него, -\(M\) определяется как \(M=1.01 \cdot F'(x_0)\), где \(x_0\) --- +\(M\) определяется как +\begin{equation} + M=1.01 \cdot F'(x_0) + \label{formula:stab_simpleiter} +\end{equation} +где \(x_0\) --- начальное приближение. Таким образом, для итерации \(i, i = 1\dots k,\) @@ -261,7 +266,7 @@ & a_{n1}x_1 + a_{n2}x_2 + a_{nn}x_n = b_n \\ \end{aligned} \right. - \label{formula:eqn_system} + \label{formula:SLE} \end{eqnarray} СЛУ также представима в матричной форме \(AX=B\), где \(A,X,B\) имеют следующий вид: @@ -303,7 +308,7 @@ будет обозначено как \(x^{(0)}\). \subsection{Метод Гаусса} \subsubsection{Описание метода} -Для решения СЛУ система (\ref{formula:eqn_system}) приводится к +Для решения СЛУ система (\ref{formula:SLE}) приводится к треугольному виду (\ref{formula:triag_eqn_system}) с помощью цепочки элементарных преобразований. \begin{eqnarray} \left\{ @@ -382,7 +387,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsection{Метод обратной матрицы} \subsubsection{Описание метода} -Исходная система (\ref{formula:eqn_system}) представляется в форме +Исходная система (\ref{formula:SLE}) представляется в форме \(AX=B\), тогда вектор неизвестных переменных \(X\) определяется по формуле (\ref{formula:inv_m_method}). \begin{equation} @@ -585,7 +590,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsubsection{Описание метода} Для матрицы СЛУ (\ref{formula:eqn_matrix_system}) размеров \(n \times n\), и начального приближения \(x^{(0)}\) приближенное -решение на итерации \(p, p = 1,2,\dots,k,k+1\) вычисляется по +решение на итерации \(p, p = 1,2,3,\dots,k,k+1\) вычисляется по следующей формуле: \begin{equation*} x^{(p+1)}_i = \frac{1}{a_{ii}} @@ -609,7 +614,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsubsection{Описание метода} Для матрицы СЛУ (\ref{formula:eqn_matrix_system}) размеров \(n \times n\), и начального приближения \(x^{(0)}\) приближенное -решение на итерации \(p, p = 1,2,\dots,k,k+1\) вычисляется по +решение на итерации \(p, p = 1,2,3,\dots,k,k+1\) вычисляется по следующей формуле: \begin{equation*} x^{(p+1)}_i = \frac{1}{a_{ii}} @@ -627,10 +632,53 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения Реализаций данного алгоритма в библиотеках numpy, scipy не найдено. \section{Численные методы решения систем нелинейных уравнений} +Система нелинейных уравнений имеет следующий вид: +\begin{equation} + \begin{aligned} + & F_1(x_1,x_2,\dots,x_n) = 0 \\ + & F_2(x_1,x_2,\dots,x_n) = 0 \\ + & \dots\dots\dots\dots\dots\dots \\ + & F_n(x_1,x_2,\dots,x_n) = 0 \\ + \end{aligned} + \label{formula:SnLE} +\end{equation} + +Для решения данной системы далее будет описано несколько итерационных +методов. Для каждого их них требуется начальное приближение +\(X^{(0)} = \{x^{(0)}_1,x^{(0)}_2,\dots x^{(0)}_n\}\). + \subsection{Метод простой итерации (метод Якоби) для систем нелинейных уравнений} \subsubsection{Описание метода} +Для каждого из уравнений системы (\ref{formula:SnLE}) составляется +уравнение \(f_i: x_i = x_i - F_i(x)/M_i\), где \(M_i\) определяется из +условия сходимости уравнения (\ref{formula:stab_simpleiter}). +В результате получим систему +\begin{equation} + \begin{aligned} + & x_1 = f_1(x_1,x_2,\dots,x_n) \\ + & x_2 = f_2(x_1,x_2,\dots,x_n) \\ + & \dots\dots\dots\dots\dots\dots \\ + & x_n = f_n(x_1,x_2,\dots,x_n) \\ + \end{aligned} + \label{formula:SnLE-Jacobi-fsys} +\end{equation} + +Для получения приближенного решения уравнения применяется формула +\begin{equation} + x^{(p+1)}_{i} = f_i(x^{(p)}_1,x^{(p)}_2,\dots,x^{(p)}_n); + \quad i=1,2,3,\dots,n +\end{equation} +где \(p, p = 1,2,3,\dots,k,k+1\) --- номер итерации. + +Заданная точность \(\varepsilon\) достигается выполнением +следующего условия: +\begin{equation*} + \forall i = 1,2,3,\dots,n;\ + \max_i |x^{(k+1)}_i - x^{(k)}_i | < \varepsilon +\end{equation*} \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного метода в библиотеках numpy, scipy не найдено. \subsection{Метод Зейделя для систем нелинейных уравнений} \subsubsection{Описание метода} From 36f397d6aadec8ebd14e9c2d9d793507cef4fb6b Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Thu, 24 Aug 2023 22:06:17 +0300 Subject: [PATCH 12/41] config: Add new package --- config.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/config.tex b/config.tex index a5016ff..949bea0 100644 --- a/config.tex +++ b/config.tex @@ -20,6 +20,7 @@ \usepackage{xstring} \usepackage{tabularx} \usepackage{enumitem} +\usepackage{mathtools} \usepackage[strict=true]{csquotes} \usepackage[abspath]{currfile} \usepackage[hidelinks,linktoc=all]{hyperref} From afc4b083ecfa2f065e25947a8ce85d9f47e057d3 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Thu, 24 Aug 2023 22:07:30 +0300 Subject: [PATCH 13/41] [content] Add 2 new methods, new section desc. Fix formulas --- main.tex | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 6 deletions(-) diff --git a/main.tex b/main.tex index 42d98cb..9308a7e 100644 --- a/main.tex +++ b/main.tex @@ -1,6 +1,9 @@ \input{vars} \input{config} +\NewDocumentCommand{\MFArgs} + {}{x^{(p)}_1,x^{(p)}_2,x^{(p)}_3,\dots,x^{(p)}_n} + \begin{document} \lstset{language=[11]C++} @@ -590,7 +593,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsubsection{Описание метода} Для матрицы СЛУ (\ref{formula:eqn_matrix_system}) размеров \(n \times n\), и начального приближения \(x^{(0)}\) приближенное -решение на итерации \(p, p = 1,2,3,\dots,k,k+1\) вычисляется по +решение на итерации \(p, p = 1,2,3,\dots,k\) вычисляется по следующей формуле: \begin{equation*} x^{(p+1)}_i = \frac{1}{a_{ii}} @@ -604,7 +607,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \ref{formula:eqn_diag_dominant}). Заданная точность достигается при выполнении условия: \begin{equation} - \max_i |x^{(p+1)}_i-x^{(p)_i}| < \varepsilon + \max_{i \le i \le n} |x^{(p+1)}_i-x^{(p)}_i| < \varepsilon \label{formula:precision_iter_sle} \end{equation} \subsubsection{Реализации метода в библиотеках numpy, scipy} @@ -614,7 +617,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsubsection{Описание метода} Для матрицы СЛУ (\ref{formula:eqn_matrix_system}) размеров \(n \times n\), и начального приближения \(x^{(0)}\) приближенное -решение на итерации \(p, p = 1,2,3,\dots,k,k+1\) вычисляется по +решение на итерации \(p, p = 1,2,3,\dots,k\) вычисляется по следующей формуле: \begin{equation*} x^{(p+1)}_i = \frac{1}{a_{ii}} @@ -669,26 +672,123 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения x^{(p+1)}_{i} = f_i(x^{(p)}_1,x^{(p)}_2,\dots,x^{(p)}_n); \quad i=1,2,3,\dots,n \end{equation} -где \(p, p = 1,2,3,\dots,k,k+1\) --- номер итерации. +где \(p, p = 1,2,3,\dots,k\) --- номер итерации. Заданная точность \(\varepsilon\) достигается выполнением следующего условия: \begin{equation*} - \forall i = 1,2,3,\dots,n;\ - \max_i |x^{(k+1)}_i - x^{(k)}_i | < \varepsilon + \max_i |x^{(p+1)}_i - x^{(p)}_i | < \varepsilon; \quad + i = 1,2,3,\dots,n \end{equation*} \subsubsection{Реализации метода в библиотеках numpy, scipy} Реализаций данного метода в библиотеках numpy, scipy не найдено. \subsection{Метод Зейделя для систем нелинейных уравнений} +Данный метод является модификацией предыдущего; отличие состоит в +условии сходимости и формуле получения приближенного решения. + +Ниже будут описаны только вышеперечисленные различия. \subsubsection{Описание метода} +Формула вычисления приближенного решения данного алгоритма следующая: +\begin{equation*} + \begin{aligned} + & x^{(p+1)}_{1} = f_1(x^{(p)}_1,x^{(p)}_2,\dots,x^{(p)}_n) \\ + & x^{(p+1)}_{2} = f_2(x^{(p)}_1,x^{(p)}_2,\dots,x^{(p)}_n) \\ + & \dots\dots\dots\dots\dots\dots\dots\dots\dots \\ + & x^{(p+1)}_{n} = f_n(x^{(p)}_1,x^{(p)}_2,\dots,x^{(p)}_n) \\ + \end{aligned} +\end{equation*} +где \(p, p = 1,2,3,\dots,k\) --- номер итерации. + +Данный алгоритм более требователен к точности начального приближения. + +Сходимость метода зависит от характера функций исходной системы, +для определения которого необходимо вычислить значения матрицы +\begin{equation} + F' = \left( + \begin{aligned} + & f^{'}_{11} \ \ f^{'}_{12} \ \ f^{'}_{13} \ \ \dots \ \ f^{'}_{1n} \\ + & f^{'}_{21} \ \ f^{'}_{22} \ \ f^{'}_{23} \ \ \dots \ \ f^{'}_{2n} \\ + & \dots \ \ \dots \ \ \dots \ \ \dots \ \ \dots \\ + & f^{'}_{n1} \ \ f^{'}_{n2} \ \ f^{'}_{n3} \ \ \dots \ \ f^{'}_{nn} \\ + \end{aligned} + \right) +\end{equation} +где \(f^{'}_{ij} = \frac{\partial f_i}{\partial x_j}\). + +Сходимость метода обеспечивается выполнением следующего условия: +\vspace{-5mm} +\begin{equation*} + |f^{'}_{i1}| + |f^{'}_{i2}| + |f^{'}_{i3}| + \dots |f^{'}_{in}| < 1; + \quad i = 1,2,3,\dots,n +\end{equation*} + \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного метода в библиотеках numpy, scipy не обнаружено. \subsection{Метод Ньютона решения систем нелинейных уравнений} \subsubsection{Описание метода} +На каждой итерации \(p, p = 1,2,3,\dots,k\) вычисляются значения +\(\Delta x^{(p)}_1, \Delta x^{(p)}_2, \Delta x^{(p)}_3, \dots, +\Delta x^{(p)}_n \). + +Для этого исходная система уравнений раскладывается в ряд Тейлора по +\(\Delta x^{(p)}_1, \Delta x^{(p)}_2, \Delta x^{(p)}_3, \dots, +\Delta x^{(p)}_n \). Сохранив линейные по данным значениям части, получим +СЛУ: +\begin{equation} + \begin{aligned} + & \splitdfrac{\frac{\partial F_1(\MFArgs)}{\partial x_1} \Delta x^{(p)}_1 + + \frac{\partial F_1(\MFArgs)}{\partial x_2} \Delta x^{(p)}_2 + \dots +} + {\frac{\partial F_1(\MFArgs)}{\partial x_n} \Delta x^{(p)}_n = -F_1(\MFArgs)} \\ + & \splitdfrac{\frac{\partial F_2(\MFArgs)}{\partial x_1} \Delta x^{(p)}_1 + + \frac{\partial F_2(\MFArgs)}{\partial x_2} \Delta x^{(p)}_2 + \dots +} + {\frac{\partial F_2(\MFArgs)}{\partial x_n} \Delta x^{(p)}_n = -F_2(\MFArgs)} \\ + & \dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots \\ + & \splitdfrac{\frac{\partial F_n(\MFArgs)}{\partial x_1} \Delta x^{(p)}_1 + + \frac{\partial F_n(\MFArgs)}{\partial x_2} \Delta x^{(p)}_2 + \dots +} + {\frac{\partial F_n(\MFArgs)}{\partial x_n} \Delta x^{(p)}_n = -F_n(\MFArgs)} \\ + \end{aligned} + \label{formula:SnLE-Newton-sys} +\end{equation} +Данную систему можно решить любым наиболее подходящим с учетом доступных +ресурсов методом. + +Решением СЛУ (\ref{formula:SnLE-Newton-sys}) будет вектор +\(\Delta X^{(p)} = (\MFArgs)\). После этого, приближенное решение +исходной задачи находится по формуле +\(X^{(p+1)} = X^{(p)} + \Delta X^{(p)}\). + +Заданная точность достигается выполнением условия +(\ref{formula:precision_iter_sle}). Стоит учитывать, что данный метод +так же, как и предыдущий, требователен к точности начального приближения. \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного алгоритма в библиотеках numpy, scipy не найдено, +кроме того, для его работы требуется нахождение частных производных. + +Для поиска частной производной в scipy есть функция \textbf{derivative} +в модуле \textbf{scipy.misc}, но она отмечена устаревшей и будет +удалена в версии 1.12.0, поэтому реализация данного метода с помощью +применения функций общей направленности библиотеки рассматриваться +не будет. \section{Аппроксимация функций} +Иногда значения некоторой функциональной зависимости +\(y= \widetilde{y}(x) \) известны для отдельных пар значений +\((x_i,y_i)\). + +Задача восстановления аналитической функции \(\widetilde{y}\) +по отдельным парам значений называется аппроксимацией. +Для получения ее однозначного решения необходимо задать общий вид +функции, включающей коэффициенты, и затем эти коэффициенты определить +с помощью подходящего метода. + +Для определения коэффициентов существует два основных подхода --- +интерполяция (когда \(y_i = \widetilde{x_i}\)), и сглаживание, когда +требуется лишь минимизировать отклонение от известных значений. + +Первые три метода решают поставленную задачу с помощью интерполяции, +последний --- с помощью сглаживания. \subsection{Интерполяционный полином в форме Лагранжа} \subsubsection{Описание метода} \subsubsection{Реализации метода в библиотеках numpy, scipy} From 9315741d735972af8eaf27336100dd421d9e2d25 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Tue, 5 Sep 2023 21:01:06 +0300 Subject: [PATCH 14/41] config: Add math environment --- config.tex | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config.tex b/config.tex index 949bea0..27e966f 100644 --- a/config.tex +++ b/config.tex @@ -182,3 +182,8 @@ % ОФОРМЛЕНИЕ ТЕКСТА \renewcommand{\baselinestretch}{1.5} \MakeOuterQuote{"} + +\newenvironment{nospaceflalign*} + {\setlength{\abovedisplayskip}{0pt}\setlength{\belowdisplayskip}{0pt}% + \csname flalign*\endcsname} + {\csname endflalign*\endcsname\ignorespacesafterend} From 4887f3ba2a1eb5f830dcdb9681395ff40112c823 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Tue, 5 Sep 2023 22:31:04 +0300 Subject: [PATCH 15/41] [content] Add 2 methods, add spline interpolation --- main.tex | 102 +++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 92 insertions(+), 10 deletions(-) diff --git a/main.tex b/main.tex index 9308a7e..08a2ead 100644 --- a/main.tex +++ b/main.tex @@ -729,13 +729,14 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \subsection{Метод Ньютона решения систем нелинейных уравнений} \subsubsection{Описание метода} На каждой итерации \(p, p = 1,2,3,\dots,k\) вычисляются значения -\(\Delta x^{(p)}_1, \Delta x^{(p)}_2, \Delta x^{(p)}_3, \dots, +\(\Delta x^{(p)}_1,\) \( \Delta x^{(p)}_2, \Delta x^{(p)}_3, \dots, \Delta x^{(p)}_n \). Для этого исходная система уравнений раскладывается в ряд Тейлора по \(\Delta x^{(p)}_1, \Delta x^{(p)}_2, \Delta x^{(p)}_3, \dots, \Delta x^{(p)}_n \). Сохранив линейные по данным значениям части, получим СЛУ: + \begin{equation} \begin{aligned} & \splitdfrac{\frac{\partial F_1(\MFArgs)}{\partial x_1} \Delta x^{(p)}_1 + @@ -745,12 +746,16 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \frac{\partial F_2(\MFArgs)}{\partial x_2} \Delta x^{(p)}_2 + \dots +} {\frac{\partial F_2(\MFArgs)}{\partial x_n} \Delta x^{(p)}_n = -F_2(\MFArgs)} \\ & \dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots\dots \\ - & \splitdfrac{\frac{\partial F_n(\MFArgs)}{\partial x_1} \Delta x^{(p)}_1 + - \frac{\partial F_n(\MFArgs)}{\partial x_2} \Delta x^{(p)}_2 + \dots +} - {\frac{\partial F_n(\MFArgs)}{\partial x_n} \Delta x^{(p)}_n = -F_n(\MFArgs)} \\ \end{aligned} \label{formula:SnLE-Newton-sys} \end{equation} +\begin{equation*} + \begin{aligned} + & \splitdfrac{\frac{\partial F_n(\MFArgs)}{\partial x_1} \Delta x^{(p)}_1 + + \frac{\partial F_n(\MFArgs)}{\partial x_2} \Delta x^{(p)}_2 + \dots +} + {\frac{\partial F_n(\MFArgs)}{\partial x_n} \Delta x^{(p)}_n = -F_n(\MFArgs)} \\ + \end{aligned} +\end{equation*} Данную систему можно решить любым наиболее подходящим с учетом доступных ресурсов методом. @@ -784,23 +789,100 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения с помощью подходящего метода. Для определения коэффициентов существует два основных подхода --- -интерполяция (когда \(y_i = \widetilde{x_i}\)), и сглаживание, когда +интерполяция (когда \(\widetilde{y}(x_i) = y_i\)), и сглаживание, когда требуется лишь минимизировать отклонение от известных значений. Первые три метода решают поставленную задачу с помощью интерполяции, последний --- с помощью сглаживания. -\subsection{Интерполяционный полином в форме Лагранжа} +\subsection{Интерполяционный полином Лагранжа} \subsubsection{Описание метода} -\subsubsection{Реализации метода в библиотеках numpy, scipy} +Для известных пар значений \((x_i,y_i), x_i \in [a,b]; i = 1,2,3,\dots,n\) +строится полином \(P(x)\), удовлетворяющий условию \(P(x_i) = y_i\). -\subsection{Интерполяционный полином в форме Ньютона} +Полином \(P(x)\) определяется следующей формулой: +\begin{equation*} + P(x) = L_{n-1}(x) = \sum_{i=1}^{n}y_i\cdot l_i(x) +\end{equation*} +\(l_i(x)\) --- фундаментальные полиномы Лагранжа, которые удовлетворяют равенству (\ref{formula:ip-lagr-eq1}), и имеют следующий вид: +\begin{equation*} + l_i(x) = \frac{(x-x_1)\dots (x-x_{i-1})(x-x_{i+1})\dots(x-x_n)}{(x_i-x_1)\dots(x_i-x_{i-1})(x_i-x_{i+1})\dots(x_i-x_n)} +\end{equation*} +\begin{equation} + l_k(x_i) = \left\{ + \begin{aligned} + & 1, i = k \\ + & 0, i \ne k. \\ + \end{aligned} + \right. + \label{formula:ip-lagr-eq1} +\end{equation} + +Из формулы полинома видно, что его степень не превышает \(n-1\). +Данное свойство сохранится и для следующего метода. + +\subsubsection{Реализации метода в библиотеках numpy, scipy} +Данный метод реализован в библиотеке scipy в виде функции +\textbf{lagrange} в модуле \textbf{scipy.interpolate}. +Одной из ее особенностей является низкая устойчивость --- авторы не +рекомендуют запускать алгоритм на более чем 20 парах \((x,y)\) входных +значений \cite{links:scipy_doc}. + +Данная функция имеет следующие параметры: +\begin{enumerate} + \item \(x\) --- array\_like + + \(x\) представляет координаты \(x\) набора точек данных. + \item \(w\) --- array\_like + + \(w\) представляет координаты \(y\) набора точек данных, + т. е. \(f(x)\). +\end{enumerate} + +Данная функция возвращает полином (тип --- \verb|poly1d| из +библиотеки \textbf{numpy} \cite{links:numpy_doc}). + +\subsection{Интерполяционный полином Ньютона} \subsubsection{Описание метода} +Интерполяционный полином Ньютона имеет вид +\begin{align*} + N_{n-1}(x) = \Delta^{0}(x_{1}) + \Delta^{1}(x_{1},x_{2})(x-x_{1}) + \Delta^{2}(x_{1},x_{2},x_{3})(x-x_{1})(x-x_{2}) + \\ + + \dots + \Delta^{n-1}(x_{1},x_{2},\dots,x_{n-1})(x-x_{1})(x-x_{2})\dots(x-x_{n-1}) +\end{align*} +где \(\Delta^{0}(x_{1})\dots\Delta^{n-1}(x_{1},x_{2},\dots,x_{n-1})\) --- +конечные разницы порядка \(0,\dots, n-1\), которые вычисляются следующим +образом: +\begin{nospaceflalign*} + & \Delta^{0}(x_i) = y_i & \\ + & \Delta^{1}(x_i,x_k) = \frac{\Delta^{0}(x_i) - \Delta^{0}(x_k)}{x_i-x_k} & \\ + & \Delta^{2}(x_i,x_j,x_k) = \frac{\Delta^{1}(x_i,x_j) - \Delta^{1}(x_j,x_k)}{x_i-x_k} +\end{nospaceflalign*} +и т.д. \subsubsection{Реализации метода в библиотеках numpy, scipy} - +Реализаций данного метода в в библиотеках numpy, scipy не найдено. \subsection{Сплайн-интерполяция} \subsubsection{Описание метода} -\subsubsection{Реализации метода в библиотеках numpy, scipy} +Между парами значений \((x_{i-1},y_{i-1}),(x_i,y_i)\) строятся +функции-сплайны \(f_1(x),\dots,f_n(x)\), соответствущие условиям +непрерывности. Комбинация таких функций образует исходную: +\begin{equation*} + \widetilde{y}(x) = \left\{ + \begin{aligned} + & f_1(x),\; x_0 \le x < x_1 \\ + & f_2(x),\; x_1 \le x < x_2 \\ + & \ \dots \qquad \quad \dots \\ + & f_n(x),\; x_{n-1} \le x < x_n \\ + \end{aligned} + \right. +\end{equation*} +Иногда выбирают сплайны такого вида, что непрерывными будут и их +производные, вплоть до нужного порядка (например, если в роли сплайнов +выбрать полиномы 3 степени, то они будут соответствовать условиями +непрерывности вместе с 1 и 2 производными). В зависимости от цели, +на сплайны могут быть наложены и иные ограничения. +\subsubsection{Реализации метода в библиотеках numpy, scipy} +В библиотеке scipy существует множество реализаций данного метода, +которые отличаются используемым видом сплайна. \subsection{Сглаживание. Метод наименьших квадратов} \subsubsection{Описание метода} \subsubsection{Реализации метода в библиотеках numpy, scipy} From a70592186a456a9aa52c6c9e2a9d2097cd9a9c15 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sun, 24 Sep 2023 23:38:25 +0300 Subject: [PATCH 16/41] [content] Add spline definition, library info --- main.tex | 203 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 190 insertions(+), 13 deletions(-) diff --git a/main.tex b/main.tex index 08a2ead..37ba9cb 100644 --- a/main.tex +++ b/main.tex @@ -1,5 +1,6 @@ \input{vars} \input{config} +\sloppy \NewDocumentCommand{\MFArgs} {}{x^{(p)}_1,x^{(p)}_2,x^{(p)}_3,\dots,x^{(p)}_n} @@ -859,30 +860,206 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения и т.д. \subsubsection{Реализации метода в библиотеках numpy, scipy} Реализаций данного метода в в библиотеках numpy, scipy не найдено. + \subsection{Сплайн-интерполяция} +Перед рассмотрением метода необходимо ввести понятие интерполяционного +сплайна. Это кусочно-заданная функция, каждый фрагмент которой является +непрерывной функцией, и на концах его значение совпадает с известными +значениями функции. + +Иногда выбирают фрагменты сплайна такого вида, что непрерывными будут и их +производные, вплоть до нужного порядка (например, если в роли сплайнов +выбрать полиномы 3 степени, то они будут соответствовать условиями +непрерывности вместе с 1 и 2 производными). В зависимости от цели, +на сплайны могут быть наложены и иные ограничения. Также, для упрощения +вычислений, чаще всего все фрагменты сплайна --- функции из одного +класса, например, многочленов фиксированной степени. \subsubsection{Описание метода} -Между парами значений \((x_{i-1},y_{i-1}),(x_i,y_i)\) строятся -функции-сплайны \(f_1(x),\dots,f_n(x)\), соответствущие условиям +Между парами значений \((x_{l(j-1)},y_{l(j-1)}),(x_{l(j)},y_{l(j)})\), +где \(l \in \textbf{N}, 0 \le l_j \le n\) строятся +фрагменты сплайна \(f_1(x),\dots,f_n(x)\), соответствущие условиям непрерывности. Комбинация таких функций образует исходную: \begin{equation*} \widetilde{y}(x) = \left\{ \begin{aligned} - & f_1(x),\; x_0 \le x < x_1 \\ - & f_2(x),\; x_1 \le x < x_2 \\ - & \ \dots \qquad \quad \dots \\ - & f_n(x),\; x_{n-1} \le x < x_n \\ + & f_1(x),\; x_0 \le x < x_l \\ + & f_2(x),\; x_{l} \le x < x_{2l} \\ + & \ \dots \qquad \quad \dots \\ + & f_n(x),\; x_{jl} \le x < x_n \\ \end{aligned} \right. \end{equation*} - -Иногда выбирают сплайны такого вида, что непрерывными будут и их -производные, вплоть до нужного порядка (например, если в роли сплайнов -выбрать полиномы 3 степени, то они будут соответствовать условиями -непрерывности вместе с 1 и 2 производными). В зависимости от цели, -на сплайны могут быть наложены и иные ограничения. +При этом +\begin{equation*} + \begin{aligned} + & f_1(x_0) = y_0, f_1(x_l) = y_l \\ + & f_2(x_l) = y_l, f_2(x_{2l}) = y_{2l} \\ + & \ \dots \qquad \quad \dots \\ + & f_n(x_{jl}) = y_{jl},f_n(x_n) = y_n \\ + \end{aligned} +\end{equation*} +Для полиномов 3 степени чаще всего \(l=3\), и в таком случае по \(l\) +точкам на каждом фрагменте строиться полином (например, интерполяционный +полином Ньютона). В общем случае \(l\) определяется исходя из условий +и ограничений поставленной перед исследователем задачи. \subsubsection{Реализации метода в библиотеках numpy, scipy} В библиотеке scipy существует множество реализаций данного метода, -которые отличаются используемым видом сплайна. +которые отличаются используемым видом сплайна. В модуле +\textbf{scipy.interpolate} описаны следующие объекты +\cite{links:scipy_doc}: +\begin{enumerate} + \item класс \textbf{CubicSpline}; + \item класс \textbf{PchipInterpolator}; + \item класс \textbf{CubicHermiteSpline}; + \item класс \textbf{Akima1DInterpolator}; + \item класс \textbf{RectBivariateSpline}, используется для многомерной + \item интерполяции; + \item функция \textbf{interp1d}, которая может интерполировать таблично + заданную функцию разными методами, в т.ч. сплайн-интерполяцией. + Признана устаревшей и поэтому не будет рассмотрена; +\end{enumerate} + + +Классы \textbf{CubicSpline}, \textbf{PchipInterpolator}, +\textbf{CubicHermiteSpline}, \textbf{Akima1DInterpolator} +используются для интерполяции функций одного аргумента, и их +применение одинаково: для создания сплайна необходимо создать +экземпляр класса, для получения значений сплайна необходимо +вызвать созданный экземпляр с необходимыми входными данными +(то есть вызвать метод \verb|__call__|). + +Каждый из классов имеет разное количество параметров, которые +можно задать при его создании его экземпляра; метод получения +данных сплайна, в свою очередь, имеет одинаковое количество +параметров для всех классов, поэтому он имеет смысл описать его сейчас. + +Метод \verb|__call__| вышеописанных классов имеет 3 параметра: +\begin{enumerate} + \item \(x\) --- array\_like + + Точки, для которых будет вычислено значения сплайна. + \item \(nu\) --- int, необязательный + + Порядок производной сплайна, который необходимо использовать при + вычислении значений. Должен быть неотрицательным. По умолчанию + --- 0 (то есть вычисляется исходный сплайн). + \item \(extrapolate\) --- {bool, \verb|"periodic"|, + \verb|None|}, необязательный + + Если bool, определяет, следует ли экстраполировать точки, + которые выходят за пределы границ, установленные первым и + последним интервалами или возвращать \verb|NaN|. Если + \verb|"periodic"|, используется периодическая экстраполяция. + Если \verb|None| (по умолчанию), используйте метод \textbf{extrapolate} + этого класса. +\end{enumerate} +Данный метод возвращает массив значений сплайна, соответствующих значений +\(x\). + +Конструктор экземпляра класса \textbf{CubicSpline} имеет следующие +параметры: +\begin{enumerate} + \item \(x\) --- array\_like, одномерный + + Одномерный массив, содержащий значения независимой переменной. + Значения должны быть действительными, конечными числами и + находиться в строго возрастающем порядке. + + \item \(y\) --- array\_like + + Массив, содержащий значения зависимой переменной. Он может + иметь произвольное количество измерений, но длина должна + совпадать с длиной \(x\). Значения должны быть конечными. + + \item \(axis\) int, необязательный + + Ось, вдоль которой предполагается изменение y. Это означает, + что для \(x[i]\) соответствующие значения равны + \verb|np.take(y, i, axis=axis)|. По умолчанию --- 0. + \item \(bc\_type\) --- string / tuple размера = 2, необязательный + + Тип граничного условия. Два дополнительных уравнения, заданные + граничными условиями, необходимы для определения всех + коэффициентов многочленов на каждом отрезке. + + Если \(bc\_type\) --- строка, то указанное в параметре условие + будет применено к обоим концам сплайна. Доступные условия: + \begin{itemize} + \item \verb|"not-a-knot"| (по умолчанию): первый и второй + сегменты на конце кривой представляют собой один и тот + же полином. Это хороший вариант по умолчанию, когда нет + информации о граничных условиях. + + \item \verb|"periodic"|: Предполагается, что интерполируемые + функции являются периодическими с периодом + \(x[-1] \ \mbox {---}\ x[0]\). + Первое и последнее значение y должны быть идентичными: + \(y[0] == y[-1]\). Это граничное условие приведет к + тому, что \(y'[0] == y'[-1]\) и \(y''[0] == y''[-1]\). + + \item \verb|"clamped"|: Первая производная на концах кривых + равна нулю. Предполагая, что y одномерный, + \(bc\_type=((1, 0.0), (1, 0.0))\) --- то же самое + условие. + + \item \verb|"natural"|: Вторая производная на концах кривой + равна нулю. Предполагая, что y одномерный, + \(bc\_type=((2, 0.0), (2, 0.0))\) --- то же самое + условие. + \end{itemize} + + Если \(bc\_type\) представляет собой кортеж из двух элементов, + первое и второе значения будут применены в начале и конце + кривой соответственно. Значения кортежа могут быть одной из + ранее упомянутых строк (кроме \verb|"periodic"|) или кортежем + \((order, deriv\_values)\), позволяющим указывать произвольные + производные на концах кривой: + + \begin{itemize} + \item \(order\): порядок производной --- 1 или 2. + + \item \(deriv\_value\): array\_like + + Содержит значения производной, форма должна быть такой + же, как \(y\), за исключением измерения \(axis\). + Например, если \(y\) --- одномерный, то + \(deriv\_values\) должен быть скаляром. Если \(y\) + является трехмерным, имеет форму \((n0, n1, n2)\) и + \(axis=1\), то \(deriv\_values\) должно быть + двухмерным и иметь форму \((n0, n2)\). + \end{itemize} + + \item \(extrapolate\) --- {bool, \verb|"periodic"|, + \verb|None|}, необязательный + + Если bool, определяет, следует ли экстраполировать точки, + которые выходят за пределы границ, установленные первым и + последним интервалами или возвращать \verb|NaN|. Если + \verb|"periodic"|, используется периодическая экстраполяция. + Если \verb|None| (по умолчанию), параметр имеет значение + \verb|"periodic"| если \(bc\_type\)=\verb|"periodic"| и + значение \verb|True| в противном случае. +\end{enumerate} + +Конструктор экземпляра класса \textbf{PchipInterpolator} имеет следующие +параметры: +\begin{enumerate} + \item +\end{enumerate} + +Конструктор экземпляра класса \textbf{CubicHermiteSpline} имеет следующие +параметры: +\begin{enumerate} + \item +\end{enumerate} + +Конструктор экземпляра класса \textbf{Akima1DInterpolator} имеет +следующие параметры: +\begin{enumerate} + \item +\end{enumerate} + + \subsection{Сглаживание. Метод наименьших квадратов} \subsubsection{Описание метода} \subsubsection{Реализации метода в библиотеках numpy, scipy} From e885a649b2b413400241926e49198bf883fb9dd3 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Tue, 3 Oct 2023 07:00:37 +0300 Subject: [PATCH 17/41] [content] Add spline classes and least_squares method --- main.tex | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 370 insertions(+), 19 deletions(-) diff --git a/main.tex b/main.tex index 37ba9cb..99acdbe 100644 --- a/main.tex +++ b/main.tex @@ -219,8 +219,8 @@ и \(x0\) --- не скаляр, возвращаемое значение равно \verb|(x, converged, zero_der)|, где: \begin{itemize} - \item converged --- ndarray из значений bool. Указывает, какие элементы сошлись успешно. - \item zero\_der --- ndarray из значений bool. Указывает, какие элементы имеют нулевую производную. + \item converged --- \verb|ndarray| из значений bool. Указывает, какие элементы сошлись успешно. + \item zero\_der --- \verb|ndarray| из значений bool. Указывает, какие элементы имеют нулевую производную. \end{itemize} \item \(disp\) --- bool, необязательный @@ -904,7 +904,8 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения и ограничений поставленной перед исследователем задачи. \subsubsection{Реализации метода в библиотеках numpy, scipy} В библиотеке scipy существует множество реализаций данного метода, -которые отличаются используемым видом сплайна. В модуле +которые отличаются используемым видом сплайна а также поддерживаемой +размерностью функции. В модуле \textbf{scipy.interpolate} описаны следующие объекты \cite{links:scipy_doc}: \begin{enumerate} @@ -912,8 +913,9 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \item класс \textbf{PchipInterpolator}; \item класс \textbf{CubicHermiteSpline}; \item класс \textbf{Akima1DInterpolator}; - \item класс \textbf{RectBivariateSpline}, используется для многомерной - \item интерполяции; + \item классы \textbf{RectBivariateSpline}, + \textbf{LSQBivariateSpline}, используются для многомерной + интерполяции; \item функция \textbf{interp1d}, которая может интерполировать таблично заданную функцию разными методами, в т.ч. сплайн-интерполяцией. Признана устаревшей и поэтому не будет рассмотрена; @@ -964,13 +966,11 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения Одномерный массив, содержащий значения независимой переменной. Значения должны быть действительными, конечными числами и находиться в строго возрастающем порядке. - \item \(y\) --- array\_like Массив, содержащий значения зависимой переменной. Он может иметь произвольное количество измерений, но длина должна совпадать с длиной \(x\). Значения должны быть конечными. - \item \(axis\) int, необязательный Ось, вдоль которой предполагается изменение y. Это означает, @@ -989,19 +989,16 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения сегменты на конце кривой представляют собой один и тот же полином. Это хороший вариант по умолчанию, когда нет информации о граничных условиях. - \item \verb|"periodic"|: Предполагается, что интерполируемые функции являются периодическими с периодом \(x[-1] \ \mbox {---}\ x[0]\). Первое и последнее значение y должны быть идентичными: \(y[0] == y[-1]\). Это граничное условие приведет к тому, что \(y'[0] == y'[-1]\) и \(y''[0] == y''[-1]\). - \item \verb|"clamped"|: Первая производная на концах кривых равна нулю. Предполагая, что y одномерный, \(bc\_type=((1, 0.0), (1, 0.0))\) --- то же самое условие. - \item \verb|"natural"|: Вторая производная на концах кривой равна нулю. Предполагая, что y одномерный, \(bc\_type=((2, 0.0), (2, 0.0))\) --- то же самое @@ -1017,7 +1014,6 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \begin{itemize} \item \(order\): порядок производной --- 1 или 2. - \item \(deriv\_value\): array\_like Содержит значения производной, форма должна быть такой @@ -1028,7 +1024,6 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \(axis=1\), то \(deriv\_values\) должно быть двухмерным и иметь форму \((n0, n2)\). \end{itemize} - \item \(extrapolate\) --- {bool, \verb|"periodic"|, \verb|None|}, необязательный @@ -1041,29 +1036,385 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения значение \verb|True| в противном случае. \end{enumerate} -Конструктор экземпляра класса \textbf{PchipInterpolator} имеет следующие -параметры: +Класс \textbf{PchipInterpolator} представляет из себя кубический +эрмитов сплайн, поэтому является дочерним классом +\textbf{CubicHermiteSpline}. Конструктор экземпляра класса имеет +следующие параметры: \begin{enumerate} - \item + \item \(x\) --- ndarray, вида (xlen,1), то есть одномерный + + Одномерный массив монотонно возрастающих действительных + значений. Не может включать повторяющиеся значения (иначе + сплайн будет слишком конкретизированный) + \item \(y\) --- ndarray, вида (...,xlen,...) + + N-мерный массив действительных значений. Длина \(y\) по оси + интерполяции должна быть равна длине \(x\). Используйте + параметр \(axis\), чтобы выбрать ось интерполяции. + \item \(axis\) int, необязательный + + См. описание одноименного метода родительского класса. + \item \(extrapolate\) --- bool, необязательный + + Определяет, следует ли экстраполировать точки, + которые выходят за пределы границ, установленные первым и + последним интервалами или возвращать \verb|NaN|. + По умолчанию \verb|None|, значение см. в пункте + \ref{list:CubicHermiteSpline-extrapolate} описания + конструктора родительского класса. \end{enumerate} Конструктор экземпляра класса \textbf{CubicHermiteSpline} имеет следующие параметры: \begin{enumerate} - \item + \item \(x\) --- array\_like, вида (n,), то есть одномерный + + Одномерный массив, содержащий значения независимой переменной. Значения должны быть действительными, конечными и находиться в строго возрастающем порядке. + \item \(y\) --- array\_like + + Массив, содержащий значения зависимой переменной. + Он может иметь произвольное количество измерений, но длина по + оси \(axis\) должна совпадать с длиной \(x\). + Значения должны быть конечными. + \item \(dydx\) --- array\_like + + Массив, содержащий производные зависимой переменной. Он может + иметь произвольное количество измерений, но длина по оси + \(axis\) должна совпадать с длиной \(x\). Значения должны быть + конечными. + \item \(axis\) --- int, необязательный + + Ось, вдоль которой предполагается изменение y. Это означает, что для \(x[i]\) соответствующие значения равны + \verb|np.take(y, i, axis=axis)|. + По умолчанию --- 0. + \item \(extrapolate\) --- {bool, \verb|"periodic"|, + \verb|None|}, необязательный + + Если bool, определяет, следует ли экстраполировать точки, + которые выходят за пределы границ, установленные первым и + последним интервалами или возвращать \verb|NaN|. Если + \verb|"periodic"|, используется периодическая экстраполяция. + Если \verb|None| (по умолчанию), используйте метод \textbf{extrapolate} + этого класса. + \label{list:CubicHermiteSpline-extrapolate} \end{enumerate} Конструктор экземпляра класса \textbf{Akima1DInterpolator} имеет следующие параметры: \begin{enumerate} - \item + \item \(x\) --- ndarray, вида (n, 1) + + Одномерный массив монотонно возрастающих действительных + значений. + \item \(y\) --- ndarray, вида (..., n, ...) + + N-мерный массив реальных значений. Длина \(y\) по оси + интерполяции должна быть равна длине \(x\). Используйте + параметр \(axis\), чтобы выбрать ось интерполяции. + \item \(axis\) --- int, необязательный + + Ось в массиве \(y\), соответствующая значениям координаты + \(x\). По умолчанию --- 0. \end{enumerate} - \subsection{Сглаживание. Метод наименьших квадратов} +С помощью метода наименьших квадратов (далее, МНК) осуществляется +интерполяция функции с помощью сглаживания. \subsubsection{Описание метода} -\subsubsection{Реализации метода в библиотеках numpy, scipy} +Задача метода --- минимизировать отклонение от исходных данных: +\begin{equation} + S = \sum_{i=1}^{n}(\widetilde{y}(x_i,a)-y_i)^2 \rightarrow \min + \label{formula:mnk} +\end{equation} +где \(a\) --- неизвестные параметры; \(n\) --- количество известных +пар значений (\(x,y\)); \(x_i,y_i\) --- значения \(i\) пары. +В качестве \(\widetilde{y}(x)\) необходимо выбрать функцию вида, +который лучше всего подходит под исходные данные. Этот процесс +называют выбором зависимости. + +Примеры зависимостей (\(a,b,c,d\) --- неизвестные параметры): +\begin{alignat*}{2} + & ax + b & \ & \text{--- линейная}; \\ + & ax^2 + bx + c & \ & \text{--- полиномиальная}; \\ + & a \ln(x) + b & \ & \text{--- логарифмическая}; \\ + & be^{ax} & \ & \text{--- экспоненциальная}; +\end{alignat*} + +В результате подстановки выбранной зависимости и известных данных +в (\ref{formula:mnk}) задача минимизации сводится к нахождению +экстремумов функции \(S(a)\), и выбору подходящего из них. +\subsubsection{Реализации метода в библиотеках numpy, scipy} +В модуле \textbf{scipy.optimize} библиотеки scipy существует +несколько реализаций данного метода, каждая из которых имеет свою +специализацию. +\begin{enumerate} + \item \textbf{curve\_fit} --- функция, предназначенная для поиска + значения параметра \(a\) произвольной зависимости \(\widetilde + {y}(x_i,a)\), при котором выполняется условие + (\ref{formula:mnk}). + \item \textbf{least\_squares} --- функция, которая отличается от + \textbf{curve\_fit} возможностью задать собственный критерий + близости, область допустимых решений; позволяет задать больше + параметров точности. + \item \textbf{leastsq} --- функция, которая является устаревшей + версией \textbf{least\_squares}, поэтому ее подробного + рассмотрения не будет. + \item \textbf{lsq\_linear} --- функция, предназначенная для + минимизации выражения \(||ax-b||^2\), где \(a,b\) --- матрицы + параметров; функция \textbf{nnls} отличается от нее + ограничением на положительность искомых значений. +\end{enumerate} + +Функция \textbf{curve\_fit} имеет следующий набор параметров: +\begin{enumerate} + \item \(f\) --- callable + + Модельная функция \(f(x,\dots)\). Она должен принимать независимую переменную в качестве первого аргумента, а параметры --- в качестве оставшихся. + \item \(xdata\) --- array\_like + + Значения независимой переменной. Обычно это должна быть последовательность длины \(M\) или массив \((k,M)\)-образной формы для функций с \(k\) предикторами, и каждый элемент должен быть конвертируемым в \(float\), если это объект, подобный массиву. + \item \(ydata\) --- array\_like + + Значения зависимой переменной, массив длиной M. + При найденных параметрах \(f\) совпадает с значением + \(f(xdata, \dots)\). + \item \(p0\) --- array\_like, необязательный + + Начальное значение параметров \(f\) (длины N). Если + \verb|None| (значение по умолчанию), то все начальные + значения будут равны \verb|1| (если количество параметров + функции можно определить с помощью интроспекции, в противном + случае генерируется исключение \verb|ValueError|). + \item \(sigma\) --- \verb|None| / sequence длины M / массив MxM, необязательный + + Задает неопределенность в \(ydata\). Если мы определим + остатки как \(r = ydata - f(xdata, *popt)\), то интерпретация + значения \(sigma\) будет зависеть от ее количества измерений: + \begin{itemize} + \item одномерная \(sigma\) должна содержать значения + стандартных отклонений ошибок в \(ydata\). Используется + при вычислении параметра + \verb|chisq = sum((r/sigma)**2)|; + \item двухмерная \(sigma\) должна содержать ковариационную + матрицу ошибок в \(ydata\). Используется + при вычислении параметра + \verb|chisq = r.T @ inv(sigma) @ r|. + \end{itemize} + \item \(absolute\_sigma\) --- bool, необязательный + + Если значение \verb|True|, \(sigma\) используется в абсолютном смысле, и предполагаемая ковариация параметра \(pcov\) отражает эти абсолютные значения. + + Если значение \verb|False| (по умолчанию), имеют значение + только относительные величины значений \(sigma\). + Ковариационная матрица возвращаемого параметра \(pcov\) + основана на масштабировании \(sigma\) постоянным + коэффициентом. Эта константа устанавливается требованием, чтобы + приведенный выше \(chisq\) для параметров + \(popt\) при использовании масштабированной \(sigma\) был равен + единице. Другими словами, \(sigma\) масштабируется так, чтобы + соответствовать выборочной дисперсии остатков после подгонки. + Математически, + \verb|pcov(absolute_sigma=False) = pcov(absolute_sigma=True)| + \verb| * chisq(popt)/(M-N)|. + \item \(check\_finite\) --- bool, необязательный + + Если \verb|True|, то входные массивы проверяются на содержание + значений \verb|NaN| или \verb|Inf|, и если они содержат, то + генерируется исключение \verb|ValueError|. + + Значение \verb|False| может привести к выдаче бессмысленных + результатов, если входные массивы содержат \verb|NaN|. + + По умолчанию установлено значение \verb|True|, если + \(nan\_policy\) не указано явно, и значение \verb|False| в + противном случае. + \item \(bounds\) --- tuple из двух array\_like / \verb|Bounds|, + необязательный + + Нижняя и верхняя границы параметров \(f\). По умолчанию нет ограничений. Есть два способа указать границы: + \begin{itemize} + \item через экземпляр класса \verb|Bounds|; + + \item \verb|tuple| из двух array\_like, где каждый элемент + кортежа должен быть либо массивом с длиной, равной + количеству параметров, либо скаляром (в этом случае + граница считается одинаковой для всех параметров). + Используйте \verb|np.inf| с соответствующим знаком, + чтобы отключить ограничения для всех или некоторых + параметров. + \end{itemize} + \item \(method\) --- \{\verb|"lm"|, \verb|"trf"|, \verb|"dogbox"|\}, необязательный + + Метод, используемый для вычисления параметров. См. описание + аналогичного параметра функции \textbf{least\_squares} для + получения более подробной информации. По умолчанию используется + \verb|"lm"| для задач без ограничений и \verb|"trf"|, если + указано значение \(bounds\). + \item \(jac\) --- callable / string / \verb|None|, необязательный + + Функция с сигнатурой \(jac(x, ...)\), которая вычисляет матрицу + Якоби модельной функции относительно параметров как плотную + array\_like структуру. Он будет масштабироваться в + соответствии с предоставленным параметром \(sigma\). Если + \verb|False| (по умолчанию), якобиан будет оцениваться + численно. Строковые ключевые слова для методов \verb|"trf"| и + \verb|"dogbox"| можно использовать для выбора + конечно-разностной схемы, см. описание + \textbf{least\_squares}. + \item \(full\_output\) --- boolean, необязательный + + Если значение = \verb|True|, эта функция возвращает дополнительную информацию: \(infodict\), \(mesg\) и \(ier\). По умолчанию + \verb|False|. + \item \(nan\_policy\) --- {\verb|"raise"|, \verb|"omit"|, \verb|None|}, необязательный + + Определяет, как действовать, если входные данные содержат nan. Доступны следующие параметры (по умолчанию — \verb|None|): + \begin{itemize} + \item \verb|"raise"|: выдает ошибку + + \item \verb|"omit"|: выполняет вычисления, игнорируя + значения \verb|NaN|. + + \item \verb|None|: никакая специальная обработка \verb|NaN| + не выполняется (кроме того, что делается с помощью + \(check\_finite\)); поведение при наличии + \verb|NaN| зависит от реализации и может измениться. + + \end{itemize} + Обратите внимание: если значение указано явно (не \verb|None|), + для \(check\_finite\) будет установлено значение \verb|False|. + \item \(**kwargs\) + + Именованные параметры, которые передаются в \textbf{leastsq} + для \(method\)='lm' или \textbf{least\_squares} в противном + случае. +\end{enumerate} +Данная функция возвращает результат в виде +\((popt,pcov,infodict,mesg,ier)\), где +\begin{enumerate} + \item \(popt\) --- вычисленные параметры + \(f\), при которых выполняется условие + \(f(xdata, *popt) - ydata \rightarrow \min\). + \item \(pcov\) --- 2-D array + + Вычисленная приблизительная ковариация \(popt\). + \item \(infodict\) --- dict + + По ключу \(nfev\) + хранится значение количества вызовов функции. Методы \("trf"\) + и \("dogbox"\) не учитывают вызовы функций для численной + аппроксимации якобиана, в отличие от метода \("lm"\). + По ключам \(fvec\), \(fjac\), \(ipvt\), \(qtf\) можно получить + дополнительную информацию о работе алгоритма. + \item \(mesg\) --- str + + Cтроковое сообщение с информацией о решении. + \item \(ier\) --- int + + Целочисленный флаг. Если он равен 1, 2, 3 или 4, решение + найдено. В противном случае решение не было найдено. \(mesg\) + предоставляет дополнительную информацию о решении. +\end{enumerate} +Разработчики отмечают, что входные данные \(xdata\), \(ydata\) и +выходные данные \(f\) должны иметь формат \(float64\), иначе данная +функция может вернуть неверные результаты \cite{links:scipy_doc}. + +Функция \textbf{least\_squares} предназначена для решения следующей +задачи минимизации \(F(x)\), где +\verb|F(x) = 0.5 * sum(rho(f_i(x)**2), i = 0, ..., m - 1)| +при ограничениях \(lb \leq x \leq ub\). То есть, \(f\) должна принимать +на вход значение параметров \(a\) и возвращать значение \(f(x,a) - y\). + +Данная функция имеет следующий набор параметров: +\begin{enumerate} + \item \(fun\) --- callable + Функция, которая вычисляет вектор остатков, имеет сигнатуру + \(fun(x, *args, **kwargs)\), т.е. минимизация продолжается + относительно ее первого аргумента. Аргумент \(x\), + передаваемый этой функции, представляет собой N-мерный массив размерности \((N,K)\) (никогда не скаляр, даже для \(N=1\)). \(fun\) должна вернуть одномерный массив типа array \((m,)\) или скаляр. Если \(x \in \textbf{C}\) или функция \(fun\) возвращает комплексные числа, ее необходимо обернуть действительнозначной функцией от действительнозначных аргументов. + \item \(x_0\) --- array\_like размерности (N,K) / float + + Начальное значение \(x\). Если параметр имеет тип float, он + будет рассматриваться как одномерный массив с одним элементом. + \item \(jac\) --- \{\verb|"2-point"|, \verb|"3-point"|, \verb|"cs"|, callable\}, необязательный + + Метод вычисления матрицы Якоби (матрица размером \(m \times n\), где элемент \((i, j)\) является частной производной f[i] по x[j]). Ключевые слова выбирают схему конечных разностей для числовой оценки. Схема \verb|"3-point"| более точная, но требует в два раза больше операций, чем \verb|"2-point"| (по умолчанию). Схема \verb|"cs"| использует комплексные шаги и, хотя потенциально является наиболее точной, она применима только тогда, когда \(fun\) правильно обрабатывает комплекснозначные входные данные и может быть аналитически продолжена на комплексную плоскость. \(method\) \verb|"lm"| всегда использует схему \verb|"2-point"|. Если параметр имеет тип \verb|callable|, он используется как \verb|jac(x, *args, **kwargs)| и должен возвращать хорошее приближение (или точное значение) для якобиана в виде array\_like (к выводу \(jac\) будет применен \verb|np.atleast_2d|), разреженной матрицы (предпочтительна \verb|csr_matrix| для производительности) или \verb|scipy.sparse.linalg.LinearOperator|. + \item \(bounds\) --- tuple из 2 array\_like / \verb|Bounds|, необязательный + + Есть два способа указать границы: + \begin{itemize} + \item через экземпляр класса \verb|Bounds|; + + \item \verb|tuple| из двух array\_like, где каждый элемент + кортежа должен быть либо массивом с размерностью + \(x0\), либо скаляром (в этом случае + граница считается одинаковой для всех параметров). + Используйте \verb|np.inf| с соответствующим знаком, + чтобы отключить ограничения для всех или некоторых + параметров. + \end{itemize} + \item \(method\) --- \{\verb|"lm"|, \verb|"trf"|, \verb|"dogbox"|\}, необязательный + + Допустимое отклонение на завершение по норме градиента. + По умолчанию --- 1e-8. Точное состояние зависит от используемого + значения \(method\): + \begin{itemize} + \item Для \verb|"trf"|: \(norm(g\_scaled, ord=np.inf) < gtol\), где \(g\_scaled\) --- значение градиента, масштабированное для учета наличия границ. + \item Для \verb|"dogbox"|: \(norm(g_free, ord=np.inf) < gtol\), где \(g_free\) --- градиент по отношению к переменным, которые не находятся в оптимальном состоянии на границе. + \item Для \verb|"lm"|: максимальное абсолютное значение косинуса углов между столбцами якобиана и вектором невязки меньше \(gtol\), или вектор невязки равен нулю. + \end{itemize} + + Если \verb|None| и \(method\) не \verb|"lm"|, завершение по этому условию отключено. Если \(method\) --- \verb|"lm"|, этот допуск должен быть выше, чем машинный эпсилон. + \item \(x\_scale\) --- array\_like / \verb|"jac"| + + Характеристический масштаб каждой переменной. + Установка \(x\_scale\) эквивалентна переформулировке проблемы + в масштабированных переменных \(xs = x/x\_scale\). + Альтернативная точка зрения состоит в том, что размер + доверительной области по j-му измерению пропорционален + \(x\_scale[j]\). Улучшения сходимости можно достичь, установив + \(x\_scale\) таким образом, чтобы шаг заданного размера по + любой из масштабируемых переменных имел аналогичный эффект на + функцию стоимости. Если установлено значение \verb|"jac"|, + масштаб итеративно обновляется с использованием обратных норм + столбцов матрицы Якоби. + \item \(loss\) --- str / callable, необязательный + + Определяет функцию потерь. Допускаются следующие значения ключевых слов: + \begin{itemize} + \item \verb|"linear"| (по умолчанию) : \verb|rho(z) = z|. Дает стандартную задачу наименьших квадратов. + \item \verb|"soft_l1"| : \verb|rho(z) = 2 * ((1 + z)**0.5 - 1)|. Гладкая аппроксимация потерь l1 (абсолютное значение). Обычно хороший выбор для надежного метода наименьших квадратов. + \item \verb|"huber"| : \verb|rho(z) = z if z <= 1 else 2*z**0.5 - 1|. Работает аналогично \verb|"soft_l1"|. + \item \verb|"cauchy"| : \verb|rho(z) = ln(1 + z)|. Сильно ослабляет влияние выбросов, но может вызвать трудности в процессе оптимизации. + \item \verb|"arctan"| : \verb|rho(z) = arctan(z)|. Ограничивает максимальный убыток по одному остатку, имеет свойства, аналогичные \verb|"cauchy"|. + \end{itemize} + + Если параметр имеет тип \verb|callable|, он должен принимать + одномерный \verb|ndarray| \verb|z=f**2| и возвращать + array\_like с формой \((3, m)\), где строка 0 содержит значения функции, строка 1 содержит первые производные, а строка 2 содержит вторые производные. \(method\) \verb|lm| поддерживает только \verb|"linear"| \(loss\). + + \item \(f\_scale\) --- float, необязательный + + Значение границы между ошибками данных и выбросами в данных, + по умолчанию - 1,0. Значение функции потерь оценивается + следующим образом: \verb|rho_(f**2) = C**2 * rho(f**2 / C**2)|, + где C --- это \(f\_scale\), а \(rho\) определяется параметром + \(loss\). Этот параметр не имеет никакого эффекта при + \(loss\)=\verb|"linear"|, но для других значений потерь он имеет решающее + значение. + \item \(max\_nfev\) --- \verb|None| / int, необязательный + + Максимальное количество выполнений функции перед завершением работы. Если \verb|None| (по умолчанию), значение выбирается автоматически: + \begin{itemize} + \item Для \verb|"trf"| и \verb|"dogbox"|: \(100 \cdot n\). + + \item Для \verb|"lm"|: \(100 \cdot n\), если \(jac\) является вызываемым, и \(100 \cdot n \cdot (n + 1)\) в противном случае (поскольку \verb|"lm"| учитывает вызовы функций в оценке Якобиана). + \end{itemize} + \item \(diff\_step\) --- \verb|None| / array\_like, необязательный + + + \item +\end{enumerate} \section{Численное интегрирование} \subsection{Метод прямоугольников} \subsubsection{Описание метода} From fd0d028c974e1b058dd85a7b41920cb36c25688a Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Fri, 6 Oct 2023 19:08:03 +0300 Subject: [PATCH 18/41] [sources] Add cartwright --- sources.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/sources.tex b/sources.tex index 5799292..6bd05fb 100644 --- a/sources.tex +++ b/sources.tex @@ -25,4 +25,5 @@ \bibitem{links:scipy_doc} Scipy API Reference [Электронный ресурс] -- URL:~\url{https://docs.scipy.org/doc/scipy/reference/index.html} (\LiteratureAccessDate[3]). \bibitem{links:tiobe_index} TIOBE. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://www.tiobe.com/tiobe-index/} (\LiteratureAccessDate). + \bibitem{journal:cartwright} Simpson's Rule Cumulative Integration with MS Excel and Irregularly-spaced Data / Cartwright, Kenneth V. // Journal of Mathematical Sciences and Mathematics Education. -- Vol.~12 -- P.~1-9 \end{thebibliography} From 55f70778965ec9a6c1b5ec1c8c146904287dabcc Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Fri, 6 Oct 2023 19:08:54 +0300 Subject: [PATCH 19/41] [content] Add trapezoidal rule, simpson rule --- main.tex | 201 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 187 insertions(+), 14 deletions(-) diff --git a/main.tex b/main.tex index 99acdbe..afd88e5 100644 --- a/main.tex +++ b/main.tex @@ -114,10 +114,10 @@ противоположные знаки. \item \(a\) --- scalar - Первый конец интервала \([a,b]\). + Первый конец интервала \([a;b]\). \item \(b\) --- scalar - Второй конец интервала \([a,b]\). + Второй конец интервала \([a;b]\). \item \(xtol\) --- number, необязательный Вычисленный корень \(x0\) будет удовлетворять @@ -797,7 +797,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения последний --- с помощью сглаживания. \subsection{Интерполяционный полином Лагранжа} \subsubsection{Описание метода} -Для известных пар значений \((x_i,y_i), x_i \in [a,b]; i = 1,2,3,\dots,n\) +Для известных пар значений \((x_i,y_i), x_i \in [a;b]; i = 1,2,3,\dots,n\) строится полином \(P(x)\), удовлетворяющий условию \(P(x_i) = y_i\). Полином \(P(x)\) определяется следующей формулой: @@ -1164,6 +1164,11 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения минимизации выражения \(||ax-b||^2\), где \(a,b\) --- матрицы параметров; функция \textbf{nnls} отличается от нее ограничением на положительность искомых значений. + + Подходят только для нахождения параметров многочленов, + не буду рассматриваться в данной работе по причине + сложной подготовки входных данных для решения задачи + метода, а также их узкой специализации. \end{enumerate} Функция \textbf{curve\_fit} имеет следующий набор параметров: @@ -1338,7 +1343,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения будет рассматриваться как одномерный массив с одним элементом. \item \(jac\) --- \{\verb|"2-point"|, \verb|"3-point"|, \verb|"cs"|, callable\}, необязательный - Метод вычисления матрицы Якоби (матрица размером \(m \times n\), где элемент \((i, j)\) является частной производной f[i] по x[j]). Ключевые слова выбирают схему конечных разностей для числовой оценки. Схема \verb|"3-point"| более точная, но требует в два раза больше операций, чем \verb|"2-point"| (по умолчанию). Схема \verb|"cs"| использует комплексные шаги и, хотя потенциально является наиболее точной, она применима только тогда, когда \(fun\) правильно обрабатывает комплекснозначные входные данные и может быть аналитически продолжена на комплексную плоскость. \(method\) \verb|"lm"| всегда использует схему \verb|"2-point"|. Если параметр имеет тип \verb|callable|, он используется как \verb|jac(x, *args, **kwargs)| и должен возвращать хорошее приближение (или точное значение) для якобиана в виде array\_like (к выводу \(jac\) будет применен \verb|np.atleast_2d|), разреженной матрицы (предпочтительна \verb|csr_matrix| для производительности) или \verb|scipy.sparse.linalg.LinearOperator|. + Метод вычисления матрицы Якоби (матрица размером \(m \times n\), где элемент \((i, j)\) является частной производной \(f[i]\) по \(x[j]\)). Ключевые слова выбирают схему конечных разностей для числовой оценки. Схема \verb|"3-point"| более точная, но требует в два раза больше операций, чем \verb|"2-point"| (по умолчанию). Схема \verb|"cs"| использует комплексные шаги и, хотя потенциально является наиболее точной, она применима только тогда, когда \(fun\) правильно обрабатывает комплекснозначные входные данные и может быть аналитически продолжена на комплексную плоскость. \(method\) \verb|"lm"| всегда использует схему \verb|"2-point"|. Если параметр имеет тип \verb|callable|, он используется как \verb|jac(x, *args, **kwargs)| и должен возвращать хорошее приближение (или точное значение) для якобиана в виде array\_like (к выводу \(jac\) будет применен \verb|np.atleast_2d|), разреженной матрицы (предпочтительна \verb|csr_matrix| для производительности) или \verb|scipy.sparse.linalg.LinearOperator|. \item \(bounds\) --- tuple из 2 array\_like / \verb|Bounds|, необязательный Есть два способа указать границы: @@ -1408,26 +1413,194 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \begin{itemize} \item Для \verb|"trf"| и \verb|"dogbox"|: \(100 \cdot n\). - \item Для \verb|"lm"|: \(100 \cdot n\), если \(jac\) является вызываемым, и \(100 \cdot n \cdot (n + 1)\) в противном случае (поскольку \verb|"lm"| учитывает вызовы функций в оценке Якобиана). + \item Для \verb|"lm"|: \(100 \cdot n\), если \(jac\) является вызываемым, и \(100 \cdot n \cdot (n + 1)\) в противном случае (поскольку \verb|"lm"| учитывает вызовы функций в оценке якобиана). \end{itemize} \item \(diff\_step\) --- \verb|None| / array\_like, необязательный - - - \item -\end{enumerate} -\section{Численное интегрирование} -\subsection{Метод прямоугольников} -\subsubsection{Описание метода} -\subsubsection{Реализации метода в библиотеках numpy, scipy} + Определяет относительный размер шага для аппроксимации якобиана конечной разностью. Фактический шаг вычисляется как \verb|x * diff_step|. Если \verb|None| (по умолчанию), то \(diff_step\) принимается за обычную "оптимальную" степень машинного эпсилона для используемой схемы конечных разностей. + \item \(tr\_solver\) --- {\verb|None|, \verb|"exact"|, \verb|"lsmr"|}, необязательный + + Метод решения подзадач доверительной области, применим только для методов \verb|"trf"| и \verb|"dogbox"|. + \begin{itemize} + \item \verb|"exact"| подходит для не очень больших задач с плотными матрицами Якоби. Вычислительная сложность на итерацию сравнима с сингулярным разложением матрицы Якобиана. + \end{itemize} + \item \(tr\_options\) --- dict, необязательный + + Параметры ключевых слов передаются в решатель доверительной + области. + \begin{itemize} + \item \verb|tr_solver="exact"|: \(tr\_options\) игнорируются. + \item \verb|tr_solver="lsmr"|: опции для \textbf{scipy.sparse.linalg.lsmr}. + Кроме того, \(method\)=\verb|"trf"| поддерживает опцию \verb|'regularize'| (bool, по умолчанию --- \verb|True|), которая добавляет член регуляризации к нормальному уравнению, что улучшает сходимость, если якобиан имеет недостаточный ранг. + \end{itemize} + \item \(jac\_sparsity\) --- {\verb|None|, array\_like, разреженная матрица}, необязательный + + Определяет структуру разреженности матрицы Якобиана для + конечно-разностной оценки, ее форма должна быть (m, n). + Если якобиан имеет лишь несколько ненулевых элементов в + каждой строке, обеспечение разреженной структуры значительно + ускорит вычисления. Нулевая запись означает, что + соответствующий элемент якобиана тождественно равен нулю. + Если предусмотрено, принудительно используется решатель + доверительной области \verb|"lsmr"|. Если \verb|None| + (по умолчанию), будет использоваться плотная разность. + Не имеет эффекта при \(method\)=\verb|"lm"|. + \item \(verbose\) --- {0, 1, 2}, необязательный + + Уровень детализации алгоритма: + \begin{itemize} + \item 0 (по умолчанию): бесшумная работа функции. + \item 1: отображение отчета о завершении. + \item 2: отображение хода выполнения во время итераций + (не поддерживается методом \verb|"lm"|). + \end{itemize} + \item \(args, kwargs\) --- tuple / dict, необязательный + + Дополнительные аргументы передаваемые в \(fun\) и \(jac\). + Оба пусты по умолчанию. Сигнатура вызова для \(fun\) --- + \(fun(x, *args, **kwargs)\), та же самая для \(jac\). +\end{enumerate} +Функция возвращает результат в виде экземпляра класса \verb|OptimizeResult|, у которого определены поля \(x, cost, fun, jac, grad, optimality, active\_mask, nfev, njev,status, \) \(message, success\). + + +\section{Численное интегрирование} +Задача численного интегрирования состоит в вычислении определенного +интеграла: +\begin{equation} + I = \int_{a}^{b} f(x) \, dx + \label{formula:nm-integral1} +\end{equation} + +Для решения данной задачи выберем на отрезке \([a;b]\) \(n\) +различных узлов: +\(a = x_0 < x_1 < x_2 < ... < x_{n-1} < x_n = b\), затем по выбранным +узлам интерполируем \(f(x)\). Если в роли интерполяционной функции +выбрать полином \(P_m(x)\), то интеграл (\ref{formula:nm-integral1}) +можно вычислить по формуле +\begin{equation*} + I \approx \int_{a}^{b} P_m(x) \, dx \quad. +\end{equation*} + +Данную формулу также называют квадратурной формулой интерполяционного +типа. + +К наиболее распространенным методам относят метод прямоугольников, +метод трапеций и метод парабол (Симпсона). Первый метод использует +использует полиномы 0 степени, второй --- 1 степени, третий --- +2 степени. + +В данной работе будет рассмотрены методы парабол и трапеций. + +Метод парабол будет точнее других в большинстве случаев +(он позволяет находить точное решение для любых +f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии с +формулой Джузеппе Пеано). Из его недостатков можно отметить низкую +точность на пикообразных функциях (т.е. значение которой резко +возрастают на отрезках малой длины). + +При этом, если количество точек, по которым строится \(P_m(x)\), четно, то метод трапеций может оказаться удобнее, тем самым прекрасно дополняя +метод парабол. \subsection{Метод трапеций} \subsubsection{Описание метода} +На отрезке \([a;b]\) выбирается \(n\) узлов, на отрезках \([x_i;x_{i+1}]\) строятся интерполяционные многочлены 1 степени \(P_1(x)\). +Если используется метод Лагранжа, то \(P_1(x)\) определяется так: +\begin{equation*} + P_1(x) = f(x_i) \frac{x-x_{i+1}}{x_i-x_{i+1}} + f(x_{i+1}) \frac{x-x_i}{x_{i+1}-x_i} +\end{equation*} +При этом +\begin{equation*} + \int_{x_i}^{x_{i+1}} P_1(x) \, dx = \frac{1}{2} \left(f(x_i)+f(x_{i+1})\right)(x_{i+1} - x_i) +\end{equation*} +В случае равноотстоящих узлов +\(x_0, x_1 = x_0 + h, ..., x_n = x_0 + nh\) значение интеграла будет таким: +\begin{equation*} + I \approx \frac{h}{2} \sum_{i=0}^{n-1} (f(x_i) + f(x_{i+1})) +\end{equation*} \subsubsection{Реализации метода в библиотеках numpy, scipy} +В библиотеке scipy найдена функция \textbf{trapezoid} в модуле +\textbf{scipy.integrate}. Она имеет следующие параметры: +\begin{enumerate} + \item \(y\) --- array\_like + + Массив входных данных по которым будет вычислятся интеграл + \item \(x\) --- array\_like, необязательный + + Массив значений \(x\), соответсвующих \(y\). Если \verb|None| + (по умолчанию), то в роли \(x\) будет создан массив равноотстоящих + значений (\(h = dx\)) + \item \(dx\) --- scalar, необязательный + + Расстояние между соседними значениями \(x\). Значение используется + когда \(x\)=\verb|None|. По умолчанию --- 1. + \item \(axis\) --- int, необязательный + Ось, относительно которой производить вычисление интеграла. По + умолчанию --- \(-1\). +\end{enumerate} + +Данная функция возвращает число, если \(y\) одномерный и массив +размерности \((n-1)\) для \(n\)-мерного \(y\). \subsection{Метод парабол (Симпсона)} \subsubsection{Описание метода} -\subsubsection{Реализации метода в библиотеках numpy, scipy} +На отрезке \([a;b]\) выбирается \(2n+1\) узлов, \(n\) троек точек +\(x_{i-1},x_i,x_{i+1} (i = 1,3,5,...\, ,2n-1)\) с соответствующими +отрезками \([x_{i-1},x_{i+1}]\). +На каждом из отрезков интерполируем \(f(x)\) полиномом +2 степени \(P_2(x)\), например, через формулу Лагранжа: +\begin{equation*} + \begin{split} + P_2(x) = \ & f(x_{i-1}) + \frac{(x-x_i)(x-x_{i+1})}{(x_{i-1}-x_i)(x_{i-1}-x_{i+1})} + f(x_i) + \frac{(x-x_{i-1})(x-x_{i+1})}{(x_i-x_{i-1})(x_i-x_{i+1})} + \\ + & + f(x_{i+1}) \frac{(x-x_{i-1})(x-x_i)}{(x_{i+1})(x_i+1-x_i)} + \end{split} +\end{equation*} +При этом +\begin{equation*} + \int_{x_{i-1}}^{x_{i+1}} P_2(x)\, dx = \frac{h}{3} + \left( f(x_{i-1})+4f(x_i)+f(x_{i+1}) \right), \quad h = \frac{b-a}{N} +\end{equation*} +В результате, значение интерграла \(I\) будет вычислятся по формуле +\begin{equation*} + I \approx \frac{h}{3} \sum_{i=0}^{n-1}(f(x_{2i})+4f(x_{2i+1})+f(x_{2i+2})) +\end{equation*} +\subsubsection{Реализации метода в библиотеках numpy, scipy} +В библиотеке scipy есть реализация данного метода в виде +функции \textbf{simpson} модуля \textbf{scipy.integrate}. + +Данная функция имеет следующие параметры: +\begin{enumerate} + \item \(y\) --- array\_like + + Массив, который необходимо интегрировать. + \item \(x\) --- array\_like, необязательный + + Если задано, точки, в которых производится выборка \(y\). + \verb|None| по умолчанию. + \item \(dx\) --- float, необязательный + + Расстояние между точками интегрирования по оси \(Ox\). + Используется только тогда, когда \(x\)=\verb|None|. + По умолчанию --- 1. + \item \(axis\) --- int, необязательный + + Ось, по которой следует интегрировать. По умолчанию + --- последняя ось (-1). + \item \(even\) --- необязательный + + Устаревший параметр, будет удален в scipy \(1.13.0\). + Отвечает за вычисление значения если количество точек четно. + По умолчанию используется метод Симпсона для первых \(n-2\) + отрезков с добавлением трехточечного параболического сегмента + для последнего интервала с использованием уравнений, изложенных + Картрайтом \cite{journal:cartwright}. +\end{enumerate} +Данная функция возвращает значение типа \(flot\) --- приблизительное +значение интеграла. + +Стоит учесть, что если ось, по которой необходимо интегрировать, имеет +только две точки, то интегрирование происходит с помощью метода трапеций. \section{Численное решение обыкновенных дифференциальных уравнений} \subsection{Метод Эйлера} From eff5d82eb6ad080c8de0a83e7786945c2b46f20d Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 7 Oct 2023 22:03:57 +0300 Subject: [PATCH 20/41] [sources] Add Fehlberg method, Fix cartwright --- sources.tex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sources.tex b/sources.tex index 6bd05fb..80685fb 100644 --- a/sources.tex +++ b/sources.tex @@ -25,5 +25,7 @@ \bibitem{links:scipy_doc} Scipy API Reference [Электронный ресурс] -- URL:~\url{https://docs.scipy.org/doc/scipy/reference/index.html} (\LiteratureAccessDate[3]). \bibitem{links:tiobe_index} TIOBE. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://www.tiobe.com/tiobe-index/} (\LiteratureAccessDate). - \bibitem{journal:cartwright} Simpson's Rule Cumulative Integration with MS Excel and Irregularly-spaced Data / Cartwright, Kenneth V. // Journal of Mathematical Sciences and Mathematics Education. -- Vol.~12 -- P.~1-9 + \bibitem{journal:cartwright} Simpson's Rule Cumulative Integration with MS Excel and Irregularly-spaced Data / Cartwright, Kenneth V. // Journal of Mathematical Sciences and Mathematics Education. -- 2017. -- Vol.~12~(2) -- P.~1-9. + \bibitem{article:fehlberg} Classical Fifth-, Sixth-, Seventh-, and Eighth-Order Runge-Kutta Formulas with Stepsize Control / Fehlberg E. // NASA technical report 287 -- 1968. + -- P.~82 \end{thebibliography} From 4e0737ce02a596d55f53d40ac254d7aef9b7ce5f Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 7 Oct 2023 22:05:30 +0300 Subject: [PATCH 21/41] [content] Fix dashes, terminology. Add ODE methods and theory --- main.tex | 320 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 303 insertions(+), 17 deletions(-) diff --git a/main.tex b/main.tex index afd88e5..b9e55ee 100644 --- a/main.tex +++ b/main.tex @@ -1273,7 +1273,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \verb|False|. \item \(nan\_policy\) --- {\verb|"raise"|, \verb|"omit"|, \verb|None|}, необязательный - Определяет, как действовать, если входные данные содержат nan. Доступны следующие параметры (по умолчанию — \verb|None|): + Определяет, как действовать, если входные данные содержат nan. Доступны следующие параметры (по умолчанию --- \verb|None|): \begin{itemize} \item \verb|"raise"|: выдает ошибку @@ -1422,7 +1422,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения Метод решения подзадач доверительной области, применим только для методов \verb|"trf"| и \verb|"dogbox"|. \begin{itemize} - \item \verb|"exact"| подходит для не очень больших задач с плотными матрицами Якоби. Вычислительная сложность на итерацию сравнима с сингулярным разложением матрицы Якобиана. + \item \verb|"exact"| подходит для не очень больших задач с плотными матрицами Якоби. Вычислительная сложность на итерацию сравнима с сингулярным разложением матрицы якобиана. \end{itemize} \item \(tr\_options\) --- dict, необязательный @@ -1435,7 +1435,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \end{itemize} \item \(jac\_sparsity\) --- {\verb|None|, array\_like, разреженная матрица}, необязательный - Определяет структуру разреженности матрицы Якобиана для + Определяет структуру разреженности матрицы якобиана для конечно-разностной оценки, ее форма должна быть (m, n). Если якобиан имеет лишь несколько ненулевых элементов в каждой строке, обеспечение разреженной структуры значительно @@ -1517,28 +1517,28 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии I \approx \frac{h}{2} \sum_{i=0}^{n-1} (f(x_i) + f(x_{i+1})) \end{equation*} \subsubsection{Реализации метода в библиотеках numpy, scipy} -В библиотеке scipy найдена функция \textbf{trapezoid} в модуле +В библиотеке scipy найдена функция \textbf{trapezoid} в модуле \textbf{scipy.integrate}. Она имеет следующие параметры: \begin{enumerate} \item \(y\) --- array\_like - - Массив входных данных по которым будет вычислятся интеграл + + Массив входных данных по которым будет вычислятся интеграл \item \(x\) --- array\_like, необязательный - - Массив значений \(x\), соответсвующих \(y\). Если \verb|None| - (по умолчанию), то в роли \(x\) будет создан массив равноотстоящих - значений (\(h = dx\)) + + Массив значений \(x\), соответсвующих \(y\). Если \verb|None| + (по умолчанию), то в роли \(x\) будет создан массив равноотстоящих + значений (\(h = dx\)) \item \(dx\) --- scalar, необязательный - - Расстояние между соседними значениями \(x\). Значение используется - когда \(x\)=\verb|None|. По умолчанию --- 1. + + Расстояние между соседними значениями \(x\). Значение используется + когда \(x\)=\verb|None|. По умолчанию --- 1. \item \(axis\) --- int, необязательный - Ось, относительно которой производить вычисление интеграла. По - умолчанию --- \(-1\). + Ось, относительно которой производить вычисление интеграла. По + умолчанию --- \(-1\). \end{enumerate} -Данная функция возвращает число, если \(y\) одномерный и массив +Данная функция возвращает число, если \(y\) одномерный и массив размерности \((n-1)\) для \(n\)-мерного \(y\). \subsection{Метод парабол (Симпсона)} \subsubsection{Описание метода} @@ -1602,20 +1602,306 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии Стоит учесть, что если ось, по которой необходимо интегрировать, имеет только две точки, то интегрирование происходит с помощью метода трапеций. -\section{Численное решение обыкновенных дифференциальных уравнений} +\section{Численное решение задачи Коши обыкновенных дифференциальных уравнений} +Обыкновенное дифференциальное уравнение (далее, ОДУ) --- это уравнение +вида \(F(x,y,y',y'',...,y^{(n)})\), где \(n\) --- порядок уравнения. +Решение ОДУ --- функция \(y=y(x)\), при подстановке которой в исходное +ОДУ получается верное тождество. + +Так как для нахождения решения необходимо провести \(n\) интегрирований, +то общее решение ОДУ --- \(y=\varphi(x,C_1,C_2,...,C_n)\). + +Частное решение, при котором будут найдены конкретные значения +\(C_1,...,C_n\), называется задачей Коши и для получения ее решения +необходимо, чтобы были заданы начальные условия: +\begin{equation*} + y(x_0) = y_0, \quad y'(x_0) = y'_0, \ ...\ , y^{(n-1)}(x_0) = y^{(n-1)}_0 +\end{equation*} + +В данном разделе будут описаны разностные методы решения задачи Коши. +Процесс решения задачи с помощью таких методов состоит из следующих +этапов: +\begin{enumerate} + \item Выборка узлов сетки --- дискретного множества точек из исходной + области изменения аргумента + \item Аппроксимация производных в узлах сетки конечно-разностными + аналогами. + \item Аппроксимация ОДУ системой разностных уравнений. + \item Решение системы разностных уравнений. +\end{enumerate} \subsection{Метод Эйлера} +Данный метод, как и его модифицированная версия, применяется для +ОДУ 1 порядка. Для решения задач Коши ОДУ порядка \(n\) необходимо +исходное уравнение свести к системе ОДУ 1 порядка с помощью замены переменных. \subsubsection{Описание метода} +Дано ОДУ 1 порядка с начальными условиями: +\begin{equation*} + y' = f(x,y), \qquad y(x_0) = y_0, x \in [a;b] +\end{equation*} + +На отрезке \([a;b]\) выберем \(n\) точек: +\begin{equation} + a = x_0 < x_1 < ... < x_n = b, \qquad x_{i+1} - x_i = h, i = 0,1,2,...,n-1 + \label{formula:euler} +\end{equation} + +Затем получаем значение производной: +\begin{equation*} + y'(x_i) \approx \frac{\Delta y}{\Delta x} = \frac{y_{i+1} - y_i}{h} +\end{equation*} +Исходя из (\ref{formula:euler}) получаем формулу Эйлера: +\begin{equation*} + y_{i+1} = y_i + hf(x_i, y_i), \quad i = 0,1, ...,n-1 +\end{equation*} +С помощью нее последовательно получаем значения \(y_1, y_2, ..., y_n\), +по которым можно провести интерполяцию, чтобы получить +\(y = \widetilde{y}(x)\). + +Погрешность метода на каждой итерации --- \(O(h^2)\). \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного метода в библиотеках numpy, scipy не найдено. \subsection{Модифицированный метод Эйлера} \subsubsection{Описание метода} +Итерационную формулу предыдущего метода разложим в ряд Тейлора: +\begin{equation} + y_{i+1} = y_i + y'_i h + \frac{1}{2}y''h^2 + O(h^3) + \label{formula:mod_euler} +\end{equation} +Приближенное значение \(y''\) вычисляется аналогично \(y'\): +\begin{equation*} + y''_i = \frac{y'_{i+1} - y'_i}{h} +\end{equation*} +Подставляем данное выражение в (\ref{formula:mod_euler}), пренебрегая \(O(h^3)\): +\begin{equation*} + y_{i+1} = y_i + \frac{h}{2} \left(f(x_i,y_i) + f(x_{i+1},y_{i+1})\right) +\end{equation*} +Данное формула получения \(y_{i+1}\) является неявной, поэтому находим +его приближенное значение в два шага --- сначала вычисляем значение +\(\widetilde{y}_{i+1}\), затем уточненное \(y_{i+1}\): +\begin{equation} + \begin{aligned} + & \widetilde{y}_{i+1} = y_i + hf(x_i,y_i) \\ + & y_{i+1} = y_i + \frac{h}{2} \left(f(x_i,y_i) + f(x_{i+1}, \widetilde{y}_{i+1})\right) + \end{aligned} + \label{formula:mod_euler_iterations} +\end{equation} + +Данный метод имеет меньшую погрешность, чем предыдущий --- \(O(h^3)\) +на каждой итерации. \subsubsection{Реализации метода в библиотеках numpy, scipy} +Реализаций данного метода в библиотеках numpy, scipy не найдено. \subsection{Метод Рунге-Кутта} \subsubsection{Описание метода} +Формулы \ref{formula:mod_euler_iterations} приводим к следующему виду: +\begin{equation*} + \begin{aligned} + & y_{i+1} = y_i + (k_0 + k_1) / 2, \\ + & k_0 = hf(x_i,y_i), \\ + & k_1 = hf(x_i + h,y_i + h) + \end{aligned} +\end{equation*} +Данная формула представляет из себя метод Рунге-Кутта второго порядка. +От величины порядка зависит точность полученного решения. Наиболее часто +встречающийся метод четвертого порядка имеет следующий вид: +\begin{equation*} + \begin{aligned} + & y_{i+1} = y_i + (k_0 + 2k_1 + 2k_2 + k_3)/6 \\ + & k_0 = hf(x_i,y_i) \\ + & k_1 = hf(x_i + h/2,y_i + k_0/2) \\ + & k_2 = hf(x_i + h/2,y_i + k_1/2) \\ + & k_3 = hf(x_i + h,y_i + k_2) \\ + \end{aligned} +\end{equation*} \subsubsection{Реализации метода в библиотеках numpy, scipy} +В библиотеке scipy реализована модификации явного метода Рунге-Кутта --- +явные методы Рунге-Кутта-Фельберга \cite{article:fehlberg}, в функции +\textbf{solve\_ivp} модуля \textbf{scipy.integrate}. +Метод Рунге-Кутты-Фельберга порядка \(n(m)\) нужно понимать как метод +Рунге-Кутты порядка \(n\) с погрешностью \(O(h^m)\). +Дополнительно, существуют классы для низкоуровневого управления +вычислениями: +\begin{enumerate} + \item \textbf{RK23} --- метод Рунге-Кутта-Фельберга 2 порядка с погрешностью \(O(h^3)\). + \item \textbf{RK45} --- метод Рунге-Кутта-Фельберга 4 порядка с погрешностью \(O(h^5)\). + \item \textbf{DOP853} --- метод Рунге-Кутта 8 порядка. +\end{enumerate} +Функция \textbf{solve\_ivp} может найти решение задачи Коши для системы +ОДУ, и включает в себя реализации нескольких методов. Далее будут +описаны только те значения параметров, которые необходимы для решения +задачи исследуемым методом (Рунге-Кутта). Также стоит учесть, что при +описании параметров, вместо \(x\) будет использваться \(t\), как и +принято в зарубежных источниках. +Данная функция имеет следующие параметры: +\begin{enumerate} + \item \(fun\) --- callable + + Правая часть системы: производная \(\frac{dy(t)}{dt}\). + Сигнатурой вызова является \(fun(t, y)\), где \(t\) --- скаляр, + а \(y\) --- массив ndarray с \verb|len(y) = len(y0)|. \(fun\) + должен возвращать массив той же размерности, что и \(y\). + См. \(vectorized\) для получения более детальной информации. + \item \(t\_span\) --- пара значений float + + Интервал интегрирования \((t0, tf)\). Решатель (\(method\)) начинает выполнение с \(t=t0\) и осуществляет интегририрование, пока не выполнится условие \(t=tf\). И \(t0\), и \(tf\) должны быть числами с плавающей запятой или значениями, интерпретируемыми функцией преобразования чисел с плавающей запятой. + \item \(y0\) --- array\_like формы (n,) + + Начальное состояние. Для задач на комплексной плоскости необходимо передавать комплексные \(y0\) (даже если начальное значение чисто вещественное). + \item \(method\) --- string / \verb|OdeSolver|, необязательный + + Используемый метод интеграции: + \begin{itemize} + \item \verb|"RK45"| (по умолчанию): Явный метод Рунге-Кутты порядка 5(4). Погрешность контролируется в предположении точности метода четвертого порядка, но шаги выполняются с использованием формулы точности пятого порядка (проводится локальная экстраполяция). При включенном \(dense\_output\) используется интерполяционный полином четвертой степени. Может применяться на комплексной плоскости. + + \item \verb|"RK23"|: Явный метод Рунге-Кутты порядка 3(2). Погрешность контролируется в предположении точности метода второго порядка, но шаги выполняются с использованием формулы точности третьего порядка (проводится локальная экстраполяция). Для плотного вывода используется кубический полином Эрмита. Может применяться на комплексной плоскости. + + \item \verb|"DOP853"|: Явный метод Рунге-Кутты восьмого порядка. Является Python-реализацией алгоритма "DOP853", первоначально написанного на Fortran. При включенном \(dense\_output\) используется интерполяционный полином 7-го порядка с точностью до 7-го порядка. Может применяться на комплексной плоскости. + + \item \verb|"Radau"|: Неявный метод Рунге-Кутты семейства Radau IIA порядка 5. Погрешность контролируется с помощью встроенной формулы третьего порядка точности. Кубический полином, который удовлетворяет условиям коллокация, используется при включенном \(dense\_output\). + \end{itemize} + Явные методы Рунге-Кутты (\verb|"RK23"|, \verb|"RK45"|, \verb|"DOP853"|) следует использовать для нежестких уравнений, неявные методы (\verb|"Radau"|) --- для жестких. Среди методов Рунге-Кутты для решения с высокой точностью (низкие значения \(rtol\) и \(atol\)) рекомендуется \verb|"DOP853"|. + + Если не уверены, сначала попробуйте запустить \verb|"RK45"|. Если он делает необычно много итераций, расходится или терпит неудачу, ваша проблема, вероятно, будет сложной, и вам следует использовать \verb|"Radau"|. + + Вы также можете передать произвольный класс, производный от \(OdeSolver\), который реализует решатель. + \item \(t\_eval\)--- array\_like / \verb|None|, необязательный + + Значения \(t\), для которых нужно сохранить вычисленные значения решения, должны быть отсортированы и находиться в пределах \(t\_span\). Если \verb|None| (по умолчанию), использутся точки, выбранные решателем. + \item \(dense\_output\) --- bool, необязательный + + Определяет, следует ли вычислять непрерывное решение. По умолчанию --- \verb|False|. + \item \(events\) --- callable / list из callables, необязательный + + События для отслеживания. Если \verb|None| (по умолчанию), + события отслеживаться не будут. Событие происходит, когда + какая-либо функция, переданная в этом параметре, равна 0. + Каждая функция должна иметь сигнатуру \(event(t, y)\) и + возвращать float. Решатель найдет точное значение \(t\), при + котором \(event(t, y(t)) = 0\), используя алгоритм поиска + корня. По умолчанию будут найдены все нули. Решатель ищет + смену знака на каждом шаге, поэтому, если в течение одного + шага происходит несколько пересечений нуля, события могут быть + пропущены. Кроме того, каждая функция \(event\) может иметь + следующие атрибуты: + \begin{itemize} + \item \(terminal\): bool, необязательный + + Определяет, следует ли прекратить интегрирование, если + произойдет это событие. По умолчанию --- \verb|False|. + \item \(direction\): float, необязательный + + Направление пересечения нуля. Если направление + положительное, событие сработает только при переходе + от отрицательного к положительному и наоборот, если + направление отрицательное. Если 0, то любое + направление вызовет появление событие. По умолчанию + --- 0. + \end{itemize} + Вы можете назначить атрибуты, например + \verb|event.terminal = True|, любой функции в Python. + \item \(vectorized\) --- bool, необязательный + + Определяет, можно ли вызвать \(fun\) векторизованным образом. + По умолчанию --- \verb|False|. + Если \(vectorized\) имеет значение \verb|False|, \(fun\) всегда + будет вызываться с \(y\) формы \((n,)\), где \verb|n = len(y0)|. + + Если векторизация имеет значение \verb|True|, \(fun\) можно + вызвать с помощью \(y\) формы \((n, k)\), где \(k\) --- целое + число. В этом случае \(fun\) должно вести себя так, чтобы + \verb|fun(t, y)[:, i] == fun(t, y[:, i])| (то есть каждый + столбец возвращаемого массива является производной \(dt/dy\) + соответствующего столбца \(y\)). + + \(vectorized\)=\verb|True| позволяет быстрее аппроксимировать + якобиан конечной разностью методом \verb|"Radau"|, но в + некоторых случаях приводит к более медленному выполнению + других методов, в том числе и для \verb|"Radau"| при некоторых + условиях (например, малое значение \verb|len(y0)|). + \item \(args\) --- tuple, необязательный + + Дополнительные аргументы для передачи пользовательским функциям. + Если заданы, дополнительные аргументы передаются всем + пользовательским функциям. Так, если, например, \(fun\) имеет + сигнатуру \(fun(t, y, a, b, c)\), то \(jac\) (если задан) и + любые функции обработки событий должны иметь одинаковую + сигнатуру, а \(args\) должен быть tuple длины 3. + По умолчанию --- \verb|None|. + \item \(**options\) + + Опции, которые передаются выбранному решателю. Все опции, + доступные для уже реализованных решателей, перечислены ниже. + \begin{itemize} + \item \(first_step\) --- float / None, необязательный + + Начальный размер шага. По умолчанию установлено + значение \verb|None|, что означает, что его должен + выбирать решатель. + \item \(max\_step\) --- float, optional + + Максимально допустимый размер шага. По умолчанию + используется \verb|np.inf|, то есть размер шага не + ограничен и определяется исключительно решателем. + \item \(rtol\), \(atol\) --- float / array\_like, необязательный + + Относительные и абсолютные погрешности при + вычислениях. Решатель сохраняет локальные оценки + погрешности меньше, чем \(atol + rtol * abs(y)\). + Здесь \(rtol\) контролирует относительную точность + (количество правильных цифр), а \(atol\) --- + абсолютную точность (количество правильных десятичных + знаков). Чтобы достичь желаемого значения \(rtol\), + установите значение \(atol <\) + \verb|min(rtol * abs(y))|, чтобы значение \(rtol\) + доминировало над допустимой ошибкой. Если \(atol\) + больше, чем \verb|rtol * abs(y)|, количество + правильных цифр не гарантируется. И наоборот, чтобы + получить желаемый \(atol\), установите \(rtol\) так, + чтобы \(atol <\) \verb|rtol * abs(y)|. Если значения + \(y\) имеют разные масштабы, возможно, было бы + полезно установить разные значения \(atol\) для + разных компонентов, передав array\_like с формой + \((n,)\) для \(atol\). Значения по умолчанию: + \(1e-3\) для \(rtol\) и \(1e-6\) для \(atol\). + \item \(jac\) --- array\_like / sparse\_matrix / callable / \verb|None|, необязательный + + Матрица Якоби правой части системы по y, необходимая + для методов \verb|"Radau"|. Матрица Якоби имеет форму + \((n, n)\) и ее элемент \((i, j)\) равен + \verb|d f_i / d y_j.|. Есть три способа определения + матрицы Якоби: + \begin{itemize} + \item Если array\_like или sparse\_matrix, матрица + Якоби считается постоянной. + \item Если callable, предполагается, что она + зависит как от \(t\), так и от \(y\); при + необходимости ее значение будет равно + возвращаемому значению \(jac(t, y)\). Для + метода \verb|"Radau"| возвращаемое значение + может быть разреженной матрицей. + \item Если \verb|None| (по умолчанию), матрица + Якоби будет аппроксимироваться конечными + разностями. + \end{itemize} + Обычно рекомендуется явно указывать матрицу Якоби, + а не полагаться на конечно-разностное приближение. + \item \(jac\_sparsity\) --- array\_like / sparse matrix / \verb|None|, необязательный + + Определяет структуру разреженности матрицы Якоби для + конечно-разностного приближения. Его форма должна быть + \((n, n)\). Этот аргумент игнорируется, если + \(jac \ne\) \verb|None|. Если матрица Якоби имеет + лишь несколько ненулевых элементов в каждой строке, + обеспечение разреженной структуры значительно ускорит + вычисления. Нулевая запись означает, что + соответствующий элемент матрицы Якоби всегда равен + нулю. Если \verb|None| (по умолчанию), матрица Якоби + считается плотной. + \end{itemize} +\end{enumerate} \chapter{Экспериментальное исследование возможностей библиотек} \chapter*{Заключение} From c3fe366e2c00366a50e632c0a3c3f5b4fc805db0 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 7 Oct 2023 22:08:25 +0300 Subject: [PATCH 22/41] [sources] Fix foreign item --- sources.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sources.tex b/sources.tex index 80685fb..19316fa 100644 --- a/sources.tex +++ b/sources.tex @@ -20,7 +20,7 @@ \bibitem{links:numpy_doc} Numpy API Reference [Электронный ресурс] -- URL:~\url{https://numpy.org/doc/stable/reference/index.html} (\LiteratureAccessDate[3]). \bibitem{links:PEP465} PEP 465 -- A dedicated infix operator for matrix multiplication [Электронный ресурс] -- URL:~\url{A dedicated infix operator for matrix multiplication} (\LiteratureAccessDate[4]). \bibitem{links:python} Python. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://www.python.org/} (\LiteratureAccessDate). - \bibitem{links:bhatia} R. Bhatia Positive definite matrices -- Princeton Series in Applied Mathematics -- 2007. + \bibitem{links:bhatia} Positive definite matrices / R. Bhatia // Princeton Series in Applied Mathematics -- 2007. \bibitem{links:scipy} Scipy. Официальный сайт проекта [Электронный ресурс] -- URL:~\url{https://scipy.org/} (\LiteratureAccessDate[2]). \bibitem{links:scipy_doc} Scipy API Reference [Электронный ресурс] -- URL:~\url{https://docs.scipy.org/doc/scipy/reference/index.html} (\LiteratureAccessDate[3]). \bibitem{links:tiobe_index} TIOBE. Официальный сайт проекта From 0f23ddeecc614d47d4d1e751ad54709fedabc3a1 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sat, 7 Oct 2023 22:40:16 +0300 Subject: [PATCH 23/41] [content] FIx misspelings --- main.tex | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/main.tex b/main.tex index b9e55ee..9ca1edd 100644 --- a/main.tex +++ b/main.tex @@ -1273,7 +1273,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \verb|False|. \item \(nan\_policy\) --- {\verb|"raise"|, \verb|"omit"|, \verb|None|}, необязательный - Определяет, как действовать, если входные данные содержат nan. Доступны следующие параметры (по умолчанию --- \verb|None|): + Определяет, как действовать, если входные данные содержат \verb|NaN|. Доступны следующие параметры (по умолчанию --- \verb|None|): \begin{itemize} \item \verb|"raise"|: выдает ошибку @@ -1313,7 +1313,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения дополнительную информацию о работе алгоритма. \item \(mesg\) --- str - Cтроковое сообщение с информацией о решении. + Строковое сообщение с информацией о решении. \item \(ier\) --- int Целочисленный флаг. Если он равен 1, 2, 3 или 4, решение @@ -1495,7 +1495,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения (он позволяет находить точное решение для любых f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии с формулой Джузеппе Пеано). Из его недостатков можно отметить низкую -точность на пикообразных функциях (т.е. значение которой резко +точность на пилообразных функциях (т.е. значение которой резко возрастают на отрезках малой длины). При этом, если количество точек, по которым строится \(P_m(x)\), четно, то метод трапеций может оказаться удобнее, тем самым прекрасно дополняя @@ -1522,10 +1522,10 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии \begin{enumerate} \item \(y\) --- array\_like - Массив входных данных по которым будет вычислятся интеграл + Массив входных данных по которым будет вычисляться интеграл \item \(x\) --- array\_like, необязательный - Массив значений \(x\), соответсвующих \(y\). Если \verb|None| + Массив значений \(x\), соответствующих \(y\). Если \verb|None| (по умолчанию), то в роли \(x\) будет создан массив равноотстоящих значений (\(h = dx\)) \item \(dx\) --- scalar, необязательный @@ -1561,7 +1561,7 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии \int_{x_{i-1}}^{x_{i+1}} P_2(x)\, dx = \frac{h}{3} \left( f(x_{i-1})+4f(x_i)+f(x_{i+1}) \right), \quad h = \frac{b-a}{N} \end{equation*} -В результате, значение интерграла \(I\) будет вычислятся по формуле +В результате, значение интеграла \(I\) будет вычисляться по формуле \begin{equation*} I \approx \frac{h}{3} \sum_{i=0}^{n-1}(f(x_{2i})+4f(x_{2i+1})+f(x_{2i+2})) \end{equation*} @@ -1718,8 +1718,8 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии В библиотеке scipy реализована модификации явного метода Рунге-Кутта --- явные методы Рунге-Кутта-Фельберга \cite{article:fehlberg}, в функции \textbf{solve\_ivp} модуля \textbf{scipy.integrate}. -Метод Рунге-Кутты-Фельберга порядка \(n(m)\) нужно понимать как метод -Рунге-Кутты порядка \(n\) с погрешностью \(O(h^m)\). +Метод Рунге-Кутта-Фельберга порядка \(n(m)\) нужно понимать как метод +Рунге-Кутта порядка \(n\) с погрешностью \(O(h^m)\). Дополнительно, существуют классы для низкоуровневого управления вычислениями: @@ -1733,7 +1733,7 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии ОДУ, и включает в себя реализации нескольких методов. Далее будут описаны только те значения параметров, которые необходимы для решения задачи исследуемым методом (Рунге-Кутта). Также стоит учесть, что при -описании параметров, вместо \(x\) будет использваться \(t\), как и +описании параметров, вместо \(x\) будет использоваться \(t\), как и принято в зарубежных источниках. Данная функция имеет следующие параметры: \begin{enumerate} @@ -1746,7 +1746,7 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии См. \(vectorized\) для получения более детальной информации. \item \(t\_span\) --- пара значений float - Интервал интегрирования \((t0, tf)\). Решатель (\(method\)) начинает выполнение с \(t=t0\) и осуществляет интегририрование, пока не выполнится условие \(t=tf\). И \(t0\), и \(tf\) должны быть числами с плавающей запятой или значениями, интерпретируемыми функцией преобразования чисел с плавающей запятой. + Интервал интегрирования \((t0, tf)\). Решатель (\(method\)) начинает выполнение с \(t=t0\) и осуществляет интегрирование, пока не выполнится условие \(t=tf\). И \(t0\), и \(tf\) должны быть числами с плавающей запятой или значениями, интерпретируемыми функцией преобразования чисел с плавающей запятой. \item \(y0\) --- array\_like формы (n,) Начальное состояние. Для задач на комплексной плоскости необходимо передавать комплексные \(y0\) (даже если начальное значение чисто вещественное). @@ -1754,26 +1754,26 @@ f\(x\), если \(f^{(4)}(x) = 0, x \in [a;b] \), в соответствии Используемый метод интеграции: \begin{itemize} - \item \verb|"RK45"| (по умолчанию): Явный метод Рунге-Кутты порядка 5(4). Погрешность контролируется в предположении точности метода четвертого порядка, но шаги выполняются с использованием формулы точности пятого порядка (проводится локальная экстраполяция). При включенном \(dense\_output\) используется интерполяционный полином четвертой степени. Может применяться на комплексной плоскости. + \item \verb|"RK45"| (по умолчанию): Явный метод Рунге-Кутта порядка 5(4). Погрешность контролируется в предположении точности метода четвертого порядка, но шаги выполняются с использованием формулы точности пятого порядка (проводится локальная экстраполяция). При включенном \(dense\_output\) используется интерполяционный полином четвертой степени. Может применяться на комплексной плоскости. - \item \verb|"RK23"|: Явный метод Рунге-Кутты порядка 3(2). Погрешность контролируется в предположении точности метода второго порядка, но шаги выполняются с использованием формулы точности третьего порядка (проводится локальная экстраполяция). Для плотного вывода используется кубический полином Эрмита. Может применяться на комплексной плоскости. + \item \verb|"RK23"|: Явный метод Рунге-Кутта порядка 3(2). Погрешность контролируется в предположении точности метода второго порядка, но шаги выполняются с использованием формулы точности третьего порядка (проводится локальная экстраполяция). Для плотного вывода используется кубический полином Эрмита. Может применяться на комплексной плоскости. - \item \verb|"DOP853"|: Явный метод Рунге-Кутты восьмого порядка. Является Python-реализацией алгоритма "DOP853", первоначально написанного на Fortran. При включенном \(dense\_output\) используется интерполяционный полином 7-го порядка с точностью до 7-го порядка. Может применяться на комплексной плоскости. + \item \verb|"DOP853"|: Явный метод Рунге-Кутта восьмого порядка. Является Python-реализацией алгоритма "DOP853", первоначально написанного на FORTRAN. При включенном \(dense\_output\) используется интерполяционный полином 7-го порядка с точностью до 7-го порядка. Может применяться на комплексной плоскости. - \item \verb|"Radau"|: Неявный метод Рунге-Кутты семейства Radau IIA порядка 5. Погрешность контролируется с помощью встроенной формулы третьего порядка точности. Кубический полином, который удовлетворяет условиям коллокация, используется при включенном \(dense\_output\). + \item \verb|"Radau"|: Неявный метод Рунге-Кутта семейства Radau IIA порядка 5. Погрешность контролируется с помощью встроенной формулы третьего порядка точности. Кубический полином, который удовлетворяет условиям коллокация, используется при включенном \(dense\_output\). \end{itemize} - Явные методы Рунге-Кутты (\verb|"RK23"|, \verb|"RK45"|, \verb|"DOP853"|) следует использовать для нежестких уравнений, неявные методы (\verb|"Radau"|) --- для жестких. Среди методов Рунге-Кутты для решения с высокой точностью (низкие значения \(rtol\) и \(atol\)) рекомендуется \verb|"DOP853"|. + Явные методы Рунге-Кутта (\verb|"RK23"|, \verb|"RK45"|, \verb|"DOP853"|) следует использовать для нежестких уравнений, неявные методы (\verb|"Radau"|) --- для жестких. Среди методов Рунге-Кутта для решения с высокой точностью (низкие значения \(rtol\) и \(atol\)) рекомендуется \verb|"DOP853"|. Если не уверены, сначала попробуйте запустить \verb|"RK45"|. Если он делает необычно много итераций, расходится или терпит неудачу, ваша проблема, вероятно, будет сложной, и вам следует использовать \verb|"Radau"|. Вы также можете передать произвольный класс, производный от \(OdeSolver\), который реализует решатель. \item \(t\_eval\)--- array\_like / \verb|None|, необязательный - Значения \(t\), для которых нужно сохранить вычисленные значения решения, должны быть отсортированы и находиться в пределах \(t\_span\). Если \verb|None| (по умолчанию), использутся точки, выбранные решателем. + Значения \(t\), для которых нужно сохранить вычисленные значения решения, должны быть отсортированы и находиться в пределах \(t\_span\). Если \verb|None| (по умолчанию), используются точки, выбранные решателем. \item \(dense\_output\) --- bool, необязательный Определяет, следует ли вычислять непрерывное решение. По умолчанию --- \verb|False|. - \item \(events\) --- callable / list из callables, необязательный + \item \(events\) --- callable / list из callable, необязательный События для отслеживания. Если \verb|None| (по умолчанию), события отслеживаться не будут. Событие происходит, когда From 5f0e7923968bb704db3caea01ef1f10d8ffe6cd5 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sun, 15 Oct 2023 16:29:44 +0300 Subject: [PATCH 24/41] Add code with non-LE test --- code/main.py | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 code/main.py diff --git a/code/main.py b/code/main.py new file mode 100644 index 0000000..cb64fc1 --- /dev/null +++ b/code/main.py @@ -0,0 +1,121 @@ +import scipy.integrate as sitg +import scipy.interpolate as sitp +import scipy.optimize as sopt +import scipy.linalg as salg +import math as m +import numpy as np + +import matplotlib.pyplot as plt + + +def create_subplot(): + return plt.subplots(layout='constrained')[1] + + +class NonLinear: + bisect_exp = "x**2 * np.sin(x)" + newton_exp = "np.sin(x) * np.sqrt(np.abs(x))" + + @staticmethod + def generate_array(min, max): + point_count = int(m.fabs(max-min))*10 + x = np.linspace(min, max, point_count) + return list(x.tolist()) + + @staticmethod + def slice_array(range: list[float], val_min, val_max): + def index_search(range: list[float], val): + i = 0 + for v in range: + if v >= val: + return i + i += 1 + return -1 + + index_l = index_search( + range, val_min) if val_min is not None else range.index(min(range)) + index_r = index_search( + range, val_max) if val_max is not None else range.index(max(range)) + return range[index_l:index_r+1] + + @staticmethod + def plt_append(sp, x: list[float], y: list[float], label: str, format: str): + sp.plot(x, y, format, label=label) + + @staticmethod + def bisect(x, x_min, x_max): + def f(x): return eval(NonLinear.bisect_exp) + y = f(np.array(x)) + root = sopt.bisect(f, x_min, x_max) + solution = root[0] if root is tuple else root + return list(y), (float(solution), float(f(solution))) + + @staticmethod + def plot_bisect(): + bounds = 0, 6 + split_val = 1 + x1 = NonLinear.generate_array(bounds[0], bounds[1]) + x2 = NonLinear.slice_array(x1, split_val, None) + + sp = create_subplot() + + sol1 = NonLinear.bisect(x1, bounds[0], bounds[1]) + sol2 = NonLinear.bisect(x2, split_val, bounds[1]) + + NonLinear.plt_append( + sp, x1, sol1[0], f"Исходные данные (y={NonLinear.bisect_exp})", "-b") + NonLinear.plt_append( + sp, *(sol1[1]), f"bisect at [{bounds[0]},{bounds[1]}]", "or") + NonLinear.plt_append( + sp, *(sol2[1]), f"bisect at [{split_val},{bounds[1]}]", "og") + + sp.set_title("scipy.optimize.bisect") + sp.legend(loc='lower left') + + @staticmethod + def newton(x, x0): + def f(x): return eval(NonLinear.bisect_exp) + y = f(np.array(x)) + root = sopt.newton(f, x0) + solution = root[0] if root is tuple else root + return list(y), (float(solution), float(f(solution))) + + @staticmethod + def plot_newton(): + bounds = -2, 7 + split_l, split_r = 2, 5 + x1 = NonLinear.generate_array(bounds[0], bounds[1]) + x2 = NonLinear.slice_array(x1, split_l, split_r) + x0_1, x0_2 = 1/100, 4 + sp = create_subplot() + + sol1 = NonLinear.newton(x1, x0_1) + sol2 = NonLinear.newton(x2, x0_2) + + NonLinear.plt_append( + sp, x1, sol1[0], f"Исходные данные (y={NonLinear.newton_exp})", "-b") + NonLinear.plt_append( + sp, *(sol1[1]), f"newton at [{bounds[0]},{bounds[1]}]", "or") + NonLinear.plt_append( + sp, *(sol2[1]), f"newton at [{split_l},{bounds[1]}]", "og") + + sp.set_title("scipy.optimize.newton") + sp.legend(loc='lower left') + + @staticmethod + def plot(method: str = "all"): + if method in ["bisect", "all"]: + NonLinear.plot_bisect() + if method in ["newton", "all"]: + NonLinear.plot_newton() + plt.ylabel("y") + plt.xlabel("x") + plt.show() + + +def main(): + NonLinear.plot() + + +if __name__ == "__main__": + main() From 00ca3f92e7cbeba91be3a67f178e268975495606 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Sun, 15 Oct 2023 16:30:41 +0300 Subject: [PATCH 25/41] [content] Add TODO --- main.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/main.tex b/main.tex index 9ca1edd..afe5f48 100644 --- a/main.tex +++ b/main.tex @@ -1,3 +1,4 @@ +% TODO: Fix terminology usage of "якобиан" \input{vars} \input{config} \sloppy From 58b7923412f9ff6de2bb7a470599c06b85905dae Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Mon, 16 Oct 2023 20:16:37 +0300 Subject: [PATCH 26/41] [code] Add SLE, Approx (empty) --- code/main.py | 126 ++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 11 deletions(-) diff --git a/code/main.py b/code/main.py index cb64fc1..50bbc72 100644 --- a/code/main.py +++ b/code/main.py @@ -12,6 +12,10 @@ def create_subplot(): return plt.subplots(layout='constrained')[1] +def plt_append(sp, x: list[float], y: list[float], label: str, format: str): + sp.plot(x, y, format, label=label) + + class NonLinear: bisect_exp = "x**2 * np.sin(x)" newton_exp = "np.sin(x) * np.sqrt(np.abs(x))" @@ -38,10 +42,6 @@ class NonLinear: range, val_max) if val_max is not None else range.index(max(range)) return range[index_l:index_r+1] - @staticmethod - def plt_append(sp, x: list[float], y: list[float], label: str, format: str): - sp.plot(x, y, format, label=label) - @staticmethod def bisect(x, x_min, x_max): def f(x): return eval(NonLinear.bisect_exp) @@ -62,11 +62,11 @@ class NonLinear: sol1 = NonLinear.bisect(x1, bounds[0], bounds[1]) sol2 = NonLinear.bisect(x2, split_val, bounds[1]) - NonLinear.plt_append( + plt_append( sp, x1, sol1[0], f"Исходные данные (y={NonLinear.bisect_exp})", "-b") - NonLinear.plt_append( + plt_append( sp, *(sol1[1]), f"bisect at [{bounds[0]},{bounds[1]}]", "or") - NonLinear.plt_append( + plt_append( sp, *(sol2[1]), f"bisect at [{split_val},{bounds[1]}]", "og") sp.set_title("scipy.optimize.bisect") @@ -92,11 +92,11 @@ class NonLinear: sol1 = NonLinear.newton(x1, x0_1) sol2 = NonLinear.newton(x2, x0_2) - NonLinear.plt_append( + plt_append( sp, x1, sol1[0], f"Исходные данные (y={NonLinear.newton_exp})", "-b") - NonLinear.plt_append( + plt_append( sp, *(sol1[1]), f"newton at [{bounds[0]},{bounds[1]}]", "or") - NonLinear.plt_append( + plt_append( sp, *(sol2[1]), f"newton at [{split_l},{bounds[1]}]", "og") sp.set_title("scipy.optimize.newton") @@ -113,8 +113,112 @@ class NonLinear: plt.show() +class SLE: + gauss_data = ([[13, 2], [3, 4]], [1, 2]) + invmatrix_data = ([[13, 2], [3, 4]], [1, 2]) + tridiagonal_data = ([[4, 5, 6, 7, 8, 9], + [2, 2, 2, 2, 2, 0]], + [1, 2, 2, 3, 3, 3]) + + @staticmethod + def var_str(index): + return f"x{index+1}" + + @staticmethod + def print_solution(data: list[float]): + print(" ", end='') + for i, val in enumerate(data[:-1]): + print(f"{SLE.var_str(i)} = {round(val,3)}, ", end='') + print(f"{SLE.var_str(len(data)-1)} = {round(data[-1],3)}") + + @staticmethod + def print_data(data: tuple[list[list[float]], list[float]], tridiagonal: bool = False): + if tridiagonal: + new_data = [] + new_len = len(data[0][0]) + zipped = list(zip(*tuple(data[0]))) + zipped[len(zipped)-1] = (zipped[len(zipped)-1][0],zipped[len(zipped)-2][1]) + complement_to = new_len - len(zipped[0]) + for i, val in enumerate(zipped): + zero_r = complement_to - i + if zero_r <= 0: + zero_r = 0 + mid_val = list(reversed(val[1:])) + list(val) + mid_end = len(mid_val) if zero_r > 0 else len( + mid_val) + (complement_to - i) + mid_beg = len(mid_val) - (new_len - zero_r) if zero_r > 0 else 0 + mid_beg = mid_beg if mid_beg >= 0 else 0 + zero_l = new_len - (zero_r + (mid_end - mid_beg)) + tmp = [0] * zero_l + \ + mid_val[mid_beg:mid_end] + [0] * zero_r + new_data.append(tmp) + data = (new_data, data[1]) + for i, val in enumerate(data[0]): + print(" ", end='') + for i_coef, coef in enumerate(val[:-1]): + if coef != 0: + print(f"({coef}{SLE.var_str(i_coef)}) + ", end='') + else: + print(f" {coef} + ",end='') + print(f"({val[-1]}{SLE.var_str(len(val)-1)})", end='') + print(f" = {data[1][i]}") + + @staticmethod + def gauss(system: list[list[float]], b: list[float]): + lup = salg.lu_factor(system) + solution = salg.lu_solve(lup, b) + return solution + + @staticmethod + def invmatrix(system: list[list[float]], b: list[float]): + m_inv = salg.inv(system) + solution = m_inv @ b + return solution + + @staticmethod + def tridiagonal(system: list[list[float]], b: list[float]): + solution = salg.solveh_banded(system, b, lower=True) + return solution + + @staticmethod + def print_gauss(): + print("Gauss method (LU decomposition)") + print(" Input system:") + SLE.print_data(SLE.gauss_data) + print(" Solution:") + SLE.print_solution(SLE.gauss(*SLE.gauss_data)) + + @staticmethod + def print_invmatrix(): + print("Inverted matrix method") + print(" Input system:") + SLE.print_data(SLE.invmatrix_data) + print(" Solution:") + SLE.print_solution(SLE.invmatrix(*SLE.invmatrix_data)) + + @staticmethod + def print_tridiagonal(): + print("Tridiagonal matrix method (Thomas algorithm)") + print(" Input system:") + SLE.print_data(SLE.tridiagonal_data, True) + print(" Solution:") + SLE.print_solution(SLE.tridiagonal(*SLE.tridiagonal_data)) + + @staticmethod + def print(method="all"): + if method in ["gauss", "all"]: + SLE.print_gauss() + if method in ["invmatrix", "all"]: + SLE.print_invmatrix() + if method in ["banded", "all"]: + SLE.print_tridiagonal() + +class Approx: + function = "np.sin(x) * np.sqrt(np.abs(x))" + def main(): - NonLinear.plot() + # NonLinear.plot() + SLE.print() if __name__ == "__main__": From be0a19645780d3be62315ba1d14de504f5ee3c75 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Mon, 16 Oct 2023 20:18:40 +0300 Subject: [PATCH 27/41] [conent] Fix function description, terminology --- main.tex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.tex b/main.tex index afe5f48..2cf5845 100644 --- a/main.tex +++ b/main.tex @@ -486,7 +486,8 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения В scipy для решения СЛУ вида (\ref{formula:eqn_banded_system}) существует две функции в модуле \textbf{scipy.linalg}: \textbf{solve\_banded} \cite{links:scipy_doc} и -\textbf{solveh\_banded} \cite{links:scipy_doc}. +\textbf{solveh\_banded} \cite{links:scipy_doc}, обе из которых могут +решать задачи для n-диагональных матриц. Различие между ними заключается в том, что \textbf{solve\_banded} не использует метод прогонки, из-за низкой устойчивости метода в общем @@ -572,7 +573,7 @@ LU-разложение \cite[с. 259]{book:levitin}. Для получения \end{tabular} Так как данная матрица не эрмитова, и, следовательно, не положительно -определенна, описание нижней формы для нее неуместно. Если взять эрмитову +определенна, нижняя форма для нее не существует. Если взять эрмитову положительно определенную матрицу \begin{tabular}[htpb]{cccccc} From b39269901c51cb4107c0373b8204e413821e9cb8 Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Tue, 17 Oct 2023 20:58:31 +0300 Subject: [PATCH 28/41] [code] Add Approx --- code/main.py | 236 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 220 insertions(+), 16 deletions(-) diff --git a/code/main.py b/code/main.py index 50bbc72..9a2141f 100644 --- a/code/main.py +++ b/code/main.py @@ -16,16 +16,16 @@ def plt_append(sp, x: list[float], y: list[float], label: str, format: str): sp.plot(x, y, format, label=label) +def generate_array(min, max, density=10): + point_count = int(m.fabs(max-min)*density) + x = np.linspace(min, max, point_count) + return list(x.tolist()) + + class NonLinear: bisect_exp = "x**2 * np.sin(x)" newton_exp = "np.sin(x) * np.sqrt(np.abs(x))" - @staticmethod - def generate_array(min, max): - point_count = int(m.fabs(max-min))*10 - x = np.linspace(min, max, point_count) - return list(x.tolist()) - @staticmethod def slice_array(range: list[float], val_min, val_max): def index_search(range: list[float], val): @@ -54,7 +54,7 @@ class NonLinear: def plot_bisect(): bounds = 0, 6 split_val = 1 - x1 = NonLinear.generate_array(bounds[0], bounds[1]) + x1 = generate_array(bounds[0], bounds[1]) x2 = NonLinear.slice_array(x1, split_val, None) sp = create_subplot() @@ -65,9 +65,9 @@ class NonLinear: plt_append( sp, x1, sol1[0], f"Исходные данные (y={NonLinear.bisect_exp})", "-b") plt_append( - sp, *(sol1[1]), f"bisect at [{bounds[0]},{bounds[1]}]", "or") + sp, *(sol1[1]), f"bisect на [{bounds[0]},{bounds[1]}]", "or") plt_append( - sp, *(sol2[1]), f"bisect at [{split_val},{bounds[1]}]", "og") + sp, *(sol2[1]), f"bisect на [{split_val},{bounds[1]}]", "og") sp.set_title("scipy.optimize.bisect") sp.legend(loc='lower left') @@ -84,7 +84,7 @@ class NonLinear: def plot_newton(): bounds = -2, 7 split_l, split_r = 2, 5 - x1 = NonLinear.generate_array(bounds[0], bounds[1]) + x1 = generate_array(bounds[0], bounds[1]) x2 = NonLinear.slice_array(x1, split_l, split_r) x0_1, x0_2 = 1/100, 4 sp = create_subplot() @@ -95,9 +95,9 @@ class NonLinear: plt_append( sp, x1, sol1[0], f"Исходные данные (y={NonLinear.newton_exp})", "-b") plt_append( - sp, *(sol1[1]), f"newton at [{bounds[0]},{bounds[1]}]", "or") + sp, *(sol1[1]), f"newton на отрезке [{bounds[0]},{bounds[1]}]", "or") plt_append( - sp, *(sol2[1]), f"newton at [{split_l},{bounds[1]}]", "og") + sp, *(sol2[1]), f"newton на отрезке [{split_l},{bounds[1]}]", "og") sp.set_title("scipy.optimize.newton") sp.legend(loc='lower left') @@ -137,7 +137,8 @@ class SLE: new_data = [] new_len = len(data[0][0]) zipped = list(zip(*tuple(data[0]))) - zipped[len(zipped)-1] = (zipped[len(zipped)-1][0],zipped[len(zipped)-2][1]) + zipped[len(zipped)-1] = (zipped[len(zipped)-1] + [0], zipped[len(zipped)-2][1]) complement_to = new_len - len(zipped[0]) for i, val in enumerate(zipped): zero_r = complement_to - i @@ -159,7 +160,7 @@ class SLE: if coef != 0: print(f"({coef}{SLE.var_str(i_coef)}) + ", end='') else: - print(f" {coef} + ",end='') + print(f" {coef} + ", end='') print(f"({val[-1]}{SLE.var_str(len(val)-1)})", end='') print(f" = {data[1][i]}") @@ -213,12 +214,215 @@ class SLE: if method in ["banded", "all"]: SLE.print_tridiagonal() + class Approx: - function = "np.sin(x) * np.sqrt(np.abs(x))" + function_exp = "np.sin(x) * np.sqrt(np.abs(x))" + least_sq_exp = "np.sin(x) * np.abs(x)" + + @staticmethod + def get_function_exp_der(*args): + function_der_exp = "(x * np.sin(x) + 2 * x**2 * np.cos(x)) / (2 * np.sqrt(np.abs(x)) ** 3)" + result = () + for i in args: + array = [] + for x in i: + array.append(eval(function_der_exp)) + + result = result + (array,) + return result + + @staticmethod + def generate_y(x_array, function): + result = [] + for x in x_array: + result.append(eval(function)) + return result + + @staticmethod + def lagrange(x, y): + return sitp.lagrange(x, y) + + @staticmethod + def get_approx_data(function=function_exp, bounds=[-6, 6]): + x1 = generate_array(bounds[0], bounds[1], 1/2) + x2 = generate_array(bounds[0], bounds[1], 1) + y1 = Approx.generate_y(x1, function) + y2 = Approx.generate_y(x2, function) + x_real = generate_array(bounds[0], bounds[1]) + y_real = Approx.generate_y(x_real, function) + return x1, x2, y1, y2, x_real, y_real + + @staticmethod + def plot_lagrange(): + x1, x2, y1, y2, x_real, y_real = Approx.get_approx_data() + + sp = create_subplot() + sol1 = np.polynomial.polynomial.Polynomial( + Approx.lagrange(x1, y1).coef[::-1]) + sol2 = np.polynomial.polynomial.Polynomial( + Approx.lagrange(x2, y2).coef[::-1]) + + plt_append( + sp, x_real, y_real, f"Исходные данные (y={Approx.function_exp})", "--b") + plt_append( + sp, x_real, sol1(np.array(x_real)), f"f1 = lagrange, кол-во точек = {len(x1)}", "-m") + plt_append( + sp, x_real, sol2(np.array(x_real)), f"f2 = lagrange, кол-во точек = {len(x2)}", "-r") + plt_append( + sp, x1, y1, f"Исходные точки для f1", ".m") + plt_append( + sp, x2, y2, f"Исходные точки для f2", ".r") + + sp.set_title("scipy.interpolate.lagrange") + sp.legend(loc='lower left') + + @staticmethod + def plot_spline(): + x1, x2, y1, y2, x_real, y_real = Approx.get_approx_data() + d1, d2 = Approx.get_function_exp_der(x1, x2) + + for interpolator in [sitp.CubicSpline, + sitp.PchipInterpolator, + sitp.CubicHermiteSpline, + sitp.Akima1DInterpolator]: + sp = create_subplot() + + if interpolator.__name__ != "CubicHermiteSpline": + args1 = x1, y1 + args2 = x2, y2 + else: + args1 = x1, y1, d1 + args2 = x2, y2, d2 + + sol1 = interpolator(*args1) + sol2 = interpolator(*args2) + plt_append( + sp, x_real, y_real, f"Исходные данные (y={Approx.function_exp})", "--b") + plt_append( + sp, x_real, sol1(np.array(x_real)), f"f1 = {interpolator.__name__}, кол-во точек = {len(x1)}", "-m") + plt_append( + sp, x_real, sol2(np.array(x_real)), f"f2 = {interpolator.__name__}, кол-во точек = {len(x2)}", "-r") + plt_append( + sp, x1, y1, f"Исходные точки для f1", ".m") + plt_append( + sp, x2, y2, f"Исходные точки для f2", ".r") + + sp.set_title(f"scipy.interpolate.{interpolator.__name__}") + sp.legend(loc='lower left') + + @staticmethod + def linear(x, a, b): + return a*x + b + + @staticmethod + def quadratic(x, a, b, c): + return a * (x**2) + (b*x) + c + + @staticmethod + def fract(x, a, b, c): + return x / (a * x + b) - c + + @staticmethod + def noise_y(y, rng): + diff = max(y) - min(y) + noise_coeff = diff*(10/100) + return y + (noise_coeff * rng.normal(size=len(y))) + + @staticmethod + def plot_least_squares_curvefit(): + rng = np.random.default_rng() + bounds = [3, 6] + x1, x2, y1, y2, x_real, y_real = Approx.get_approx_data( + Approx.least_sq_exp, bounds) + x_real = np.array(x_real) + + y_real = Approx.noise_y(y_real, rng) + base_functions = [Approx.linear, + Approx.quadratic, (Approx.fract, "x/(ax+b)")] + + sp = create_subplot() + plt_append( + sp, x_real, y_real, f"y={Approx.least_sq_exp} на [{bounds[0]};{bounds[1]}], с шумом", ".b") + for bf in base_functions: + if isinstance(bf, tuple): + bf, desc = bf[0], bf[1] + else: + bf, desc = bf, None + optimal_params, _ = sopt.curve_fit(bf, x_real, y_real) + desc_str = f" ({desc}) " if desc is not None else "" + plt_append( + sp, x_real, bf(np.array(x_real), *optimal_params), + f"МНК, вид функции - {bf.__name__}{desc_str}", "-") + sp.set_title(f"scipy.optimize.curve_fit") + sp.legend(loc='lower left') + + @staticmethod + def plot_least_squares(): + rng = np.random.default_rng() + + def exponential(x, a, b, c): + return np.sin(x) * np.sqrt(np.abs(x)) + exponential.str = "np.sin(x) * np.sqrt(np.abs(x))" + + def gen_y(x, a, b, c, noise=0., n_outliers=0): + y = exponential(x, a, b, c) + error = noise * rng.standard_normal(x.size) + outliers = rng.integers(0, x.size, n_outliers) + error[outliers] *= 10 + return y + error + + def loss(params, x, y): + return (exponential(x, params[0], params[1], params[2])) - y + params0 = np.array([0.1, 1, 0]) + bounds = [-5, 3] + params_real = (3, 1, 5) + x_approx = np.array(generate_array(bounds[0], bounds[1], 4)) + y_approx = np.array(gen_y(x_approx, *params_real, + noise=0.3, n_outliers=4)) + + params_lsq = sopt.least_squares( + loss, params0, loss='linear', args=(x_approx, y_approx)).x + params_soft_l1 = sopt.least_squares( + loss, params0, loss='soft_l1', args=(x_approx, y_approx),f_scale=0.1).x + params_cauchy = sopt.least_squares( + loss, params0, loss='cauchy', args=(x_approx, y_approx), f_scale=2).x + + x_real = np.array(generate_array(bounds[0], bounds[1])) + y_real = np.array(gen_y(x_real, *params_real, 0, 0)) + + sp = create_subplot() + sp.plot(x_real, y_real, "-b", + label=f"y={exponential.str} на [{bounds[0]};{bounds[1]}]") + sp.plot(x_approx, y_approx, ".r", label=f"Табличные значения с шумом") + sp.plot(x_real, gen_y(x_real, *params_lsq), color="green", + label=f"loss=\"linear\"", linestyle=(0, (5, 10))) + sp.plot(x_real, gen_y(x_real, *params_soft_l1), color="magenta", + label=f"loss=\"soft_l1\"", linestyle=(5, (5, 10))) + sp.plot(x_real, gen_y(x_real, *params_cauchy), color="black", + label=f"loss=\"cauchy\"", linestyle=(7, (5, 10))) + + sp.set_title(f"scipy.optimize.least_squares") + sp.legend(loc='lower left') + + @staticmethod + def plot(method: str = "all"): + if method in ["lagrange", "all"]: + Approx.plot_lagrange() + if method in ["spline", "all"]: + Approx.plot_spline() + if method in ["least_squares_curvefit", "all"]: + Approx.plot_least_squares_curvefit() + if method in ["least_squares", "all"]: + Approx.plot_least_squares() + plt.ylabel("y") + plt.xlabel("x") + plt.show() + def main(): - # NonLinear.plot() + NonLinear.plot() SLE.print() + Approx.plot() if __name__ == "__main__": From 4ad96347fb2789da16610f8a8245abfe93f2ab2f Mon Sep 17 00:00:00 2001 From: AVAtarMod Date: Tue, 17 Oct 2023 20:58:52 +0300 Subject: [PATCH 29/41] assets: Add images --- assets/Akima1DInterpolator.png | Bin 0 -> 58940 bytes assets/CubicHermiteSpline.png | Bin 0 -> 55173 bytes assets/CubicSpline.png | Bin 0 -> 57771 bytes assets/Gauss.png | Bin 0 -> 49247 bytes assets/Inverted.png | Bin 0 -> 47054 bytes assets/PchipInterpolator.png | Bin 0 -> 57748 bytes assets/Thomas.png | Bin 0 -> 118626 bytes assets/bisect.png | Bin 0 -> 28482 bytes assets/curve_fit.png | Bin 0 -> 48317 bytes assets/lagrange.png | Bin 0 -> 50732 bytes assets/least_squares.png | Bin 0 -> 34187 bytes assets/newton.png | Bin 0 -> 31128 bytes 12 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/Akima1DInterpolator.png create mode 100644 assets/CubicHermiteSpline.png create mode 100644 assets/CubicSpline.png create mode 100644 assets/Gauss.png create mode 100644 assets/Inverted.png create mode 100644 assets/PchipInterpolator.png create mode 100644 assets/Thomas.png create mode 100644 assets/bisect.png create mode 100644 assets/curve_fit.png create mode 100644 assets/lagrange.png create mode 100644 assets/least_squares.png create mode 100644 assets/newton.png diff --git a/assets/Akima1DInterpolator.png b/assets/Akima1DInterpolator.png new file mode 100644 index 0000000000000000000000000000000000000000..c53ca94a06618418013d6ca1bfd35b785462683b GIT binary patch literal 58940 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

|X3R#)im|7^n$N8vRb`x}P;5vi3ZRdCjPUZ&n~ z$S!Q(lqPt3(TKUN-JfRQiOv-M{`m7Z=^0k8=ipMO5``omHbBW3F{*Svchj4|85iU$ zI$i8{WQLoDr+WNZ<|Y1Q`7$9v)$)H}@-voz5%<(XL(UQzde{5w8^IP2uAU9LlEw_h z^5w=i9gY>wTW^j}VG9(YtBz~MTq-ZG z{qbrWlu3HROq|o7wMB~#B-(9&LS(F(aI;itUJoQNyTo+o$^yn960VzYDcGel_2t=< zyS~Bh(B-4pZb8x(-Pu7V?+O3B-VIhZCm}JKM)p`o`z9rzk{#{BdZxG>e->-B z8D@4dt{cx`ot~{#jGUuOJc}Op7CDXO{)+Tq-rtms#Y3DCQg1)OnO%N-yAPqMFghF8 zaD~Kk;hMAY`eHphT^2a%%N*4w^yxjJKONx?hiN*Wll5p(i&OO&NbCetjX_S)+2my)@3NPh-1=#DRCPv8BQjQT57iR%( z=@?HD?tc}?CU9A0y%oOMdRz%A;ga1BANa4VX$2|rMP7q{;9akDtAunjn#rFJl%XF9 z(flP}9&&U+zq=o&Zd9gUr% z3eyoc(!PHU6U*R#?UbzdpGKW2U>fC4PD)L%^D{a4M(K}WO-XbI_n zVKLexj(&E@VM^rH7}Z96uZK2^IlE7OwH)pp@7YtF5xg|}Qn9}<1J^?j828oOaZsd#K+SIFNvU+}O#fU8u2cp8vyeGBP|&^G^@Ag1>-8lW7eR4@ z)OLJ7>Q`^0RQS~&1}G#O%z8ink7YaiVRc!}?xG^TN08dq;h5m=7x{`CkhMQh3FQ zD~4dT!iSMe7Nbf2*&&j5{$x$|0s$-D#mf+Jdzbjve>!-Jyt+AGfrw7hspIg@MTQyy zWBO-Pkgbz*TdB_cdJuM1=l5sN-t++r?knu4rt2Xtr>+g{jB)kIr=XFdA03r?@LJj3 zg#=+=pnrRQl?<%N9RnH93~2tL@FG1OG;krmJQD{iO~x^w!6q6JXecc7<@KEvgze`= z1Z+>%M^hX~O&MlS+vh?Ja429Kzlaydr*vuvY=n2e>`mOI4=1u9U0ss}h#K|U0tPhl zH(P{FaL*P8ARM=TexT)w;pU#MC*t0b$3)^uk} z`qpJM6S1~#mq`BWSQEFYIi2_4F-ilrmWOOd0kGVcDJo=b6N^P8YN?1Kr!#i;(VXyQ z7)3JScl~*hOyOtbD{k~Gg3tcQD+;Tk3S!qoo?EZ9cC4vk_(b88FM>{u(`cy0Ir_aC z)EK-LNGpleI?lzqu7|v|Gl}fxW*3%|fsE98-1-1njNjvhWHB%B zaBIEd4yZt>DelI`4skkWOl(H}t+L_3|E^94)-%e!f5*f}Vk0ag+USzjbKW6u^3C_E zw(vC^YBdIa92h#u{g&L1q}tp&PSa;|fSmoR{qD^#>#L>XVQ!fs)dYX)F9!1e)vZ}& zHk7mGxvXZ)`>85NwV$D0w7CH3x(VAXw_ARvTHn_$!yuFQFV1Ln*w2_K;@a3&`J7xW zA+i{?M^YZon~gTG`vv$ep;s7v_C+}E!jlu#M52y-QThp>CT%j;kB19Yy|h}P9I^Hn zBgw``5=<|6>C%-t)_bZ)awD6#dr*M zd|Z2!takG?zk_=arS+jwVG{IB0$O+W^26&_1a;V=TJb8ML~VZvTee0tFRs&yGb% zfdqJ_nC{zy#UMC;SyW3ZMroi9jwodNRc(mn?A@D_lV^XjPJY7z;`;W|zsjsnneJSN zguNJliRMbd{22}*CKHhLuiWGtctyATciw|y+myIo7#HmnG*De=ycE_2U-;>i~ zh(Z`Q6|SIAyMNPag0jE?nR8`*-=Q_YX#crM7;Zx%Pm)wzyIye(0VGM7$o#>CUp&sV zPA5ldw4U|)E_E(PPvrXEcD9M|nbrr;pxhtBRhsI(J&T%CJyn*0xZje6l%xJI0LY84 z_?4HHW7CmK^u&nz)csTk!bLr}(F^mPNF}iU->TrwG+H*O>>opf-#r$Y#oemg?-`$c z241S!v^plz#@_pK#XZNOcVy;Kqj}Mpo$L!Pxjlr@aE8HnHo}e0uP*Bc-4q-nBXD+# zLM6GT45UgDBY9|9WfHT>9xRG%Tah}l<01^2{L_}SWp}8gS+l%iQT9ru36s+?Zt9BV zj1*j{l?cou#*%37{MXfe)I|2rLw}^|vB_!i^5C56r0nVMhtMjUL?iigm=iy$Phfe) zp7of*br}tgvQv?F;wExpb2%c6OlA(UUra@|xE^_T1XR7QudOW*dt8+`jY~o2y;BX; zyn2URHuKpqTZyS&KujE2Ru#C_Am{p%A9h#I`V;g!TrDv=KJ$x&*K}XH{#m!4jhJDx zD-^`=``o@V=7t$cG$Y;1$f)#omoi_wItF1)E^VJPln(8!e$oCD4UnseV)8Er)X1y; zv*usd#V&h#ulaMp6+k1*zuCi5Ekk%z+20a>URX8f@jkBK#^oiRmarA*E%O|o+x4;1 zzqZhc=!+t6!|ZB;XQ{#it(Mnx)_tu0x#qxt5f}R*(~w6KPuso8G43?6Y$;-&n#6VQ?^Q&3Cd5L9%k^Amn1uNrh!|^a5KQc5GXS7C1gTkm?$b~t9x(W~BrPn(V zbr%bAS~%c*8+InQiB^0p$6q+4^(iD$!(67X*d88rm|uMfMCtB9e0<;xlG5T!v%u$y zE>~xAA0@-*CrL4a-LDl^V>?=Qt;*W=!^ERNUWL!i>3;go-Fs@qz~aOtrcU(X^U;gd zPM4cWhA+-J`Anubjc3DoXy}ODh|mCR>PmxoDrttZcuf~%1hgM|<`2*x*SBqZQ^(ze z1|$KPxIxk0??oNhOa&mpL*1<2h$^;?R8&#Rf{L2_VGE)wWxvXF+mn+17Clk;(ZqF! z`Ttn2>`|*0sOZA3>~iJhtqQ_NBiO-;8$xh)Uai>SE`lE&l2qf9>V_hb8)%Cirh3Yl zv;7Oqp0|&@uT$;O`86wHJ2=a6a}xgC!mahf;Nyt8mrQ^iI(_ty)8eZT%-O(b)gQ0V zth=+^na4#zE#qgCFe}FqN)o|pjVx40ZbjkhKUiVLBzkFP_JHB#Gh(z$^b0eCVj#8+ zTiQ5W%N_S8TCX=E$*3W8#kGX(CWA)?Cz~xvi0^f%C*8z!1onR`Va+~HVztKD0VEBr z#i&I@MEM=?IE~VSLf-!icr($++9;JqeLdL(+yHm9_yK=iuVBCOZm=1uGqG zW`+yg*nf084XW;l(=xSx>i@l9>023)s>f(w`j;y5YH+|Mfa1Axxmq_}*rr`!6a8(^ zF?@>q5AD0Ru&Pqi_1-aa<7jkWoY%Gxp6*Ef{M*9$eOw*Nr(Yt?+pQ*AO|v$mI`8wM zV!zFSUA^2D$t7gpCHjrI8A9Q;t?Hb8S7RG@I*}zyd4A|>xV2nu;Ue^H_BtU*%X@;k7nBshNi01adI+%=V$l`YPtD44&b2=ZFg8vEq%{7*d zF|Ucdfm0A>jwq~Cbt8{T!)vc%Y_5OXIkr5CqXP|9mfClPR+|&`m|T5*-pfttl7wKi zXR`a+$?5*b;Ysa}(F}jf6OFL+^^WVTF=NO*b<1BgQyd`t_j0S_CF{r+xiCUXV&v z7YzPFR^)06)DgY{$M`J6Pwfx;!a^5&G(UbYS^QZhN0w#apoAIh>q`gsVs7VSBEuZX zmXn(wARIVL-uJL?o^k?~9UIQD=f|!6wZ5^JViwCpD6|HVN=2M2Vj-teS|6QDbtWQI{4=kP9;}l8tAsJ_Y6b}4bDD)fbRN2Pgwvv`T;z&$f%{{9; z^TWm%v>~>LPb|n)GF=4;crY4pNPBO-922))FRQet8FA}lxLruhK%=B%-H(TLnak<} z*v#tIBKs(yJjJ#GLObGdFG|zWyaopcb+zi%YS1g_Q`i0EN*vvrGV{hQp=KN0M=-6I zheXMluQLAG%n}_CmW5kr)cn45CBP@!D8^JRv}|KlD^8z_H2m~^)UTVL)o?`vtu)mU z0T-H#YK^~C>UsLTYyXy4Bqz_Ybm>gBu}{afMfn0<1W98z5+W7P1jEnYl2HOC&v@Hu zx-WYYZatH^lm|~d^FMd}(WQJc=5t0o?VXGHJRM8N%&s5V#G06-!MQ;^_>P~C)T|zN zN|p~#)M^e(NpZh`_xp9o;5_y}6qSiW^A>yV9zGrffzD&3c_QP_nWO}3j!QR1?uS;6 zMxq@qxjCK>Z_>hkpd3-y_VPubIT}TsNOx(<4H?U=4%I-6pH@CO+&5}%2l|bMGw4B3 zQBY_&_QPEa4XMB!M|~KKgxOS(m!#)$;|R`jFTPSrY}E?^MMab_oCrrUY4LyPTTz}g zsrQ*TDp5t?_w2a2HD`=`s5Uy29tyJ#InP2z;A%l_WkrG@4TsijBG7wC9>*i4NDlqR2akAm z>~#2X@#&t#tZBM%#3`Vif|~~O<69q9Y%?9}g4%b1)qX2tXPsI%yyu#hqJK1wJXTQG z%qf4teA;~#*rnU1TekCKHxl>tzbXq>$lfnvyC;LReA+r!n?BXJua{fq5QSueLWOrI+R|V*0WG!fC-;0r)^)|Hjweofok07Ktu^o3dU1_=Rjyh9xdgMBU)^C2Dm>k)*J+Ls- z4&}-=jft=%-hkgTMrJ*FQGGk#?>l~+qj{P9nZ6#+S+8SrjxZB zC-oz+noR`rRqqnWUb}nvAR@*dQZGy(YuyH)XE5&n*mu5rZwP<&=g6tk#rSFZyj8Bl zhqE)3^LkP_txh3z@bJdLJddsLz0%=|{s;II_zlv&oYikIHvv_a`f8?qqB(67UXHd_ zZmM^yj!mQPdS?Vnc zG4f>6a}xrZt!C-%xWXHQl){lte-;}FThfHU*sN0N*`I;)*s#riFz3KS=rXP^i_N3k ze`QLBSKegE_=o7$UsCdR!e$}vIpd3i+b&K@81*t;c=ni5%Xwc6zFsgeSwd6F4BCv1 z$FMg`Rz0^5>TV48I&&sOY={X3ETWH`2^*6$_$(hHoXswaR>Bs`WF4|zimCVDqJNU?=6ozT73ColHshw?OO8p zpPDD722$2us7kk5wFqtxE8Fvt(^lNSyD@KccsISS1lxc(?xd;rGB)I?+36p$6O=bF zXe@U1@)Y){39tF?`M&UCf8=g820(xXZ?&vbA*L-Wi{xvzwwfxB&4yxy)5wb#jx9=w zv*nJAlZAPX-$1K>0IRhi%N2IEsX=Muyy*(GJ>xWl6&0D43?j}Au->1^mr|vgY|7K2 zcpTmzLwCYGMLgX~E}AwNt(tNjViud~^eqP8npwPlQ^4H2{0D8jF$$;~Yn0QgmBIVk z@w{ip*;x;HdnBNfFZ`5dLWb1ig+aZ-F26Vex9!bowyUzAcfaGPqAi5XIv&u>It(4U2GM1fo<$`aMfZ=Ibrf@3^x zYJGuBRccEnToSCN+ZLr3k@y!t!9rZy%lTk#>j#tV^_gEK%Ew=Ce@g zx3+{@ybS2*4t!Y2UYzxHqGt8o!qanVS$*_EDSz*$*vNfdY zyK*om;8H(LniK&U;YGA+c0RGypP!wxqBaqqkXgj}LuV?aog=Wlr1JfGo78xPWXjxd z#^>SFfx|AjxRAmZ&>zv?S2A8sQqN@Xsm_tj@wVOS%FP$MP2DAOzoN+tkq=Sr30)Qz zIzmsMf68BT^d&`*zcJrzP@PuzVB(qm`%;Y{Ph9Rjhl|pCJb1T-TdZ+*zldh?+L_v8 z(t_ruA_+B2Va&WzH%_~Qo!eFaTrvekgOyTu3b!P$whn=_mOpX%+?UCYt9d!CE2Iw4 zpQck@A|zv~ZJ-YEV02M;F;DH-TWxWDdUunf|1t&P553vXl?zIoXoemswx)!L!+{%e zCmP4(>u*=0UP84#g)iiM&c8lEO8Y&C$)`&AZftQlzTGkhp$)N@R!u0$;hFTMJ`Fui zKIQYxP-{p(jO<}s%L`;lK0*e)4TbK0%2~Pa&XZV*@6#6ry&?6LHY1fGx0R$qOyO#6 z8G9<W8d!cBdcRteI-!T*8tTqm2Cx*v& z#gJ+T@Oy)t{u)SkRYQ3E)XJ&oMH$Z@rmf%+y^)$I4&LAItVq(C$}mR_`;|x)k^_Ig za+LX>hZ~7_Kuu!zpayrj6`}gp7J%WH?eBme6$X|cFMLnt)PrNQOM8iipO%uiDu!fMh zzRZ?tI#4;_bf|V%65HZWi2poTdEEe?azWb*t%vUf^4~WXw+_SMddIOSby~|0zD943D zrsx?sUzauZCRO09cr5jH`L-C6WGwhjfdqz>6Zzs;T_jAv?h{)m-5q)Ik)w0mt;|%q zjoamqf0{ubXxDUuDls4AKIy=9(AvegHEj_0uXi3`Q@AI8Sx#Z`&$G~^lHm0B0z;K9 zsna>Pt9!Tbw0^_?R>F;*av$&;U#eFe67ZO6>d-e!CRWaoIW(}r)f$Zy1Q#6ZldD-S z3LPD(H>$Zz=qjRWC}Gu`=l72VvVEZ}-X6%sb3X1ha}lBNlkdZCwf*~;@#|?p$hLT2 z{dR-VOz(B#f|cPFTmwiS;dOr!=AkoN^X@ZPd=%=DEgv>s?F*iVjHx&5tEv`TiC&Ld zzvM%Z;A9J4&d+a85xI=!(3Ad6Nz(}V2Trl_#2UO|T<=ek`%^MBhC73aPbb1r${w}r}aRH0-Emo#0{PkZo7@A-oD>JP< z4}~~@#9W?;hzF!)sqGu>(#GK~n4!pIfBF(;D3@{3wf9>ekOiY;EYL!km@1W8a zZrj8~=n?{u#;4{9-Sfbcw6P>%Z9nV^Dal}7hz-{_pIhfj?2ggE1rh5 z#|_YwKPU%0FzoO$cT4jSBBx78UP~6&#XW=0Ctc^Y_mKHQ4`nlQ+zhOOdSq>3gytU$ zFgsU|{@AVe9XICt^}m|J54vJ4I~x4iinH2g(2`m%i#Wnv|8ies&v5T2o8~wi*Eq3X zb*JcTud*P{Eqburi2M~-QLwDLvkf97L=;HI*o$`Y=?Wt@qPhg%-iVjXS2|c$ZQ!1#l>h*N=7{2&W$50o>0w3yzDI~OdtP{^70!m2hy!=GS<6TKLf}A;> zUqI?r`JN2tW#7kXc1`K|@=q_15KNooLzMQ_)@F)zak+|i@-d2~7HX>{ki*tgkKQrg zu&6%ZNHmS?>!5u4T}2-9tB5V~QsmEaI?5U|z*#TVT&z)6FH?-@;w~x0wLa=9EqkCn z_LWejZL1rb<8wcg32Vhoy|1#l+*J(C7Vc$P3$tTJh?hJK;>DYvNJvz*I3rjtEygEk zTd%*87t6#*LMEhey|Uf!&NM%KqtP~tD>`sBC%rW1_BebnZYpAxmHv{BpC!>qvY5G; z=OO)aKM%vJd0+Yc-_ua}&qJpf z>}Q}QE$!t7t6SNMF687eDITh>Csi|*7_AKYrTE65YF=6DSZ*8*ZnnxJs=M&!fOZ#b zCZeV*d&PMj|9_MsuN+x>JbeDTMT;fG@hSWMPy0_p!f#LShtrAj7sgq@<{SIQxd&${tmC? zzi+n#8EA5aNh?;#Q>=gwB@x~m$`x5Mv6o%MiQJ~DXxerVh5ecp!9o9fF}W_@N1`({ z=>5eH;Y_wIXsjCO(2vH-C9z{jP=SdAivVVZ6hhpp-r&eg~nS%~7xe$szu_Oc9r#JGOLPkLykeUEZ&KCj)1!Zwf<)id9 ztQ*MP0IsRB)$y-xtDoG*Xn85yA zAlAu>+4_^iZB0irWz?@C_GeItY6%jyC6wLn;F6mtN#bfCjF(?(H9Bi)OU)Kf)II+g z+59%yVyQ-FAl3#wLpPX^Fo1sQr?MVnKEL|yl-23!@EqvOMMS>))z}!tMmY({lS+Uv+5F1CztS%@?Uw@vR5VZ% zGrdV@;~KcU78C+5VQROg{#$ z%_n8m#pwMa1Hfg<~)M8K^}Wm+n5JXa1m?E?A~ z9N98~L!_b%9e@A!2PF~uHJ{c#LiJOlSIDo?OOs8|O{uaM4fnK7>R2Cuk`j_jar2(M zEA&*!qt49RODJcALllY%Rb(;)E<;G!6)fbnah1hho0kkoYKUT(pF2nQ%_)IlsY4TtGiQ&IIzRXtZ?MBrTYOGFs z`1-;D{qIqaJ&c8n`#8I(!K-XcWeRsiE|Zq5^mv>MtuTg*AGX`=e^}>XN!L?HeA|4h z3av-UEc$9}sQyYLrC<5^5MRDr^vx_KLLUnZpAp+m+OlqqARot~q4fv**K5fqh9XkG zru3#oU?xclyko7h50g+YKB{S`h%TOw3bBJref?<7Av=3Ku?4H=xp})F3`3NbeFy*1|Tqw82(pjE2FtMDNA_ z!8XNmGh#AL?+@eS?&Y?&EP6?P9#hTp>l}BN#u8QI%#4PWTuxw7)^rrReDQ&;`abq&tX4*K2&otH!OMXt} zy`pxJYFqcH+K)8t+447G#0?uCg9X#ul+ljsrV-WhCDoXViADr)6hrd!iCn%D-r+rd z<2aJdDiO2pKr1!F8@z3Vl=L8)6e)qB0e3mtZJ{F+^6M~dg~a|8L04;)0a<11=bIT# z^Qq|1tX9+2sIqN}eJ^Ab+z{X@_p#esDyP`!R2A~qLsNtaMl3=(XC*FU&Cz+j!@SPH zQxuxFGSPpG3Tj&t__b}OetA-5DwyNh-4;fSi3JO7f%?!SL(dDf<w)V z`X^W0F!6f2xmO@$*R5Pc33FVCl$}#iU`v%I*A5{|I022?(#f9hZ|Oc=XT#9U|2wB{ z=3mRl^ryj9R17hE{K@}1jIzrXhbuVkVW@Arn+%P!)s_kGl=SSo_Ginf{4ovIKb z0^7j*%+10rO??Wq7Pqj6pIq1ZZw_1@KQz6=`j;;XOG8_jX!X<6xAH~AiYKGaLjfx4 z0l~Llc$G@Aycmb%j#kAVgm5wx^Rm&{iuUQ${7aIpErEAMNKH^_t>;Qr zSp@91md9HO?lFA)@icJ%Qp8%Lq8Xa3!(j7EQaB5i17-g632V%%dQ@~xBZv(g|MhTp z$bL9565SzwhiDAF^M*?o!N3z%Wy|RKq8C&i0h+fRR?#iO1Z^?0V(M$CtwLuxIrHJ82)|!!|1YsSey58pZo7|WXSds#;YFZ z4psL_Tibt*X!F?!d=Ni9YG4ShJcw_Uob=H8FlKM}Pu9j^bBAb*v);}-5b2v}-GL?| zc|W!&LWx;vKGLiKbD1+w8X1cUWUP_VLvM1Z}@4{-lj-$DZUj8JOFB{C`GHtSYa* z|JoMgKbz6_`6<|Qqdr#u*7+au&SW1h`JS*W&YqhJVpm4QqY+o%S^o@}NTElB$k;x~ znFkDfUlJl06MZ%nt1*N-v#Lz`Cz{z>XUx>a8EC;(-cL8D-_d-FKH+V*|F;q(Qk8eT zvJq}YxFSb-)pDm$T}j}lJS=KFkF)I5;V-+_^KROgV8l4+#5)H)y5RdQF-d+0!5JF< zkJ2aHJY*VLFo^ioKZj=YGR&H76y>X;&dI+Ie9EBkeC}h$8tRR7xb`d0MCE=0{aY+P zNeDy91%_9i{Kuzie7e{1ylMpj3Zhf^8gSx&{qJkt!ld}}9XY-Z8gfj^nt5L&-rX2T z^I>Q3L;&DGF>-_}Xi@*p&h*?`0oW)*hmsc}Cwlv~?>jk)?8dA}kM#z}F`%jfab9x7 zXSe&M8%s|zsUE5eOh4X!GTmsWxiC6smb2BBgoH_O2zj{Od?)|gSKF+=CV6r2piifo z1!_cfHgKS<(PHBJO7~9XR~LAM0YZT?5{7> zae_0{PvbQjR(~m<=3Q<)$()|0n<;}UJ>>vZ2ES$OU@cdEqB?YRK35s2b?~RU;lvd< zC<56UYr@O7v?R~FX|r`aQPoiJlf9JJWV^qZQA}n$4fl^c#Ylu1>YT=gdNXU)M^3iQnvjXoFF}eG#ZpD{#-Par0dHUy9NIrQ(pm8)fer3 z>2B!~kuCx0?vhUFPHE{Bq)Q~F1?f&{q*FqqrMtW9+xP#zH}htkVPuAT?m1`gwbowi z7k@4>i^DHr%1L>>AISzrr@@wDjRRZi579#v%=LofR2H-DN6l@HRP?|1breJ^Jsxzj z@711?!L{N_N_sj7u|#{;afsffJmEj|Q?4|f_=K`+E&xn3KwxTyE2B}Ci2PVQf5N#} zJ=`=KSyW`xm&wp07bNGK4MZSlq~??9__n_Y>GB3ZBr8nAqnZoh@l zGZtKkOb*V)2X_vuB#!GxFJc~JE%z4+mf3r*BJ526?o~}zuCDAiz2N-X#+Tm1hI@&L z6!=9YB-ILxP}eh#KgLRj4+Erak;YThPV{f%(8qg|P9OuYuh^kMI{U2(VCXiKaLGF;xw^G*mJdL4$b3R2E2oVzsLf$2Wb+4Nb+%$iPFVa~P z8P|P=Hj!&%w|hOZ*x|05xpwp$k@1~KFCd@8T7(B&MprCzruFI}y!^Y?O$=z~wNiEn zBdN5DzjD{0JBV>&!l|k~8A9@Wi&m2{X{vqyI6r@r7c=+zowCyqpVX9zVlBZ`qM%U4 zMbO&%YP?KjBpOrTZr*kMVbF>|P<_oCFHFp~kFv*3kQ2(>7`QUO4XlF*?FL*hOjQH{ zXZLMKDW*?x@PtE%#c+m;l0s@oTBRL=4OW|Hc$axG2E9C+v-GQ2eI!H0){~C8=QRPx zf5@0Jr}m&DR-p-9!nAhff&CqVO4GEDVHS0iD(Q-S)2UUp$G@{LFlEBkpzDgi%{y}! zvEg^e-Q2H2O-eQLN=rj9_apPJ_N@~PKN~Y~eSz;EONQ@t+BN1bV8hno`sAL6V>{!n z`WZj&xW4`K5+SN}@BF1<&BSjEO#M7JI3^J=%`PH>lA@J{ldHI86(ENl%)%X9o2bO^ z5cl2{!-=jHMwmMcd>rzU+Dv|M9D>V5AhWANWx|bpoKh0pqmt4UV^L^ft#MzoLyZvfkm4J*VdSlCQb%m81=HG3WEp zHmy#z)L!+VY#NVhfmjHd{x#E<@fQ|f)yG&EQw|R2%pa0qq*-8EbDp2NC!WP1ORFfl z%I$>DeNeJi-GhN7r~-{K*%N;9#Pty05rlD*P6Dwu|hLGBu~aWrYk2R zcBpaOriJ@U-M^0}Ee&wqv7@_^;vy=FufxQo_{&8=#R@HYycWG}4OTAqS8tpPv-n*k zUuRtkRcF=VUdv}dZv)A$IceRZs?0{??)s>DXrS+c@X;Ir-g zKnCO$e5$Ib-f%k%CWi}UNgtdgDYRo2*9YoRxqnbGL(P8I!;iN<@X&wsKi$%GoDpYd z_`#|~<7QJbImX6VP`5leu#g@$F(X@#RFc`zeY6PVo#xm3DZ`!~ZDz5>*$io<^#gP1 z|E3k;hbg6Ii5_L?FPfef;dIfCkwRwAw*(j0u-DX}n@Km*N6$4w<=Cxk7|HjMR}kfib3EN%j19nsFegtT2ksZALf`AHM(ko8cFQ}vvvz;CGOVrP_{6i~=7Y_Fu^l|yOyY-U#nR>QW-ECZm z8tzJ|l?v$M`jOC=g0?gBO?>2r_At23$G|1#glbjyeX2QAn9-yMcJp|g(6Z8+l`hlC z?spie(X@-xTdwRX=0_3|?$1`)Ec^6KKWCk*iT+`jmkqtN`0lUJL+)7U2G_#!)xlU* z7KT;!uFRQ=@z-01^t1m#u-V4Zj3c%znN z{Hc35%zt;hSmh*vxxX%ULBCVIDk+hsOT0&xmoi=vD1y*`nr;Pcm12ym$U#@we=>FRM>Z*k4m#^=a+0(=BMYk=0~~`ihu4{al69M zGX+g$7(#WyyT?u%e z45g5iI$d%TaT1(*7e#QgS9;f{_w5_p$Y0NP#GIJn`t1d`2bN z)(l$2%%+@a{-oaSV(;g$7RjY1{O5RzVU%=&VM?)7!GdcKkW9g3lk zqK}{-f1N{2Cxlu?JQ$H5K{td*C|aua9IL}wCwuOeMQ<&<45q2+L;6NDR+hLv3Me#_ zjH^ZGFG1w#KkL<<$tbPBH=#Nkloe%?TCI|I-WfW&;pwloS~xA*{sac}KsbcL7aOv$ zuKdRG8P42lpVUlKqdb5MOy*4$>YYEXP1InCs_GDauZPXbV6FWmbqieq9DA6y%82cr z+gX7ar25|FBxBRR$V_P+jVK}}^Lam76qMLbY{Z?&bgTaPljGqyFeuh`Ye^xWIdF42 z-X<^*9#~`XhPdcfS`o6g998)8d(Mbi(N!upW|h*hCXaqIkU%&j-iJ;aeY9w$Q?Sl z4hzNrORgc?ARPj$5zxp_;U-j-Rp+u%PR-hpHQv8F-7)SX&fXz}n9PcRg_SIL$En;w zjQ&f85@0aArQf?TM2i3ZS*P`@XAF06QLm4SKSmmpYZpPZGXufG$DWE+9Ave8_{C*% zB-E)`djOEv_ht#6Q@IGEd&=}gjZN~qPH-yk+7MTIaDojasn_c6iGUpVJ)zx2+han} zxnHQfrRX6K3-@**O@~f9y)~5+#IqfAkQhwXmpn3kFyUun&?|gw5=nH^M+>~Pnjy5p zI3kaB6fh#Y#a!#_ZF=y|{Xp~koj8jIij5Qc*i0DQ7vT-Q`2bHI1cdQYZB-@uj|BJ9 z%=Ef3qa&9Ur3kuf^^`}F?Q2TWB>j8J9?84)A&uD3WrmB7P%mwp-S=C;-}(0Ph1Bc2u|F=xy>*ErYW^VxxShr0iPxV+9!J3P| z6cj>%pz)h*6Qk6-5CzAcS=ZkIDm8k_j5LTpz$f+q4$aY#QaZOikSNRKRA&3E{uzC` zZ~f%{_{;v?sVKmjAn%dGWAa`EG2>{z|MB=u`ss<8gt*^CJ|Ya{+p7<{b=)=XEFf@u z`s|bE^XiS<5WV0D0f!Sgy=1L|8BEGtzmrxPXxl@WmjwnYHsl3l%ZNEG(ZNM?0ZbBT znIr)f65f4WabB7AK5TM~+gqK0$F_H@@6ThP`NeCnMpB<9{zMM8{suna-m;9q5xO+s z;UOw{h?0^!q}jaLGSM=s-;cnZ=JF3!;J;bGH1!glL#(Ycgy*YtkFUhAo0M>A#bz5Hjyw?AWdvy<)oB;AxD9 zZ9KH9h2eu5@x%cJ6k{N51?07Cr%Lb_41sRbS9$9_R(krEoSe0@DcBapx9dD#Ke0XC z_A}_zx4)~fiKxQn+xv^_44&M4K7Lg$pDz(bGgthckw_2>)AM)?)`KAsXfQs|c{5Pg zj0z2PBU2+Almm39li&;VuR9zDZ)4mJ>$JS89I0TEl>2z=#`S+#*%!zX8YblxuIp@Z z4iK_$YVN@`d0A0iFN|M#Sh30(4f5th_AS?biC6GmAC#*J-x@%wjZl6ANI2dyw5(x+ z3z8$LM3A+$2U_osBV6!*xN!zD zhCnswb~~GnnmTW1e@B&^V3$BMEmw3NW4_Qi&cY%ElJ@vpvV#ai^j|9P!R;R`jit;* z&F$+mQ$-voUz7hphqKg8KtP-w1=hHpVxpiji&`VJ#6VwjHtvswu`?EJJ`O| ze-Ek1_zG!mYMLm~WCHRvBXlHT-JL*;q|*(EsVzMbL?IX%Et?vNC)THRH=A|Kn%`l5 zxF4zzRJf^fVpb(UjV1VUqALE)KwH0EA}q&3L8p}{l^df#Q9^1}#PF5p#ie%`T}4pm zhw(@WIOs59^|Xtl$)P829%&474sEWc_@Ivx)tR->4Mya4Bf%HQ_du0;ph}AU6h6^F zuMrWGYzMl&HS8TRHjK_OAY{JFm(UWXnKXi!5boD152o2Lefkldv)9utQbij9AV@h< z7=!=~8wi={8{5Jo&*}Jrj!4!Yj>y6@H!B0q*w?oD-}EQj?2ud(g+C_@%*XL018M|) z#3b&omgDwxpb2?8#8f%n>gC$-VGa%>$hqzba(oT6GTI?C`W+~8IYqgKAf+wvzhGsR!G>j%O8+K6G&mF zEiOHWZ><#_sYON-9>9|o)Dy(f($Ltr-oM(g@RAdczkbZ>`@$iSGDSbh;+JRCtUi_z zah1xKZQU=FaGlg4pB7;Oax~14G5!0?&fq$SmG)!=3(XMu@N?M-Sk?NeVmJ(vz~6`p z$%(u9!QKd~K5cBVf=FTgl&q6>QC~MV#j=HUn^O4eHT@%eU#u5kU=t0r@8LPGh+T%2 zn7tXU93A?xmChyhDPcMLwQJk{@K>)zHzp!H{REF}f+Zd!f5PcDJCpYptru(`l&~zG^;J zMCT!|?kTE<4*K0&AEQ6S9ASu_T{Kw)JknYgI;ie&SA@C z#!w*~7ZkJb#z!6kkZQdE>H3t%y4KWFb}+))4Jb%xU@jl2G}4?q1R^eg))psJ(j|e_ znal#FJFeS7VMr*HcD>ErVW0DeUiiTguv%R4;GMgmL%;z2Gs{8<7e79Nopw2X)m>=F zhCFo+z1dd?DomaKl_kds&EKqnPjhy|4|f#o6q4vskd>94;37;L1ZK1Z+RmY*l}!Y^ zQpHfmjww{hryJ_OMPk!^%r(tyW$f*yvlM?0hp>e&rBN<%E_#JVqyn`#DS>sII`cQLq zQz(;y^Kj)lp8yJTQb+igt9a#?Y#=r`P&x2RG7P14{sspDgjgm1y+0(GL%(@ct=3|S%!)_Aze86e3Z?{D)WQJR?Gv$+eC!%0cEo6)>Hzj*y-f#@W&^w5@y zM{+>%+J$xA-$^l}1Wfplj9Y}W`>184)(EzwpE`YU3+SMC1C(m2tg!x8WOXCS+~@qr zw?TVW%0!Li9%b7teGU4|f}~z+GfT?b;ZpC_UWDi-*us3ddH-r|q1M2?Jn-2@2#aT% z#dKQ<;B!F$01C|vhEuKV9}jbdzLhHTR80)aUZh$>Fylm`SrpRXGg#Ph+HVm>Ei@O! zQlAzur%G&|w~usVM8J@)Ie?lXN>tVSOh$8kc`18EAh;6-Da*yc+1iW^bdQwYOVlDW zAkz=U0@><}Lrie8dYTjz0(-hh7+O6GLaXPGHOzRMAErvXW64jB5Ali{&cZkogkQai zypjWrv!j3}qP7?fpPaPV2~?q}G##{FMJoIEBmk{y;2K{i+9h}jaUX5_+$}5mjb>4YYKI}Jfn`rz;q096#ieUGuQ(Z#{tZQci8u~Bf zV&pKq7o(ujfxJr^G{#kk_&qLak|lP?e1=u0oebb7kJ_};ch1KpPImHouZBg^l+S$2 znOIG&B~h$!HRZr?1i&db4hNY^PoyFlMwTc5Wvx+Fr&6sK{1@3x z&3+`>;3YcR1&`wu(0yr$$~Lo_lZG0lGZOH`3(!q!OHRk zWu>&?9X#_8MnZPPtad%YZDX~wj=I5@90MN~ucgkLSx#QTd&=!>qyM5p58qzg5u9jL zE7!sMisg6u&zMi}iv*zabkilaxK@NN{P;qvQtS55+ti#Ma%=)bK))qWsD30vz{C|% zgnwz;SoegYIJ@$umBhzHp%#Znc=VR=4as*s4~XsId&!^|5Nr!k=q;@-ybKb0vWLv_ z>{=O^72jGaz7ak9XU;KgzC)rQm6VDMP|eDm5d!u!%6Ghn8*y~iSygKUtI{gRFUfQ9 zH{(0abEX9fbvH!hUs`#@PFAi?Xp7rLUQIQ@vfeO|UN2Q9au_%MK& za~%svna21x)esIE^WL^pqhNZ1O{lW|%?EN~c#}CAFB)TIXN<}b-y)FGGj?GgUq+7} zIpiUXHJMS)D=uCyM_H(K$D6{{I}|ZT2l@6u#maiuYt0-_aE7(%dSSm!V5{vQP|uuU zf?y&%N@+#kGY5^9;s97|$D&({xVH8Yqj$sVmF2tt+Gi%ii8O1hQnxF9rdngtTqNux zw=l1$*%}?!wj+4Y$f2;tYyM@9Xf=(u(rnaVD1avp9oQM_XPql8<-mYr-xmE!^FNUc z55xc2_k_3Z48co??&+O zm?ZO*9SbDR^;d^hV~6A7Ct2rI4(k3iUw{!!U&3_~I;j`r-(pbrd}`LW(%g;t=0MnK`2l{hc19D=l86Q+1NDfx8I5$v#y@_ANouR%dt(5{46!UAfRQq z$p6oBO~JmG1P6w^Pt8$Dl!xO2WDrxMnMesM(D(E*tXW#!aaDfUa8-iE<~!@+{2bDl`o*6L-W^MlMPp9DoUNoAg>t~54L@*Sr+X-SV_UB= z6eKJCpta-vPu}xv8wX<-l_jc$CY_rT;Mg z0*(>JF2(6=V=7xM+!gd`B^hpnbKT&UznqY}>)r0S#CWr!{@il_$5pnp0Tr4Uk`SAI zEhX%(QjCtgV|DiN>UF98r#aWd*Y6Y{FI^Jro+3)uan{8^?@&~)w&lcgRWp0?@M;0l zm?Xt)=sQ)2whenzA@PQ$O9*o863{%uljgZNA zFClx!lU!x~{e?f_D5z_(^H(=R{#+|T^#pfFhu=d7TiM*gv$cn0WCp!MU1a?5al$1+ z{<}0J(L3+bxl@GSxfMyV6aa>RYP8zTdwrqcLD9usw@wIMyO7w>*Thl*lXUJ`xFF=0 zncpO)dFf=Sply2Wd@gHjNbKD5SI?K)+s1DJZfX?C4Z{00<;*do6<_m)oAh6Rh`B|4 zfT9l(XTjgF0_JyA9v-aHM$`b$uTS)&$ktcy9{04dA^BoS=E`)qF)~>o563L-2%_VE z-%GrTq6#(MNSu!0`AUEtMa7gL5;g{S%dY@R5SLq2!Td_7eT=Ubd$*HRg!^r$__Atyl)`&KkH*fAK+cHIguv9R!JQ?V6zNx9Y@BRekJIC2 zzQ9{E%}}kIxm?*@!c|;aN4!g`X(WWNmbCG$D~v;pA9sUnIXWv0E*w1|47Us!02e~V zDp7ulwI=$#&5bW0;Jts}zxQ3tk{x6izd9$7sx7qsh^^1f>CRLxj>3-lr1n00K6xRd zFf6Wu{L+Q^*`$~?Bs;|JW_iU(qrhg2+K2vKd)NEUrzhU|#T+61w(#HSnfTwap|#Tc zk$T%I9$h^W*4s=rx7dQlf%tgOzo);v|H=PrP|3@#VrazS@Is%W z4X#_fL|~vh;Foa??feY@to0VffPg{gqt`Hs!#6i3(j84)6xr3*P&xQ3TiXZ$EA9`u zzj^NT9U+*Tjm>9OM5!bH+Hv>+U?snh>9#XAdamgz4kUltpcu<&o%FXdattaYf*yKE zZe@WJF)!dpKqiid&$_l5&LaI-wi#MvDwjXMO1 zf_`z;E9{K-H1i6nvCoxDA3qW;?Vx(^D+;dMu-=ii_1)vCpG&_P*jh|ZJA zB>PcR1HC_&rWV(oyJwh{+sAEwBv(*B%=GQpzjk#ia81r<#0td>!K2GjrG9F26QrO3 zDp!dmJDk5J24y-i0VnXphGDopq6Y8L^zBl49qh#O;yoqi!Y12-^`2h=sSYhXniUwT zhcl^|MT7c)!H(ib!5*2S^HKa!PjnPaom)_)C&b(9^zr(QRc!L1-z^PWbgk3P8=#5Z z+>NIE;_F)ph0k`;$v2oRH-+&pSn7eJ1GVy=B|{#p zy$aD^zgcqLb0!AC-dyRmb@M6rgd?}6d7FTV!pYnjr`~N14UG+uSOsc1<+?EW*_UY8 z&tihHzDUpxPC!D(xf#eYf_6S?$F<&|Dd+S#(TL48>C4q&wa-27ketJX4%?>jXLabw zCQ0Q5QPC8xznFSX0Cst7WQBkj9N*4pupC(Nxx{w;Nljffoo72@gYfqLcvwKn z#D%JVNdyrJbj+czIY=LuvF=dm3JZm++W%y*LHV2QvD3kUsM!O-_~UxWz=95MFf?Hl zBT@X?+WJ)E#uO0ysBmmOx(yGE=BNT^4#?txA5fQsZl59AK+q&Ts-6 zYaxwlpFVxC`9ldg??2urdpBs?{vOY@>Kg#sOG$lU`urIlxQ;;Xnaqgqobg1N;E42x z1S?KFsrsS9we0I=KQ5JbMvd9WcfzEJ{OaX&!wh*f?t)u9X1pmH94r=c^Ja&Y5g1j) zKt}4(49_T{VL1F6ur>R`kxJNbJ3NvTDXsYNyB6sQ$QFZ3hA_5pB_-TPY(nI^KeuWF zt>)VF6dt7GSRZX-TixJgD&W9K8hH zA*tRAue2)NH9zN;fYDxfDYm<-M64%IPq=&!=e5|!w0MFOkcG!+=KV(9x%s z$|7{BZ+ELmsUnmkmM+2%8QOj65YJ8glXIp;2GH@Az2SukoIS1nb4Lf%w*(}LPII}b z{f?=9Y*a2+>Y+oU7T4N(#L%4ER#W$PMSXf1 zmQRtnD%b1HU?geoII^|#!$n{2YD11lSl{PiE$ZDvJqn$Y=;6GSTaLo!32B(~hQNva zwt{F1WTuA^JV0_vg@HvXaHh!gICyR4F`AFX&xLtn`D8xV`r#XfzF0n1YV_v3_l@8b z>2Up}EajW`DZJ24Z+oy1=5pj{(n5Z z5@=sz_-4r^)cwYz4NTYDRf-RIo!z5c?@&LhoqwsEs0`BK)5zV8;c*npV&4_T0)wDe zL|SGH$!d6UgaE={{;EVFpCMYURl)yY@tXh^FT{?D`OxmGm69w9U2Y_9dx5JNl+<~u zb~TJvB|<7Ozm7f?g)sUc9*Whc)NTbMpw*>CBEtDT@qLeX@I6Dh*hlhMdVe=b=Klzg z(~)432!tz0YwgN}xng;V-vIz!d5sZikm7lD8snr{s26HxmN)MSTz@25me!hBA z!caxl1_)BA^=wizaBRG@E1AYk_4xF<%0S@Y=@Wc>q{(j)G7-au>PV&p!j%3k_jJDM zu9(N6@!Z!cy`p`=c+MRK2R<=ieRL7OqBdEREHAbl#GN(pPx!SLTne3J)~i^|w6#=i z@9C=?jERhmWMr`i8)4tuyMX_!Ieq&7GY(VBG#m zE~w6%iTxVDtw#}iSA=Y)z@@1AV4BS!T-W8 z{R%2UP1NY;v#Y#b#WyP=-x3#Yg5$|`;v!Zh-k2PK6p+Sh6WKW|I)7~Q;kb$1M+rr4 zrPyJmNb#JVcz907P_;*OXXO_s((IC%K*I~trSevr`tSIcLfq?0Bwjq+llhy+Z^7bq z%FU0dQ+AnP>gdT*@xQnH7qn=7q6w$+(%JhJ-FDVrx-aZzD=T8<2yR{Q!jFl$xnn^5 z|8DZNlEP~+;wgBE$8Kh(Pf&qfu@w+3@;jNk8`NTtV;>uU3(V8O3abH7H1T(LKl)3j z;;af1{H$;HR&I?RPMpm6S$1-xOtZTEG9!dU=#SDEHjq*8`qtb&pM}xKt@9W5DkdY> zgO?EBNdcldCJj@y{aq;KCvO|)hgryRYo5^42Hqd?_GscfA#xtfherhu61$QliArk) z+uqo?UNU2M|1$AJst$Mmw+8bs@+#w_J2+=&ZK=QQbB-OX9Aaeh!<-CvqurbM#o(ya}y%`P;&(R^PA_4G# zFS83mh}mgVACg{Li~-gGW6Pg6T1@B^W%#4**M>V2!81lP@(^xoNSvVBoqBIfOn0(v zcWE`@6lL)dU>?&;_|M!9NozL9&|bSsKKXO11r@gp!qG6)(D zjJ6BZS5FbU`&ORkUnWm~zCdA^x4Iq)=v@7UgNKHL!!OMQ%6uh{^LxKp!1}be>1jJ_ zT3Xdlgu;Li5J8L3yleyzky5I8AE!L;?GM$RRIpp0g^@6SAyHbhDQ2P5s_!Z#oEu^G z7f`_ET=~?qr*3Gc6$t`x&V;Q26W|r)5tJPGnX|P}&X*@e@YOTQl1^72ZNrmo`iArm zz0=f;9TOgH4&9f{N{=>G3#Y(SJWu-V9Our~a2%^#ceI@8f7kHz3B)O01D((DQ!_SZ zFhYfGXboourNgxibZ#Q$Xusl7WOz>>QDzLC#wdtH41FG!e6Vr5Dm%mhiKVVs@8_RP zX}6*SA79E@Du5%0`6)YLrFxtC@b2whF-|p}X(vRE^Q+FXlMtO2v9taHV&J}-zsaF) zCR};$S=>hL`W5-X-7+iC$!JaYnRM|}FzxR%eM6{RO@@iQ_>2Xi$XwXRabO4Uc@?%? z*z*hCXY4f6{?j=BzXBNwCGN`gxu=0GNJs7lexhL#>6dyc5QCPk=O-lV;pYn7{LSEe zmb(lv#0KL65V=(Bz9#zNB&C?Dgj0|!PSPkkqj^w2s|Y_|-pBR)n1)=eVm&>*jCr`> zpy<~NnL9t|iHS2acy#+QqAbSF)YD2LG3>?yWJuK4*q`m|{|w!+K_;$&4GDIVTHZ9p z!-O#}1cq~|;8oeYA(*1WyQB5QZyU=TlcyM_T*&+0F+4o-0R753o8Fo>_rto=fc*Ft zkrXf(8ej7-r3F<+24yMs<~!qU(fI>=3DC|4(JhwTM)B3pwNC7cM+c^7>1TztXvwzj zvcDRdn;WeOWsZ$mjqJ|6XJj{vJ2HR_8t%og)vDD|`*?k*)jB{kyb;&*zMCVu8K{|} zuD-f1BSWR2XtZC3A5PgPAEjZC4neFXT~S>K4x&alXtkrmEX6}2AQwOcN=rgO&o(^P za#cJ$s-<-jh+PQ5U9Swdfl^ikr~86jmPPVChc|HqIjI8R9-r(jqzMTkWavizf$^bO zl9--$sp(RtNvP%zF8}$#Vw+rGFmAXX*XH}G2Zl|1F5i-3E1Q?^m74v-@@d*kc1LI{ z=0%)nxV$_HN~l*3$IW2=9R64|YTc%xNyM+g^l7{R1D#Y_ z4Hqi1zW3A1bypex*-#LOaJ;!AD!%p-dLhAZA01L8$&5qL6(;Spln~A#gl9xVKk2cfLcx z@iZvvmiYXgep7BM^qu>^reG*NvzZq^8k?Ipn%ieV<-9(Ps4Y5QcxIHGafZN_B@oAB zSL6u`nq2eCRJp!w`?En#c-{uqk<4m2Wv{raQP7#orcTCoHmXKdyTPq033ZNk2 z*;)>O@^=)~3lb}MdQ9A^?mHKY5Ewr#!W9ea*gi#+{1*C?={>mzTbLZhS@ie5jH3K! z1MS`}J;;t#`E#-v$pc$g>9;&vMpq(D+0g-<8Iv_eD8=sf(aT$;Jhi)Nim%5M5fMQ9 zB*xCeNC8M$WNXjoza{Y|%jK8jeP{V@#$Y{YfGs;WIoLxC2q*db`{~LtVM$3LKoi3IX$|&q4m8WjDFV}OX|L*Ik*4J%5huAD= zw#d()y^RG_MF6RstsO06`cP~_wK0nQUmtU+5^z+Q-?450fEnM_D*G9hZEZ1rg7}Wm zcPReO5Tnw?@3gWVDiGRW50szjBz?W)hiakkq8Am-ofnykmT zW2Br36E}x7^o7-&zNH= zg>;R#fZctOvsNL_&?#<*S?y1Y+NMQIz>4ca3{eF~`TR6zI!VnuOww46 zOKQtE$l1qMT^lCQIw4%GhwhiBiee^RE8B6o>mI`f&j;fpnn}CQBJwHv@4I9NA>&u8l#H z;BzcN?81P(nVwm{=+c&uCr@R$hir;eedz&dQ{3mcI)vTsun;59$3@`6;^$NTF#IQ9 zm*?dD!Ic7C073-92iw|+pgaV?pi%~DkiAheD;dbG+TOY}-dd_5oBv&u^L<=Qa$Dc( z-v(p$n7ZXZvIfbzt!~&VBd9>I=)`6-&#(Tfv$R^f>2^*-GpXydIGBii0Tgk-Fx@y} z&ib1sF#>;y`U*-dGk`N#>f&qY_3d-mm9VuIf{iqluu|Qb?@_?hj=4rOZ0uA`= z{U|Inax)g!;N~X%P&6IfP#je)!i&5kCsgU}PP|%GXp&xa2+KiM>&-J8eTWpw%rF5k6HC09U?@+GSWUhB| z-af2mO1gCa{#6_$mPpV5878Rs(US*1_@R>O^n@xF`Mwh%kg_lYPd}_gXv49Aa&X14 zo7}Z%j;8zyNLwjZe+_t(-TLTd?l{PLSCdtj^|J%8;bEH*v0Z$Sf$W27w8dn?mu#%b zwrumyj|OVTB;qSr*@=U0W14DNnnOnYgxWef0|p+8y#Tk=A5C0wpJ(}Vp;~wBISxNJ z*SdYj;_P@1rBusQ$O-a_afy+DN= z_;h+#1!?gL+Wl5)m3Oo5shPAs^8Z|YtYJVzI4)T7Ri`+Yeq4)AGTS~f>pn8`z8B_Q8&+M$>^Y4LM9hdl6iy>7 zQnzT0{^JV0)5?IWv0}BUVuPtdwTWWG*MPU#55c#+mRy8pfA_xK0oAOIJQ07MyHx_; zq`4D(U7)}=rwxleUKq^Et{<-65@WXY^NpF6Y>3$Knm$P@(uE6${&9f? zh9%AOyBln8-@Z^*RwjmLWqix^_5~2K*Z4)EM$U6Q%*Lo|$ZN|_FU$NZeiNec?+}G3 zIJk3$P)6tV6)chr9tJ!T>B)I;QPJ12yb?x$k}KoWLWT|g33E+<&9PsAllvAJDFW-P z*R-^1z@6>v`sYqTdet4g3d=scvVJY9lV4OqE`8&SQ$G+G1lu#JZL5l(o>xn>GaeSe zY|FhEv_Mh*ftZ^t1oLkYCyRfiYUhf(R%uQPjO!O-*Q#k-YfNslVu#MQWPv00 zBcjD&nOqO5T{LZz9x==M`J27O2ZL}{6yPEbgMfe_HO9%w$<|o8=)flb6vXIc2gF8; z2|Qe43s&tF`u&-SuPyQIIAqz6R#w+PvHT9DJ|ZHJp~8&bSST@DE_EA|G~NEuDH%h= zGqR1ou71b}bTC5t(Nxdy|2cKDj9~mk{&{xYuZjHf^=sg?3jcwdbjx*l+?z9|B;rI>ZM7_cm|HCU6 ze%i}Xf%<@e2i0^3wvT4q63^BwFc*7#n-es)jCA*~9>d=-p%=l39&ZkCN^?qzsyK5aDxP({j`=qf*?T5)AnaBxz|8e3<_s zbfoeL_t%#%z+6G2Y}~ZkxO#v;E7#Vw5PD6cq zK#E$#tga@cfRs}gj#!AeY#{43d{`Zbr=C4@hhiFmclE$%#jJszc;nZ>?igPqd%60l zsDK~-kBFN;^>)QwT&Yg~mMxQT{rb?iV)+Z#el=#-ar~&=J+R@V)kDFtRnN<=*G%tj z<^3|A>#tj`uB(GN2^}3GU>}^13N^h2)|zYU>zUqmNmBCisKD*1r?+>P92E``J!edl zHCR(eM;3q_-?w?Y701~uG_Z&~UZ_{wXM@He@T+ESD6eTv8G(C3`jz?66GV9i#zwaM zACZL{T@fI*VX@w!E@Db@5F@Wmd|jY430y-5Mn?;zq6q4&CxS3Yxp~(w@U`SR4lpT& zqgz^9>O7Glf~KWr{R2#tN^8Yb)a-_~UeRY88Z&H#&Hg~9e|6l@1fwfj5V>IwYzPmmi1DBJpe+EXO}R@=G4zg@$dg&;eMCe+#31Pb?v*#dTPv!; z;K--=pY3v1Mh^YM!0XF>DQ_8dn#U)BIuXrhQ(pTlfvO-%5BYZH-`r|c> zli34vg;RS$BWl27nuMJaS5Q*-X~4=4c$LJ_tHTATl|68yzVWJj1Ww$*4;=?YZ&+B^ zo%wnuVDOqD==HU#>dg{xwa)d^t~88EPbUp{IF*@b0DqJUjAmE2wxoei*MBDHEq&l^ zjHM6yxub1@=X82~&F212U!N>8x5{FeU}cR31Gos&N2-;%SpbLgkH<^oz={$$HTS_` z35vS7oY8b>gIBiQdk4M=VH>dA#-0D^7}wDu0-V48xi8oQFxN_ubGF|Su*I$1xoz4; z*qN=S2DzYlh23g@vQW9&JRj)#0w?sea4^SeZMB{(_)Vji?tkY>oVn1@YoPj)A)>L7 z7ag(Ni)9Cxu~SS;O!Vrq%kO`*yZ9lOP-NJ~4=#mH%3Y>&hK+~U4ICnefU|j2N(vzk z4h~d>nI`)U6WZ6WI@Qk9Gnf?7DIFrf3maGy#~v zUaKWb{MH&YpqfxC;a$VbOAP699)&;BVg#ww2LZ`+8pt_3PL0Mn*;yeiy2!Xk*N5 zSL<;iP^VF-6*MYpu6m);#>NJu%6VNH*j|gqQ3yp|J@1uPo?Km7F1PzhEreo{%L8BK z!QWG~6vQ#o4sDlOP}??BXXt}MKqL5)lA>LAsCJwm+2vOSCZ}M^$uFR|ZCyeT2ImrB z6hlmkFl8&sXYW@zj$*7d8q#rguF!G(UgVx|@!IyWqwO3sYVYRm#$#7O20>LUQofo> zJ~s$a<5e=D0`6Na6|;y`Yi1m&UR<+oT(h@Y<3$tM2zh-DqVYN^Vq(~0oz3C#x%6(= ztpWPiLp3ddsAa!=v$lm>JS>m?an{!QjAeau1qSDkrKKeob;9cEYGAjGq}%WzB8rf0 z4Fm#ko1r?)p~b}vz?C%g_I!ICJUq|aGaBh=qE7$>4+N1ZAc0^PzRMCM1_rMZzP^He zag=eYdD2vZg8E9Kz^7L1H7@vPGM*zwxm3gf!mu<54!4^S@a}(sjltx*9D+u8R+GF1 zjN?=J-54tj+ahbN#>5pBF+d3NW!o&``1aNY7nqiZgTTK6oP|$LPsJ(?+srAH@*`TE zU|1dd;3Fi*HF?4c3+cqg#m$F8fL9lA@LUd+H!|uNF$3<095Z2IVe7z(7`U?bgV=&Y zLJ|oQ&^HcXrXHA(fM;rIij9pO2u#e^FLozuV553Pbx9(lqS%f5&>KCDW6!r8O^1`C zh#CBAP(`m?Y#hW8Javeao;#D2?G0V(&zi-XY-*< zAs>Q>h=}m;aBygUiU9izdQ%WcyzFNxiBMxu=J|eOYrld0gHig5n%cz5imq1Bcw->` z!Af0UKf2@ji4+tb0D014-WA3`LdnX?O3%vLw>g|*@n@Jsfh&~T{>xk=SDkA3KXRlY zwy~nOrlyeXlz$hM1>5~Lk8qH28BrxAOqd)%1Ze_+RZw15HV6D@5_C9?U&~XboU3VW z9vW_L8BVqz7j^QgVAVliJPA4f+RQ2{X=JN`#_Q@$mfQ*iYkiel<5Y zH!=QQq8P--#O&;`?WX4DKj7tA4Bg+{sAQ{Ug10RSzAWk{^(#~q6e{r8zuBp&tD``A zdwQ^ORKW{@$tf#i<|D4HsX>N>goWYZOn{?z5r}sj78Jt1vJiR(hT@T{wDj~zU=x1h z3VA_5P*C>();=<@IO=6c)Qe0?!Y?;$Yaa1lTwJsP30S_8K@J#Y4}x0=4?kM&P~O|y zySO^7cEs*V-)Qp=i6g)^e_G_wfW#rH@R%e*VNYLb#;k1wf^|=8dg-hLVtA< z7Y`35@Ly^shehN_{ZM<# zZx_|498M@WqA8bx^gP;AfBupuv(oU#v1Y6Rr_z#Ms#m?DM6PfyOMFjEqc^ zU@<}!b5L-wD%dT4_g5ysLz~0oBe+{o-_WWrft~``eH=Dp*&^DFGcIy2E-vO}vcTtD zO=q;L3lP~H2#!lWUS3Gx$j9f5s z7#Oz*M_3esUl_Lc_848C|J*{}m=9oorVEZ4Sg{Ju@ekQ5n8B+8h%q70cc6d_|MsZcT` znQuu+WC$UV%p@9wl9@6j2@QnILWDx5?|1F}KJUA}^?l!et^c$3v-WD!JzUp$9_Md5 zj;rZ@Q&`%>`M!5-{%~w0-O_nc{3N}e?yIi$4@^ufEBbTd^xLMUpxaclC$W2_{Qh17 z*q|p)9xSK9M*3h?|HcAYR4WY)js2hrfPuk*)a&}Ok%zqNafrnQ6l7(sQ&X#vUV3Ex z+Pkp0`0mor!+13NA#3aH=Pz84x;$ZaEjT!}q=b_+uh`kz8u{ciuGU-eCKBISL6hrG zSH-@)0E!_~^tuxFUJ#LC?=5vdt1Yl8A*a7>8DIzxo~PE+*H0}dh(P6N@Eb`=N;)m( ze*b`{rnB=N?0fp%yLY1~QeQwIKO%x<@5P?5M~{j>-fjG9ck0wtJoS7=_hfo!e>A=e z@v`GO>Oi$|j_-W+9U6W^u_nbD3+^*2nDtjUlkUqXf;Z~wex|g>s zH1sPXjA_VT_YorZ!Z4`zT+OcF%#Nn%6$_ zwsv+#XSk@Tsg=Txoe%yh9fo}G0?(W|wC&i6y@JrEm z?)>_4Hbpr0J2sh+n3$N+aX{wNXU?3O9e!8hyC|(3Y~&`+N}Blg%5=CVoPDCM#QV1+ z*3tIIGH04q@Crv#l$aSA(NgQv^i6c%J`U8flFO5cdVW~XI-gX@Nh2r0i65j<$Gfwf z+8Um+=<4EGiHXTcm$5d%43n7M)V&-Tk=g=;|;{e*-Z*~>YZnY%KN zrnF4;SIVWPTJgTlE1L?<$PkQ%eSLu{rI(__V^<$mm1C3zG`bz@pP~ zbaX78ln+=v*xu2xW7EwAxgf!Y=H_e3$;sVQ`43%d%ji^=&b_^U za?c?~4I#(RG*{j;%j8nWPS9GJ8&B^m)M<*lVJ4=Drzik<9>`#90qDMkQ$)mMBQCEL z9{CG;#`)*ZpM~Lj7dLWnys^BpjfaPoogICfHmiwv)W`GlU!s-YzkmNGR@7ATRBed# zkcp9z+U)Sf`OJ=1gM;el&!4wFe?AP*s|L@3=a8_D5&c;1=RGxb zz@_OuF%!3vIPq zk_wg}4zje?HVF+Ow(72~t-#9D^XT#qqVDhS@9XQM;6quxy39-{6C`K-6#g4|Z9-ki z&0oH7fiw9QTvog?O*)!<@cWwViI}swv^YJH~Lo?)(1az9~JGE}x z4mT&n117(?*t^HQcVXcQey=J0+iM?=#l=OpF?JqZxvM8@C~vj3S3d`rUCc85F&my5 z@2`2WbM3dwn>S_5y2QCd(~=}l4d6x~32_;xPBC>YExZ2On|+*npO_K80X69o z9$5#Ht$p;U7D_Dx14HB7gpnrX%9+_&*QGxfwki8>L-mUj9hjPmz_YZOxTI-7f=hhn z_Z&$+coW-6nE8tD{Ld3&CtkAQaMuFLmJqF!j|FV1uC2u!+)r-+vLmCTui~N9T4$W9 zavz|I+ZrIrO9i__0T%(9zN0 z*$XPtr6*FVU7abXaAi5APlqI(`uo_Zl6?p6Thm>MHz?@ST^B1(f(6DO^pc*Q6OOzS z6m{rOJo_f9?3^8Pa_qWyTU4EYYAnz9`XwoNFyOM-izp9$$C?AXY7C2rkLPT@w~qzr zHxSYVm{Kr7KLBxj6FwE#4B+zONlqg^y-R-PB`!N!k`-^^wk|9#rXC8SEAw1IFZg$? zK74*sU*=4)^S4m^xcikS3s&7V1EFhJGAMi>@=PVdc$K%`m9VJ?{g}o>wvEqSuWW2& zpb8{`z15*mEbuK`TU(cSjK>|zGdCX{9j*NIiDRF0w+fDc^0ieTCT>}Jh&ZH@1m>~m z4Wx4)9?}vd4M#{n&g}RDo_ijqr49WM7Za<6@)a2wNdeX0AtXe9XP5rf#6<2rHsxWU z23)|Db)W|>(|v~y`uqvwH3A+`Ls4-4_OkBNZjZ1f%uV?4%JU*xyBUG5>Q+~l?0;_M z;u7AcL@);@r(0*aI@=Wtu4$1?7#goN<(^S|xrh<#pE)bROpJnpiPsOT-I5JOvDXJuwar>5@6x2Z@z zYFXj0_~q-@Xq*KE`o}rAQ1;*fAJv1oNCl~~Tbo1X&b2-uGxI3K05=UK;e}Be)WA{& z?Ck6|F$53x^{E(T>g~9x{;=>pFm+>$iN!qjK*NU*QF!kh=oW0OtUCr;4Zj@WYg=Lr zR{cA8=jbS|%?^I^-5dEI;%H06L)FbKE$`qn;GsAn=xx{Dcd^I#vo(Kkl9D zJ9d{y-G}5CQZg0L6esS`U4mu_?6e506DPcGM1OnbdFS=(m36-j9oWK|L4gP+^su-$ z7#qwW?|8npuI?=*=(aCi2k3096*goH>=hFWQ@A{#ef|1%xQ|-*6+EI{>p@K0i!a)= zb#`WA2e;w6i^!h28M0-Ml{VI$z()Gh^?0a;zYfUafq=(vPr_%pPMSWV+4kNU+Dm(h zt6kfN52*#^e?xI!7M%zU!2b=(N&`fIu&}VVxaZ|7e=Hg3=?(VN;S|%tH`wjx=f{2E zve?u_Ok|`A*8gcoN1d-gc(LpUw8 z_Ikiy05?sndFipC@QlO-J@1!NVe}6!#Yc_$2j#+g&6_@9MT>}BQT6xuu}UtGAccxI;1xmU?|zK)~v zo^YAtH<5Vyk^o*IA=2~b(|Ye2Sa{vW&Gqz4nN@PH0wWDTQ}oGtO{rMuXT%AR?nsmS zeQvUg1F}aADju$DM(c|UmSUDK!aSLB=+B%vWA^+Uo^q)zP>p4C+JDV)+)R3F2gjO2 zV%iRAm$fcI73SXY1-qfoQk9#}0$03OG!fHZqz~y)|7^TN!n%wS6~zk9G`QvQ$jHb} zl$BaL-zQMZiHo0Zzj!lj+l9|v)F!FxEG;eV8e@bn zdLHun!-TIy)0cCTdb_=A06+I$8Zj}YA4TsvRMpjKK<=+Y2nbHK5MeBRpTQlk9H0aR z#`tlX;feH2r8_^1CK$VF_ZjlEw^Q`*Q7oQn^u0kh{dAN3?)~3)6?#e-)ZG&=V+@@W z%Opv2^2%&lkE!ov-oc-!6a}IcWo0{BA8|dX&9;;;^JnfZXVw;2IGVM-ZKiZuRzHz6 zJgm29*M7orP-g7I&d6AH8o`?7>^%4675noy-e&KYm#5gh&bq??Jt=*9f~)mGpkuH| zAiv@*eXT z#!Y=!bI!5OFfK*`75mt@^;lq$y{2vMPU3bj)^S>V0N_=z9LX_QR*}LRH!PVqz3JWa z&%Ed8ipDOn2LgNIUtBhMaUB;`Mn0U!mF#S=Tk7ejYq=IlUu(ubb~`aAtkT9)nPqHb ztq9W0FzjDJD&Drb4d2u~6v)@*yBe^u4d2by2Jg2A} z95#mB`tXQr#CKOm*UYTEXz#!q70vptzp`><#S>R!>vzi5C}yis#cXgmX>TG&UdP3B zz42T-eaLg(ru*l=RR<@YD%)pnu2THC?yWLwiy*EbnUIN9&YcswykTb+>!SA1;)=^?gg2d9X(?8 zZ^*1W_V4GseED)~XQw8xP$Aq4lUsG!0zZyUAKDvcyS%*JWhNcv^+VEra#^_xv%;c0 z<%v{l`RiPbTuD9hBh{ZhuWvzYsOsoo$3fWI+FA*j8A{tV2u}H5hvZ%QNKiOv*R8uT zf9#+>R0m6Dr>&4Rg29PwzdW_KkFx}HROe?UXsq3v4V+uI)_nXpo91P}e!Pe}ScN2e zfdNvG>W<=U3AxyNYwwzALipNun3=h}JZ0y;etJgm>;^q;O5YIU9@%T*%(e~<9a$N) zoSbjXR&&dE!Xj=1vDyI_&~MmKKiO66x-fl`n9q~D<(A>$Io^l2HiprW$}IlmVaup;0g*4UC2XO4h2xHG-q*8>f9CgZLDKR`dwW4sIucGrlY4sGw{M?= zOY;$PtVVI0ButZGM@5yE%0t8o3K`s&u}+161HUJ`?&5|*qG`n+bpf&mVLJi#v_qEB zIehqg0fg^V|CPT{2?<=FA5ph%G2U15vq&w=$;qLiK;ami(P=t%>B|YU(Huk1v=5*Z3(Mf6pu!@rc-NNijQ%irIC{?QVz2dMo_{shNt+j87GFO{#%1m@m%E@- zRaNEY_sZqF>OZf<$r)KtP*D9oOE$w^Pgi#**gpdk(<7Gy88LAuzU&wJGT-I~kp(bM z@Y7>3C?njt!@9b2t}vPd3pg&opy541-Kz#(1FVneY5Fy;=syBXMg|B03M$m zIrSvoAEL_B7cX}4@bEaon8%Gj>c9gfv>k>7KFGD^g&aMQcx^ScptU2Cr*m@Nr?)M< z6uiSp#ZM8EH}B|g*PfXPnfrZnHE`Xhi1AN(76KVUZO~0rri-@kmcgQ-d?iFchUN zAnVX3fDf5DxJj8Ql&x}?eqt-Am9VhzO{6Aahc~S>_ zUR{0e?{6n4gWN@q9iiBHH`V#QSN_VevU+)YM;90GLw)*)8-4EUa|dEMc;gjWU3Ef| zleYt0-g<0P(IVRN^XJd>f`X$USeaeL7e9hz9X2(+yZ6G^+NN&>Hp;+r=@7(?prLtr zc^Lw-=M)x3h787vW~{`=sK>-*s()^jloOM^lLGeCfl}cdbl{!48GeVt8NOO$h3m~w5HngP>3 zY-DuDA0z&CbaWo#MWP|T5vIPnu@vnzkUjspb?bd>5LTndkM^5{ zUjs!~XQ>}+aA?(9%(~2M>^GM0P7r#A4I7C4_Vnq~hf;R+(*tFXjV#$JC%bOme#pYA z65U{AEPf<>aMY0h#NaWd=lf|5s#3m0&}w+cX|i*&=WT0nTF?J`r-|Ft z*KKfmC|+d!L|NfyJ&-I;w%%hdKz{Bf?x7vX;`Gc6Ri!?f^yNY&rUnOTpark`!WN=u zWURqtFDZw%bm=J*R=BL|AdshLWW<^~|LU)E@$_6rqM@OAaIv@i?fdsPK<~@Tm1JdQ zPb(F}bm?o16@@8J`LmVf79uFy5QJHppBnk33D52n@(hyVMgA>qZJTg-Tvy?vynd|+ z>8cQNY#7ghEhw4$eHSDl=lA{i!9e<4Zq`QbYT?;9C@ zC@4r476p4P{YiQj5=cItZygRYWcxE(kVHD5r6nLBU|Sc8=E_+R@SfKPK<18DS>F{D z6=j5VtBz0dYrNoM)?JaWE#Q`9MqqA1j9zPr@w z)#L=*3(HV^UjreUO2518ei1=ILElG4Zrt3wn~Icqun*4uvn6`Fw)S)22`drR>fz*=yIYs~&5l zp18s5UkR(zb#-~sEmoOy4&`*sI``TOhmV@O#FdqKK~PAf!QtUkf9J+rE?v6z=#*bt~NUVnpTCNuf4mr1Hvy%AI((j!Yc7~eR-EGxgV8aW$W|>gAUU^Q< z3_%wdj8P_yw&&al(<2NCj8TO03M+{)fKkh47D0?}V0qv_u{Roh_iu0O-=+aJbIAj5 zNxyI3z65^6hRei`Sy@@d{LwIH@tlLhcRZ@x_=+EbVAd7>;rN49 zraq3JNG>Ciwzj16KJA+k__AavbI$^%G7PD!{9dDCv;0o*idRQTc$-r?t%1HN^9lD? zxf4Ad`p~;zcb+(t0FrzPRy@Njp*+GRw43g$k{&ztgcIg%9mZ+4WD&8z1ynHCnZd&u zzhl)=-vT83K8xzy5lKX<;SE$D)@|De$y`l{tsg#TuB=wj$orImFVD`xkn=iQsw-L} z*zoS120}s`Aqw3!ZF`xf=a6$g_a$#{F%R+$R}F+}fx@Ni8`%&hnY}OG2NjuIY5tVW zu+50Fr9OUq z8z$=Cpwkli#>Q;R%gd53eJZ4GwZgKoub$uU+7MLzi_gtn!s4j> z`t|Sq@iO1Vr_GCmh>!J^otIbh;R7o?^?!URxxdSc?gD~>!hVsa>AdVk#Lw^$aW@a> zABSFVD*JTF!-GJwShoYnLvkwnA6)%AF0tCf?r`?fC0QmWrq)O1GQsob9-mIsfWY4Lt2EKUlf)i2{kbW?#nectO)mC9!>^5vKBD1o5)@7uHhm;h2&#-BFmipa>Yki&K zd8 zLbiohr^dI@76+MITrmLb!7J00Vw%HQ_nVOed~{gCrd%D%DKs=R5;X@E2ALUgv!|yU zeC9bS>o{Zl_uxR1l$4|(sDrv3My95-U7Q&zeD`^Of@Oj$S-0?O zz;)%X2Vt6-mAVP`?gnao126jt3OVX3AEHY*Y7Ze2(ff)wKR+LJ)C&#Fp9sPgM8_Rm3gha$j$Fe;Py89`d6J{0uD?an45a#4k*Us>KKt*cBY*Qf0%{4bzB#Q6DKPIH{ zi7(Hj+y>uz9&bhxC@ML5!%g+8klXuk(c-1;8A!&5U!8^R0UA&Rvj^7QKUWpDhs@cQ zw=tU;g3I{rZEY!d-xqaNTE!#+8Lg$=jeo=tab-hHesgDCxRdrpMP zyAKP+=qk`ImtXPreh9;#Q_))%IZW!C>QqQ5_@eGeA3vlyX}hv#XZBEdEzNn7|4OVd zWvaUOVrGEiLC4|0hht0AV;=0i9=LV!v498Cd-uludKz=g zz(A$GzJ3ld(T!1MrZYGU4?|xjVs7f8uPM55rh`S6UpGt3$;Cp!(gYI%EO5Z(BRCCi z*KmR1ma97Hq;T&&uZ?EAX2u0(xy6L^kbpHow_5R*t!Db+*~XXXuOB>`yzyJPzWTS9 zu00hOlT&$Rp!Jbi4duoXb-aG{(?Y_IymM#gv&?lQh}t;L8MbcSIvUfH8(Ni3Mt1T6 zoK`vit%{+ifHgkq70{rp1Qhvc*?3h#8wU;pUR=lax``dcMHAcrg!lj zP>WN$K_Ux`W_g9_5<*{F3o zXKrGAZ7kV{#KlAhNpFCOhvAV~9`*O;2CM2&_M{v1K_kEH`l^FBy~0+K=b$dUUd^V0 z-?Wwdv7ecn)a$oy^vHeE=os16wKB&jcguL%sa~ZcySG2Gnf;3*_AAi7dO_)NwzpW&Rv@}mm&7Z0AFmnSM30)== zb2a{+Ywe^f6L?d4X9Ew#vwLLOF6e)L{nxKb8!7!?5nt~4^_f00$XDUr8u01%m<>gk zV#2_B64Dl!>UJ8cL#11v=S4q%{@l%;jh<%zrBQ>ThT1Dl5XU||K5@Y07T()YK26iL zDm5$Pecz@(lhi-I8RZ*@T&JI&p&DK{7<8oEoTGtFMbkWKzbh30;C}BpA@Eg;V&`x{ z-MGFxN0s37lAD{GC2cE7I4(_Y9Yp-~YFykF{K|^;r(5d$b2yx$GosI8-vfV9V531^ zLo{osk)DQX6(I>VoEv=r>rNS&q9AlJovBO^)tNXY`)asVe#0i^w>(Zv>H=m7N~r$oM#~x z>-Jk@gk^KZUs;+`iNIa@Bn}RfTfeI$X$u)jVIITWqA@i!jlXwKO-+p&si7d8$JBF zi?l3uM#GXn;b6ki3IPx`E)8Q(Ei5$X;5TN_5aT<4{~;#wW@cu_L-Rolh%F8&Nj%w7 zZ6i=BN>?=~8S(D$HS)|YG)*MWFwHBh)E)f!?$4yhSB4Uwee-|)2K>736_iss);!jp zV?`NIBMI%^t+fZ~=m=;mLJ|^3X`^2&18&ezlmYPB;+o#R)I}J0 zXIhdD!H(;%S$8_oRsMPw?a zrKAY;&JZvr;3D!WKYA=0?zpIv^_b#$1{e8tFE%ff=& z+|0}X0QuM#(K~H3B56ox0(hPShcuCh2_+;o&mGDs+184X)Y5`D=Fc>9n%v6R6ng7s z^ShhRBaa4qYg*Z;Yvx)!^3;qFrRUXd z^78T|3K)ZMKDgA0)=!@#W|3|dGfJMGb|Kb2zzxA&(ZiG0{=>|`fW*toi^dgxY3WT! zxtUm8M642GSSnl$WQeVM^X4BH`k*(9mestpf~Qt%R;eU}e3OmJ5Nh2>cOS6pQexwgB-@ zUY;o6CW1V)h_9(@YVsqV0n$6#k#{#NV+z|1S}=J zW=>mc0@H!+@05|*jM4xPB+GTGM-hQ3Gl#8><62`^?pPeo7&&?JBoXoeP`r+i1(Bx! zYg4^)r9^K9Lfo6mKx$SFjvENVRWxalNF{ztdyz4aJlnF(clO=BsrDn@vpff&MWDO_ zUKynNKtUTEJPkB$hjsk+-8;Xm%eo0lehO=gNAK-r25=sJj|?U~W$AW-Rp} zmqe?xvcT@$rX?xRGp`YF7POoA8Ny3=@`gmP2ev2_{>=nVKB~Nu{=5ackXwKR;R~PS z60OGlF*G(d9-iV%8-9@gZjyVn11BpvJ&T(9_j{FDlMSuetbIRn9iND- z>ljPB=yjIS)_G4>a9;o0clj=_ju%+xx6f$Rns5C3XHpk}fPLp|e;uRFKapa<^#l(l zL?klx5~@=U$jci-GY{BP^b$Ac7!HtZ-1wOL_r;CYv6+#I(P%b8JjQ^Af_zI;mbm%T zYY440P2gryE4U4YSoJKKk!ELR=t<(1FZ53GK(wTxK=fU3BGg33&xJei(u1*sSSWm8 z!_nFTz7~iStq)dJ@R|__IDw>8&VvUk!xq@=5Y_JLeeJ6X%0U29UH#IJcHv{DrdbeD zrWX;P;U`@hYGmh+E`A)VX{apnV<-uF1upr{7SdU zatjeUMp~uQF)u#mr7c;2|D|it+ooHieTVvc+6_hfMn716Oq*CP*>`7Gjz#*sYE*KD z;UxF&pf|m}ym0(WU>hV1p{Ur}+D`Y(H?*|W1AyEAlz}{T{@Y7-AXaJn8%$hOun`EO zY$HSPQ0F&z@2h^es$>%Kt5Jt>*z~+?XNMy!G3FfEJPQ3h04*a8CF$cQ$gwZX+-m$t2`F zh%+)SF3#5mlAvN($N{3u?@ z&pW|xD+%5d5v;(c1xcPM$eh3GE-5RGj#PH5MoH8AD4B2$zW?PE%XW2G8jkzl-LW@a(wmr=JN zRrn^@-{w`m{a9yj1_<3g@P!Xg?6j1^Ly-0*RD^_e2Dn{??juL6%C4{?zavr111_`d zGZvI{DN&vO`+HoDUl2%^LDveOKE3+l#a>W#D~Ao65z8f!?n(PU&}rNqYnZ!VWG6HR z9)(K=`#sJ$IyP3Araw2PHTf1X)pZ+i!VdZwabytc2OKfgd7cUx=vRbr)67m|exbYM ziC*@@XrtR;lvqz&4UCPi0SMUvFAxO{4sCCqIIqq)X0$;zffSAK(>jDF5&s^jS%f4? zXvO%>L0DMLMTE||1PAAk5Zfsj!vsC|{xgwNcrhJiCvH5y0)hd8e-kTP=dXhT6A?nF z@==R2Z?o+${bZV(Xeg~-U6v{IdX_7gyT!Vk#@Ck=F0-a0BfjBgYWv(H8R)`UXiN%n!Fs4?&iA_o-}dtYr3 z7Z(qyJC*PpeM@#zJ+F!UeQN6W1E0JQ7AEXMp=|r1KU3n3MXl0<%TYu;C{yYnGO?ZD8~Xi9i?$c z9#t(YE!m-^YypxYN*M(*APJ$Ks5aqGED2P%F`I+}`dPeevi zQU#FBUQ7vcArWT9aRaa&1l&n<89B7*i}-<`~CjP;!fYF=v9xIpPN(16Qut6o955x0NCSk zd_W$FRWcK8i-3zsG&!LBJ;={zhD_2NFGUUAi%3yp>9`KQW!-l0%0{eouc=j$n?S*` zvLz|8;5md#zkoi*R%Almx_~u9&<1b{acbU@@ImJNUp** zdpLDZGIF+o8Q|^{$^x{3ESpSCEUAr3;OyFbZH`tSXNzj{);pb zkcD?~9pR0&1G_w=PquIOUt(Su+ue8G)HLa?1(`#F?!|e-qH`T;hO9IL$Unx?%pEMdnlPcy0yMro^~l+2+$xgnTP$)GKAadM&r zu}Szi?2e2cVi)g};HmG&kvrJmuZnWr4jmaHGcVfLKLAK9N;j&y#7v|`j}bqE9E=1* zETeFm7?a*%1humVO+j9Ql-8Er+t6OMo_gn|o99s3+7W$6%5fK>V?_2)*_mBFVx#L3 zKE^F#d_dG~9UX$;oPUSz+MI4IbK;B-og08Fn1kd4w&bstleTQ~pUuq8W4ShIMx`~P zL6i6qnq~9cy1ah>FnL;3lmYG@X(l#2o}IlpL#mTyx}5QClg9>W__y40 zfZf!NCf7F921e?%khUZ22wljct&gBo$dNcg?q|kB#L$*_SUvR3IJ_ZrAbSgJx=CnT z4@Q?v-I!uG$OtHka7$+XW{R!1cUmdon6fqP8ml5 zEN>L@&}trbM}`8M3Qbf_S8P-$UfCAg=+x}n>&~`)vZ3~wr%KbJwv<^%bz=j0Z9bqf zYG>}2d>x@ZbdT}_-$VqqClA>AIm$>nZ`;RVt!9{hwxfAJ)R{Dk^(-t}C>#khZ*1ES zm9HiHe0#n1ZDMJRS<7;dY?$)Ki!S};_(%O=CoGiZL=EU%#iQL^ z=X#SFZ#|W&-X5u$5wCQ=OSB|zhuoa_sQ-qmYpc{hKCN^7o?=4;-%RwdsKb*9vCCs? zU|&AF%Zm4d{gU*OlBCBcsQ*kD=DWl;2d%68HPF3OFn09kI|`YiIXRnP{~zf~n(H8E z;Zv%zGK$p+89qKrQ@q?Q8RpJSnA-4S<;OQU4$a+v+-?e1Yjy1UN0oz4m8N&;5@qo8 zo;}G=q$t8hlwJD!?|dn!$TfS}m|DX5`DLt+az%%dbIr}+cTOhLBhf@;4JsRyz_;fg zrsp-$($kcbP~NBHsb@9{$i+IPVjYy3Wz5;uJwvsZCVpG#Ov ze9eohO{jkSpy;d9d$zsW*IM$#kv3QLzrT;F?0I_a;^)?7oRn7wB;5o?HlijYbD?5nv>8Jw42sa4pB8uO?3>_V za*rALT{9&^xAa=6d+7yP$BlW%$p|mzi`)86htIwVVWq^yInw5xLX<8TiTwt}kg47Z z7UX5<9dk@s)jag>E3jj@g&I^1RW&s-35UKS;zpzk7cM+^`g9~?Mv61+Yt6)$?(x+X zl~eqbi{)emkABsx_6L>o2?>=`74B0(yABS$D52gz)oXF&z7c7bcW9^RLB_=h* zIFAs=G+qy$qk#nuoI*xGUC9q^XPjayuUc@AV^!|$?WW^WuZ$M#J9s8w?f&e;&lPKv zm$`Z6$2-Xd*5t{rQU$6E3pu31ABi>GMsnUN6R1LgbwE-<*yg_^#vkunS`cPX zQNPJtJuomp^bNqUOFLO}ofD}lyo+-1%FcYtFFS_7&WY0obnE1W3*khBCD!!o&*;vA zwzw}qoN6{Uyl;A!ZH05ya!U%#pJ}E}WK7xo75Zia2IOY15F7>FA=L-f63iU!uC(|A~&{?$91CB3BQdwNrz(fQYw zdH?RbDQ~;$(8ifFe~aS|1Zd~TFxal9`3i*VQl&2)*QKQYI^5y7o{65G0{ICMd;2!r zH=ukL*s+_?cU6lJL*L{i^Aj^BBO@aMS`zXD(l@_mhIGR)&>O5k6`Y1d`hO}GNlfSs z{Z~AxesCx#_xWq!m{Ei^lJ4Jk#Ti1V!LamAyludLtzW}wEnfOrX~D@wao%paSrajccBtxJ9SJU3hm{d;bPu>lK* zOdgux(E+6k9q$s!HVH{X!%satdiS_I8fq{979JaUH);O(;vIgvnPW2#>{5+}=9Li` zBV2q05g=kW42s*7Z$sNQJV3>i&fIH@rTDUh`3ed`2h=w@Hg*zyBfkE4;3DBW1HqHJ zEeZi|{}VE4VrOGhK`U5y_cjuG-V6~!5S*l?Ky!o(XkPDKdZBBtnS}+>8bgpn;ew2e zx+X{_iln~x8ILTA7!Nzk)EY6-4Z1ut35)cY_ffe0 zgy7A$vGVO(?Z@wlvmiI}3l9xx?suuA$hTMSTgO*>*T^eNdFn4_O1$p)to+ha@vGaq zJ5I`uq^8!f-Xy%8i}68Wut9mz4@#j|UA_XZs)`E9*U#_!=%~=$-9@B04O}7^qfajO zDiIw!=$-JjM*JUW^mOB^($XDLQii)Gq25V~owXeq98|%_Rkjc|Uq_T9$UC1uf3}_Y z`~+5Ll&Cw@fNM8yG_{ZP^^qu+1%H%;oSgnmr@(-)b#x5Hx>DsA&i_>*C@04b*NsR) z;eTyz$eL+ktk4#53*#M&$rWEVsVibif3WdF= zBd|9qoD>DyiVx)$j{*qpck^b|+UmS=@FIJHMHwWFWE>}u6Q4hq#iJRA?+;;c#CEh! zHj3GSmOy!%9f{yoKAz2|p{ZG2SxG`t@t=-NPR@{&^d;4Dj2_@XbD;b7;{Wk3j7k z(zP8qvS*L%7ICU0_xq`6Z*$dnee%DyA#$>ddSNE~_r+0z%0``x8ROj^^;Gnv@OoL* ztmj8u7(9u8os-RUjERf}U8SL;>xai11YlV$jmD=~*HIyTe0Cxw2c5|dp*P~&M@B_e zjT}%=fNrZZO!T61W$Z@o3Fg8fko%?>Qq5^PsPmOtSI)MT=}Gk(a{pp%*WC=yCFU42#u`6RSde z(FGO(p>w#|6IlqTKejLDQqI%v_x!ny=x;J-`}Lzkln|k?u^kc+tVu;i#l_Y25Cmj2 zCz+k(A~u$SLo23!4EDTFtdloYvAp+@uBa%`?oCO4ztX1aYRV0k-lXpGcY>tK<^+*D zhuM7#sp8|}t|O{gSzk}5nz|8=vKr)bcrT)Ad@)`fAwL4 zHCN0kl3Vya|4f(@{r8?fyQ@H1pE!S0kQda4kOq0-Tu)hPwZ|1`bfl6Z50KdEBzgD9 zq$Dw;Gl+aFKs0!emuLCd3PJ&5QE%WRqOsRz+Ak>~VLhp=+8GO zIJ}MgY7d0EH zKKJ0zkOt@?+xG28xVgF6xVRebf?5f}AF&_~pBGSlK-dKF--u}zun(ubkRYc~8T*dz zMRj#Z-56dZu+tPEk^BI`Qht607M8oEFOWBpg=5;-+?2meCTGv1t`5y26Uw5IuWwoNZ)Eah;iB;KBiG9h zsDe2jY$*k!&xFI#RfQ=$Z#(j|!Hq2t|Qx+ITioI#^WNUn4$*6*oU`g z7g>X*3yEn5%uwfFzW>7G?%nc!qiR^Q(mpMe8-ZkH`8_;R@>1*huE`=B#_nf9cYtMkcB1Y)-`NCFSHy5_=(s zIL4sg2EPzU`ItpRW&3_R-;VG4XkV$U3hM58d1Hg?6RJF}Ckq$Cemt=wk7T|N)W279 zjY4s}J7V{sO`L)h7co-;6F`XB6v6iBoIc(E{x#Go=;F;p{(>_sg8Pr2NQ!;a{-6}A zOV1{|$jZt;S5{Xl@7f%{r)T-XB{U_9H8P1QepMl2*7)+};pRZz)EjR0Ge@>mQYaMZ z;vqCEz_ohOEQb?H6WPPQfq^vRMQRGv1R#hIWki2Ob8h|{X)tbRPGCTebow`Kx~28) zZHs$1xwGH7L*1PG?lrKxq@Ndy*^{ff@rtiHMFScQ9Lc^Afq2`GbD=6MZ=fSYVB85T zm1_{9f~7jrB^m!ocR2D-B+NOdXS#WBui(;lm1@mmarrB2dx2S9!_^3nNHJ@Er@*_P zR9nAMc4?k`B&qMRsJwgT5g|&b7$sJkb{UDAOXHpB&b?GgfXuMK3f8 z&oVjv0s^G_3a$z^dQ2SNw4}&{^_M2-)6+>CHmIo67%-2g+q|ZlTV3TX{!|dRB`aY@ zwZl2LcD?#bBXtN?M4A)o)Gw)4)O@^TyZ*Lq`xPZ5SU8oo35j^$F)Y|#tQ(Rc$PrSi z#nSiXnNCH34nt(h+8?U|p{4xYZzsNZXf7JnuM?$U8iXc-u8_sxtT4iX4^jTlOQHj? z)u6JLro=n8>&)F0_vcT@tBW`8;gL;@({qD=V)6u@J}nXX)?L!~o>Vfa)Ao^k;FZGo zqGdG8anj22vWhwb%Ri|TPLI3b`YcwYh2N$sXH1)h-<2--3=el-nixrOPwZN!S}VU5 zg!J@T_vDHaUoIRbqSH%L-CI07k`#(KB#A4=8TMo7mIP_+^7!nC`x7d!iHZHX4-&YF z4O+k5pWU`dx*8N%bEDKAb(*aCQUlS?q%-^71X}aE)^*re{?nMJ3wW=J!v7#A=O&VV zgk}>|)wj61RNPr{XE*s~x_V~XPM1GoXV-__8n{ZIthi)7Z~la8;g{CWl|(repWRZD zj%+Ribj5!p_b&u}HN0obV9VY=GO~M~uRLH)344t%o7#CK?1%l2_MCH{9`isuBIbI* zuCaX^xuX!A0=aQ0-~Lgn%xCe19M80DvwgF{S!QFYwrjp6^@k6YyHi}OtF5h*=uE4I zPODdjab1k9;F6)NA6OqRN&Z~KXka?BMIi2pozaQ3pTd-g?XFH6wksZMn1ol{6r-#e z>w>s2F)fhDHSXO@blBr?x()-kA0a`E0;?s)T;NzVxz&h-5g!#9vC6I?&$>E-=n4sH3bee(O>1wbYa}s(Y{}>wF{HeP3%%5 zC>V4_rGm2K2EO1$G%w>{y#8t8mLcu!A*7!kO>ry*&zJ!6ast*M^Z_v(*4GcmfQCaE zuU4TM68Sx(`JhtgU_r+uBpilP-(HAzk+LgNHhV=xLXn~y!<0nduh2gmIy>X=XV`dv-b9gld!Oid_*9Q z=&d2LBk0zZg}Vr6CZT#Yx-)+w@@c@DKO|W}VBshXEI=olD9E`p(H2oQH{v$&SqjQ~ z8iFgr`F0>dG!(*nHu%#vFX0#SQv&C2sWcHqoQ3w?fGLE;_@d>u6n*n~ruFL$%`PKE z*p9TZbXTRVWQ-9)6ACR?X}bqLcf<+_W~g6RSAFg+7xhxX^ugiH4;6h%=A^X`@Az6e z`|dZH++9S9itep>d5vE&@244wSCOhd+~?J_3jg;l!N=1CCI6k8=i=cJdpIIIoG=nw zG2VqR)j0N85%e9&dh|p;0UIO~4Py2I#zsgXRjZdMyI!$pqGC2$**!mK7z>FyTG8LtiRQV4Ww3Z6Acbr+8uZO}K6A9#f^IQp zRywH;%`;An%+IfKp^Hj=BUL;}Zh>(x--Tr}Gv(Jzk4`j|u596O-)C032{A_Y7t;fo zSnih3BW*l#AGg%)$ff)Lo<-KWhqXpXN{SUR5b2?B-QAJ#@j`e0Kp!j3|D$sUumFDY zs!&cyT){}k)ot+5AfIh}2BviGxv!NR&IQ@CG z&DHjH<11cXJ3tN57^>5BqQajC6db{B!{EU?KT}RvD^~y!d3$^7%w9Nuej~OPgaPyw z4YBg_Mi2oXj3a^h{wPKPGQfb^lNZAeD{0obJ5dkxQ#5W-)>fwU$viyQijr^jbl%*b z|79x`zqaXzJhiLLvfGb;yAarNKTvsXFs5S9iTy|K!q$2_rJ9uTL=BN&+`+Z+U&2%V?;W?N%M}QzQy8l@hr`Z9P3qxDwl?7td$<9x=x2s2H}` z+$8~P<<-+d@YL&Zp#Q6>fM&^;ynH$HRHz@SEB@v+w5hg9V~mL^nhN^*`>%qiiN&l> zv2n#a2jl-$n5BcDmVbf5KU%@LR*E~Rr}*gc8?)1-+q~=i4}76A{u}W!k-PZH^mjEr zfosA6Q!3w%k;pCmBi{|hJPCKQpaJrIx9kw%g7B%JN! zhBtqSQ z>WEq1RDcvijRuKG=*t{=jolC*J zz2sisnn{7`EBlN`VwU3^8L5rlX{-5(nLkzEr6`8S&oCjFPJ?-$(Fnf~{n@ZF(DO}< z^mKrqigVOvFS;TCHXL9{`dXO7mkkaO_FDM+chiSg!GTfRF^u2=*yk~H)SzqO|IW?R zw|V-H`LXR_rcV3cSh?3p+mH|_dit0aw&RIebMcpkhMNErHua5-Iu4wi_TO!Ew*Q|z z+?O*RbJx(@k%`8cUyGqi_u4>@8?FTy{wcMDWo*t8RAq2P-+xyJ_jV-#29^xW-|Qt9v*exD*SYY~l5=XWF=rg$`itD@rK=rBqd4XVP; z`}XeDgp^&>SZgaO!uv<>RJ%GTyT_aL=^N6}k1l&uODn5+9v~_LxDE)#HvAvUW zIyI;%OqyT*i~Mo2UsJ@AVg;%G=1zs)1A=5_543aAPVba7mL~Sj?V4nbu&>ox1tfU) zR4N&hAwYGUO!Z60%*^OX_Vs@;6N8HP_2v*X7X#cb?stR>wUM4?$g1w2w;JM)(k84Z ztTt7(fwTZ$X-@lNJ2eY0xi3Y(viav74ZooQBoz%L%^Rz-f3?%o8t`p=fHq!4Zqy77 zqjKwr52ne3t}os1<=>e!_0u$kD90c*etF6GFYyXvxwaDipO0_%AG;GDzUn`IWSBy| z))P7dZnIf<-y(6>2+V|UcX>xgfexMYnh;whW_3}Kz_?As*8juAm^3E`5e|W}Per01 z-TC>GCVx~PkRv*&vg1DM5JPMLQIP+6doi>*o7>FBl=o3V9!uw^j&;2Fv!ly0Ug;t+98NI zTa(xl@yr{su~UJpa9bIz{?=(vT+r5cwJ#wvJ~P8Db3hloYiM!hpmI>NVZ=gVE4}sq zt21WAyD@w@XK{te0D$}uM$wk#G+IA9>cUwBDpb4oApnt}5TTLIqqqk73*Jf&wA#vb z4Wd*bPs@;N+}PWB`MZSVM-L>bpySao$syU)?XxU2~HBw)I@gj4RS@l%I z0_utjexSmPyIPv4Bu@EYwB{TJOHfOc(jkJ_U^~giJJXk4q*<=hdCc9=`~fd_C^qXJ zxZ%d0>Ehyow`$5NCr8Z=9Y-tW*B4BVYWq~I-~vW2jq{oX+=^oi*vif=)-h<%@qDcfKTPS0V)CDY|~mTnuccp6g;iq39?_GV(;YuPsj|CmmO zL>HwsxID};FwjJNSYcN~X&gYl8#Eqz!Rq>YHxx*%pKY&P_SU&OuHQo;DT?r=0MQbQ zRZvh+4OPtvAmCW}mPJ|_5b!M1#EZ>o51t2i>qV(H2gMo>!HeG~)S57UMh6FcOO>(lRz}7ti87IB-cJ zFXJ6g=aezd&Y|A!N478hI$~%>sB&~iZvMH^2>_Vtg%SN!POR?L()Di+VuO@TjQ+}E zKD04V)%i=lEUT#Dktx?5@G4W^d?9cAwPAs8Yh}{G^Wo=?&d=y|XGpGtr#_9u&zxo^ z@z|NvjvM%_-I9`$@}|xk#}2#Bf4I8y(t3TrSJ^IVpzZeUkL!Z~?U?hFEOI8h@5Awl zm7Y_?`KVgc;_&`Hvg>TzkM^FB&1q>i&`y2#?;oAjHB}uWu6m;MWB~D2k~CG3tL901 z9>ubY*|qQ?Fc=(a9BY^&Vs^0Sdr->MB$O7`d~66Ps7CWGJMPf@zC5+?APj2}+n%Fa zz>$icM$r+U9IbeId8J_*H6OPa1G4bi0K3k<9JhCz7m~RBbVP}rR770L!5Zn5!CGM5 zozwG(sGly<*}FOfj-Lsd0wyllo-AKMF~HIASV=!PC=ItiNX+264}C z`XP^LvSh-^627qNdSj#eM8D;spej^+5^%D?<`o4!zdkU&vzXfu)PcBF$>OG8eDBfG zddL|7e<$?pVvyi#WK64!$_%FIXXC73<`q4rOy}DkJy78&wn!0SAGe|g78^6?{697Z zVr7B5HgJiW>gv#xWD8y#%rdvXiZcd@69f87=HEozoS$xLcV%ullOJ2iEk6y~|0`#kIIFK2_(8bu>dVDTD+0MXcbSkH=e4O8UB7j18QxbIMp xw33z=TWh>WTCV1wEo0cCXDfY5_kHr02gm#UT2wUX9(GmXCn9{Mwmd9h_unQ$9gP40 literal 0 HcmV?d00001 diff --git a/assets/CubicHermiteSpline.png b/assets/CubicHermiteSpline.png new file mode 100644 index 0000000000000000000000000000000000000000..90ea2566484b23db81a19136d51bce27b9ff0288 GIT binary patch literal 55173 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 QHG%(WYaCS1SG5cIKSA`{*#H0l literal 0 HcmV?d00001 diff --git a/assets/CubicSpline.png b/assets/CubicSpline.png new file mode 100644 index 0000000000000000000000000000000000000000..8049602d3fb791e10a0a9a63f45456afaf57ffcf GIT binary patch literal 57771 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