numcheat 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
numcheat/core.py ADDED
@@ -0,0 +1,1956 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ CHEATSHEET по численным методам — быстрый доступ к коду и теории по понятному имени.
4
+ Готова к превращению в библиотеку (всё хранится в словарях PRACTICE / THEORY).
5
+
6
+ Как пользоваться (в Jupyter или python):
7
+ from cheatsheet import code, theory, find, ls, show
8
+
9
+ ls() # показать всё, что есть (практика и теория)
10
+ find("шур") # найти, где искать по ключевому слову из билета
11
+ code("предиктор-корректор") # печатает готовый код И кладёт его в буфер обмена
12
+ theory("штрассен") # печатает ответ на теорвопрос И кладёт в буфер
13
+ show("ньютон") # просто посмотреть код на экране (без копирования)
14
+
15
+ Русские синонимы (для совместимости): код=code, теория=theory, найти=find, список=ls, показать=show.
16
+
17
+ Практика (code) и теория разделены: code(...) -> реализация, theory(...) -> текст ответа.
18
+ Весь код использует только разрешённые на экзамене средства (без классов, без np.dot/linalg и т.п.).
19
+ """
20
+
21
+ import subprocess
22
+
23
+
24
+ # ---------------------------------------------------------------- буфер обмена
25
+ def _copy(text):
26
+ """Кладёт текст в буфер обмена. Сначала pyperclip, затем macOS pbcopy."""
27
+ try:
28
+ import pyperclip
29
+ pyperclip.copy(text)
30
+ return True
31
+ except Exception:
32
+ try:
33
+ subprocess.run(["pbcopy"], input=text.encode("utf-8"), check=True)
34
+ return True
35
+ except Exception:
36
+ return False
37
+
38
+
39
+ # ============================================================ ОБЩИЕ ПОМОЩНИКИ
40
+ # Подставляются автоматически к тем методам, которым нужны (матрицы / ОДУ).
41
+ _IMPORTS = "import numpy as np\nimport matplotlib.pyplot as plt\n"
42
+
43
+ _PI = "pi = 4 * np.arctan(1.0) # число пи (np.pi в список разрешённых не входит)\n"
44
+
45
+ _BASE_MATRIX = '''# --- базовые матричные операции (без np.dot/@/linalg) ---
46
+ def dot(u, v): # скалярное произведение векторов
47
+ return sum(u[i] * v[i] for i in range(len(u)))
48
+
49
+ def vec_norm(v): # евклидова норма sqrt(сумма квадратов)
50
+ return np.sqrt(sum(c * c for c in v))
51
+
52
+ def identity(n): # единичная матрица
53
+ I = [[0.0] * n for _ in range(n)]
54
+ for i in range(n):
55
+ I[i][i] = 1.0
56
+ return I
57
+
58
+ def trace(A): # след матрицы
59
+ return sum(A[i][i] for i in range(len(A)))
60
+
61
+ def transpose(A):
62
+ return [[A[j][i] for j in range(len(A))] for i in range(len(A[0]))]
63
+
64
+ def mat_add(A, B):
65
+ return [[A[i][j] + B[i][j] for j in range(len(A[0]))] for i in range(len(A))]
66
+
67
+ def mat_sub(A, B):
68
+ return [[A[i][j] - B[i][j] for j in range(len(A[0]))] for i in range(len(A))]
69
+
70
+ def mat_mul(A, B): # наивное умножение матриц
71
+ n, m, p = len(A), len(B), len(B[0])
72
+ C = [[0.0] * p for _ in range(n)]
73
+ for i in range(n):
74
+ for j in range(p):
75
+ s = 0.0
76
+ for k in range(m):
77
+ s += A[i][k] * B[k][j]
78
+ C[i][j] = s
79
+ return C
80
+
81
+ def mat_vec(A, x): # матрица на вектор -> np.array
82
+ n, m = len(A), len(x)
83
+ return np.array([sum(A[i][k] * x[k] for k in range(m)) for i in range(n)])
84
+ '''
85
+
86
+ _QR_FUNC = '''# --- QR-разложение методом Грама-Шмидта (нужно QR-алгоритму и Шуру) ---
87
+ def qr_gram_schmidt(A):
88
+ A = np.array(A, dtype=float)
89
+ m = len(A); n = len(A[0])
90
+ Q = np.zeros((m, n)); R = np.zeros((n, n))
91
+ for j in range(n):
92
+ v = np.array([A[i][j] for i in range(m)], dtype=float)
93
+ for i in range(j):
94
+ qi = np.array([Q[k][i] for k in range(m)])
95
+ R[i][j] = dot(qi, [A[k][j] for k in range(m)])
96
+ v = v - R[i][j] * qi
97
+ R[j][j] = vec_norm(v)
98
+ for k in range(m):
99
+ Q[k][j] = v[k] / R[j][j]
100
+ return Q, R
101
+ '''
102
+
103
+ _BASE_ODE = '''# --- общее для методов ОДУ ---
104
+ def n_steps(t0, t_end, h):
105
+ return int(round((t_end - t0) / h))
106
+
107
+ def rk4_step(f, t, y, h): # один шаг РК4 (нужен для разгона Адамса)
108
+ k1 = f(t, y)
109
+ k2 = f(t + h/2, y + h/2 * k1)
110
+ k3 = f(t + h/2, y + h/2 * k2)
111
+ k4 = f(t + h, y + h * k3)
112
+ return y + h/6 * (k1 + 2*k2 + 2*k3 + k4)
113
+ '''
114
+
115
+
116
+ # ====================================================================== ПРАКТИКА
117
+ # Единый формат каждого сниппета:
118
+ # --- ПАРАМЕТРЫ (меняй здесь) --- всё, что правят под конкретную задачу из билета
119
+ # --- АЛГОРИТМ (не трогай) --- сам метод, его менять не нужно
120
+ # --- ЗАПУСК --- вызов с твоими параметрами
121
+ # "bases" — какие блоки помощников подставить ("matrix","qr","ode","pi"); они копируются автоматически.
122
+ PRACTICE = {}
123
+
124
+ def _p(keys, bases, code):
125
+ entry = {"bases": bases, "code": code}
126
+ for k in keys:
127
+ PRACTICE[k] = entry
128
+
129
+ # ---- 1. Бисекция ----
130
+ _p(["бисекция", "половинное деление"], [],
131
+ '''# ===== БИСЕКЦИЯ: корень уравнения f(x)=0 делением отрезка пополам =====
132
+
133
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
134
+ def f(x): # уравнение в виде f(x)=0
135
+ return x**3 - 2*x - 2
136
+ a, b = 1, 2 # отрезок [a, b]; на концах f ОБЯЗАНА иметь разные знаки (f(a)*f(b)<0)
137
+ eps = 1e-5 # точность
138
+
139
+ # --- АЛГОРИТМ (не трогай) ---
140
+ def bisect_method(f, a, b, eps):
141
+ while abs(b - a) > eps: # пока отрезок длиннее точности
142
+ c = (a + b) / 2 # середина
143
+ if f(a) * f(c) <= 0: # знак меняется слева -> корень в [a, c]
144
+ b = c
145
+ else: # иначе корень в [c, b]
146
+ a = c
147
+ return (a + b) / 2
148
+
149
+ # --- ЗАПУСК ---
150
+ print("Корень:", bisect_method(f, a, b, eps))
151
+ ''')
152
+
153
+ # ---- 2. Простая итерация (1D) ----
154
+ _p(["простая итерация", "функциональная итерация"], [],
155
+ '''# ===== ПРОСТАЯ ИТЕРАЦИЯ: f(x)=0 приводим к x=phi(x) и повторяем =====
156
+
157
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
158
+ def f(x): # уравнение f(x)=0
159
+ return x**2 - 2
160
+ x0 = 1.4 # стартовое приближение
161
+ psi = -0.5 # параметр: phi(x)=x+psi*f(x); подбери так, чтобы |1+psi*f'(x)|<1
162
+ eps = 1e-6 # точность
163
+
164
+ # --- АЛГОРИТМ (не трогай) ---
165
+ def simple_iteration(f, x, psi, eps, max_iter=1000):
166
+ for _ in range(max_iter):
167
+ x_new = x + psi * f(x) # phi(x) = x + psi*f(x)
168
+ if abs(x_new - x) <= eps:
169
+ return x_new
170
+ x = x_new
171
+ return x
172
+
173
+ # --- ЗАПУСК ---
174
+ print("Корень:", simple_iteration(f, x0, psi, eps))
175
+ ''')
176
+
177
+ # ---- 3. Секущих / хорд ----
178
+ _p(["секущих", "хорд"], [],
179
+ '''# ===== МЕТОД СЕКУЩИХ: производную заменяем разностью по двум точкам =====
180
+
181
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
182
+ def f(x): # уравнение f(x)=0 (производная НЕ нужна)
183
+ return x**2 - 2
184
+ x0, x1 = 1, 2 # ДВА стартовых приближения (нужны оба)
185
+ eps = 1e-6 # точность
186
+
187
+ # --- АЛГОРИТМ (не трогай) ---
188
+ def secant_method(f, x0, x1, eps, max_iter=1000):
189
+ for _ in range(max_iter):
190
+ x2 = x1 - f(x1) * (x1 - x0) / (f(x1) - f(x0))
191
+ if abs(x2 - x1) < eps:
192
+ return x2
193
+ x0, x1 = x1, x2
194
+ return x1
195
+
196
+ # --- ЗАПУСК ---
197
+ print("Корень:", secant_method(f, x0, x1, eps))
198
+ ''')
199
+
200
+ # ---- 4. Ньютон (1D) ----
201
+ _p(["ньютон", "ньютона", "касательных"], [],
202
+ '''# ===== МЕТОД НЬЮТОНА: x_new = x - f(x)/f'(x) =====
203
+
204
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
205
+ def f(x): # уравнение f(x)=0
206
+ return np.exp(x) - x - 2
207
+ def df(x): # производная f'(x) (обязательно правильная!)
208
+ return np.exp(x) - 1
209
+ x0 = 1.0 # стартовое приближение
210
+ eps = 1e-6 # точность
211
+
212
+ # --- АЛГОРИТМ (не трогай) ---
213
+ def newton_method(f, df, x, eps, max_iter=1000):
214
+ for _ in range(max_iter):
215
+ x_new = x - f(x) / df(x)
216
+ if abs(x_new - x) < eps:
217
+ return x_new
218
+ x = x_new
219
+ return x
220
+
221
+ # --- ЗАПУСК ---
222
+ print("Корень:", newton_method(f, df, x0, eps))
223
+ ''')
224
+
225
+ # ---- 5. Модифицированный Ньютон (1D) ----
226
+ _p(["модифицированный ньютон", "модиф ньютон", "ньютон с фиксированной производной"], [],
227
+ '''# ===== МОДИФИЦИРОВАННЫЙ НЬЮТОН: производную берём один раз в x0 =====
228
+
229
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
230
+ def f(x): # уравнение f(x)=0
231
+ return np.exp(x) - x - 2
232
+ def df(x): # производная f'(x)
233
+ return np.exp(x) - 1
234
+ x0 = 1.0 # стартовая точка (в ней же фиксируется производная)
235
+ eps = 1e-4 # точность
236
+
237
+ # --- АЛГОРИТМ (не трогай) ---
238
+ def modified_newton(f, df, x0, eps, max_iter=1000):
239
+ x = x0
240
+ d = df(x0) # производную считаем ОДИН раз в x0
241
+ for _ in range(max_iter):
242
+ x_new = x - f(x) / d
243
+ if abs(x_new - x) < eps:
244
+ return x_new
245
+ x = x_new
246
+ return x
247
+
248
+ def newton_method(f, df, x, eps, max_iter=1000): # обычный Ньютон для сравнения
249
+ for _ in range(max_iter):
250
+ x_new = x - f(x) / df(x)
251
+ if abs(x_new - x) < eps:
252
+ return x_new
253
+ x = x_new
254
+ return x
255
+
256
+ # --- ЗАПУСК ---
257
+ print("Модифицированный:", modified_newton(f, df, x0, eps))
258
+ print("Обычный Ньютон: ", newton_method(f, df, x0, eps))
259
+ ''')
260
+
261
+ # ---- 6. Дихотомия (минимум) ----
262
+ _p(["дихотомия"], [],
263
+ '''# ===== ДИХОТОМИЯ: ищем МИНИМУМ функции (не корень!) на отрезке =====
264
+
265
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
266
+ def g(x): # функция, минимум которой ищем
267
+ return x**3 - x**2
268
+ a, b = 0.5, 1 # отрезок, где ровно один минимум (функция унимодальна)
269
+ eps = 1e-5 # точность
270
+
271
+ # --- АЛГОРИТМ (не трогай) ---
272
+ def dichotomy_min(g, a, b, eps):
273
+ delta = eps / 2
274
+ while abs(b - a) > eps:
275
+ c = (a + b) / 2
276
+ if g(c - delta) < g(c + delta): # слева меньше -> минимум левее
277
+ b = c
278
+ else:
279
+ a = c
280
+ return (a + b) / 2
281
+
282
+ # --- ЗАПУСК ---
283
+ print("Точка минимума:", dichotomy_min(g, a, b, eps))
284
+ ''')
285
+
286
+ # ---- 7. Простая итерация для систем ----
287
+ _p(["итерация системы", "функциональная итерация системы", "система простая итерация"], [],
288
+ '''# ===== ПРОСТАЯ ИТЕРАЦИЯ ДЛЯ СИСТЕМ: x = phi(x), повторяем =====
289
+
290
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
291
+ def phi(x): # система в виде x = phi(x); x это вектор, возвращаем вектор
292
+ return np.array([0.5 * np.cos(x[1]) + 0.3,
293
+ np.sin(x[0] - 0.5) - 1.5])
294
+ x0 = [0.13, -1.8] # стартовый вектор
295
+ eps = 1e-6 # точность (по всем компонентам)
296
+
297
+ # --- АЛГОРИТМ (не трогай) ---
298
+ def simple_iteration_system(phi, x, eps, max_iter=1000):
299
+ x = np.array(x, dtype=float)
300
+ for _ in range(max_iter):
301
+ x_new = phi(x)
302
+ if all(abs(d) < eps for d in (x_new - x)): # все компоненты сошлись
303
+ return x_new
304
+ x = x_new
305
+ return x
306
+
307
+ # --- ЗАПУСК ---
308
+ print("Решение:", simple_iteration_system(phi, x0, eps))
309
+ ''')
310
+
311
+ # ---- 8. Гаусса-Зейделя (нелинейная система) ----
312
+ _p(["гаусса-зейделя", "зейделя", "гаусс зейдель"], [],
313
+ '''# ===== ГАУССА-ЗЕЙДЕЛЯ: новое значение компоненты идёт В ДЕЛО СРАЗУ =====
314
+
315
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
316
+ # Систему приводим к x=phi1(...), y=phi2(...). Зейдель: новый x0_new СРАЗУ в формулу для x1_new.
317
+ # Пример системы: x^2 - y = 1, x - y^2 = 0 -> x = sqrt(1+y), y = sqrt(x)
318
+ def шаг(x):
319
+ x0_new = np.sqrt(1 + x[1]) # x = sqrt(1+y)
320
+ x1_new = np.sqrt(x0_new) # y = sqrt(x); используем УЖЕ новый x0_new
321
+ return np.array([x0_new, x1_new])
322
+ x0 = [1.5, 0.5] # стартовый вектор
323
+ eps = 1e-6 # точность
324
+
325
+ # --- АЛГОРИТМ (не трогай) ---
326
+ def gauss_seidel(шаг, x, eps, max_iter=1000):
327
+ x = np.array(x, dtype=float)
328
+ for _ in range(max_iter):
329
+ x_new = шаг(x)
330
+ if all(abs(d) < eps for d in (x_new - x)):
331
+ return x_new
332
+ x = x_new
333
+ return x
334
+
335
+ # --- ЗАПУСК ---
336
+ print("Решение:", gauss_seidel(шаг, x0, eps))
337
+ ''')
338
+
339
+ # ---- 9. Ньютон 2D ----
340
+ _p(["ньютон 2d", "ньютон для систем", "ньютон двумерный"], [],
341
+ '''# ===== НЬЮТОН ДЛЯ СИСТЕМЫ 2x2: решаем J*dx=-F, сдвигаем x =====
342
+
343
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
344
+ def F(x): # система F(x)=0; x это [x, y]; возвращаем 2 числа
345
+ return np.array([x[0]**2 + x[1]**2 - 4.0,
346
+ x[0] - x[1]])
347
+ def jacobian(x): # матрица Якоби: строки — уравнения, столбцы — производные по x[0], x[1]
348
+ return np.array([[2*x[0], 2*x[1]],
349
+ [1.0, -1.0]])
350
+ x0 = [1.5, 1.5] # стартовый вектор (НЕ ставь его в точку, где det(J)=0)
351
+ eps = 1e-8 # точность
352
+
353
+ # --- АЛГОРИТМ (не трогай) ---
354
+ def solve_2x2(A, b): # решаем A z = b (2x2) по правилу Крамера
355
+ det = A[0,0]*A[1,1] - A[0,1]*A[1,0]
356
+ z0 = (b[0]*A[1,1] - A[0,1]*b[1]) / det
357
+ z1 = (A[0,0]*b[1] - b[0]*A[1,0]) / det
358
+ return np.array([z0, z1])
359
+
360
+ def newton_2d(F, jacobian, x, eps, max_iter=100):
361
+ x = np.array(x, dtype=float)
362
+ for _ in range(max_iter):
363
+ dx = solve_2x2(jacobian(x), -F(x)) # J*dx = -F
364
+ x_new = x + dx
365
+ if all(abs(d) < eps for d in (x_new - x)):
366
+ return x_new
367
+ x = x_new
368
+ return x
369
+
370
+ # --- ЗАПУСК ---
371
+ print("Решение:", newton_2d(F, jacobian, x0, eps))
372
+ ''')
373
+
374
+ # ---- 10. Модифицированный Ньютон 2D ----
375
+ _p(["модифицированный ньютон 2d", "модиф ньютон 2d", "ньютон 2d фиксированный якобиан"], [],
376
+ '''# ===== МОДИФИЦИРОВАННЫЙ НЬЮТОН 2D: якобиан считаем один раз в старте =====
377
+
378
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
379
+ def F(x): # система F(x)=0
380
+ return np.array([x[0]**2 + x[1]**2 - 4.0, x[0] - x[1]])
381
+ def jacobian(x): # матрица Якоби
382
+ return np.array([[2*x[0], 2*x[1]], [1.0, -1.0]])
383
+ x0 = [1.5, 1.5] # стартовый вектор (в нём же фиксируется якобиан)
384
+ eps = 1e-8 # точность
385
+
386
+ # --- АЛГОРИТМ (не трогай) ---
387
+ def solve_2x2(A, b):
388
+ det = A[0,0]*A[1,1] - A[0,1]*A[1,0]
389
+ return np.array([(b[0]*A[1,1]-A[0,1]*b[1])/det, (A[0,0]*b[1]-b[0]*A[1,0])/det])
390
+
391
+ def modified_newton_2d(F, jacobian, x0, eps, max_iter=500):
392
+ x = np.array(x0, dtype=float)
393
+ J0 = jacobian(x) # якобиан фиксируем один раз
394
+ for _ in range(max_iter):
395
+ dx = solve_2x2(J0, -F(x))
396
+ x_new = x + dx
397
+ if all(abs(d) < eps for d in (x_new - x)):
398
+ return x_new
399
+ x = x_new
400
+ return x
401
+
402
+ # --- ЗАПУСК ---
403
+ print("Решение:", modified_newton_2d(F, jacobian, x0, eps))
404
+ ''')
405
+
406
+ # ---- 11. Линейная интерполяция ----
407
+ _p(["линейная интерполяция"], [],
408
+ '''# ===== ЛИНЕЙНАЯ ИНТЕРПОЛЯЦИЯ: соединяем узлы отрезками =====
409
+
410
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
411
+ xs = [0.1, 0.5, 1.0, 2.5] # узлы по x (строго ПО ВОЗРАСТАНИЮ)
412
+ ys = [0.1, 2.0, 0.4, -1.0] # значения y в узлах (та же длина)
413
+
414
+ # --- АЛГОРИТМ (не трогай) ---
415
+ def linear_interp(xs, ys, x):
416
+ for i in range(len(xs) - 1):
417
+ if xs[i] <= x <= xs[i+1]: # нашли интервал, куда попал x
418
+ return ys[i] + (ys[i+1] - ys[i]) / (xs[i+1] - xs[i]) * (x - xs[i])
419
+ return None
420
+
421
+ # --- ЗАПУСК ---
422
+ grid = np.linspace(xs[0], xs[-1], 300)
423
+ plt.plot(grid, [linear_interp(xs, ys, x) for x in grid])
424
+ plt.scatter(xs, ys, color="red", zorder=5)
425
+ plt.grid(True); plt.title("Линейная интерполяция"); plt.show()
426
+ ''')
427
+
428
+ # ---- 12. Лагранж ----
429
+ _p(["лагранж", "лагранжа", "интерполяционный многочлен"], [],
430
+ '''# ===== ИНТЕРПОЛЯЦИЯ ЛАГРАНЖА: один многочлен через все узлы =====
431
+
432
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
433
+ xs = [0, 2, 5, 8] # узлы по x (порядок любой)
434
+ ys = [15, 18, 22, 20] # значения в узлах (та же длина)
435
+ x_eval = 4 # точка, в которой нужно значение
436
+
437
+ # --- АЛГОРИТМ (не трогай) ---
438
+ def lagrange_interp(xs, ys, x):
439
+ total = 0.0
440
+ for i in range(len(xs)):
441
+ term = ys[i]
442
+ for j in range(len(xs)):
443
+ if i != j:
444
+ term *= (x - xs[j]) / (xs[i] - xs[j]) # базис Лагранжа
445
+ total += term
446
+ return total
447
+
448
+ # --- ЗАПУСК ---
449
+ print("Значение в x =", x_eval, ":", lagrange_interp(xs, ys, x_eval))
450
+ grid = np.linspace(xs[0], xs[-1], 300)
451
+ plt.plot(grid, [lagrange_interp(xs, ys, x) for x in grid])
452
+ plt.scatter(xs, ys, color="red", zorder=5)
453
+ plt.grid(True); plt.title("Интерполяция Лагранжа"); plt.show()
454
+ ''')
455
+
456
+ # ---- 13. Кубический сплайн ----
457
+ _p(["кубический сплайн", "сплайн", "сплайн-интерполяция"], [],
458
+ '''# ===== КУБИЧЕСКИЙ СПЛАЙН (естественный): гладкая кривая через узлы =====
459
+
460
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
461
+ xs = [-2, -1, 0, 1] # узлы по x (ПО ВОЗРАСТАНИЮ)
462
+ ys = [4, 1, 0, 2] # значения в узлах
463
+ x_eval = -0.5 # точка, в которой нужно значение
464
+
465
+ # --- АЛГОРИТМ (не трогай) ---
466
+ def spline_moments(xs, ys):
467
+ n = len(xs) - 1
468
+ h = [xs[i+1] - xs[i] for i in range(n)]
469
+ a = [0.0]*(n+1); b = [0.0]*(n+1); c = [0.0]*(n+1); d = [0.0]*(n+1)
470
+ b[0] = 1.0; b[n] = 1.0 # естественный сплайн: M0 = Mn = 0
471
+ for i in range(1, n):
472
+ a[i] = h[i-1]; b[i] = 2*(h[i-1]+h[i]); c[i] = h[i]
473
+ d[i] = 6*((ys[i+1]-ys[i])/h[i] - (ys[i]-ys[i-1])/h[i-1])
474
+ for i in range(1, n+1): # прогонка (Томас), прямой ход
475
+ w = a[i] / b[i-1]; b[i] -= w*c[i-1]; d[i] -= w*d[i-1]
476
+ M = [0.0]*(n+1); M[n] = d[n]/b[n] # обратный ход
477
+ for i in range(n-1, -1, -1):
478
+ M[i] = (d[i] - c[i]*M[i+1]) / b[i]
479
+ return M
480
+
481
+ def spline_eval(xs, ys, M, x):
482
+ for i in range(len(xs)-1):
483
+ if xs[i] <= x <= xs[i+1]:
484
+ h = xs[i+1]-xs[i]; A = (xs[i+1]-x)/h; B = (x-xs[i])/h
485
+ return A*ys[i] + B*ys[i+1] + ((A**3-A)*M[i] + (B**3-B)*M[i+1])*h*h/6
486
+ return None
487
+
488
+ # --- ЗАПУСК ---
489
+ M = spline_moments(xs, ys)
490
+ print("Значение в x =", x_eval, ":", spline_eval(xs, ys, M, x_eval))
491
+ grid = np.linspace(xs[0], xs[-1], 300)
492
+ plt.plot(grid, [spline_eval(xs, ys, M, x) for x in grid])
493
+ plt.scatter(xs, ys, color="red", zorder=5)
494
+ plt.grid(True); plt.title("Кубический сплайн"); plt.show()
495
+ ''')
496
+
497
+ # ---- 14. Наивное умножение ----
498
+ _p(["наивное умножение", "умножение матриц"], ["matrix"],
499
+ '''# ===== НАИВНОЕ УМНОЖЕНИЕ МАТРИЦ (три цикла, O(n^3)) =====
500
+ # умножение делает mat_mul (определён выше в блоке помощников)
501
+
502
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
503
+ A = [[1, 2, 4], [3, 4, 4]] # матрица A (число столбцов A = число строк B)
504
+ B = [[1, 2], [3, 2], [1, 6]] # матрица B
505
+
506
+ # --- ЗАПУСК ---
507
+ print("A*B =", mat_mul(A, B))
508
+ print("Операций умножения:", len(A) * len(B) * len(B[0])) # n*m*p
509
+ ''')
510
+
511
+ # ---- 14б. Наивное умножение со счётчиком ----
512
+ _p(["наивное умножение со счётчиком", "умножение со счётчиком", "подсчёт операций"], [],
513
+ '''# ===== НАИВНОЕ УМНОЖЕНИЕ + ПОДСЧЁТ числа операций умножения =====
514
+
515
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
516
+ n = 4 # размер случайных квадратных матриц n x n
517
+ A = np.random.rand(n, n)
518
+ B = np.random.rand(n, n)
519
+
520
+ # --- АЛГОРИТМ (не трогай) ---
521
+ def naive_mul_count(A, B):
522
+ n, m, p = len(A), len(B), len(B[0])
523
+ C = [[0.0]*p for _ in range(n)]
524
+ mul = 0
525
+ for i in range(n):
526
+ for j in range(p):
527
+ for k in range(m):
528
+ C[i][j] += A[i][k] * B[k][j]
529
+ mul += 1 # считаем каждое умножение
530
+ return C, mul
531
+
532
+ # --- ЗАПУСК ---
533
+ C, mul = naive_mul_count(A, B)
534
+ print("Число умножений:", mul, " (теория n^3 =", n**3, ")")
535
+ ''')
536
+
537
+ # ---- 15. Штрассен ----
538
+ _p(["штрассен", "штрассена"], ["matrix"],
539
+ '''# ===== АЛГОРИТМ ШТРАССЕНА: 7 умножений блоков вместо 8, O(n^2.81) =====
540
+
541
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
542
+ n = 4 # размер КВАДРАТНЫХ матриц = СТЕПЕНЬ ДВОЙКИ (2,4,8,16)
543
+ A = [[c for c in row] for row in np.random.rand(n, n)]
544
+ B = [[c for c in row] for row in np.random.rand(n, n)]
545
+
546
+ # --- АЛГОРИТМ (не трогай) ---
547
+ def strassen(A, B):
548
+ n = len(A)
549
+ if n == 1:
550
+ return [[A[0][0] * B[0][0]]]
551
+ m = n // 2
552
+ A11=[r[:m] for r in A[:m]]; A12=[r[m:] for r in A[:m]]
553
+ A21=[r[:m] for r in A[m:]]; A22=[r[m:] for r in A[m:]]
554
+ B11=[r[:m] for r in B[:m]]; B12=[r[m:] for r in B[:m]]
555
+ B21=[r[:m] for r in B[m:]]; B22=[r[m:] for r in B[m:]]
556
+ P1=strassen(A11, mat_sub(B12,B22)); P2=strassen(mat_add(A11,A12), B22)
557
+ P3=strassen(mat_add(A21,A22), B11); P4=strassen(A22, mat_sub(B21,B11))
558
+ P5=strassen(mat_add(A11,A22), mat_add(B11,B22))
559
+ P6=strassen(mat_sub(A12,A22), mat_add(B21,B22))
560
+ P7=strassen(mat_sub(A11,A21), mat_add(B11,B12))
561
+ C11=mat_add(mat_sub(mat_add(P5,P4),P2),P6); C12=mat_add(P1,P2)
562
+ C21=mat_add(P3,P4); C22=mat_sub(mat_sub(mat_add(P5,P1),P3),P7)
563
+ C=[[0.0]*n for _ in range(n)]
564
+ for i in range(m):
565
+ for j in range(m):
566
+ C[i][j]=C11[i][j]; C[i][j+m]=C12[i][j]
567
+ C[i+m][j]=C21[i][j]; C[i+m][j+m]=C22[i][j]
568
+ return C
569
+
570
+ # --- ЗАПУСК ---
571
+ print("Штрассен совпал с наивным:",
572
+ all(abs(strassen(A,B)[i][j]-mat_mul(A,B)[i][j])<1e-9 for i in range(n) for j in range(n)))
573
+ ''')
574
+
575
+ # ---- 15б. Штрассен + график времени ----
576
+ _p(["штрассен с таймером", "штрассен график времени", "время от размера матрицы"], ["matrix"],
577
+ '''# ===== ШТРАССЕН + ГРАФИК времени работы от размера матрицы =====
578
+ import time
579
+
580
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
581
+ sizes = [2, 4, 8, 16, 32] # размеры для графика (степени двойки)
582
+
583
+ # --- АЛГОРИТМ (не трогай) ---
584
+ def strassen(A, B):
585
+ n = len(A)
586
+ if n == 1: return [[A[0][0]*B[0][0]]]
587
+ m = n // 2
588
+ A11=[r[:m] for r in A[:m]]; A12=[r[m:] for r in A[:m]]
589
+ A21=[r[:m] for r in A[m:]]; A22=[r[m:] for r in A[m:]]
590
+ B11=[r[:m] for r in B[:m]]; B12=[r[m:] for r in B[:m]]
591
+ B21=[r[:m] for r in B[m:]]; B22=[r[m:] for r in B[m:]]
592
+ P1=strassen(A11, mat_sub(B12,B22)); P2=strassen(mat_add(A11,A12), B22)
593
+ P3=strassen(mat_add(A21,A22), B11); P4=strassen(A22, mat_sub(B21,B11))
594
+ P5=strassen(mat_add(A11,A22), mat_add(B11,B22))
595
+ P6=strassen(mat_sub(A12,A22), mat_add(B21,B22))
596
+ P7=strassen(mat_sub(A11,A21), mat_add(B11,B12))
597
+ C11=mat_add(mat_sub(mat_add(P5,P4),P2),P6); C12=mat_add(P1,P2)
598
+ C21=mat_add(P3,P4); C22=mat_sub(mat_sub(mat_add(P5,P1),P3),P7)
599
+ C=[[0.0]*n for _ in range(n)]
600
+ for i in range(m):
601
+ for j in range(m):
602
+ C[i][j]=C11[i][j]; C[i][j+m]=C12[i][j]
603
+ C[i+m][j]=C21[i][j]; C[i+m][j+m]=C22[i][j]
604
+ return C
605
+
606
+ # --- ЗАПУСК ---
607
+ t_naive, t_str = [], []
608
+ for nsz in sizes:
609
+ A = [[c for c in row] for row in np.random.rand(nsz, nsz)]
610
+ B = [[c for c in row] for row in np.random.rand(nsz, nsz)]
611
+ s=time.time(); mat_mul(A,B); t_naive.append(time.time()-s)
612
+ s=time.time(); strassen(A,B); t_str.append(time.time()-s)
613
+ plt.plot(sizes, t_naive, "o-", label="наивный O(n^3)")
614
+ plt.plot(sizes, t_str, "s-", label="Штрассен O(n^2.81)")
615
+ plt.xlabel("размер n"); plt.ylabel("время, с"); plt.legend(); plt.grid(True); plt.show()
616
+ ''')
617
+
618
+ # ---- 16. Характеристический многочлен ----
619
+ _p(["характеристический многочлен", "собственные значения многочлен", "char poly"], ["matrix"],
620
+ '''# ===== СОБСТВЕННЫЕ ЗНАЧЕНИЯ через характеристический многочлен =====
621
+
622
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
623
+ A = [[2, -1, 0], [-1, 2, -1], [0, -1, 2]] # КВАДРАТНАЯ матрица
624
+
625
+ # --- АЛГОРИТМ (не трогай) ---
626
+ def char_poly_coeffs(A): # коэффициенты det(A - lambda*I) (Фаддеев-Леверье)
627
+ n = len(A); M = identity(n); coeffs = [1.0]
628
+ for k in range(1, n+1):
629
+ AM = mat_mul(A, M); ck = -trace(AM) / k; coeffs.append(ck)
630
+ M = mat_add(AM, [[ck if i==j else 0.0 for j in range(n)] for i in range(n)])
631
+ return coeffs
632
+
633
+ def char_poly_eigenvalues(A):
634
+ return np.roots(char_poly_coeffs(A)) # roots разрешён для хар. уравнения
635
+
636
+ # --- ЗАПУСК ---
637
+ ev = char_poly_eigenvalues(A)
638
+ print("Собственные значения:", ev)
639
+ plt.scatter([e.real for e in ev], [e.imag for e in ev]) # СЗ на комплексной плоскости
640
+ plt.axhline(0, color="gray"); plt.axvline(0, color="gray")
641
+ plt.xlabel("Re"); plt.ylabel("Im"); plt.grid(True); plt.show()
642
+ ''')
643
+
644
+ # ---- 17. Степенной метод ----
645
+ _p(["степенной метод", "степенной", "наибольшее собственное значение"], ["matrix"],
646
+ '''# ===== СТЕПЕННОЙ МЕТОД: наибольшее по модулю собственное значение =====
647
+
648
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
649
+ A = [[2, -1, 0], [-1, 2, -1], [0, -1, 2]] # КВАДРАТНАЯ матрица
650
+ x0 = [1, 1, 1] # стартовый вектор (любой ненулевой)
651
+ eps = 1e-5 # точность
652
+
653
+ # --- АЛГОРИТМ (не трогай) ---
654
+ def power_method(A, x, eps, max_iter=1000):
655
+ x = np.array(x, dtype=float); x = x / vec_norm(x)
656
+ lam_old = 0.0
657
+ for _ in range(max_iter):
658
+ y = mat_vec(A, x); x = y / vec_norm(y)
659
+ lam = dot(x, mat_vec(A, x)) / dot(x, x) # отношение Рэлея
660
+ if abs(lam - lam_old) < eps:
661
+ return lam, x
662
+ lam_old = lam
663
+ return lam, x
664
+
665
+ # --- ЗАПУСК ---
666
+ lam, vec = power_method(A, x0, eps)
667
+ print("Наибольшее СЗ:", lam)
668
+ print("Собственный вектор:", vec)
669
+ ''')
670
+
671
+ # ---- 18. Степенной со сдвигом ----
672
+ _p(["степенной со сдвигом", "степенной метод со сдвигом", "сдвиг"], ["matrix"],
673
+ '''# ===== СТЕПЕННОЙ СО СДВИГОМ: степенной метод к матрице (A - mu*I) =====
674
+
675
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
676
+ A = [[5,1,2,3],[1,5,4,5],[2,4,5,-2],[3,5,6,5]] # КВАДРАТНАЯ матрица
677
+ x0 = [1, 1, 1, 1] # стартовый вектор
678
+ eps = 1e-4 # точность
679
+ # mu (сдвиг) задаём ниже: для поиска наименьшего СЗ берём mu = наибольшее СЗ
680
+
681
+ # --- АЛГОРИТМ (не трогай) ---
682
+ def power_method(A, x, eps, max_iter=1000):
683
+ x = np.array(x, dtype=float); x = x / vec_norm(x); lam_old = 0.0
684
+ for _ in range(max_iter):
685
+ y = mat_vec(A, x); x = y / vec_norm(y)
686
+ lam = dot(x, mat_vec(A, x)) / dot(x, x)
687
+ if abs(lam - lam_old) < eps: return lam, x
688
+ lam_old = lam
689
+ return lam, x
690
+
691
+ def shifted_power(A, mu, x, eps, max_iter=1000):
692
+ n = len(A)
693
+ B = [[A[i][j] - (mu if i==j else 0.0) for j in range(n)] for i in range(n)] # A - mu*I
694
+ _, v = power_method(B, x, eps, max_iter)
695
+ lam = dot(v, mat_vec(A, v)) / dot(v, v) # СЗ исходной A через Рэлея
696
+ return lam, v
697
+
698
+ # --- ЗАПУСК ---
699
+ lam_max, _ = power_method(A, x0, eps) # наибольшее СЗ
700
+ print("lambda_max =", lam_max)
701
+ lam_min, _ = shifted_power(A, lam_max, x0, eps) # сдвиг mu=lam_max -> ловим наименьшее
702
+ print("lambda_min =", lam_min)
703
+ ''')
704
+
705
+ # ---- 19. Вращения Якоби ----
706
+ _p(["вращения якоби", "метод вращений", "якоби", "собственные векторы"], ["matrix"],
707
+ '''# ===== МЕТОД ВРАЩЕНИЙ ЯКОБИ: все СЗ и собственные векторы (для СИММЕТРИЧНОЙ A) =====
708
+
709
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
710
+ A = [[2, -1, 0], [-1, 2, -1], [0, -1, 2]] # СИММЕТРИЧНАЯ матрица
711
+ eps = 1e-3 # точность (порог внедиагональных элементов)
712
+
713
+ # --- АЛГОРИТМ (не трогай) ---
714
+ def jacobi_eig(A, eps, max_iter=1000):
715
+ A = [row[:] for row in A]; n = len(A)
716
+ V = identity(n) # копим собственные векторы
717
+ for _ in range(max_iter):
718
+ p, q, mx = 0, 1, 0.0 # наибольший внедиагональный A[p][q]
719
+ for i in range(n):
720
+ for j in range(i+1, n):
721
+ if abs(A[i][j]) > mx:
722
+ mx = abs(A[i][j]); p, q = i, j
723
+ if mx < eps:
724
+ break
725
+ theta = (A[q][q] - A[p][p]) / (2 * A[p][q]) # угол поворота (t-формула)
726
+ sign = 1.0 if theta >= 0 else -1.0
727
+ t = sign / (abs(theta) + np.sqrt(theta*theta + 1))
728
+ c = 1 / np.sqrt(t*t + 1); s = t * c
729
+ J = identity(n); J[p][p]=c; J[q][q]=c; J[p][q]=s; J[q][p]=-s
730
+ A = mat_mul(mat_mul(transpose(J), A), J) # A = J^T A J
731
+ V = mat_mul(V, J)
732
+ return [A[i][i] for i in range(n)], V
733
+
734
+ # --- ЗАПУСК ---
735
+ vals, V = jacobi_eig(A, eps)
736
+ print("Собственные значения:", sorted(vals))
737
+ ''')
738
+
739
+ # ---- 20. QR-алгоритм ----
740
+ _p(["qr алгоритм", "qr-алгоритм"], ["matrix", "qr"],
741
+ '''# ===== QR-АЛГОРИТМ: повторяем A = R*Q, на диагонали проступают СЗ =====
742
+ # (qr_gram_schmidt и mat_mul определены выше в блоке помощников)
743
+
744
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
745
+ A = [[2,-1,0],[-1,2,-1],[0,-1,2]] # КВАДРАТНАЯ матрица
746
+ eps = 1e-9 # порог поддиагональных элементов
747
+
748
+ # --- АЛГОРИТМ (не трогай) ---
749
+ def qr_algorithm(A, eps, max_iter=1000):
750
+ A = [[float(x) for x in row] for row in A]; n = len(A)
751
+ for _ in range(max_iter):
752
+ Q, R = qr_gram_schmidt(A)
753
+ A = mat_mul(R.tolist(), Q.tolist()) # A_{k+1} = R * Q
754
+ off = max((abs(A[i][j]) for i in range(n) for j in range(i)), default=0.0)
755
+ if off < eps:
756
+ break
757
+ return sorted(A[i][i] for i in range(n))
758
+
759
+ # --- ЗАПУСК ---
760
+ print("СЗ:", qr_algorithm(A, eps))
761
+ ''')
762
+
763
+ # ---- 21. Разложение Шура ----
764
+ _p(["шур", "шура", "разложение шура"], ["matrix", "qr"],
765
+ '''# ===== РАЗЛОЖЕНИЕ ШУРА: A = U*T*U^T (T верхнетреугольная, на диагонали — СЗ) =====
766
+
767
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
768
+ A = [[5,1,2,3],[1,6,4,9],[2,4,7,10],[3,9,10,8]] # КВАДРАТНАЯ матрица
769
+ eps = 1e-9 # порог поддиагональных элементов
770
+
771
+ # --- АЛГОРИТМ (не трогай) ---
772
+ def schur(A, eps, max_iter=1000):
773
+ n = len(A); T = [[float(x) for x in row] for row in A]; U = identity(n)
774
+ for _ in range(max_iter):
775
+ Q, R = qr_gram_schmidt(T)
776
+ T = mat_mul(R.tolist(), Q.tolist()) # T = R Q
777
+ U = mat_mul(U, Q.tolist()) # копим ортогональную U
778
+ off = max((abs(T[i][j]) for i in range(n) for j in range(i)), default=0.0)
779
+ if off < eps:
780
+ break
781
+ return U, T
782
+
783
+ # --- ЗАПУСК ---
784
+ U, T = schur(A, eps)
785
+ print("Диагональ T (собственные значения):", [round(T[i][i],4) for i in range(len(A))])
786
+ recon = mat_mul(mat_mul(U, T), transpose(U)) # проверка: U T U^T = A
787
+ print("Восстановление A, макс ошибка:",
788
+ max(abs(recon[i][j]-A[i][j]) for i in range(len(A)) for j in range(len(A))))
789
+ ''')
790
+
791
+ # ---- 21б. Проверка нормальности ----
792
+ _p(["проверка нормальности", "нормальная матрица", "нормальность"], ["matrix"],
793
+ '''# ===== ПРОВЕРКА НОРМАЛЬНОСТИ: матрица нормальна, если A*A^T == A^T*A =====
794
+
795
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
796
+ A = [[5,1,2,3],[1,6,4,9],[2,4,7,10],[3,9,10,8]] # матрица (симметричная -> нормальна)
797
+
798
+ # --- АЛГОРИТМ (не трогай) ---
799
+ def is_normal(A, tol=1e-9):
800
+ At = transpose(A)
801
+ AAt = mat_mul(A, At); AtA = mat_mul(At, A)
802
+ return all(abs(AAt[i][j] - AtA[i][j]) < tol for i in range(len(A)) for j in range(len(A)))
803
+
804
+ # --- ЗАПУСК ---
805
+ print("Матрица нормальна?", is_normal(A))
806
+ ''')
807
+
808
+ # ---- 22. QR-разложение ----
809
+ _p(["qr разложение", "qr-разложение", "грама-шмидта", "грам шмидт"], ["matrix", "qr"],
810
+ '''# ===== QR-РАЗЛОЖЕНИЕ (Грам-Шмидт): A = Q*R =====
811
+ # (qr_gram_schmidt и mat_mul определены выше в блоке помощников)
812
+
813
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
814
+ A = [[1, 2], [3, 4]] # матрица для разложения
815
+
816
+ # --- ЗАПУСК ---
817
+ Q, R = qr_gram_schmidt(A)
818
+ print("Q =\\n", Q)
819
+ print("R =\\n", R)
820
+ print("Q*R =", mat_mul(Q.tolist(), R.tolist())) # = A
821
+ print("Q^T*Q =", mat_mul(transpose(Q.tolist()), Q.tolist())) # = единичная
822
+ ''')
823
+
824
+ # ---- 23. Эйлер ----
825
+ _p(["эйлер", "эйлера", "метод эйлера"], ["ode"],
826
+ '''# ===== МЕТОД ЭЙЛЕРА: y_new = y + h*f(t,y) =====
827
+
828
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
829
+ pi = 4 * np.arctan(1.0) # пи (если нужен в отрезке/шаге)
830
+ def f(t, y): # правая часть y' = f(t, y). Для ОДНОГО уравнения y — число
831
+ return -y + np.sin(t)
832
+ y0 = 0.0 # начальное значение y(t0)
833
+ t0 = 0.0 # начало отрезка
834
+ t_end = 2*pi # конец отрезка
835
+ h = pi/20 # шаг
836
+
837
+ # --- АЛГОРИТМ (не трогай) ---
838
+ def euler(f, y0, t0, t_end, h):
839
+ ts = [t0]; ys = [y0]; t = t0; y = y0
840
+ for _ in range(n_steps(t0, t_end, h)):
841
+ y = y + h * f(t, y); t = t + h
842
+ ts.append(t); ys.append(y)
843
+ return ts, ys
844
+
845
+ # --- ЗАПУСК ---
846
+ ts, ys = euler(f, y0, t0, t_end, h)
847
+ plt.plot(ts, ys, ".-"); plt.grid(True); plt.title("Метод Эйлера"); plt.show()
848
+ ''')
849
+
850
+ # ---- 24. Предиктор-корректор (Хойн) ----
851
+ _p(["предиктор-корректор", "предиктор корректор", "хойн", "хойна"], ["ode"],
852
+ '''# ===== ПРЕДИКТОР-КОРРЕКТОР (Хойн): предиктор Эйлером, корректор трапецией =====
853
+
854
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
855
+ # Для СИСТЕМЫ: y0 = np.array([...]), а f возвращает np.array (как ниже)
856
+ def f(t, y): # правая часть y' = f(t, y)
857
+ return np.array([np.log(1 + y[1]**2), -np.cos(y[0])])
858
+ y0 = np.array([1.0, 1.0]) # начальное значение (число или np.array для системы)
859
+ t0 = 0.0
860
+ t_end = 1.0
861
+ h = 0.05
862
+
863
+ # --- АЛГОРИТМ (не трогай) ---
864
+ def heun(f, y0, t0, t_end, h):
865
+ ts = [t0]; ys = [y0]; t = t0; y = y0
866
+ for _ in range(n_steps(t0, t_end, h)):
867
+ fn = f(t, y)
868
+ y_pred = y + h * fn # предиктор (Эйлер)
869
+ y = y + h/2 * (fn + f(t + h, y_pred)) # корректор (трапеция)
870
+ t = t + h; ts.append(t); ys.append(y)
871
+ return ts, ys
872
+
873
+ # --- ЗАПУСК ---
874
+ ts, ys = heun(f, y0, t0, t_end, h)
875
+ print("y(t_end) =", ys[-1])
876
+ plt.plot(ts, [y[0] for y in ys], label="y1"); plt.plot(ts, [y[1] for y in ys], label="y2")
877
+ plt.legend(); plt.grid(True); plt.title("Предиктор-корректор"); plt.show()
878
+ ''')
879
+
880
+ # ---- 25. РК4 ----
881
+ _p(["рунге-кутта", "рунге кутта", "рк4", "runge-kutta"], ["ode"],
882
+ '''import numpy as np
883
+ import matplotlib.pyplot as plt
884
+
885
+ def n_steps(t0, t_end, h):
886
+ return int(round((t_end - t0) / h))
887
+
888
+ def rk4_step(f, t, y, h): # один шаг РК4 (нужен для разгона Адамса)
889
+ k1 = f(t, y)
890
+ k2 = f(t + h/2, y + h/2 * k1)
891
+ k3 = f(t + h/2, y + h/2 * k2)
892
+ k4 = f(t + h, y + h * k3)
893
+ return y + h/6 * (k1 + 2*k2 + 2*k3 + k4)
894
+
895
+ # ===== РУНГЕ-КУТТА 4-го порядка: точный метод O(h^4) =====
896
+
897
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
898
+ # Для СИСТЕМЫ: y0 = np.array([...]), f возвращает np.array
899
+ def f(t, y): # правая часть y' = f(t, y)
900
+ return -y + t
901
+
902
+ def y_exact(t): # точное решение из условия
903
+ return t - 1 + np.exp(-t)
904
+
905
+ y0 = 0
906
+ t0 = 0.0
907
+ t_end = 1.0
908
+ h = 0.1
909
+
910
+ # --- АЛГОРИТМ (не трогай) ---
911
+ def runge_kutta_4(f, y0, t0, t_end, h):
912
+ ts = [t0]; ys = [y0]; t = t0; y = y0
913
+ for _ in range(n_steps(t0, t_end, h)):
914
+ y = rk4_step(f, t, y, h) # rk4_step определён выше
915
+ t = t + h
916
+ ts.append(t); ys.append(y)
917
+ return ts, ys
918
+
919
+ # --- ЗАПУСК ---
920
+ ts, ys = runge_kutta_4(f, y0, t0, t_end, h)
921
+
922
+ # --- СРАВНЕНИЕ С ТОЧНЫМ РЕШЕНИЕМ ---
923
+ exact = [y_exact(t) for t in ts] # точное в тех же узлах
924
+ errors = [abs(ys[i] - exact[i]) for i in range(len(ts))]
925
+
926
+ print(" t РК4 точное ошибка")
927
+ for i in range(len(ts)):
928
+ print(f"{ts[i]:.1f} {ys[i]:.8f} {exact[i]:.8f} {errors[i]:.2e}")
929
+
930
+ print("\nМаксимальная (глобальная) ошибка:", max(errors))
931
+
932
+ # --- ГРАФИК: два решения наложены ---
933
+ plt.plot(ts, ys, "o-", label="РК4 (численное)")
934
+ plt.plot(ts, exact, "-", label="точное t-1+e^(-t)")
935
+ plt.grid(True); plt.legend(); plt.title("РК4 vs точное решение")
936
+ plt.xlabel("t"); plt.ylabel("y"); plt.show()
937
+ ''')
938
+
939
+ # ---- 26. Адамса-Башфорта ----
940
+ _p(["адамса-башфорта", "адамс башфорт", "башфорт"], ["ode"],
941
+ '''# ===== АДАМС-БАШФОРТ 4 (явный многошаговый); старт через РК4 =====
942
+
943
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
944
+ def f(t, y): # правая часть y' = f(t, y) (работает и для систем)
945
+ return -y
946
+ y0 = 1.0
947
+ t0 = 0.0
948
+ t_end = 1.0
949
+ h = 0.1
950
+
951
+ # --- АЛГОРИТМ (не трогай) ---
952
+ def adams_bashforth(f, y0, t0, t_end, h):
953
+ N = n_steps(t0, t_end, h)
954
+ ts = [t0]; ys = [y0]; t = t0; y = y0
955
+ for _ in range(min(3, N)): # 3 стартовые точки через РК4
956
+ y = rk4_step(f, t, y, h); t = t + h; ts.append(t); ys.append(y)
957
+ fs = [f(ts[i], ys[i]) for i in range(len(ys))]
958
+ while len(ys) <= N:
959
+ n = len(ys) - 1
960
+ y_new = ys[n] + h/24 * (55*fs[n] - 59*fs[n-1] + 37*fs[n-2] - 9*fs[n-3])
961
+ t_new = ts[n] + h
962
+ ts.append(t_new); ys.append(y_new); fs.append(f(t_new, y_new))
963
+ return ts, ys
964
+
965
+ # --- ЗАПУСК ---
966
+ ts, ys = adams_bashforth(f, y0, t0, t_end, h)
967
+ print("y(t_end) =", ys[-1])
968
+ ''')
969
+
970
+ # ---- 27. Адамса-Мултона ----
971
+ _p(["адамса-мултона", "адамс мултон", "мултон", "мултона"], ["ode"],
972
+ '''# ===== АДАМС-МУЛТОН (предиктор-корректор ABM, 4-й порядок) =====
973
+ # ЧЕМ УНИКАЛЕН: многошаговый метод. ПРЕДСКАЗЫВАЕТ значение явным Адамсом-Башфортом и
974
+ # УТОЧНЯЕТ его неявным Адамсом-Мултоном. Точнее Эйлера на порядки, на шаг дешевле РК4
975
+ # (одно новое вычисление f). Первые 3 точки получает методом РК4 (сам разогнаться не может).
976
+ #
977
+ # СЦЕНАРИИ (выбери под билет):
978
+ # A) ОДНО уравнение y'=f(t,y), y — ЧИСЛО. Обычно просят сравнить с Эйлером и нарисовать y(t).
979
+ # B) СИСТЕМА: y0 = np.array([...]), f возвращает np.array. Обычно строят фазовый портрет.
980
+ # Ниже настроен СЦЕНАРИЙ A. Как переключить на B — пометки в самом низу.
981
+
982
+ # --- ПАРАМЕТРЫ (меняй здесь) --- [сценарий A: одно уравнение]
983
+ def f(t, y): # правая часть y' = f(t, y); y — ЧИСЛО
984
+ return -0.2*y + np.cos(t)
985
+ y0 = 1.0 # начальное значение y(t0)
986
+ t0 = 0.0 # начало отрезка
987
+ t_end = 10.0 # конец отрезка
988
+ h = 0.1 # шаг
989
+
990
+ # --- АЛГОРИТМ (не трогай) ---
991
+ def euler(f, y0, t0, t_end, h): # Эйлер — для сравнения
992
+ ts = [t0]; ys = [y0]; t = t0; y = y0
993
+ for _ in range(n_steps(t0, t_end, h)):
994
+ y = y + h * f(t, y); t = t + h; ts.append(t); ys.append(y)
995
+ return ts, ys
996
+
997
+ def adams_moulton(f, y0, t0, t_end, h):
998
+ N = n_steps(t0, t_end, h); ts = [t0]; ys = [y0]; t = t0; y = y0
999
+ for _ in range(min(3, N)): # разгон методом РК4
1000
+ y = rk4_step(f, t, y, h); t = t + h; ts.append(t); ys.append(y)
1001
+ fs = [f(ts[i], ys[i]) for i in range(len(ys))]
1002
+ while len(ys) <= N:
1003
+ n = len(ys) - 1
1004
+ y_pred = ys[n] + h/24 * (55*fs[n] - 59*fs[n-1] + 37*fs[n-2] - 9*fs[n-3]) # предиктор AB4
1005
+ t_new = ts[n] + h; f_pred = f(t_new, y_pred)
1006
+ y_new = ys[n] + h/24 * (9*f_pred + 19*fs[n] - 5*fs[n-1] + fs[n-2]) # корректор AM4
1007
+ ts.append(t_new); ys.append(y_new); fs.append(f(t_new, y_new))
1008
+ return ts, ys
1009
+
1010
+ # --- ЗАПУСК (сценарий A: одно уравнение + сравнение с Эйлером) ---
1011
+ ts, ys_am = adams_moulton(f, y0, t0, t_end, h)
1012
+ _, ys_eu = euler(f, y0, t0, t_end, h)
1013
+ print("Адамс-Мултон y(t_end) =", ys_am[-1])
1014
+ print("Эйлер y(t_end) =", ys_eu[-1])
1015
+ plt.plot(ts, ys_am, label="Адамс-Мултон")
1016
+ plt.plot(ts, ys_eu, "--", label="Эйлер")
1017
+ plt.xlabel("t"); plt.ylabel("y"); plt.legend(); plt.grid(True); plt.title("Решение ОДУ"); plt.show()
1018
+
1019
+ # --- СЦЕНАРИЙ B (СИСТЕМА + фазовый портрет): поменяй ПАРАМЕТРЫ на векторные ---
1020
+ # def f(t, y): return np.array([t**2 - y[1], y[0] + t]) # f возвращает вектор
1021
+ # y0 = np.array([1.0, 0.0])
1022
+ # и вместо графика y(t) поставь фазовый портрет:
1023
+ # plt.plot([y[0] for y in ys_am], [y[1] for y in ys_am])
1024
+ # plt.xlabel("y1"); plt.ylabel("y2"); plt.grid(True); plt.show()
1025
+ ''')
1026
+
1027
+ # ---- 27б. Фазовый портрет ----
1028
+ _p(["фазовый портрет"], ["ode"],
1029
+ '''# ===== ФАЗОВЫЙ ПОРТРЕТ системы: траектория в координатах (y1, y2) =====
1030
+
1031
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1032
+ # Систему из 2 уравнений пакуем в вектор y=[y1,y2]; f возвращает [dy1/dt, dy2/dt].
1033
+ # Пример (осциллятор dx/dt=y, dy/dt=-x): y[0]=x, y[1]=y -> f=[y[1], -y[0]]
1034
+ def f(t, y):
1035
+ return np.array([y[1], -y[0]])
1036
+ y0 = np.array([1.0, 0.0]) # начальный вектор [y1(0), y2(0)]
1037
+ t0 = 0.0
1038
+ t_end = 10.0
1039
+ h = 0.1
1040
+
1041
+ # --- АЛГОРИТМ (не трогай) ---
1042
+ def rk4_solve(f, y0, t0, t_end, h):
1043
+ ts = [t0]; ys = [y0]; t = t0; y = y0
1044
+ for _ in range(n_steps(t0, t_end, h)):
1045
+ y = rk4_step(f, t, y, h); t = t + h
1046
+ ts.append(t); ys.append(y)
1047
+ return ts, ys
1048
+
1049
+ # --- ЗАПУСК ---
1050
+ ts, ys = rk4_solve(f, y0, t0, t_end, h)
1051
+ plt.plot([y[0] for y in ys], [y[1] for y in ys])
1052
+ plt.xlabel("y1"); plt.ylabel("y2"); plt.axis("equal")
1053
+ plt.grid(True); plt.title("Фазовый портрет"); plt.show()
1054
+ ''')
1055
+
1056
+ # ---- 28. ДПФ / ОДПФ ----
1057
+ _p(["дпф", "дискретное преобразование фурье"], ["pi"],
1058
+ '''# ===== ДПФ (по определению, O(N^2)) и обратное ОДПФ =====
1059
+
1060
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1061
+ x = [0.0, 1.0, 2.0, 3.0, 4.0, 3.0, 2.0, 1.0] # сигнал (список чисел)
1062
+
1063
+ # --- АЛГОРИТМ (не трогай) ---
1064
+ def dft(x):
1065
+ N = len(x); X = []
1066
+ for k in range(N):
1067
+ s = 0 + 0j
1068
+ for n in range(N):
1069
+ theta = 2 * pi * k * n / N
1070
+ s += x[n] * (np.cos(theta) - 1j * np.sin(theta)) # e^{-i*theta}
1071
+ X.append(s)
1072
+ return X
1073
+
1074
+ def idft(X):
1075
+ N = len(X); x = []
1076
+ for n in range(N):
1077
+ s = 0 + 0j
1078
+ for k in range(N):
1079
+ theta = 2 * pi * k * n / N
1080
+ s += X[k] * (np.cos(theta) + 1j * np.sin(theta))
1081
+ x.append(s / N)
1082
+ return x
1083
+
1084
+ # --- ЗАПУСК ---
1085
+ X = dft(x)
1086
+ print("Спектр (модули):", [round(abs(v), 3) for v in X])
1087
+ print("Восстановление (ОДПФ):", [round(v.real, 3) for v in idft(X)])
1088
+ ''')
1089
+
1090
+ # ---- 28б. Обратное ДПФ ----
1091
+ _p(["обратное дпф", "одпф"], ["pi"],
1092
+ '''# ===== ОБРАТНОЕ ДПФ: восстановить сигнал из спектра X =====
1093
+
1094
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1095
+ # X — спектр (список комплексных), полученный из dft(x) или fft(x)
1096
+
1097
+ # --- АЛГОРИТМ (не трогай) ---
1098
+ def idft(X):
1099
+ N = len(X); x = []
1100
+ for n in range(N):
1101
+ s = 0 + 0j
1102
+ for k in range(N):
1103
+ theta = 2 * pi * k * n / N
1104
+ s += X[k] * (np.cos(theta) + 1j * np.sin(theta)) # e^{+i*theta}
1105
+ x.append(s / N)
1106
+ return x
1107
+
1108
+ # --- ЗАПУСК ---
1109
+ # signal_restored = [v.real for v in idft(X)]
1110
+ ''')
1111
+
1112
+ # ---- 29. БПФ / ОБПФ ----
1113
+ _p(["бпф", "быстрое преобразование фурье", "fft"], ["pi"],
1114
+ '''# ===== БПФ (Кули-Тьюки, O(N log N)) и обратное ОБПФ =====
1115
+
1116
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1117
+ x = [0.0, 1.0, 2.0, 3.0, 4.0, 3.0, 2.0, 1.0] # сигнал; длина = СТЕПЕНЬ ДВОЙКИ (2,4,8,16,32)
1118
+
1119
+ # --- АЛГОРИТМ (не трогай) ---
1120
+ def fft(x):
1121
+ N = len(x)
1122
+ if N == 1:
1123
+ return [x[0] + 0j]
1124
+ even = fft(x[0::2]); odd = fft(x[1::2])
1125
+ X = [0j] * N
1126
+ for k in range(N // 2):
1127
+ theta = 2 * pi * k / N
1128
+ w = np.cos(theta) - 1j * np.sin(theta) # поворотный множитель
1129
+ X[k] = even[k] + w * odd[k]
1130
+ X[k + N // 2] = even[k] - w * odd[k]
1131
+ return X
1132
+
1133
+ def ifft(X):
1134
+ N = len(X)
1135
+ conj = [v.real - 1j * v.imag for v in X]
1136
+ y = fft(conj)
1137
+ return [(v.real - 1j * v.imag) / N for v in y]
1138
+
1139
+ # --- ЗАПУСК ---
1140
+ X = fft(x)
1141
+ print("БПФ (модули):", [round(abs(v), 3) for v in X])
1142
+ print("Восстановление (ОБПФ):", [round(v.real, 3) for v in ifft(X)])
1143
+ ''')
1144
+
1145
+ # ---- 29б. Амплитудный спектр ----
1146
+ _p(["амплитудный спектр", "сигнал спектр", "пики в спектре", "генерация сигнала"], ["pi"],
1147
+ '''# ===== АМПЛИТУДНЫЙ СПЕКТР: сигнал -> БПФ -> амплитуды на частотах -> график =====
1148
+ # ВАЖНО про частоты: в билете частота "m/N" — это бин (индекс) k = m. Амплитуда = amp[m].
1149
+
1150
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1151
+ N = 16 # длина сигнала (СТЕПЕНЬ ДВОЙКИ)
1152
+ nn = np.linspace(0, N, N, endpoint=False) # номера отсчётов 0..N-1
1153
+ # сигнал из билета (ВЕЗДЕ nn — номер отсчёта; число перед nn/N = частота, т.е. сколько волн):
1154
+ signal = np.sin(2*pi*(2*nn/N)) + 0.3*np.cos(2*pi*(4*nn/N)) + 0.05*np.random.rand(N)
1155
+ targets = [2, 4] # какие частоты ищем как m (частота = m/N, индекс k = m)
1156
+
1157
+ # --- АЛГОРИТМ (не трогай) ---
1158
+ def fft(x):
1159
+ N = len(x)
1160
+ if N == 1: return [x[0] + 0j]
1161
+ even = fft(x[0::2]); odd = fft(x[1::2])
1162
+ X = [0j] * N
1163
+ for k in range(N // 2):
1164
+ theta = 2 * pi * k / N
1165
+ w = np.cos(theta) - 1j * np.sin(theta)
1166
+ X[k] = even[k] + w*odd[k]; X[k + N//2] = even[k] - w*odd[k]
1167
+ return X
1168
+ def ifft(X):
1169
+ N = len(X); conj = [v.real - 1j*v.imag for v in X]; y = fft(conj)
1170
+ return [(v.real - 1j*v.imag)/N for v in y]
1171
+
1172
+ # --- ЗАПУСК ---
1173
+ X = fft(list(signal))
1174
+ amp = [2*abs(X[k])/N for k in range(N//2)] # amp[k] = амплитуда на частоте k/N
1175
+ for m in targets:
1176
+ print(f"частота {m}/{N}: амплитуда = {round(amp[m], 3)}")
1177
+
1178
+ restored = [v.real for v in ifft(X)] # восстановление сигнала (если в билете просят)
1179
+ fig, ax = plt.subplots(1, 2, figsize=(11,4))
1180
+ ax[0].stem(range(N//2), amp); ax[0].set_title("Амплитудный спектр"); ax[0].grid(True)
1181
+ ax[0].set_xlabel("k (частота = k/N)")
1182
+ ax[1].plot(nn, signal, "o-", label="исходный"); ax[1].plot(nn, restored, "x--", label="восстановленный")
1183
+ ax[1].legend(); ax[1].set_title("Сигнал"); ax[1].grid(True); plt.show()
1184
+ ''')
1185
+
1186
+ # ---- Центральная разность ----
1187
+ _p(["центральная разность", "численное дифференцирование", "производная", "разность"], [],
1188
+ '''# ===== ЧИСЛЕННОЕ ДИФФЕРЕНЦИРОВАНИЕ (конечные разности) =====
1189
+
1190
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1191
+ def f(x): # функция, производную которой приближаем
1192
+ return x**3 * np.exp(-x)
1193
+ def df_exact(x): # точная производная (если в билете дана — для сравнения)
1194
+ return (3*x**2 - x**3) * np.exp(-x)
1195
+ x_eval = 1 # точка, где считаем производную
1196
+ hs = [0.1, 0.01] # шаги h (меньше h — точнее, но при очень малом растёт округление)
1197
+
1198
+ # --- АЛГОРИТМ (не трогай) ---
1199
+ def deriv1_central(f, x, h): # 1-я производная, центральная разность, ошибка O(h^2)
1200
+ return (f(x + h) - f(x - h)) / (2 * h)
1201
+ def deriv1_forward(f, x, h): # 1-я производная, прямая разность, ошибка O(h)
1202
+ return (f(x + h) - f(x)) / h
1203
+ def deriv2_central(f, x, h): # 2-я производная, центральная разность, ошибка O(h^2)
1204
+ return (f(x + h) - 2 * f(x) + f(x - h)) / (h * h)
1205
+
1206
+ # --- ЗАПУСК ---
1207
+ for h in hs:
1208
+ print(f"h={h}: центральная={deriv1_central(f,x_eval,h):.6f} точно={df_exact(x_eval):.6f}")
1209
+ # для 2-й производной возьми deriv2_central(f, x_eval, h)
1210
+ ''')
1211
+
1212
+ # ---- Суммирование Кахана ----
1213
+ _p(["кахан", "кахана", "суммирование кахана", "kahan"], [],
1214
+ '''# ===== СУММИРОВАНИЕ КАХАНА: точное сложение многих чисел =====
1215
+
1216
+ # --- ПАРАМЕТРЫ (меняй здесь) ---
1217
+ data = [0.1] * 10 # список чисел для суммирования
1218
+
1219
+ # --- АЛГОРИТМ (не трогай) ---
1220
+ def kahan_sum(nums):
1221
+ s = 0.0 # сумма
1222
+ c = 0.0 # компенсация потерянных младших разрядов
1223
+ for x in nums:
1224
+ y = x - c
1225
+ t = s + y
1226
+ c = (t - s) - y # что потерялось при сложении
1227
+ s = t
1228
+ return s
1229
+
1230
+ # --- ЗАПУСК ---
1231
+ print("Кахан: ", kahan_sum(data)) # точнее обычной суммы
1232
+ print("Обычная:", sum(data))
1233
+ ''')
1234
+
1235
+
1236
+ # ====================================================================== ТЕОРИЯ
1237
+ THEORY = {}
1238
+ def _t(keys, text):
1239
+ for k in keys:
1240
+ THEORY[k] = text
1241
+
1242
+ _t(["бисекция", "почему бисекция сходится"],
1243
+ """БИСЕКЦИЯ — почему она работает и как отвечать на вопросы про неё.
1244
+
1245
+ Бисекция это самый надёжный способ найти корень уравнения, и идея у неё почти бытовая. Если
1246
+ функция непрерывна и на одном конце отрезка она отрицательна, а на другом положительна, то
1247
+ где-то между ними она обязана пройти через ноль. Это и есть теорема о промежуточном значении
1248
+ (теорема Больцано), и именно она гарантирует, что корень на отрезке существует. Мы берём
1249
+ середину отрезка, смотрим, в какой из двух половин по-прежнему сохраняется смена знака, и
1250
+ выбрасываем вторую половину. За счёт этого отрезок с корнем на каждом шаге становится ровно
1251
+ вдвое короче и неизбежно стягивается к точке, где функция равна нулю.
1252
+
1253
+ Платим за надёжность скоростью. Сходимость здесь линейная, и чтобы добраться до точности eps,
1254
+ нужно примерно log2 от (b минус a), делённого на eps, шагов. На практике это значит, что один
1255
+ новый верный десятичный знак появляется примерно за три-четыре итерации. Метод не использует
1256
+ производную и в принципе не умеет расходиться, поэтому его часто берут как страховку или чтобы
1257
+ грубо нащупать корень перед запуском более быстрого метода.
1258
+
1259
+ Если спрашивают, как сократить число итераций, ответ один и простой. Число шагов зависит только
1260
+ от длины стартового отрезка, поэтому надо заранее изолировать корень и взять отрезок покороче.
1261
+ Если сравнивают с методом секущих, то бисекция медленнее (секущих сходится почти со скоростью
1262
+ золотого сечения), зато секущих может улететь при неудачном старте и сильнее страдает от ошибок
1263
+ округления, когда соседние значения функции почти совпадают и в знаменателе получается почти ноль.""")
1264
+
1265
+ _t(["ньютон", "ньютона", "ньютон vs бисекция", "метод ньютона теория"],
1266
+ """МЕТОД НЬЮТОНА — идея, скорость и подводные камни.
1267
+
1268
+ Ньютон это самый быстрый из классических методов поиска корня. В каждой точке мы заменяем
1269
+ функцию её касательной и берём за следующее приближение ту точку, где касательная пересекает
1270
+ ось. Геометрически мы как бы скользим по касательным и очень быстро сваливаемся в корень.
1271
+ Алгебраически это означает, что новая ошибка примерно пропорциональна квадрату старой, поэтому
1272
+ число верных цифр на каждом шаге примерно удваивается. Это и называют квадратичной сходимостью,
1273
+ и из-за неё Ньютон обычно сходится за считанные итерации.
1274
+
1275
+ За скорость приходится платить требованиями. Во-первых, нужна производная, причём правильная,
1276
+ иначе метод поедет не туда. Во-вторых, нужно достаточно хорошее начальное приближение. Если
1277
+ стартовая точка далеко от корня или производная там близка к нулю, касательная почти
1278
+ горизонтальна, и следующее приближение может улететь очень далеко или метод начнёт прыгать
1279
+ туда-сюда без сходимости. Поэтому на практике Ньютона часто подстраховывают, начиная с грубой
1280
+ локализации корня бисекцией.
1281
+
1282
+ Когда сравнивают Ньютона и бисекцию, главная мысль такая. Бисекция медленная, но железобетонно
1283
+ надёжная и не требует производной. Ньютон очень быстрый, но капризный к старту и к производной.
1284
+ Разумная тактика часто состоит в том, чтобы сперва изолировать корень надёжным методом, а потом
1285
+ дошлифовать его Ньютоном.""")
1286
+
1287
+ _t(["модифицированный ньютон", "модиф ньютон", "когда модифицированный ньютон"],
1288
+ """МОДИФИЦИРОВАННЫЙ НЬЮТОН — когда он удобнее и при чём тут ошибки производной.
1289
+
1290
+ Главная идея в том, что производную (а в многомерном случае целую матрицу Якоби) мы считаем
1291
+ только один раз, в стартовой точке, и дальше используем это же значение на всех шагах. Обычный
1292
+ Ньютон пересчитывает наклон касательной в каждой точке, а здесь мы фиксируем наклон по первой
1293
+ касательной и просто двигаемся с ним. Это заметно дешевле по вычислениям, особенно когда сама
1294
+ производная или матрица Якоби считается тяжело.
1295
+
1296
+ Расплата за дешевизну это скорость. Обычный Ньютон сходится квадратично, а модифицированный
1297
+ только линейно, поэтому итераций понадобится больше. По сути это частный случай простой итерации,
1298
+ и сходится он, пока множитель ошибки по модулю меньше единицы.
1299
+
1300
+ Самое интересное в этом методе как раз связано с ошибками производной, и про это любят спрашивать.
1301
+ Поскольку мы используем фиксированное приближённое значение производной, метод всё равно сходится
1302
+ к настоящему корню, даже если это значение посчитано неточно. Неточность в производной меняет лишь
1303
+ скорость сходимости, но не саму точку, в которую мы приходим. В этом сила модифицированного метода,
1304
+ он устойчив к погрешности в производной. Но если приближение производной сильно завышено или
1305
+ занижено, множитель сходимости может стать по модулю больше единицы, и тогда метод разойдётся.
1306
+ Поэтому модифицированный Ньютон хорош, когда производная дорогая, меняется слабо, а старт неплохой.""")
1307
+
1308
+ _t(["гаусса-зейделя", "зейделя", "ньютон vs зейдель", "липшиц", "константа липшица"],
1309
+ """ГАУССА-ЗЕЙДЕЛЯ ПРОТИВ НЬЮТОНА и роль константы Липшица.
1310
+
1311
+ Метод Гаусса-Зейделя для нелинейной системы это та же идея простой итерации, где систему
1312
+ приводят к виду икс равно фи от икс и крутят по кругу. Отличие одно, но важное. Как только мы
1313
+ пересчитали одну компоненту вектора, мы сразу подставляем её новое значение в вычисление
1314
+ следующих компонент, не дожидаясь конца итерации. За счёт того что в работе всегда самая свежая
1315
+ информация, хорошие поправки расходятся по системе быстрее, и обычно Зейдель сходится заметно
1316
+ шустрее обычной простой итерации.
1317
+
1318
+ Сходимость тут линейная и держится на том, что отображение фи является сжимающим. Здесь и
1319
+ появляется константа Липшица. Она показывает, во сколько раз отображение в худшем случае
1320
+ растягивает расстояние между точками. Если эта константа меньше единицы, отображение сжимающее,
1321
+ расстояние между приближением и решением каждый шаг уменьшается, и итерации гарантированно
1322
+ сходятся к единственной неподвижной точке. Чем меньше константа, тем быстрее сходимость. Если же
1323
+ она больше единицы, ошибки наоборот раздуваются, и метод расходится.
1324
+
1325
+ Когда сравнивают Зейделя с методом Ньютона для систем, мысль такая. Ньютон сходится квадратично,
1326
+ очень быстро, но требует матрицы Якоби, её обращения и хорошего старта, а ещё чувствителен к тому,
1327
+ насколько матрица хорошо обусловлена. Зейдель сходится медленнее и только при условии сжатия, зато
1328
+ не нуждается в производных, проще и сам подправляет накапливающиеся ошибки округления, пока
1329
+ выполняется условие Липшица.""")
1330
+
1331
+ _t(["интерполяция", "глобальная локальная", "сплайн", "интерполяция теория"],
1332
+ """ИНТЕРПОЛЯЦИЯ В ЦЕЛОМ — глобальная и локальная, и где сплайны.
1333
+
1334
+ Интерполяция это восстановление функции по нескольким известным точкам так, чтобы кривая прошла
1335
+ ровно через них. Рядом стоят два соседних понятия. Экстраполяция это попытка предсказать значение
1336
+ за пределами интервала с узлами, и она гораздо менее надёжна. Аппроксимация это когда мы не
1337
+ обязаны попадать точно в точки, а лишь хотим пройти рядом, что особенно разумно для зашумлённых
1338
+ данных.
1339
+
1340
+ Важно различать глобальную и локальную интерполяцию. При глобальной один многочлен строится сразу
1341
+ по всем узлам, и сдвиг любого узла меняет всю кривую. Таков многочлен Лагранжа. При локальной
1342
+ каждый кусок кривой зависит только от ближайших узлов, поэтому далёкие точки на него не влияют.
1343
+ Таковы ступенчатая и линейная интерполяция и сплайны. Локальные методы устойчивее и не страдают
1344
+ от диких колебаний.
1345
+
1346
+ Сплайны это золотая середина. На каждом интервале строится свой невысокий многочлен, а в узлах их
1347
+ сшивают так, чтобы совпадали значение и производные, и кривая получается гладкой без изломов. У
1348
+ кубического сплайна сшивают значение, первую и вторую производные, поэтому глаз вообще не замечает
1349
+ стыков. По сравнению с одним многочленом высокой степени сплайн не даёт жутких осцилляций у краёв
1350
+ (того самого феномена Рунге), а по сравнению с ломаной он гладкий. Поэтому сплайны и стали
1351
+ стандартом для плавных кривых.""")
1352
+
1353
+ _t(["лагранж", "степень полинома", "интерполяция точность", "лагранжа теория"],
1354
+ """ИНТЕРПОЛЯЦИЯ ЛАГРАНЖА — почему она точная в узлах и чем опасна высокая степень.
1355
+
1356
+ Многочлен Лагранжа это один-единственный многочлен, который проходит точно через все заданные
1357
+ узлы. Собирается он из специальных базисных многочленов, каждый из которых хитро устроен. Базис с
1358
+ номером i равен единице в своём узле и равен нулю во всех остальных узлах. Поэтому когда мы
1359
+ подставляем какой-то узел, вся длинная сумма схлопывается ровно до значения функции в этом узле,
1360
+ и попадание в узлы получается точным по построению. По n точкам строится многочлен степени не
1361
+ выше n минус один, и такой многочлен через данные точки единственный.
1362
+
1363
+ Самое важное и любимое на экзамене это связь степени и точности. Кажется, что чем больше узлов и
1364
+ выше степень, тем точнее, но это обманчиво. Для равномерно расставленных узлов высокой степени
1365
+ возникает феномен Рунге, когда многочлен начинает дико колебаться у краёв отрезка, и точность не
1366
+ растёт, а падает.
1367
+
1368
+ Отдельно про зашумлённые данные. Интерполяция обязана пройти точно через каждую точку, поэтому
1369
+ она честно протаскивает кривую через весь шум, и результат скачет. Если данные с погрешностью,
1370
+ обычно лучше не интерполировать, а аппроксимировать методом наименьших квадратов или брать
1371
+ локальные методы вроде сплайнов, которые не так чувствительны к отдельным выбросам.""")
1372
+
1373
+ _t(["штрассен", "сложность штрассена", "штрассена теория"],
1374
+ """АЛГОРИТМ ШТРАССЕНА — как он экономит умножения и при чём тут память.
1375
+
1376
+ Если перемножать матрицы по определению, то для матрицы размера n на n нужно порядка n в кубе
1377
+ операций умножения, и это довольно дорого. Штрассен заметил, что произведение блочных матриц
1378
+ два на два можно посчитать не за восемь умножений подматриц, а за семь, если хитро скомбинировать
1379
+ суммы и разности блоков. Одно умножение мы выкупаем ценой нескольких лишних сложений. Дальше тот
1380
+ же трюк применяется рекурсивно, пока блоки не станут совсем маленькими.
1381
+
1382
+ Откуда берётся ускорение, видно из рекуррентного соотношения. Задача размера n сводится к семи
1383
+ задачам половинного размера плюс работа на сложения порядка n в квадрате. Решение такого
1384
+ соотношения даёт сложность примерно n в степени 2.81 вместо n в кубе. Главное тут именно семь, а
1385
+ не восемь подзадач на каждом уровне, а лишние сложения дают лишь квадратичный вклад и порядок не
1386
+ портят.
1387
+
1388
+ Про память и архитектуру тоже любят спрашивать, и вот суть. Реальная скорость зависит не только от
1389
+ числа операций, но и от того, как данные ложатся в кэш процессора. Наивное умножение обходит вторую
1390
+ матрицу по столбцам, прыгая по памяти, и часто промахивается мимо кэша, поэтому порядок вложенных
1391
+ циклов влияет на время в разы. У Штрассена много промежуточных матриц, а значит больше обращений к
1392
+ памяти и накладных расходов, поэтому выигрыш заметен только на больших матрицах, а на маленьких
1393
+ наивный способ даже быстрее. Применяют Штрассена и его потомков там, где надо умножать действительно
1394
+ большие плотные матрицы.""")
1395
+
1396
+ _t(["сложность", "big-o", "нотация", "профилирование"],
1397
+ """СЛОЖНОСТЬ АЛГОРИТМОВ И НОТАЦИЯ BIG-O.
1398
+
1399
+ Нотация big-O это способ грубо описать, как растёт время работы или расход памяти с увеличением
1400
+ размера входных данных. Нас интересует не точное число операций, а характер роста, причём с
1401
+ точностью до постоянного множителя и без учёта мелких слагаемых. Запись O от n в кубе означает,
1402
+ что при удвоении размера время вырастет примерно в восемь раз, а O от n логарифм n растёт почти
1403
+ линейно и потому считается очень хорошим.
1404
+
1405
+ Полезно держать в голове несколько ориентиров из курса. Наивное умножение матриц это n в кубе,
1406
+ Штрассен примерно n в степени 2.81, обычное дискретное преобразование Фурье n в квадрате, а
1407
+ быстрое преобразование Фурье всего лишь n логарифм n. Для методов поиска корня считают не
1408
+ арифметику, а число итераций. Бисекция тратит порядка логарифма от длины отрезка, делённой на
1409
+ точность, а Ньютон сходится квадратично, то есть удваивает число верных знаков за шаг.
1410
+
1411
+ Если спрашивают про профилирование в Python, достаточно назвать инструменты. Модули time и timeit
1412
+ меряют время выполнения, cProfile показывает, в каких функциях программа проводит больше всего
1413
+ времени, а в ноутбуке Jupyter удобны волшебные команды процент timeit для быстрых замеров,
1414
+ процент prun для разбивки по функциям и процент lprun для построчного профиля.""")
1415
+
1416
+ _t(["степенной метод", "сдвиг", "зазор собственных значений", "скорость сходимости степенного"],
1417
+ """СТЕПЕННОЙ МЕТОД — что он находит, как быстро и зачем сдвиги.
1418
+
1419
+ Степенной метод находит наибольшее по модулю собственное значение матрицы и соответствующий ему
1420
+ собственный вектор, причём без всякого характеристического многочлена. Берём произвольный вектор и
1421
+ многократно умножаем его на матрицу, каждый раз нормируя. Чтобы понять, почему направление
1422
+ сходится к главному собственному вектору, разложим стартовый вектор по собственным векторам. После
1423
+ многих умножений каждое слагаемое умножается на свою степень собственного значения, и слагаемое с
1424
+ самым большим по модулю значением растёт быстрее всех остальных. Через несколько шагов оно
1425
+ перевешивает, и нормированный вектор выстраивается вдоль главного направления. Само значение
1426
+ удобно вытаскивать через отношение Рэлея.
1427
+
1428
+ Скорость сходимости здесь линейная, и определяется она зазором между собственными значениями.
1429
+ Точнее, скоростью отношения второго по величине значения к первому. Если первое заметно больше
1430
+ второго, метод сходится быстро, а если они почти равны по модулю, сходимость становится мучительно
1431
+ медленной. Поэтому зазор так важен, и про него часто спрашивают.
1432
+
1433
+ Сдвиги это способ управлять этим зазором и нацеливанием. Применяя метод не к самой матрице, а к
1434
+ матрице минус мю на единичную, мы сдвигаем все собственные значения на мю, оставляя собственные
1435
+ векторы прежними. Подбирая мю, можно сделать доминирующим нужное значение или увеличить разрыв и
1436
+ тем ускорить сходимость. Например, сдвинув на уже найденное наибольшее значение, можно поймать
1437
+ наименьшее. И не забывай нормировку на каждом шаге, она удерживает числа от переполнения и не даёт
1438
+ ошибкам округления раздуваться при медленной сходимости.""")
1439
+
1440
+ _t(["шур", "шура", "нормальная матрица", "почему шур диагональный", "нормальность теория"],
1441
+ """РАЗЛОЖЕНИЕ ШУРА И НОРМАЛЬНЫЕ МАТРИЦЫ.
1442
+
1443
+ Теорема Шура говорит замечательную вещь. Любую квадратную матрицу можно записать как U умножить на
1444
+ T умножить на U сопряжённое транспонированное, где U унитарная (а для вещественных матриц просто
1445
+ ортогональная), а T верхнетреугольная. При этом на диагонали треугольной T стоят собственные
1446
+ значения исходной матрицы. На практике это разложение получают тем же QR-алгоритмом, только
1447
+ попутно накапливают произведение всех ортогональных множителей, и оно складывается в матрицу U.
1448
+
1449
+ Почему для нормальных матриц T получается не просто треугольной, а диагональной, это и есть
1450
+ ключевой вопрос. Матрица называется нормальной, если она перестановочна со своим сопряжённым
1451
+ транспонированным, то есть A на A транспонированное равно A транспонированное на A. Оказывается,
1452
+ свойство нормальности сохраняется при унитарном подобии, а нормальная треугольная матрица обязана
1453
+ быть диагональной. Значит для нормальной матрицы T выходит диагональной, и это в точности означает,
1454
+ что у такой матрицы есть полный ортонормированный набор собственных векторов, то есть она унитарно
1455
+ диагонализуема. На экзамене проверить нормальность проще всего, сравнив A на A транспонированное с
1456
+ A транспонированное на A поэлементно.
1457
+
1458
+ Если просят сравнить Шура с QR-разложением, держи в голове разницу. QR-разложение это однократная
1459
+ факторизация матрицы на ортогональную и треугольную части, стоит порядка n в кубе и само по себе
1460
+ собственных значений не даёт. Разложение Шура это уже результат многих QR-итераций, оно сходится к
1461
+ треугольной форме и выдаёт спектр, но обходится дороже из-за итераций. Зато оно очень устойчиво
1462
+ численно, потому что использует только ортогональные преобразования, которые не раздувают ошибки.
1463
+ Применяют Шура для вычисления собственных значений, матричных функций, решения матричных уравнений
1464
+ и в анализе устойчивости.""")
1465
+
1466
+ _t(["qr", "qr разложение теория", "собственные значения теория", "qr алгоритм теория"],
1467
+ """QR-РАЗЛОЖЕНИЕ И QR-АЛГОРИТМ — зачем они и как связаны.
1468
+
1469
+ QR-разложение раскладывает матрицу на произведение ортогональной матрицы Q и верхнетреугольной R.
1470
+ Получают его ортогонализацией столбцов методом Грама-Шмидта. Мы идём по столбцам слева направо и из
1471
+ каждого вычитаем его проекции на уже найденные ортонормированные направления, а остаток нормируем.
1472
+ В итоге Q хранит ортонормированный базис, натянутый на столбцы исходной матрицы, а R показывает, как
1473
+ исходные столбцы выражаются через этот базис. Поскольку столбцы Q взаимно перпендикулярны и единичны,
1474
+ Q транспонированное на Q даёт единичную матрицу, а проверка QR равно A подтверждает корректность.
1475
+
1476
+ QR-алгоритм это уже способ найти собственные значения, и он построен поверх QR-разложения. Идея
1477
+ неожиданно простая. Мы раскладываем матрицу на Q и R, а потом перемножаем их в обратном порядке,
1478
+ получая R на Q, и повторяем. Каждая такая операция это ортогональное подобие, поэтому собственные
1479
+ значения не меняются, а сама матрица постепенно становится почти треугольной, и на её диагонали
1480
+ проступают собственные значения. Скорость сходимости зависит от отношений соседних собственных
1481
+ значений, а чтобы её резко ускорить, на практике добавляют сдвиги. Именно QR-алгоритм стоит внутри
1482
+ библиотечных функций поиска спектра, а для эффективности матрицу сначала приводят к почти
1483
+ треугольной верхне-гессенберговой форме.""")
1484
+
1485
+ _t(["центральная разность", "численное дифференцирование", "точность разности", "разность теория"],
1486
+ """ЧИСЛЕННОЕ ДИФФЕРЕНЦИРОВАНИЕ — конечные разности и их точность.
1487
+
1488
+ Когда производную нельзя или не хочется брать аналитически, её приближают по соседним значениям
1489
+ функции. Простейший вариант это прямая разность, когда мы берём значение в точке и чуть правее и
1490
+ делим разность на шаг. Она грубовата, её ошибка падает лишь линейно с уменьшением шага. Куда точнее
1491
+ центральная разность, где мы используем точки слева и справа от нашей и делим их разность на двойной
1492
+ шаг. У неё ошибка падает уже как квадрат шага, потому что несимметричные погрешности слева и справа
1493
+ взаимно гасятся. Вторую производную тоже приближают симметрично, через значение слева, в точке и
1494
+ справа, и её ошибка тоже ведёт себя как квадрат шага.
1495
+
1496
+ Самое тонкое место это выбор шага, и про него любят спрашивать. Здесь спорят две ошибки. С одной
1497
+ стороны ошибка усечения, она тем больше, чем крупнее шаг, ведь мы заменяем гладкую функцию грубой
1498
+ разностью. С другой стороны ошибка округления, она наоборот растёт, когда шаг слишком мал, потому
1499
+ что мы вычитаем почти одинаковые числа и теряем значащие цифры, а потом ещё делим на крошечный шаг.
1500
+ Поэтому существует оптимальный шаг где-то посередине, и делать его сколь угодно малым бессмысленно.
1501
+
1502
+ Если сравнивают центральную разность с прямой, вывод такой. Центральная точнее при том же порядке
1503
+ затрат, ведь её ошибка квадратичная против линейной у прямой, поэтому её и предпочитают, когда можно
1504
+ взять точки по обе стороны от интересующей нас точки.""")
1505
+
1506
+ _t(["эйлер", "методы оду", "порядок точности", "устойчивость оду методы"],
1507
+ """МЕТОДЫ РЕШЕНИЯ ОДУ — порядок точности и устойчивость по-человечески.
1508
+
1509
+ Все эти методы решают одну задачу. Дано уравнение на производную и начальное значение, и надо
1510
+ по шагам построить решение вперёд по времени. Самый простой это метод Эйлера, который делает шаг по
1511
+ касательной, зная наклон в текущей точке. Он наглядный, но грубый, его глобальная ошибка падает лишь
1512
+ линейно с уменьшением шага, поэтому говорят, что он первого порядка. Чтобы стало точнее, наклон
1513
+ оценивают аккуратнее. Метод Хойна усредняет наклоны в начале и в предсказанной точке и даёт второй
1514
+ порядок, а Рунге-Кутта четвёртого порядка пробует целых четыре наклона внутри шага и оказывается
1515
+ очень точным. Многошаговые методы Адамса экономят вычисления, переиспользуя уже посчитанные значения
1516
+ из предыдущих точек.
1517
+
1518
+ Полезно различать локальную и глобальную ошибку. Локальная это ошибка за один шаг, а глобальная это
1519
+ то, что накапливается на всём отрезке. Обычно глобальная ошибка на один порядок шага хуже локальной,
1520
+ потому что шагов примерно обратно пропорционально шагу. Отсюда и удобное правило, что у метода
1521
+ порядка p уменьшение шага вдвое уменьшает итоговую ошибку примерно в два в степени p раз.
1522
+
1523
+ Про устойчивость мысль такая. Метод устойчив, если небольшие ошибки со временем затухают, а не
1524
+ раздуваются. У явных методов вроде Эйлера есть ограничение на размер шага, и при слишком крупном шаге
1525
+ на жёстких задачах решение разваливается, накопленные ошибки начинают расти лавинообразно. Неявные
1526
+ методы устойчивее и терпят большие шаги. Поэтому фазовый портрет так полезен, он наглядно показывает,
1527
+ сохраняет ли численное решение правильное качественное поведение или метод его искажает.""")
1528
+
1529
+ _t(["фазовый портрет", "адаптивный шаг", "устойчивость оду"],
1530
+ """ФАЗОВЫЕ ПОРТРЕТЫ, АДАПТИВНЫЙ ШАГ И УСТОЙЧИВОСТЬ.
1531
+
1532
+ Фазовый портрет это картинка траекторий системы в координатах самих переменных, без явного времени
1533
+ на осях. Для системы из двух уравнений мы просто рисуем одну переменную против другой. Такая картинка
1534
+ очень помогает понять качественное поведение, не решая систему в формулах. На ней видно положения
1535
+ равновесия, замкнутые циклы, притягивает система к равновесию или разбегается. Поэтому фазовые
1536
+ портреты любят в анализе динамических систем.
1537
+
1538
+ Здесь же всплывает важная тема искажений. Численный метод вносит свои ошибки, и они могут испортить
1539
+ портрет. Классический пример это гармонический осциллятор, у которого настоящая траектория это
1540
+ замкнутая окружность. Явный метод Эйлера потихоньку добавляет энергию, и вместо окружности рисуется
1541
+ раскручивающаяся спираль. Это наглядный признак того, что схема неустойчива на такой задаче.
1542
+
1543
+ Адаптивный шаг это идея менять размер шага по ходу решения. Там, где решение меняется бурно, шаг
1544
+ делают мельче, а на спокойных участках крупнее. Это позволяет держать заданную точность меньшими
1545
+ усилиями и помогает проскочить трудные жёсткие места. Но это не панацея. Слишком мелкий шаг не только
1546
+ замедляет счёт, но и увеличивает число операций, а значит и накопление ошибок округления. Так что всё
1547
+ упирается в баланс между ошибкой метода, устойчивостью и ошибками округления.""")
1548
+
1549
+ _t(["дпф", "бпф", "фурье", "почему бпф быстрее", "шум в спектре"],
1550
+ """ДИСКРЕТНОЕ И БЫСТРОЕ ПРЕОБРАЗОВАНИЕ ФУРЬЕ.
1551
+
1552
+ Дискретное преобразование Фурье раскладывает сигнал на гармоники, то есть переводит его из
1553
+ временного представления в частотное. Чтобы понять, как оно выделяет частоты, представь, что мы
1554
+ домножаем сигнал на эталонное колебание определённой частоты и складываем. Если в сигнале такая
1555
+ частота есть, произведение в среднем не нулевое и сумма большая, а вклады всех остальных частот при
1556
+ суммировании взаимно гасятся, потому что эталонные колебания взаимно ортогональны. В итоге модуль
1557
+ каждого коэффициента показывает, насколько сильно представлена соответствующая частота, а его фаза
1558
+ её сдвиг.
1559
+
1560
+ Про шум обязательно спросят. Шум добавляет фон по всему спектру и приподнимает общий уровень, из-за
1561
+ чего слабые настоящие компоненты могут утонуть в этом фоне. Чтобы уменьшить его влияние, берут
1562
+ сигнал подлиннее, ведь чем больше отсчётов, тем лучше разрешение по частоте и тем чище проступают
1563
+ пики, а ещё усредняют спектры нескольких записей или применяют оконные функции и фильтрацию. И
1564
+ помни, что разрешение по частоте определяется частотой дискретизации, делённой на число отсчётов,
1565
+ поэтому чтобы различить две близкие частоты, нужно больше отсчётов.
1566
+
1567
+ Быстрое преобразование Фурье считает ровно то же самое, но несравнимо быстрее. Прямое вычисление по
1568
+ определению это двойная сумма и стоит порядка n в квадрате. Алгоритм Кули-Тьюки делит сигнал на
1569
+ отсчёты с чётными и нечётными номерами и сводит задачу размера n к двум задачам половинного размера.
1570
+ За счёт периодичности промежуточных сумм одна пара даёт сразу два выходных значения, это так
1571
+ называемая бабочка. В результате сложность падает до n логарифм n, как у сортировки слиянием, и
1572
+ именно поэтому спектральный анализ стал практичным.""")
1573
+
1574
+ _t(["ieee754", "округление", "ошибки округления", "плавающая точка", "кахан теория"],
1575
+ """ЧИСЛА С ПЛАВАЮЩЕЙ ТОЧКОЙ И ОШИБКИ ОКРУГЛЕНИЯ.
1576
+
1577
+ Компьютер хранит вещественные числа в стандарте IEEE 754 с конечной точностью, у типа double это
1578
+ примерно пятнадцать-шестнадцать значащих десятичных цифр. Беда в том, что большинство чисел в
1579
+ двоичной системе не представимы точно, даже безобидная одна десятая записывается бесконечной
1580
+ двоичной дробью, и компьютер хранит лишь её ближайшее приближение. Эта неустранимая погрешность
1581
+ называется ошибкой представления, а её масштаб задаёт машинный эпсилон порядка двух на десять в
1582
+ минус шестнадцатой.
1583
+
1584
+ Дальше эти крошечные погрешности живут своей жизнью. В длинных суммах и в итерационных методах они
1585
+ накапливаются. Особенно коварна потеря значимости при вычитании двух близких чисел, когда старшие
1586
+ одинаковые цифры сокращаются и в результате остаются почти одни шумовые младшие разряды. Из-за
1587
+ конечной точности арифметика ещё и перестаёт быть ассоциативной, то есть результат сложения может
1588
+ немного зависеть от порядка слагаемых.
1589
+
1590
+ Когда спрашивают, как с этим бороться, называй практические приёмы. Складывать лучше числа близкого
1591
+ порядка, избегать вычитания почти равных величин, пользоваться устойчивыми формулами. Для длинных
1592
+ сумм существует суммирование по Кахану, которое заводит дополнительную компенсирующую переменную и
1593
+ аккуратно возвращает в сумму то, что иначе потерялось бы при округлении, и за счёт этого сумма
1594
+ получается заметно точнее обычной.""")
1595
+
1596
+ _t(["верные цифры", "значащие цифры", "погрешность", "абсолютная относительная погрешность"],
1597
+ """ПОГРЕШНОСТИ И ВЕРНЫЕ ЦИФРЫ.
1598
+
1599
+ Когда мы заменяем точное число приближённым, возникает погрешность, и её меряют двумя способами.
1600
+ Абсолютная погрешность это просто модуль разности между точным и приближённым значением, она в тех
1601
+ же единицах, что и само число. Относительная погрешность это абсолютная, делённая на само значение,
1602
+ и она безразмерная, её удобно выражать в процентах. Относительная честнее отражает качество, ведь
1603
+ ошибка в один метр это много при измерении комнаты и пустяк при измерении расстояния между городами.
1604
+
1605
+ Значащие цифры это все цифры числа начиная с первой ненулевой. Когда мы округляем приближённое
1606
+ число, мы как раз оставляем столько цифр, сколько оправдано его точностью, лишние цифры создавали бы
1607
+ ложное впечатление, будто мы знаем больше, чем на самом деле.
1608
+
1609
+ Отдельно различают верные цифры в строгом и в широком смысле, и это любят спрашивать. Цифра считается
1610
+ верной в строгом, то есть узком, смысле, если абсолютная погрешность числа не превосходит половины
1611
+ единицы того разряда, где стоит эта цифра. Цифра верна в широком смысле, если погрешность не
1612
+ превосходит целой единицы этого разряда. То есть узкий критерий строже широкого ровно вдвое, и одна
1613
+ и та же цифра может быть верной в широком смысле, но уже не дотягивать до верной в строгом.""")
1614
+
1615
+
1616
+
1617
+ # ----- ОШИБКИ (отдельная шпаргалка под теорвопросы про погрешности и устойчивость) -----
1618
+
1619
+ _t(["ошибки округления", "потеря значимости", "накопление ошибок", "ошибки представления"],
1620
+ """ОШИБКИ ОКРУГЛЕНИЯ — общая картина для любого билета.
1621
+
1622
+ Компьютер хранит вещественные числа с конечной точностью, у типа double это около пятнадцати-
1623
+ шестнадцати значащих цифр. Отсюда сразу три источника бед, которые всплывают почти в каждом методе.
1624
+ Первый это ошибка представления, ведь большинство чисел в двоичной системе не записываются точно,
1625
+ даже одна десятая хранится лишь приближённо, и каждое число уже приходит с крошечной погрешностью.
1626
+ Второй это потеря значимости, она возникает при вычитании двух близких чисел, когда старшие
1627
+ одинаковые цифры сокращаются и остаются в основном шумовые младшие разряды. Третий это накопление,
1628
+ когда в длинных суммах и в итерационных методах мелкие погрешности складываются и постепенно
1629
+ вырастают.
1630
+
1631
+ Из-за конечной точности арифметика ещё и перестаёт быть строго ассоциативной, то есть результат
1632
+ может слегка зависеть от порядка операций. Поэтому два математически одинаковых выражения на
1633
+ компьютере могут дать чуть разные ответы.
1634
+
1635
+ Бороться с этим помогает несколько привычек. Складывать лучше числа близкого порядка, избегать
1636
+ вычитания почти равных величин, пользоваться устойчивыми формулами, а для длинных сумм применять
1637
+ суммирование по Кахану, которое заводит компенсирующую переменную и аккуратно возвращает в сумму
1638
+ то, что иначе потерялось бы при округлении.""")
1639
+
1640
+ _t(["ошибка ньютона", "ньютон округление", "округление ньютон", "ошибка округления ньютон"],
1641
+ """ОШИБКА ОКРУГЛЕНИЯ И МЕТОД НЬЮТОНА.
1642
+
1643
+ Шаг Ньютона это икс минус эф делить на эф штрих, а в двумерном случае это деление на определитель
1644
+ матрицы Якоби. У этой формулы два слабых места. Первое возникает, когда производная близка к нулю,
1645
+ тогда мы делим на крошечное число и любая мелкая ошибка округления в значении функции или
1646
+ производной многократно усиливается, шаг становится грубым, и метод может прыгнуть далеко в сторону
1647
+ или застрять. Второе слабое место это сам корень, ведь возле него функция стремится к нулю и
1648
+ вычисляется как разность почти равных величин, отсюда потеря значимости, и именно там, где нужнее
1649
+ всего точность, значение функции считается хуже всего.
1650
+
1651
+ В итоге Ньютон очень быстро подходит к корню, но у самого корня упирается в уровень ошибок
1652
+ округления и точнее этого пола уже не становится. А если рядом ещё и производная мала, этот пол
1653
+ оказывается выше обычного, и квадратичная сходимость деградирует в медленную линейную.
1654
+
1655
+ Самый яркий пример это кратный корень, например функция икс минус один в квадрате. В корне единица
1656
+ производная равна нулю, поэтому Ньютон и теряет скорость, и делит у корня на почти ноль. Такой же
1657
+ эффект даёт система, где кривые касаются в решении, как окружность икс квадрат плюс игрек квадрат
1658
+ равно два и гипербола икс игрек равно один, которые касаются в точке один-один и дают вырожденный
1659
+ Якоби, отчего решение получается с дрожащими последними знаками.""")
1660
+
1661
+ _t(["ошибка степенного метода", "степенной округление", "степенной ошибки", "ошибки степенного"],
1662
+ """ОШИБКА ОКРУГЛЕНИЯ И СТЕПЕННОЙ МЕТОД.
1663
+
1664
+ Степенной метод многократно умножает вектор на матрицу, и тут есть две вещи про ошибки. Во-первых,
1665
+ без нормировки длина вектора каждый шаг растёт или падает в разы и быстро упирается в переполнение
1666
+ или в исчезновение в ноль, поэтому нормировка на каждом шаге это не только математика, но и защита
1667
+ от ошибок представления чисел с плавающей точкой. Во-вторых, скорость сходимости определяется
1668
+ отношением второго собственного значения к первому, и когда они близки по модулю, сходимость очень
1669
+ медленная, итераций нужно много, а значит мелкие ошибки округления успевают заметно накопиться, и
1670
+ последние знаки собственного значения становятся ненадёжными.
1671
+
1672
+ Стандарт хранения чисел тоже вносит свою лепту. Каждый элемент матрицы и вектора уже представлен
1673
+ приближённо, и эти крошечные погрешности на каждом умножении и сложении немного подрастают.
1674
+
1675
+ Минимизировать влияние помогает несколько приёмов. Обязательно нормировать вектор на каждом шаге,
1676
+ считать само собственное значение через устойчивое отношение Рэлея, а не через деление компонент,
1677
+ не задавать чрезмерно жёсткую точность остановки, которой всё равно не достичь из-за округления, и
1678
+ по возможности работать с симметричными или хорошо обусловленными матрицами, где зазор между
1679
+ собственными значениями побольше.""")
1680
+
1681
+ _t(["ошибка оду", "ошибка эйлера", "локальная глобальная ошибка", "ошибки усечения", "ошибки оду"],
1682
+ """ОШИБКИ В ЧИСЛЕННОМ РЕШЕНИИ ОДУ.
1683
+
1684
+ Здесь различают два совершенно разных вида ошибок. Ошибка усечения это ошибка самого метода, она
1685
+ появляется оттого, что мы заменяем точное решение приближённой формулой, и зависит от порядка метода
1686
+ и от шага. Ошибка округления это уже про арифметику с плавающей точкой и появляется из-за конечной
1687
+ точности чисел. Важно ещё отличать локальную ошибку от глобальной. Локальная это промах за один шаг,
1688
+ а глобальная это то, что накопилось на всём отрезке. Обычно глобальная ошибка на один порядок шага
1689
+ хуже локальной, потому что число шагов примерно обратно пропорционально шагу. Отсюда удобное правило,
1690
+ что у метода порядка пэ уменьшение шага вдвое уменьшает итоговую ошибку примерно в два в степени пэ раз.
1691
+
1692
+ Очень поучительно, что делать шаг сколь угодно мелким нельзя. Пока шаг крупный, главная беда это
1693
+ ошибка усечения, и уменьшение шага помогает. Но когда шаг становится совсем маленьким, начинает
1694
+ расти ошибка округления, ведь мы делаем очень много шагов и на каждом немного округляем, а ещё
1695
+ складываем большое значение с очень маленькой добавкой и теряем младшие разряды. Поэтому существует
1696
+ оптимальный шаг где-то посередине.
1697
+
1698
+ Отдельно про устойчивость. У явных методов вроде Эйлера есть ограничение на размер шага, и на
1699
+ жёстких задачах при слишком крупном шаге накопленные ошибки начинают расти лавинообразно, решение
1700
+ разваливается. Неявные методы устойчивее и терпят большие шаги. Фазовый портрет хорошо показывает,
1701
+ сохраняет ли численное решение правильное качественное поведение или метод его искажает из-за
1702
+ накопления ошибок.""")
1703
+
1704
+ _t(["ошибка зейделя", "зейдель округление", "устойчивость к округлению", "ошибки итерационных"],
1705
+ """УСТОЙЧИВОСТЬ ИТЕРАЦИОННЫХ МЕТОДОВ К ОШИБКАМ ОКРУГЛЕНИЯ.
1706
+
1707
+ У итерационных методов вроде простой итерации и Гаусса-Зейделя есть приятное свойство, они сами
1708
+ подправляют ошибки. Пока отображение является сжимающим, то есть константа Липшица меньше единицы,
1709
+ расстояние до решения каждый шаг уменьшается. А значит и любая случайная ошибка округления, которая
1710
+ чуть сдвинула текущее приближение, на следующих шагах тоже затухает, ведь метод просто продолжает
1711
+ притягиваться к неподвижной точке из новой точки. Поэтому такие методы очень устойчивы к округлению,
1712
+ лишняя погрешность не накапливается, а гасится.
1713
+
1714
+ Если же константа Липшица больше или равна единице, картина обратная, ошибки не затухают, а
1715
+ раздуваются, и метод расходится. То есть устойчивость к округлению напрямую завязана на то же
1716
+ условие сжатия, что и сама сходимость.
1717
+
1718
+ Полезно сравнить с прямыми методами. В них нет самокоррекции, и ошибки округления, сделанные по
1719
+ ходу вычислений, остаются в ответе и могут накапливаться, особенно на плохо обусловленных задачах.
1720
+ Поэтому для больших или плохо обусловленных систем итерационные методы часто оказываются надёжнее
1721
+ по части ошибок округления, хотя и сходятся медленнее.""")
1722
+
1723
+ _t(["ошибка штрассена", "штрассен округление", "штрассен ieee754", "ошибки штрассена"],
1724
+ """ОШИБКИ ПРЕДСТАВЛЕНИЯ И АЛГОРИТМ ШТРАССЕНА.
1725
+
1726
+ Каждое число в матрице хранится в стандарте IEEE 754 лишь приближённо, и это исходная ошибка
1727
+ представления. Дальше всё зависит от того, сколько арифметики мы над этими числами совершаем.
1728
+ Наивное умножение для каждого элемента считает честную сумму произведений, и ошибки там накапливаются
1729
+ довольно предсказуемо. Штрассен ради экономии умножений делает много дополнительных сложений и, что
1730
+ важнее, вычитаний промежуточных блоков. А вычитание близких величин это потеря значимости, поэтому у
1731
+ Штрассена больше возможностей для усиления ошибок округления.
1732
+
1733
+ Из этого следует практический вывод. Штрассен асимптотически быстрее, но численно менее устойчив,
1734
+ чем наивный способ. На хорошо обусловленных данных разница незаметна, но на плохо обусловленных
1735
+ матрицах, где элементы сильно различаются по величине, лишние сложения и вычитания промежуточных
1736
+ результатов могут заметно ухудшить точность итога.
1737
+
1738
+ Поэтому Штрассена и его потомков применяют там, где важна скорость на действительно больших
1739
+ матрицах и где данные не слишком капризны, а когда нужна максимальная точность, часто остаются на
1740
+ обычном умножении или комбинируют подходы.""")
1741
+
1742
+ _t(["ошибка интерполяции", "переполнение интерполяция", "overflow интерполяция", "ошибки интерполяции"],
1743
+ """ОШИБКИ И ПЕРЕПОЛНЕНИЕ В ИНТЕРПОЛЯЦИИ.
1744
+
1745
+ В интерполяции ошибки приходят с нескольких сторон. Если данные зашумлены, интерполяция честно
1746
+ протаскивает кривую через каждую шумную точку, и шум переносится в результат, поэтому для
1747
+ зашумлённых данных разумнее аппроксимация, а не точная интерполяция. Если же узлов много и мы строим
1748
+ один многочлен высокой степени, возникает феномен Рунге с дикими колебаниями у краёв, и формальная
1749
+ точность не растёт, а падает.
1750
+
1751
+ Отдельная опасность это переполнение. У многочлена высокой степени в промежуточных вычислениях
1752
+ появляются очень большие произведения и разности, и при больших значениях аргумента или большом
1753
+ числе узлов эти величины могут вылезти за диапазон представимых чисел и превратиться в бесконечность,
1754
+ а заодно по дороге теряется точность из-за вычитания близких больших чисел. Грубо говоря, чем выше
1755
+ степень, тем сильнее раздуваются промежуточные числа и тем выше риск и переполнения, и потери
1756
+ значимости.
1757
+
1758
+ Лекарство простое по сути. Брать локальные методы вроде линейной интерполяции и сплайнов, где
1759
+ каждый кусок зависит лишь от соседних узлов и промежуточные величины остаются скромными, либо
1760
+ снижать степень многочлена. Тогда и переполнение не грозит, и колебаний нет, и устойчивость к
1761
+ ошибкам округления заметно выше.""")
1762
+
1763
+ _t(["ошибка дифференцирования", "ошибка разности", "оптимальный шаг", "ошибки дифференцирования"],
1764
+ """ОШИБКИ ЧИСЛЕННОГО ДИФФЕРЕНЦИРОВАНИЯ.
1765
+
1766
+ Когда производную приближают конечными разностями, спорят две ошибки. Ошибка усечения это ошибка
1767
+ самой формулы, она тем больше, чем крупнее шаг, потому что мы заменяем гладкую производную грубой
1768
+ разностью. У прямой разности эта ошибка падает линейно с уменьшением шага, у центральной разности
1769
+ быстрее, как квадрат шага, потому что несимметричные погрешности слева и справа взаимно гасятся.
1770
+
1771
+ Но уменьшать шаг бесконечно нельзя, и это главная мысль про ошибки здесь. При очень маленьком шаге
1772
+ в числителе мы вычитаем почти одинаковые значения функции, происходит потеря значимости, а потом ещё
1773
+ делим на крошечный шаг, что усиливает уже накопившуюся ошибку округления. Поэтому ошибка округления
1774
+ наоборот растёт, когда шаг слишком мал.
1775
+
1776
+ Из-за этого существует оптимальный шаг где-то посередине, при котором сумма ошибки усечения и ошибки
1777
+ округления минимальна. Делать шаг сколь угодно малым в надежде на точность бессмысленно, после
1778
+ некоторого предела точность не улучшается, а ухудшается. Центральную разность при этом предпочитают
1779
+ прямой, потому что при том же порядке затрат её ошибка усечения квадратичная, а не линейная.""")
1780
+
1781
+ _t(["устойчивость", "распространение ошибки", "стабильность", "обусловленность"],
1782
+ """УСТОЙЧИВОСТЬ И РАСПРОСТРАНЕНИЕ ОШИБКИ.
1783
+
1784
+ Устойчивость это про то, что происходит с маленькими ошибками по ходу вычислений. Алгоритм называют
1785
+ устойчивым, если внесённые погрешности, будь то ошибки округления или неточность входных данных, со
1786
+ временем остаются ограниченными или затухают, а не раздуваются. Если же на каждом шаге ошибка
1787
+ умножается на число больше единицы, она нарастает лавинообразно, и метод неустойчив, его результат
1788
+ быстро теряет смысл. Удобно держать в голове множитель распространения ошибки. Когда он по модулю
1789
+ меньше единицы, ошибка гаснет, когда больше единицы, ошибка растёт.
1790
+
1791
+ Важно отличать устойчивость алгоритма от обусловленности самой задачи. Обусловленность это свойство
1792
+ задачи, она показывает, насколько сильно ответ меняется при малом изменении входных данных. Если
1793
+ задача плохо обусловлена, то даже идеально устойчивый алгоритм не спасёт, ответ будет чувствителен к
1794
+ любым погрешностям. А неустойчивый алгоритм может испортить ответ даже у хорошо обусловленной задачи.
1795
+
1796
+ В численных методах эта идея встречается повсюду. У итерационных методов устойчивость это условие
1797
+ сжатия с константой Липшица меньше единицы. У методов решения ОДУ устойчивость ограничивает размер
1798
+ шага. У матричных вычислений устойчивость обеспечивают ортогональными преобразованиями, которые не
1799
+ раздувают нормы и потому не усиливают ошибки.""")
1800
+
1801
+ _t(["минимизация ошибок", "как уменьшить ошибки округления", "уменьшить ошибки", "кахан"],
1802
+ """КАК УМЕНЬШИТЬ ВЛИЯНИЕ ОШИБОК ОКРУГЛЕНИЯ.
1803
+
1804
+ Полный набор приёмов, который пригодится почти в любом билете, где спрашивают, как минимизировать
1805
+ ошибки. Старайся не вычитать почти равные числа, потому что именно там происходит потеря значимости,
1806
+ и по возможности переписывай формулу так, чтобы опасного вычитания не было. Складывай числа близкого
1807
+ порядка, а не подмешивай к огромной сумме крошечные слагаемые, которые просто теряются. Для длинных
1808
+ сумм используй суммирование по Кахану, оно заводит дополнительную компенсирующую переменную и на
1809
+ каждом шаге аккуратно возвращает в сумму ту часть, что иначе была бы потеряна при округлении, за счёт
1810
+ чего сумма получается заметно точнее обычной.
1811
+
1812
+ Помогает и выбор устойчивых формул и алгоритмов. В линейной алгебре это ортогональные преобразования,
1813
+ которые не раздувают ошибки, в интерполяции это локальные методы и невысокие степени, в численном
1814
+ дифференцировании это разумный, не слишком маленький шаг. Полезно нормировать величины, чтобы они не
1815
+ уходили в переполнение или в исчезающе малые значения.
1816
+
1817
+ И наконец, иногда самый простой выход это увеличить точность вычислений, перейдя к более широкому
1818
+ типу чисел, либо не требовать от метода точности тоньше, чем позволяет машинная арифметика, ведь
1819
+ ниже уровня ошибок округления улучшения всё равно не будет.""")
1820
+
1821
+
1822
+
1823
+ # ====================================================================== ДОСТУП
1824
+ # Синонимы -> канонические ключи (чтобы искать как удобно по формулировке билета)
1825
+ ALIASES = {
1826
+ "ньютон касательных": "ньютон", "касательная": "ньютон",
1827
+ "метод ньютона": "ньютон", "метод бисекции": "бисекция",
1828
+ "метод эйлера": "эйлер", "метод хорд": "секущих",
1829
+ "predictor": "предиктор-корректор", "heun": "хойн",
1830
+ "qr": "qr разложение", "spline": "кубический сплайн",
1831
+ "lagrange": "лагранж", "strassen": "штрассен",
1832
+ "power": "степенной метод", "jacobi": "вращения якоби",
1833
+ "schur": "шур", "dft": "дпф", "euler": "эйлер",
1834
+ }
1835
+
1836
+
1837
+ def _resolve(name, table):
1838
+ """Находит ключ: точное совпадение -> алиас -> подстрока."""
1839
+ key = name.strip().lower()
1840
+ if key in table:
1841
+ return key
1842
+ if key in ALIASES and ALIASES[key] in table:
1843
+ return ALIASES[key]
1844
+ hits = [k for k in table if key in k]
1845
+ if len(hits) == 1:
1846
+ return hits[0]
1847
+ if len(hits) > 1:
1848
+ print("Уточни, есть несколько вариантов:", ", ".join(sorted(hits)))
1849
+ return None
1850
+ return None
1851
+
1852
+
1853
+ def _assemble(key):
1854
+ """Собирает полный код метода (импорты + нужные помощники + сама реализация)."""
1855
+ e = PRACTICE[key]
1856
+ parts = [_IMPORTS]
1857
+ if "pi" in e["bases"]:
1858
+ parts.append(_PI)
1859
+ if "matrix" in e["bases"]:
1860
+ parts.append(_BASE_MATRIX)
1861
+ if "qr" in e["bases"]:
1862
+ parts.append(_QR_FUNC)
1863
+ if "ode" in e["bases"]:
1864
+ parts.append(_BASE_ODE)
1865
+ parts.append(e["code"])
1866
+ return "\n".join(parts)
1867
+
1868
+
1869
+ def code(name):
1870
+ """Молча кладёт готовый код метода в буфер обмена (вообще без вывода)."""
1871
+ key = _resolve(name, PRACTICE)
1872
+ if key is None:
1873
+ print(f"Не нашёл '{name}'. ls() — имена.")
1874
+ return
1875
+ full = _assemble(key)
1876
+ if not _copy(full): # только если буфер недоступен — показать код для ручного копирования
1877
+ print(full)
1878
+
1879
+
1880
+ def show(name):
1881
+ """Если нужно ПОСМОТРЕТЬ код (а не копировать) — печатает его."""
1882
+ key = _resolve(name, PRACTICE)
1883
+ if key is None:
1884
+ print(f"Не нашёл '{name}'. ls() — имена.")
1885
+ return
1886
+ print(_assemble(key))
1887
+
1888
+
1889
+ def theory(name):
1890
+ """Молча кладёт ответ на теоретический вопрос в буфер обмена (вообще без вывода)."""
1891
+ key = _resolve(name, THEORY)
1892
+ if key is None:
1893
+ print(f"Не нашёл теорию '{name}'. ls() — имена.")
1894
+ return
1895
+ text = THEORY[key]
1896
+ if not _copy(text): # только если буфер недоступен — показать текст
1897
+ print(text)
1898
+
1899
+
1900
+ def find(word):
1901
+ """Ищет ключевое слово среди практики и теории — где смотреть по билету."""
1902
+ w = word.strip().lower()
1903
+ pr = sorted({k for k in PRACTICE if w in k})
1904
+ th = sorted({k for k in THEORY if w in k})
1905
+ print(f"Поиск '{word}':")
1906
+ print(" ПРАКТИКА (код):", ", ".join(pr) if pr else "—")
1907
+ print(" ТЕОРИЯ: ", ", ".join(th) if th else "—")
1908
+
1909
+
1910
+ _ГРУППЫ = [
1911
+ ("Нелинейные ур-я", ["бисекция", "простая итерация", "секущих", "ньютон",
1912
+ "модифицированный ньютон", "дихотомия"]),
1913
+ ("Системы нелин.", ["итерация системы", "гаусса-зейделя", "ньютон 2d",
1914
+ "модифицированный ньютон 2d"]),
1915
+ ("Интерполяция", ["линейная интерполяция", "лагранж", "кубический сплайн"]),
1916
+ ("Умножение матриц", ["наивное умножение", "наивное умножение со счётчиком",
1917
+ "штрассен", "штрассен с таймером"]),
1918
+ ("Собств. значения", ["характеристический многочлен", "степенной метод",
1919
+ "степенной со сдвигом", "вращения якоби", "qr алгоритм",
1920
+ "шур", "проверка нормальности", "qr разложение"]),
1921
+ ("ОДУ", ["эйлер", "предиктор-корректор", "рунге-кутта",
1922
+ "адамса-башфорта", "адамса-мултона", "фазовый портрет"]),
1923
+ ("Фурье", ["дпф", "обратное дпф", "бпф", "амплитудный спектр"]),
1924
+ ("Прочее", ["центральная разность", "кахан"]),
1925
+ ]
1926
+
1927
+
1928
+ _ТЕМЫ_ОБЩИЕ = ["бисекция", "ньютон", "модифицированный ньютон", "гаусса-зейделя", "интерполяция",
1929
+ "лагранж", "штрассен", "сложность", "степенной метод", "шур", "qr",
1930
+ "центральная разность", "эйлер", "фазовый портрет", "дпф", "ieee754", "верные цифры"]
1931
+
1932
+ _ТЕМЫ_ОШИБКИ = ["ошибки округления", "ошибка ньютона", "ошибка степенного метода", "ошибка оду",
1933
+ "ошибка зейделя", "ошибка штрассена", "ошибка интерполяции",
1934
+ "ошибка дифференцирования", "устойчивость", "минимизация ошибок"]
1935
+
1936
+
1937
+ def ls():
1938
+ """Компактно показывает всё содержимое по темам."""
1939
+ print("ПРАКТИКА -> code(\"имя\") (копирует в буфер)")
1940
+ for title, names in _ГРУППЫ:
1941
+ print(f" {title:18s}: " + ", ".join(names))
1942
+ print("ТЕОРИЯ -> theory(\"имя\") (копирует в буфер)")
1943
+ print(" Общая : " + ", ".join(_ТЕМЫ_ОБЩИЕ))
1944
+ print(" Ошибки: " + ", ".join(_ТЕМЫ_ОШИБКИ))
1945
+
1946
+
1947
+ # русские синонимы (для обратной совместимости со старым кодом)
1948
+ код = code
1949
+ теория = theory
1950
+ найти = find
1951
+ список = ls
1952
+ показать = show
1953
+
1954
+
1955
+ if __name__ == "__main__":
1956
+ ls()