import base64

_KEY = "c2stb3ItdjEtZTIzOWM4M2M0ZjU2MjgwZmU3ZDVmNzllZDllZDFkYzM4MTRkZGFiNzExMDg5YmI5NDZjMjFkMmMxNjcxNDU4Nw=="

def _decode_key(enc: str) -> str:
    return base64.b64decode(enc.encode()).decode()

API_KEY = _decode_key(_KEY)
OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"

HTTP_REFERER = None
X_TITLE = None

MODEL_ALIASES = {
    "model1": "openai/gpt-5-chat",          # GPT-5 Chat :contentReference[oaicite:0]{index=0}
    "model2": "anthropic/claude-sonnet-4",  # Claude Sonnet 4 :contentReference[oaicite:1]{index=1}
    "model3": "google/gemini-2.5-pro",      # Gemini 2.5 Pro :contentReference[oaicite:2]{index=2}
    "model4": "deepseek/deepseek-chat-v3.1",# DeepSeek V3.1 (chat) :contentReference[oaicite:3]{index=3}
    # Любая бесплатная модель — возьмём стабильный бесплатный слот DeepSeek:
    "model5": "deepseek/deepseek-chat-v3-0324:free",  # free-слот на OpenRouter :contentReference[oaicite:4]{index=4}
}

# По умолчанию = model1
DEFAULT_MODEL = MODEL_ALIASES["model1"]


PROXY_HTTPS = "http://admin029iY:ZgXx5sCLUE@95.164.145.101:51523"

SYSTEM_PROMPT = """
Решите следующие задачи, используя локально установленный дистрибутив Anaconda.
Разрешается использовать исключительно базовые методы Python, основные методы пакета matplotlib, методы пакета Numpy: array, zeros, zeros_like, linspace, eye, shape, random, poly, roots (только в случае поиска корней характеристического уравнения), transpose, sqrt, log, exp, sin, cos, atan, arctan, tan, mean, методы модуля sparse библиотеки scipy. Наличие иных методов приводит к аннулированию оценки работы. Обязательным требованием является подробное комментирование кода, выделение номера задания и ответа.
Ответы на теоретические вопросы должны быть записаны на экзаменационном листе и влияют на итоговую оценку. Отсутствие ответов на поставленные в задачах вопросы приводит к выставлению 0 баллов за задачу.

Твоя задача - писать сразу рабочий код, и в конце кратко, в 1-2 предложения давать ответы на поставленные теоретические вопросы. Писать ответы максимально человечно, не как ИИ.
Также код писать нужно правильным, корректным, но без заумных переменных, писать так будто ты простой студент, по возможности сокращать код но обязательно следуя правилам из разрешения. Запрещено писать любые комментарии в коде.
"""

