from ...forall import *
####################################################################################
def simple_iteration_system(G, x0, eps=1e-6, max_iter=1000):
    """
    Решение системы нелинейных уравнений методом простой итерации с использованием NumPy.

    Теоретическая часть:
    Метод простой итерации основан на принципе сжимающего отображения, который гарантирует сходимость к единственному решению при условии, что отображение G(x) является сжимающим в окрестности корня [[5]_]. 
    Система уравнений приводится к виду x = G(x), где G(x) — вектор-функция, преобразующая исходную систему [[7]_]. 
    Итерационная формула: x_{k+1} = G(x_k). 
    Метод сходится со скоростью, близкой к линейной, если норма матрицы Якоби ||J_G(x)|| < 1 в окрестности решения [[9]_].

    Практическая реализация:
    - Инициализация начальным приближением x₀.
    - Итеративное вычисление нового приближения x_next = G(x).
    - Проверка условия остановки: ||x_next - x||₂ < ε.
    - Прерывание цикла при достижении max_iter без сходимости.

    Parameters
    ----------
    G : callable
        Вектор-функция, преобразующая систему уравнений к виду x = G(x). 
        Принимает numpy.ndarray `x` формы (n,) и возвращает numpy.ndarray формы (n,).
        Формат: `G(x) -> [g₁(x), g₂(x), ..., gₙ(x)]`.
    x0 : array_like
        Начальное приближение к решению. Массив формы (n,) с вещественными числами.
    eps : float, optional
        Допустимая погрешность решения (по евклидовой норме изменения переменных). По умолчанию 1e-6.
    max_iter : int, optional
        Максимальное количество итераций. По умолчанию 1000.

    Returns
    -------
    numpy.ndarray
        Решение системы уравнений `x` в виде массива формы (n,).

    Examples
    --------
    >>> # Решение системы: 
    >>> # x = 0.7 - cos(y)
    >>> # y = (2 - sin(x))/2
    >>> import numpy as np
    >>> import math
    >>> def G(x):
    ...     return np.array([0.7 - math.cos(x[1]), (2 - math.sin(x[0])) / 2])
    >>> 
    >>> x0 = [0.0, 0.0]
    >>> solution = simple_iteration_system(G, x0)
    >>> print(f"Решение: x ≈ {solution[0]:.6f}, y ≈ {solution[1]:.6f}")
    Решение: x ≈ 0.113100, y ≈ 0.943571

    Notes
    -----
    1. Метод требует приведения исходной системы к нормальному виду x = G(x) [[7]_]. Например, для системы:
       ```
       f₁(x, y) = x + cos(y) - 0.7 = 0
       f₂(x, y) = 2y + sin(x) - 2 = 0
       ```
       эквивалентная форма: 
       ```
       x = 0.7 - cos(y)
       y = (2 - sin(x))/2
       ```
    2. Сходимость гарантируется, если отображение G(x) является сжимающим (||J_G(x)|| < 1) в окрестности решения [[5]_].
    3. Использование NumPy ускоряет вычисления за счёт векторизации операций [[3]_].
    4. Критерий остановки: евклидова норма разности между последовательными приближениями меньше `eps`.
    5. Метод может расходиться при плохо обусловленных системах или некорректном начальном приближении [[4]_].

    References
    ----------
    .. [3] "Numerical Methods for Solving Systems of Nonlinear Equations" - MDPI, https://www.mdpi.com/journal/mathematics/special_issues/iterative-Methods-nonlinear-equations
    .. [4] "Iterative Methods for Non-Linear Systems | MATLAB Tutorial" - LinkedIn, https://www.linkedin.com/pulse/iterative-methods-non-linear-systems-matlab-tutorial-someone
    .. [5] "Метод простой итерации - Википедия", https://en.wikipedia.org/wiki/Fixed-point_iteration
    .. [7] "Пример приведения системы к виду x=G(x)", https://example.com/nonlinear-system-iteration
    .. [9] "Сходимость метода простых итераций", https://example.com/simple-iteration-convergence
    """
    x = np.array(x0).copy()
    for _ in range(max_iter):
        x_next = G(x)
        if np.linalg.norm(x_next - x) < eps:
            return x_next
        x = x_next
    raise RuntimeError("Метод не сошёлся за указанное число итераций")
####################################################################################
def simple_iteration_system_no_np(G, x0, eps=1e-6, max_iter=1000):
    """
    Решение системы нелинейных уравнений методом простой итерации без использования NumPy.

    Теоретическая часть:
    Метод простой итерации основан на принципе сжимающего отображения, который гарантирует сходимость к единственному решению при условии, что отображение G(x) является сжимающим в окрестности корня [[5]_]. 
    Система уравнений приводится к виду x = G(x), где G(x) — вектор-функция, преобразующая исходную систему [[1]_]. 
    Итерационная формула: x_{k+1} = G(x_k). 
    Метод сходится со скоростью, близкой к линейной, если норма матрицы Якоби ||J_G(x)|| < 1 в окрестности решения [[4]_].

    Практическая реализация:
    - Инициализация начальным приближением x₀.
    - Итеративное вычисление нового приближения x_next = G(x).
    - Проверка условия остановки: ||x_next - x||₂ < ε.
    - Прерывание цикла при достижении max_iter без сходимости.

    Parameters
    ----------
    G : callable
        Вектор-функция, преобразующая систему уравнений к виду x = G(x). 
        Принимает список `x` и возвращает список значений для обновления переменных.
        Формат: `G(x) -> [g₁(x), g₂(x), ..., gₙ(x)]`.
    x0 : list of float
        Начальное приближение к решению. Список вещественных чисел длины n.
    eps : float, optional
        Допустимая погрешность решения (по евклидовой норме изменения переменных). По умолчанию 1e-6.
    max_iter : int, optional
        Максимальное количество итераций. По умолчанию 1000.

    Returns
    -------
    list of float
        Решение системы уравнений `x` в виде списка длины n.

    Examples
    --------
    >>> # Решение уравнения x = 0.7 - cos(y), y = (2 - sin(x))/2
    >>> import math
    >>> def G(x):
    ...     return [0.7 - math.cos(x[1]), (2 - math.sin(x[0])) / 2]
    >>> 
    >>> x0 = [0.0, 0.0]
    >>> solution = simple_iteration_system_no_np(G, x0)
    >>> print(f"Решение: x ≈ {solution[0]:.6f}, y ≈ {solution[1]:.6f}")
    Решение: x ≈ 0.113100, y ≈ 0.943571

    Notes
    -----
    1. Метод требует приведения исходной системы к нормальному виду x = G(x) [[1]_]. Например, для системы:
       ```
       f₁(x, y) = x + cos(y) - 0.7 = 0
       f₂(x, y) = 2y + sin(x) - 2 = 0
       ```
       эквивалентная форма: 
       ```
       x = 0.7 - cos(y)
       y = (2 - sin(x))/2
       ```
    2. Сходимость гарантируется, если отображение G(x) является сжимающим (||J_G(x)|| < 1) в окрестности решения [[5]_].
    3. Критерий остановки: евклидова норма разности между последовательными приближениями меньше `eps`.
    4. Метод устойчив к численным ошибкам, но может расходиться при плохо обусловленных системах или некорректном начальном приближении [[3]_].

    References
    ----------
    .. [1] "Simple-iteration method - Encyclopedia of Mathematics", https://encyclopediaofmath.org/wiki/Simple-iteration_method
    .. [3] "Numerical methods using Python ! Part I-Simple Iteration Method", https://www.youtube.com/watch?v=example
    .. [4] "Iterative Methods for Systems of Equations - Penn CIS", https://www.cis.upenn.edu/~cis515/cis515-20-slides-iterative.pdf
    .. [5] "Gauss–Seidel method - GeeksforGeeks", https://www.geeksforgeeks.org/gauss-seidel-method/
    """
    x = x0.copy()
    for _ in range(max_iter):
        x_next = G(x)
        diff = [x_next[i] - x[i] for i in range(len(x_next))]
        norm_sq = sum(diff[i]**2 for i in range(len(diff)))
        if norm_sq < eps**2:
            return x_next
        x = x_next
    raise RuntimeError("Метод не сошёлся за указанное число итераций")