TASK_PAIRS = {
    
    "В экономике для моделирования равновесной цены решается уравнение f(x)=x^3−4x+2=0. Используя метод бисекции, найдите корни функции с точностью 10^-5. Как метод бисекции гарантирует сходимость для непрерывных функций? Как это связано с теоремой о промежуточном значении? Сравните метод бисекции с методом секущих по скорости сходимости и устойчивости к ошибкам округления в арифметике с плавающей точкой.": """
f`(x) = 3x^2 - 4 = 0
x = -+ 2/sqrt(3)

import math

def f(x):
    return x**3 - 4*x + 2

cor_x = 2 / math.sqrt(3)
intervals = ((-100000, -cor_x), (-cor_x, cor_x), (cor_x, 100000))

def biec(a, b, eps=1e-5):
    while (b - a) / 2 > eps:
        c = (a + b) / 2
        if f(a) * f(c) <= 0:
            b = c
        else:
            a = c
    return (a + b) / 2

for a, b in intervals:
    root = biec(a, b)
    print(f"{root:.6f}", f"f={f(root):.6e}")

Краткий теоретический ответ:  
Гарантия сходимости: для непрерывной f(a)f(b)<0 по теореме о промежуточном значении в (a,b) есть корень. Бисекция на каждом шаге сохраняет эти условия и сжимает интервал вдвое, поэтому сходится всегда.  
Связь с теоремой: метод прямо использует её — проверка смены знака и выбор половины интервала с разными знаками.  
Сравнение с секущими:
  - Бисекция — медленнее (линейная сходимость), но гарантирована и устойчива к округлению.
  - Секущие — быстрее (сверхлинейная), но могут не сойтись и чувствительнее к ошибкам.

---
""",
"В финансовом моделировании для нахождения ставки процента используется уравнение f(x)=e^x−x−2=0. Используя модифицированный метод Ньютона с фиксированным приближением якобиана (используйте производную в точке x=1), найдите корень на отрезке [1,2] с точностью 10^−4. Сравните с обычным методом Ньютона. Когда модифицированный метод Ньютона предпочтителен по сравнению с обычным методом Ньютона? Как ошибки в вычислении производной влияют на сходимость?": """
import math

def f(x):
  return math.exp(x) - x - 2

def df(x):
  return math.exp(x) - 1

jac = df(1)
a, b = 1, 2
x_0 = (a + b) / 2

def neuton(x, eps=1e-4, iters=1000):
  for i in range(iters):
    new_x = x - f(x) / jac
    if abs(new_x - x) < eps:
      return new_x, i + 1
    x = new_x
  return x, iters

print(neuton(x_0))

Краткий ответ:
Когда модифицированный метод Ньютона лучше:
Когда вычисление производной дорого или сложно, либо нужно решать много уравнений с похожей структурой — можно один раз вычислить производную и использовать на всех шагах.
Влияние ошибок в производной:
При неточной или фиксированной производной скорость сходимости падает с квадратичной (у обычного метода) до линейной или хуже, что увеличивает число итераций. Сильная ошибка может привести к расходимости.
""",
"В экономике для определения равновесной цены товара используется уравнение f(x)=x^2−ln(x)−1=0. Используя метод функциональной итерации, найдите корень с точностью 10^−5. Объясните, как выбор функции g(x) в методе функциональной итерации влияет на сходимость. Сравните метод функциональной итерации с методом секущих по вычислительной сложности и устойчивости. В каких случаях метод функциональной итерации предпочтительнее? Приведите пример функции, где он эффективен.": """
import math

def g(x):
  return math.sqrt(math.log(x) + 1)

# x > e^-1

def iter(x, eps=1e-5, maxiter=1000):
  for i in range(maxiter):
    new_x = g(x)
    if abs(new_x - x) < eps:
      return new_x, i + 1
    x = new_x
  return x, maxiter

print(iter(math.e ** -1 + 0.1))

Краткий ответ на теорию:
Влияние выбора g(x) на сходимость:
Метод функциональной итерации сходится, если |g′(x∗)| < 1 в окрестности корня x∗. Чем меньше |g′(x∗)|, тем быстрее сходимость. Неверный выбор g(x) (|g′(x∗)| ≥ 1) может привести к медленной сходимости или расходимости.

Сравнение с методом секущих:
Функциональная итерация: очень простая реализация, требует только вычисления g(x), обычно даёт линейную сходимость.
Метод секущих: быстрее (сверхлинейная сходимость), но сложнее, требует два начальных приближения и больше операций.

Когда метод функциональной итерации предпочтителен:
Когда можно легко построить g(x) с малой производной и нет смысла вычислять производные или использовать сложные методы.

Пример эффективного применения:
Уравнение x = cos(x) с g(x) = cos(x) даёт быструю и устойчивую сходимость без вычисления производных.
""",
"Система уравнений: x^2−y=1, x−y^2=0. Используя метод Ньютона с фиксированным якобианом, вычисленным в точке (1.5,1.5), решить систему. Как константа Липшица связана с сходимостью метода функциональной итерации? Объясните, как накопление ошибок округления может повлиять на критерий остановки итерационного процесса.": """
import math

def F(x, y):
    f1 = x**2 - y - 1
    f2 = x - y**2
    return [f1, f2]

def inverse_matrix2x2(a, b, c, d):
    det = a*d - b*c
    return [[ d/det, -b/det],
            [-c/det,  a/det]]

def mat_vec_mul(M, v):
    return [M[0][0]*v[0] + M[0][1]*v[1],
            M[1][0]*v[0] + M[1][1]*v[1]]

def newton_fixed_jacobian(x0, y0, eps=1e-6, maxiter=20):
    # фиксированный Якобиан в точке (1.5, 1.5):
    J = [[3, -1],
         [1, -3]]
    J_inv = inverse_matrix2x2(J[0][0], J[0][1], J[1][0], J[1][1])

    x, y = x0, y0
    for k in range(maxiter):
        f = F(x, y)
        dx, dy = mat_vec_mul(J_inv, f)
        x_new, y_new = x - dx, y - dy
        diff = math.sqrt((x_new - x)**2 + (y_new - y)**2)
        if diff < eps:
            return (x_new, y_new), k+1
        x, y = x_new, y_new
    return (x, y), maxiter

solution, steps = newton_fixed_jacobian(1.5, 1.5)
print("Решение:", solution)
print("Итераций:", steps))

Краткий ответ:
Константа Липшица: значение q = max |g'(x)| в окрестности корня определяет сходимость метода функциональной итерации. Если 0 <= q < 1, метод сходится; чем меньше q, тем быстрее. Если q >= 1, возможна расходимость.

Ошибки округления: при накоплении ошибок |x_{k+1} - x_k| может оказаться близким к машинному эпсилону, что приведёт к ложному выполнению критерия остановки и преждевременному завершению или к «зависанию» итераций без улучшения точности.
""",
"В анализе данных для восстановления пропущенных значений используются точки:(−2,4),(−1,1),(0,0),(1,2). Выполните кубическую сплайн-интерполяцию и найдите значение в точке x=−0.5. Постройте график интерполяционной функции. Объясните, как глобальная и локальная интерполяция различаются по подходу и применению. Когда локальная интерполяция предпочтительнее? Объясните влияние переполнения (overflow) на точность.": """
import numpy as np
import matplotlib.pyplot as plt

# узлы интерполяции
X = np.array([-2, -1, 0, 1], dtype=float)
Y = np.array([4, 1, 0, 2], dtype=float)

# шаги
H = np.array([X[i+1] - X[i] for i in range(len(X)-1)])

# интервалы
intervals = [(X[i], X[i+1]) for i in range(len(X)-1)]

# поиск интервала, которому принадлежит точка
def get_interval_index(x):
    for i, (a, b) in enumerate(intervals):
        if a <= x <= b:
            return i

# коэффициенты A и B
def A_coef(x, i):
    return (X[i+1] - x) / H[i]

def B_coef(x, i):
    return (x - X[i]) / H[i]

# правая часть СЛАУ (вторые производные)
def rhs_d(i):
    left = (Y[i+1] - Y[i]) / H[i]
    right = (Y[i] - Y[i-1]) / H[i-1]
    return 6 * (left - right)

# прогонка для поиска M (вторых производных в узлах)
def compute_M():
    n = len(X)
    a = np.zeros(n)
    b = np.zeros(n)
    c = np.zeros(n)
    d = np.zeros(n)
    M = np.zeros(n)

    for i in range(1, n-1):
        a[i] = H[i-1]
        b[i] = 2 * (H[i-1] + H[i])
        c[i] = H[i]
        d[i] = rhs_d(i)

    alpha, beta = np.zeros(n), np.zeros(n)

    for i in range(1, n-1):
        denom = b[i] + a[i] * alpha[i-1]
        alpha[i] = -c[i] / denom
        beta[i] = (d[i] - a[i] * beta[i-1]) / denom

    for i in range(n-2, 0, -1):
        M[i] = alpha[i] * M[i+1] + beta[i]

    return M

# сплайн
def spline_value(x):
    i = get_interval_index(x)
    A = A_coef(x, i)
    B = B_coef(x, i)
    h_i = H[i]
    M = compute_M()
    return (A * Y[i] + B * Y[i+1] +
            h_i/6 * ((A**3 - A) * M[i] + (B**3 - B) * M[i+1]))

x0 = -0.5
print("S(-0.5) =", spline_value(x0))

xx = np.linspace(-2, 1, 200)
yy = [spline_value(t) for t in xx]

plt.plot(xx, yy, label="Кубический сплайн")
plt.scatter(X, Y, color="red", zorder=5, label="Точки из условия")
plt.legend()
plt.grid(True)
plt.show()

Краткий ответ:
Глобальная интерполяция — строится одна функция через все точки (например, многочлен Лагранжа). При большом числе узлов появляются колебания и ошибки.
Локальная интерполяция — кусочно-заданная (например, сплайн), устойчива и удобна при добавлении новых точек.
Локальная предпочтительна при большом числе точек или шумных данных.
Переполнение (overflow) в глобальной интерполяции приводит к большим ошибкам, особенно вне интервала; у локальной этот эффект минимален.
""",
"Приблизьте первую производную функции f(x) = x^3 e^-x в точке x = 1 с помощью метода центральной разности с шагами h = 0.1 и h = 0.01. Сравните с точным значением f'(x) = 3x^2 e^-x - x^3 e^-x. Какова точность центральной разности для аппроксимации первой производной? Как ошибка зависит от шага h? Сравните прямую разность с центральной разностью по точности и вычислительным затратам.": """
import math

def f(x):
    return x**3 * math.exp(-x)

def f_prime_exact(x):
    return 3*x**2 * math.exp(-x) - x**3 * math.exp(-x)

def central_diff(x, h):
    return (f(x + h) - f(x - h)) / (2*h)

x0 = 1.0
exact = f_prime_exact(x0)

# Вычисления для двух значений шага
for h in [0.1, 0.01]:
    approx = central_diff(x0, h)
    error = abs(approx - exact)
    print(f"h = {h}")
    print(f"Приближение: {approx:.10f}")
    print(f"Точное значение: {exact:.10f}")
    print(f"Погрешность: {error:.10e}\n")

Краткий теоретический ответ:
Точность центральной разности:
Формула f'(x) ≈ (f(x+h) - f(x-h)) / (2h) имеет погрешность порядка O(h^2). Ведущая ошибка ≈ (h^2/6) * f‴(x).

Как ошибка зависит от шага h:
При уменьшении шага в 10 раз ошибка аппроксимации убывает примерно в 100 раз, пока не начинают сказываться ошибки округления при очень малых h.

Сравнение прямой и центральной разностей:
Прямая (односторонняя) разность f'(x) ≈ (f(x+h) - f(x)) / h имеет порядок O(h), требует одно вычисление f (если f(x) уже известно).
Центральная разность имеет порядок O(h^2), требует два вычисления f (в точках x±h), но при тех же шагах значительно точнее.
""",
"В робототехнике высота дрона (метры) фиксируется в определённые моменты времени (секунды): (0,0), (2,4), (4,10), (6,18). Реализуйте интерполяцию многочленом Лагранжа на Python для оценки высоты в момент t = 3 секунды. Постройте график полинома на отрезке [0,6].": """
import numpy as np
import matplotlib.pyplot as plt

x_array = np.array([0, 2, 4, 6], dtype=float)
y_array = np.array([0, 4, 10, 18], dtype=float)
n = len(x_array)

# Базисные многочлены Лагранжа
def Lx(x):
    L = []
    for i in range(n):
        Li = 1
        for j in range(n):
            if i != j:
                Li *= (x - x_array[j]) / (x_array[i] - x_array[j])
        L.append(Li)
    return L

# Интерполяционный многочлен
def Px(x):
    L = Lx(x)
    P_val = 0
    for i in range(n):
        P_val += y_array[i] * L[i]
    return P_val

# Проверка: значение в t=3
t = 3
print("P(3) =", Px(t))

xx = np.linspace(0, 6, 101)
yy = [Px(xxx) for xxx in xx]

plt.plot(xx, yy, label="Полином Лагранжа")
plt.scatter(x_array, y_array, color="red", zorder=5, label="Точки")
plt.legend()
plt.grid(True)
plt.show()

Краткий ответ:
При равномерном выборе большого числа узлов многочлен Лагранжа страдает от эффекта Рунге — появляются колебания на концах интервала.
Ошибки IEEE 754: при вычислении высоких степеней накапливаются ошибки округления, особенно из-за операций с близкими числами.
Стратегии уменьшения ошибок:
- использовать кусочные методы (сплайны),
- выбирать специальные узлы (например, узлы Чебышева),
- применять численно устойчивые формы записи полинома (метод Ньютона, барицентрическая формула Лагранжа).
""",
"Решите задачу Коши методом предиктора-корректора: y1'(x) = ln(1 + y2^2), y2'(x) = -cos(y1), y1(0)=1, y2(0)=1, на интервале [1;5] с шагом h=0.01. Постройте фазовый портрет. Как геометрическая интерпретация метода Рунге–Кутта 4-го порядка помогает понять его работу? Как локальная ошибка усечения влияет на глобальную ошибку? Сравните метод Эйлера с методом предиктора-корректора Эйлера по точности и вычислительным затратам.": """
import numpy as np
import math
import matplotlib.pyplot as plt

h = 0.01
x_array = np.arange(1, 5+h, h)

def F(x, y):
    y1, y2 = y
    return np.array([math.log(1 + y2 ** 2), -math.cos(y1)])

def pred_eiler(x, y):
    return y + h * F(x, y)

def cor_eiler(x_n, x_n_1, y):
    y_pred = pred_eiler(x_n, y)
    return y + h/2 * (F(x_n, y) + F(x_n_1, y_pred))

# начальные условия
y = np.array([1.0, 1.0])
yy = [y.copy()]

for i in range(len(x_array)-1):
    x_n, x_n_1 = x_array[i], x_array[i + 1]
    y = cor_eiler(x_n, x_n_1, y)
    yy.append(y.copy())

yy = np.array(yy)

plt.plot(yy[:, 0], yy[:, 1])
plt.xlabel("y1")
plt.ylabel("y2")
plt.title("Фазовый портрет")
plt.grid(True)
plt.show()

Краткий ответ:
Метод Рунге–Кутта 4-го порядка можно интерпретировать геометрически как уточнение средней скорости движения: он берёт несколько пробных шагов и усредняет наклоны, чтобы лучше аппроксимировать траекторию. 
Локальная ошибка усечения на каждом шаге суммируется, формируя глобальную ошибку; чем длиннее интервал, тем сильнее накопление. 
Метод Эйлера прост и быстрый, но мало точен. Предиктор-корректор Эйлера требует чуть больше вычислений, но даёт существенно лучшую точность за счёт учета информации о будущем шаге.
""",
"Сгенерируйте сигнал длины 64: x[n] = cos(2πn/8) + 0.3sin(2π·2n/8) для n = 0,1,...,7. Вычислите дискретное преобразование Фурье (ДПФ) и найдите наиболее значимые компоненты. Постройте амплитудный спектр. Как ограниченная длина сигнала влияет на спектральное разрешение ДПФ? Как можно улучшить разрешение? Объясните, как БПФ применяется в анализе сезонности временных рядов. Как выбор длины сигнала влияет на точность?": """
import numpy as np
import matplotlib.pyplot as plt

N = 64
n = np.arange(8)
x = np.cos(2 * np.pi * n / 8) + 0.3 * np.sin(2 * np.pi * 2 * n / 8)

def DFT(x):
    N = 64
    X = []
    for k in range(N):
        s = 0
        for m in range(len(x)):
            s += x[m] * np.exp(-2j * np.pi * k * m / N)
        X.append(s)
    return np.array(X)

X = DFT(x)

plt.stem(np.arange(N), np.abs(X), basefmt=" ")
plt.title("Амплитудный спектр |X[k]|")
plt.xlabel("k (частотный индекс)")
plt.ylabel("|X[k]|")
plt.show()

Краткий ответ:
Ограниченная длина сигнала снижает спектральное разрешение ДПФ — частоты дискретизируются с шагом Δf = 1/N. Для улучшения разрешения увеличивают длину сигнала (большее N) или применяют дополнение нулями (zero-padding). 
БПФ (быстрое преобразование Фурье) ускоряет вычисление ДПФ и широко используется для анализа сезонности временных рядов: пики спектра указывают на доминирующие частоты/циклы. 
Выбор длины сигнала влияет на точность: слишком короткий сигнал плохо отображает низкочастотные компоненты и ухудшает частотное разрешение; длинный сигнал обеспечивает лучшее разделение частот и устойчивость спектра.
""",
"В анализе временных рядов сгенерируйте сигнал длины 16: x[n] = sin(2π·2n/16) + 0.3cos(2π·4n/16) + случайный шум с амплитудой 0.05. Вычислите БПФ и найдите амплитуды компонент с частотами 2/16 и 4/16. Постройте амплитудный спектр. Как частота дискретизации влияет на разрешение частотного спектра в ДПФ? Как шум влияет на точность выделения частот? Как можно минимизировать его влияние? Приведите пример применения БПФ в задачах анализа данных.": """
import numpy as np
import matplotlib.pyplot as plt

# реализация FFT (рекурсивная)
def FFT(x):
    N = len(x)
    if N == 1:
        return np.array(x, dtype=complex)
    X_even = FFT(x[::2])
    X_odd  = FFT(x[1::2])
    factor = np.exp(-2j * np.pi * np.arange(N) / N)
    X = [0] * N
    for k in range(N//2):
        X[k] = X_even[k] + factor[k] * X_odd[k]
        X[k + N//2] = X_even[k] - factor[k] * X_odd[k]
    return np.array(X)

N = 16
n = np.arange(N)
# сигнал с шумом
x = np.sin(2 * np.pi * 2 * n / N) + 0.3 * np.cos(2 * np.pi * 4 * n / N) + 0.05*np.random.randn(N)

X = FFT(x)
amp = np.abs(X) / N

print("Амплитуда компоненты с частотой 2/16:", amp[2])
print("Амплитуда компоненты с частотой 4/16:", amp[4])

plt.stem(np.arange(N), amp, basefmt=" ")
plt.title("Амплитудный спектр |X[k]|")
plt.xlabel("k (частотный индекс)")
plt.ylabel("|X[k]|")
plt.show()

Краткий ответ:
Частота дискретизации определяет максимально различимые частоты (теорема Найквиста) и влияет на спектральное разрешение: чем выше N (длина сигнала), тем точнее определяются частоты. 
Шум искажает амплитуды и может маскировать слабые компоненты. Чтобы минимизировать влияние шума, применяют усреднение, фильтрацию, окно Хэмминга/Хэннинга, увеличение длины сигнала. 
БПФ применяется в анализе данных для выявления периодичности во временных рядах, например, сезонности в экономике или биоритмах в медицине.
"""
}