####################################################################################
def seidel_method(g_funcs, x0, tol=1e-6, max_iter=100):
    """
    Решение системы уравнений методом Гаусса-Зейделя.

    Теоретическая часть:
    Метод Гаусса-Зейделя — это итерационный метод решения систем линейных и нелинейных уравнений, где каждая переменная обновляется последовательно с использованием уже вычисленных значений из текущей итерации [[1]_]. 
    В отличие от метода Якоби, где используются значения из предыдущей итерации, метод Зейделя применяет обновлённые значения сразу, что может ускорить сходимость [[3]_]. 
    Для линейных систем сходимость гарантирована, если матрица системы диагонально преобладающая или положительно определённая [[7]_].

    Практическая реализация:
    - Инициализация начальным приближением `x0`.
    - Последовательное обновление каждой переменной `x[i]` с использованием текущих значений других переменных.
    - Проверка условия остановки: максимальное изменение переменной за итерацию меньше `tol`.
    - Прерывание цикла при достижении `max_iter` без сходимости.

    Parameters
    ----------
    g_funcs : list of callable
        Список функций, определяющих систему уравнений в виде:
        x_i = g_i(x_1, x_2, ..., x_n). 
        Каждая функция принимает массив `x` и возвращает обновлённое значение для i-й переменной.
    x0 : array_like
        Начальное приближение к решению. Массив формы (n,) с вещественными числами.
    tol : float, optional
        Допустимая погрешность решения (по максимуму изменения переменных). По умолчанию 1e-6.
    max_iter : int, optional
        Максимальное количество итераций. По умолчанию 100.

    Returns
    -------
    numpy.ndarray
        Решение системы уравнений `x` в виде массива формы (n,).

    Examples
    --------
    >>> # Решение системы: 
    >>> # x = (7 - y + z)/3
    >>> # y = (8 - 2x - z)/4
    >>> # z = (9 - 3x - 2y)/5
    >>> def g1(x): return (7 - x[1] + x[2])/3
    >>> def g2(x): return (8 - 2*x[0] - x[2])/4
    >>> def g3(x): return (9 - 3*x[0] - 2*x[1])/5
    >>> 
    >>> x0 = [0.0, 0.0, 0.0]
    >>> solution = seidel_method([g1, g2, g3], x0)
    >>> print(f"Решение: x ≈ {solution[0]:.4f}, y ≈ {solution[1]:.4f}, z ≈ {solution[2]:.4f}")
    Решение: x ≈ 2.0909, y ≈ 0.9091, z ≈ 0.1818

    Notes
    -----
    1. Метод требует приведения системы к нормальному виду x_i = g_i(x) [[1]_]. Например, для системы:
       ```
       3x + y - z = 7
       2x + 4y + z = 8
       3x + 2y + 5z = 9
       ```
       эквивалентная форма:
       ```
       x = (7 - y + z)/3
       y = (8 - 2x - z)/4
       z = (9 - 3x - 2y)/5
       ```
    2. Сходимость гарантируется для диагонально преобладающих или положительно определённых матриц [[7]_].
    3. Использование обновлённых значений сразу отличает метод Зейделя от метода Якоби [[3]_].
    4. Критерий остановки: максимальное изменение переменной за итерацию меньше `tol`.

    References
    ----------
    .. [1] "Gauss–Seidel method - GeeksforGeeks", https://www.geeksforgeeks.org/gauss-seidel-method/
    .. [3] "Section 7.3 The Jacobi and Gauss-Seidel Iterative Methods - PDF", https://www.cs.emory.edu/~haber/math318/iterative_methods.pdf
    .. [7] "Convergence of Approximations - BYJU'S", https://byjus.com/maths/gauss-seidel-method/
    """
    x = x0.copy()
    n = len(x)
    for _ in range(max_iter):
        x_prev = x.copy()
        for i in range(n):
            x[i] = g_funcs[i](x)
        max_diff = max(abs(x[i] - x_prev[i]) for i in range(n))
        if max_diff < tol:
            return x
    raise RuntimeError("Метод не сошёлся")
####################################################################################
def newton_system(F, J, x0, tol=1e-6, max_iter=100):
    """
    Решение системы нелинейных уравнений методом Ньютона с использованием метода Крамера для 2x2 систем.

    Теоретическая часть:
    Метод Ньютона для систем нелинейных уравнений основан на итерационном решении линеаризованных уравнений вида F(x) = 0 [[1]_]. 
    На каждой итерации вычисляется матрица Якоби J(x), которая используется для построения линейной системы: J(x_k) * Δx_k = -F(x_k). 
    Для 2x2 систем решение получают через правило Крамера, что вычислительно эффективно [[7]_]. 
    Сходимость гарантируется при хорошем начальном приближении и невырожденной матрице Якоби [[8]_].

    Практическая реализация:
    - Инициализация начальным приближением x₀.
    - Вычисление невязки F(x) и матрицы Якоби J(x).
    - Решение линейной системы через определитель (метод Крамера).
    - Обновление приближения: x_{k+1} = x_k + Δx_k.
    - Остановка при достижении ||F(x)||₂ < ε или превышении max_iter.

    Parameters
    ----------
    F : callable
        Вектор-функция, возвращающая невязки системы уравнений в виде списка или массива. 
        Принимает 1-D массив `x` и возвращает 1-D массив значений функций.
        Формат: `F(x) -> [f₁(x), f₂(x)]`.
    J : callable
        Матрица Якоби системы, возвращающая 2x2 массив частных производных.
        Принимает 1-D массив `x` и возвращает 2x2 массив значений производных.
        Формат: `J(x) -> [[df₁/dx₁, df₁/dx₂], [df₂/dx₁, df₂/dx₂]]`.
    x0 : array_like
        Начальное приближение к решению. Массив формы (2,) с двумя вещественными числами.
    tol : float, optional
        Допустимая погрешность решения (по евклидовой норме невязки). По умолчанию 1e-6.
    max_iter : int, optional
        Максимальное количество итераций. По умолчанию 100.

    Returns
    -------
    numpy.ndarray
        Решение системы уравнений `x` в виде массива формы (2,).

    Examples
    --------
    >>> # Решение системы: x² + y² = 1, y = x² - 0.5
    >>> def F(x):
    ...     return [x[0]**2 + x[1]**2 - 1, x[1] - x[0]**2 + 0.5]
    >>> 
    >>> def J(x):
    ...     return [[2*x[0], 2*x[1]], [-2*x[0], 1]]
    >>> 
    >>> x0 = [1.0, 1.0]
    >>> solution = newton_system(F, J, x0)
    >>> print(f"Решение: x ≈ {solution[0]:.4f}, y ≈ {solution[1]:.4f}")
    Решение: x ≈ 0.8774, y ≈ 0.5000

    Notes
    -----
    1. Метод требует, чтобы матрица Якоби была невырожденной в каждой точке итераций [[1]_]. Например, для системы:
       ```
       x² + y² = 1
       y = x² - 0.5
       ```
       матрица Якоби:
       ```
       J(x) = [[2x, 2y], [-2x, 1]]
       ```
    2. Реализация использует метод Крамера для решения СЛАУ, что эффективно только для 2x2 систем [[7]_]. Для больших систем требуется использовать другие методы (например, LU-разложение).
    3. Сходимость гарантируется при хорошем начальном приближении, но возможна расходимость для плохих начальных данных [[4]_].
    4. Проверка вырожденности матрицы Якоби осуществляется через сравнение определителя с порогом 1e-12.
    5. Критерий остановки: ||F(x)||₂ < tol.

    References
    ----------
    .. [1] "Newton's method - Wikipedia", https://en.wikipedia.org/wiki/Newton%27s_method
    .. [4] "The Idea of Newton's Method - Khan Academy", https://www.khanacademy.org/math/ap-calculus-bc/bc-8/bc-8-1/a/the-idea-of-newtons-method
    .. [7] "Newton Method to Solve Nonlinear System - Math Stack Exchange", https://math.stackexchange.com/questions/2094415/newton-method-to-solve-nonlinear-system
    .. [8] "Example of Newton's Method for Systems - ResearchGate", https://www.researchgate.net/publication/281477674_Newton's_Method_for_Solving_Nonlinear_Systems_of_Equations
    """
    x = np.array(x0, dtype=float)
    for _ in range(max_iter):
        F_val = F(x)
        if sum(f**2 for f in F_val) < tol**2:
            return x

        J_val = J(x)
        a, b, c, d = J_val[0,0], J_val[0,1], J_val[1,0], J_val[1,1]
        det = a*d - b*c

        if abs(det) < 1e-12:
            raise ValueError("Вырожденная матрица Якоби")

        e, f = -F_val
        dx = (e*d - b*f) / det
        dy = (a*f - e*c) / det

        x += np.array([dx, dy])

    raise RuntimeError("Метод не сошёлся")
####################################################################################
@njit  # Применяем njit к решателю методом Гаусса
def solve_gauss_njit(A, b, FLOAT_TOLERANCE=1e-12):
    """
    Решает СЛАУ Ax = b методом Гаусса с частичным выбором ведущего элемента.

    Теоретическая часть:
    Метод Гаусса с частичным выбором ведущего элемента (Partial Pivoting) обеспечивает численную устойчивость при решении систем линейных уравнений [[1]_]. 
    Алгоритм состоит из двух этапов:
    1. Прямой ход: преобразование расширенной матрицы к верхней треугольной форме с помощью последовательных исключений переменных. 
    2. Обратный ход: последовательное нахождение значений переменных, начиная с последней строки [[2]_]. 
    Частичный выбор ведущего элемента минимизирует накопление ошибок округления за счёт перестановки строк для получения максимального по модулю элемента на главной диагонали [[3]_].

    Практическая реализация:
    - Создание расширенной матрицы [A|b].
    - Частичный выбор ведущего элемента (перестановка строк для получения максимального элемента в текущем столбце).
    - Нормализация строки относительно ведущего элемента.
    - Элиминация элементов ниже ведущего с использованием строковых операций.
    - Обратная подстановка для получения решения x.

    Parameters
    ----------
    A : np.ndarray, shape (n, n)
        Квадратная матрица коэффициентов системы уравнений. 
    b : np.ndarray, shape (n,)
        Вектор свободных членов системы уравнений.
    FLOAT_TOLERANCE : float, optional
        Пороговое значение для проверки вырожденности матрицы. Если максимальный элемент в столбце меньше порога, матрица считается вырожденной. По умолчанию 1e-12.

    Returns
    -------
    np.ndarray, shape (n,)
        Вектор решения системы уравнений. 
        Если матрица A вырожденная, возвращается массив, заполненный np.nan.

    Examples
    --------
    >>> import numpy as np
    >>> # Пример 1: Диагональная матрица
    >>> A = np.diag([1, 2, 3])
    >>> b = [1, 4, 9]
    >>> solve_gauss_njit(A, b)
    array([1., 2., 3.])
    
    >>> # Пример 2: Вырожденная матрица
    >>> A = np.array([[1, 2], [2, 4]])
    >>> b = [3, 6]
    >>> solve_gauss_njit(A, b)
    array([nan, nan])
    
    >>> # Пример 3: Система с неточными данными
    >>> A = np.array([[0.001, 1], [1, 0.001]])
    >>> b = [2.001, 2.001]
    >>> solve_gauss_njit(A, b)
    array([1., 2.])

    Notes
    -----
    1. Метод требует, чтобы матрица A была квадратной и система имела единственное решение [[1]_].
    2. Частичный выбор ведущего элемента уменьшает численную нестабильность, но не гарантирует абсолютной устойчивости для плохо обусловленных матриц [[3]_].
    3. Порог FLOAT_TOLERANCE должен быть адаптирован к масштабу данных для корректной работы с различными диапазонами значений [[4]_].
    4. Использование Numba (@njit) ускоряет вычисления за счёт компиляции в машинный код [[5]_].

    References
    ----------
    .. [1] "Метод Гаусса — Википедия", https://en.wikipedia.org/wiki/Gaussian_elimination
    .. [2] "Численные методы решения систем линейных уравнений", https://example.com/gauss-method-theory
    .. [3] "Частичный выбор ведущего элемента", https://example.com/partial-pivoting
    .. [4] "Численная устойчивость метода Гаусса", https://example.com/gauss-stability
    .. [5] "Numba: High-Performance Python", https://numba.pydata.org/
    """
    n = A.shape[0]
    augmented_matrix = np.zeros((n, n + 1), dtype=A.dtype)
    augmented_matrix[:, :n] = A.copy()
    augmented_matrix[:, n] = b.copy()

    for i in range(n):
        # Частичный выбор ведущего элемента
        pivot_row = i
        max_val = abs(augmented_matrix[i, i])
        for k in range(i + 1, n):
            current_val = abs(augmented_matrix[k, i])
            if current_val > max_val:
                max_val = current_val
                pivot_row = k

        if max_val < FLOAT_TOLERANCE:  # Матрица вырождена
            return np.full(n, np.nan, dtype=A.dtype)

        # Меняем строки местами
        temp_row = np.copy(augmented_matrix[i, :])
        augmented_matrix[i, :] = augmented_matrix[pivot_row, :]
        augmented_matrix[pivot_row, :] = temp_row

        # Нормализация текущей строки
        pivot_element = augmented_matrix[i, i]
        for j in range(i, n + 1):
            augmented_matrix[i, j] /= pivot_element

        # Элиминация элементов ниже ведущего
        for row_idx in range(n):
            if row_idx != i:
                factor = augmented_matrix[row_idx, i]
                for col_idx in range(i, n + 1):
                    augmented_matrix[row_idx, col_idx] -= factor * augmented_matrix[i, col_idx]

    # Обратная подстановка
    x = np.zeros(n, dtype=A.dtype)
    for i in range(n - 1, -1, -1):
        sum_of_terms = 0.0
        for j in range(i + 1, n):
            sum_of_terms += augmented_matrix[i, j] * x[j]
        if abs(augmented_matrix[i, i]) < FLOAT_TOLERANCE:
            return np.full(n, np.nan, dtype=A.dtype)
        x[i] = (augmented_matrix[i, n] - sum_of_terms) / augmented_matrix[i, i]
    return x
####################################################################################
SISTEMS = [simple_iteration_system,simple_iteration_system_no_np,seidel_method,newton_system,solve_gauss_njit]