executable-engineering 0.1.29__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.
@@ -0,0 +1,32 @@
1
+ from .neuralnet_module import init_weights, tanh, tanh_derivative, forward, compute_loss, backward, plot_nn_diagram, init_model, forward_pass, backward_pass, step, reset_model, save_function, change_depth, change_width, draw_network, update_plots
2
+ from .openrootfinder_module import RootFinderOpen
3
+ from .closedrootfinder_module import RootFinderClosed
4
+ from .closedoptimization_module import OptimizerClosed
5
+ from .openoptimization_module import OptimizerOpen
6
+ from .gradientoptimization_module import OptimizerGrad
7
+
8
+
9
+ __all__ = [
10
+ "init_weights",
11
+ "tanh",
12
+ "tanh_derivative",
13
+ "forward",
14
+ "compute_loss",
15
+ "backward",
16
+ "plot_nn_diagram",
17
+ "init_model",
18
+ "forward_pass",
19
+ "backward_pass",
20
+ "step",
21
+ "reset_model",
22
+ "save_function",
23
+ "change_depth",
24
+ "change_width",
25
+ "draw_network",
26
+ "update_plots",
27
+ "RootFinderOpen",
28
+ "RootFinderClosed",
29
+ "OptimizerClosed",
30
+ "OptimizerOpen",
31
+ "OptimizerGrad",
32
+ ]
@@ -0,0 +1,282 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from matplotlib.animation import FuncAnimation
4
+ from IPython.display import HTML, display
5
+ import ipywidgets as widgets
6
+
7
+ class OptimizerClosed:
8
+ def __init__(self, f, a=None, b=None, tol=1e-6, max_iter=30):
9
+ self.f = f
10
+ self.a = a
11
+ self.b = b
12
+ self.tol = tol
13
+ self.max_iter = max_iter
14
+ self.gold_steps = self._run_golden_section()
15
+ self.brent_steps = self._run_brent()
16
+
17
+ def _run_golden_section(self):
18
+ if self.a is None or self.b is None:
19
+ return []
20
+
21
+ a, b = self.a, self.b
22
+ gr = (np.sqrt(5) - 1) / 2
23
+ c = b - gr * (b - a)
24
+ d = a + gr * (b - a)
25
+ steps = []
26
+
27
+ for _ in range(self.max_iter):
28
+ steps.append((a, b, c, d))
29
+
30
+ if abs(b - a) < self.tol:
31
+ break
32
+
33
+ if self.f(c) < self.f(d):
34
+ b = d
35
+ d = c
36
+ c = b - gr * (b - a)
37
+ else:
38
+ a = c
39
+ c = d
40
+ d = a + gr * (b - a)
41
+ return steps
42
+
43
+
44
+ def _run_brent(self):
45
+
46
+ if self.a is None or self.b is None:
47
+ return []
48
+
49
+ a, b = self.a, self.b
50
+ C = (3.0 - np.sqrt(5.0)) / 2.0
51
+
52
+ x = w = v = a + C * (b - a)
53
+ fx = fw = fv = self.f(x)
54
+
55
+ d = e = b - a
56
+ steps = []
57
+
58
+ for iteration in range(self.max_iter):
59
+ m = 0.5 * (a + b)
60
+ tol1 = self.tol * abs(x) + 1e-12
61
+ tol2 = 2.0 * tol1
62
+
63
+ if abs(x - m) <= tol2 - 0.5 * (b - a):
64
+ break
65
+
66
+ parabolic_accepted = False
67
+ method = "golden"
68
+ g = e
69
+
70
+ if abs(g) > tol1:
71
+ r = (x - w) * (fx - fv)
72
+ q = (x - v) * (fx - fw)
73
+ p = (x - v) * q - (x - w) * r
74
+ q = 2.0 * (q - r)
75
+
76
+ if q != 0:
77
+ if q > 0:
78
+ p = -p
79
+ q = abs(q)
80
+
81
+ p_over_q = p / q
82
+ u_candidate = x + p_over_q
83
+
84
+ cond1 = (a + tol2) <= u_candidate <= (b - tol2)
85
+ cond2 = abs(p_over_q) < abs(0.5 * g)
86
+
87
+ if cond1 and cond2:
88
+ u = u_candidate
89
+ method = "parabolic"
90
+ parabolic_accepted = True
91
+
92
+ if not parabolic_accepted:
93
+ if x < m:
94
+ e = b - x
95
+ else:
96
+ e = a - x
97
+ d = C * e
98
+ u = x + d
99
+ method = "golden"
100
+
101
+ if abs(u - x) < tol1:
102
+ u = x + np.sign(u - x) * tol1
103
+
104
+ fu = self.f(u)
105
+
106
+ steps.append((a, b, x, w, v, u, method))
107
+
108
+ if fu <= fx:
109
+ if u >= x:
110
+ a = x
111
+ else:
112
+ b = x
113
+ v, fv = w, fw
114
+ w, fw = x, fx
115
+ x, fx = u, fu
116
+ else:
117
+ if u < x:
118
+ a = u
119
+ else:
120
+ b = u
121
+ if (fu <= fw) or (w == x):
122
+ v, fv = w, fw
123
+ w, fw = u, fu
124
+ elif (fu <= fv) or (v == x) or (v == w):
125
+ v, fv = u, fu
126
+
127
+ e = d if parabolic_accepted else e
128
+
129
+ return steps
130
+
131
+
132
+ def _animate_golden(self, interval_ms=1000):
133
+ steps = self.gold_steps
134
+ if not steps:
135
+ return None
136
+
137
+ x_vals = np.linspace(self.a, self.b, 400)
138
+ y_vals = self.f(x_vals)
139
+
140
+ fig, ax = plt.subplots(figsize=(10, 6))
141
+ ax.plot(x_vals, y_vals, label='f(x)', color='blue')
142
+ ax.set_title("Golden Section Search", fontsize=14)
143
+ ax.set_xlabel("x")
144
+ ax.set_ylabel("f(x)")
145
+ ax.grid(True, linestyle=':')
146
+ ax.legend()
147
+
148
+ a_line = ax.axvline(0, color='red', linestyle=':', lw=1.5, label='x1')
149
+ b_line = ax.axvline(0, color='green', linestyle=':', lw=1.5, label='x2')
150
+
151
+ a_point, = ax.plot([], [], 'o', color='gold')
152
+ b_point, = ax.plot([], [], 'o', color='gold')
153
+ c_point, = ax.plot([], [], 'o', color='gold', label='x3')
154
+ d_point, = ax.plot([], [], 'o', color='orange', label='x4')
155
+ conn, = ax.plot([], [], '--', color='purple', alpha=0.5)
156
+
157
+ ax.legend()
158
+
159
+ def update(i):
160
+ a, b, c, d = steps[i]
161
+ y_a, y_b, y_c, y_d = self.f(a), self.f(b), self.f(c), self.f(d)
162
+
163
+ a_line.set_xdata([a])
164
+ b_line.set_xdata([b])
165
+
166
+ a_point.set_data([a], [y_a])
167
+ b_point.set_data([b], [y_b])
168
+ c_point.set_data([c], [y_c])
169
+ d_point.set_data([d], [y_d])
170
+ conn.set_data([a, c, d, b], [y_a, y_c, y_d, y_b])
171
+
172
+ ax.set_title(f"Golden Section Search - Step {i+1}\n"
173
+ f"Interval: [{a:.4f}, {b:.4f}]")
174
+ return a_line, b_line, a_point, b_point, c_point, d_point, conn
175
+
176
+ anim = FuncAnimation(fig, update, frames=len(steps),
177
+ interval=interval_ms, blit=True, repeat=False)
178
+ plt.close(fig)
179
+ return HTML(anim.to_jshtml())
180
+
181
+
182
+ def _animate_brent(self, interval_ms=1200):
183
+ steps = self.brent_steps
184
+ if not steps:
185
+ return None
186
+
187
+ x_vals = np.linspace(self.a, self.b, 800)
188
+ y_vals = self.f(x_vals)
189
+
190
+ fig, ax = plt.subplots(figsize=(10, 6))
191
+ ax.plot(x_vals, y_vals, label='f(x)', color='blue')
192
+ ax.set_title("Brent's Method (parabolic interpolation + golden fallback)", fontsize=13)
193
+ ax.set_xlabel("x")
194
+ ax.set_ylabel("f(x)")
195
+ ax.grid(True, linestyle=':')
196
+
197
+ a_line = ax.axvline(0, color='red', linestyle=':', lw=1.5, label='a')
198
+ b_line = ax.axvline(0, color='green', linestyle=':', lw=1.5, label='b')
199
+ x_dot, = ax.plot([], [], 'ro', label='x (best)')
200
+ w_dot, = ax.plot([], [], 'go', label='w')
201
+ v_dot, = ax.plot([], [], 'bo', label='v')
202
+ u_dot, = ax.plot([], [], marker='*', markersize=10, color='gold', label='u (candidate)')
203
+ parabola_line, = ax.plot([], [], '--', color='purple', alpha=0.8, label='parabola (if stable)')
204
+
205
+ annotation_texts = []
206
+ ax.legend()
207
+
208
+ def good_for_parabola(xs):
209
+ xs = np.asarray(xs, dtype=float)
210
+ min_spacing = np.min(np.abs(np.diff(np.sort(xs))))
211
+ domain_width = abs(self.b - self.a) if self.b != self.a else 1.0
212
+ return (min_spacing > 1e-10 * domain_width)
213
+
214
+ def update(i):
215
+ for t in annotation_texts:
216
+ t.remove()
217
+ annotation_texts.clear()
218
+
219
+ a, b, x, w, v, u, method = steps[i]
220
+ fa, fb, fx, fw, fv, fu = self.f(a), self.f(b), self.f(x), self.f(w), self.f(v), self.f(u)
221
+
222
+ a_line.set_xdata([a])
223
+ b_line.set_xdata([b])
224
+
225
+ x_dot.set_data([x], [fx])
226
+ w_dot.set_data([w], [fw])
227
+ v_dot.set_data([v], [fv])
228
+ u_dot.set_data([u], [fu])
229
+
230
+ xs_to_fit = np.array([x, w, v])
231
+ min_spacing = np.min(np.abs(np.diff(np.sort(xs_to_fit))))
232
+ domain_width = abs(self.b - self.a) if self.b != self.a else 1.0
233
+
234
+ xs_parabola = np.linspace(min(x, w, v) - 0.05*domain_width, max(x, w, v) + 0.05*domain_width, 300)
235
+ draw_parabola = False
236
+ if min_spacing > 1e-10 * domain_width:
237
+ try:
238
+ coeffs = np.polyfit([x, w, v], [fx, fw, fv], 2)
239
+ parabola = np.polyval(coeffs, xs_parabola)
240
+ if np.all(np.isfinite(parabola)):
241
+ parabola_line.set_data(xs_parabola, parabola)
242
+ draw_parabola = True
243
+ except Exception:
244
+ draw_parabola = False
245
+
246
+ if not draw_parabola:
247
+ parabola_line.set_data([], [])
248
+
249
+ labels = [
250
+ (a, fa, 'a'),
251
+ (b, fb, 'b'),
252
+ (x, fx, 'x'),
253
+ (w, fw, 'w'),
254
+ (v, fv, 'v'),
255
+ (u, fu, f'u ({method[:3]})')
256
+ ]
257
+
258
+ ax.set_title(f"Brent's Method - Step {i+1} (method: {method})\nInterval: [{a:.6f}, {b:.6f}]")
259
+ return (a_line, b_line, x_dot, w_dot, v_dot, u_dot, parabola_line, *annotation_texts)
260
+
261
+ anim = FuncAnimation(fig, update, frames=len(steps),
262
+ interval=interval_ms, blit=False, repeat=False)
263
+ plt.close(fig)
264
+ return HTML(anim.to_jshtml())
265
+
266
+ def show_toggle_closed(self, interval_ms=1000):
267
+ gold_anim_html = self._animate_golden(interval_ms)
268
+ brent_anim_html = self._animate_brent(interval_ms)
269
+
270
+ if gold_anim_html is None or brent_anim_html is None:
271
+ print("Cannot create animations. Check initial interval [a, b].")
272
+ return
273
+
274
+ gold_content = widgets.HTML(value=gold_anim_html.data)
275
+ brent_content = widgets.HTML(value=brent_anim_html.data)
276
+
277
+ tab_container = widgets.Tab()
278
+ tab_container.children = [gold_content, brent_content]
279
+ tab_container.set_title(0, 'Golden Section Search')
280
+ tab_container.set_title(1, "Brent's Method")
281
+
282
+ display(tab_container)
@@ -0,0 +1,155 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from matplotlib.animation import FuncAnimation
4
+ from IPython.display import HTML, display
5
+ import ipywidgets as widgets
6
+
7
+ class RootFinderClosed:
8
+ def __init__(self, f, a=None, b=None, tol=1e-6, max_iter=20):
9
+ self.f = f
10
+ self.a = a
11
+ self.b = b
12
+ self.tol = tol
13
+ self.max_iter = max_iter
14
+ self.bisect_intervals, self.bisect_guesses = self._run_bisection()
15
+ self.falsepos_intervals, self.falsepos_guesses = self._run_false_position()
16
+
17
+ def _run_bisection(self):
18
+ if self.a is None or self.b is None:
19
+ return [], []
20
+ a, b = self.a, self.b
21
+ if self.f(a) * self.f(b) > 0:
22
+ return [], []
23
+
24
+ intervals = []
25
+ guesses = []
26
+ for _ in range(self.max_iter):
27
+ c = (a + b) / 2.0
28
+ intervals.append((a, b, c))
29
+ guesses.append(c)
30
+ if abs(self.f(c)) < self.tol or abs(b - a) < self.tol:
31
+ break
32
+ if self.f(a) * self.f(c) < 0:
33
+ b = c
34
+ else:
35
+ a = c
36
+ return intervals, guesses
37
+
38
+ def _run_false_position(self):
39
+ if self.a is None or self.b is None:
40
+ return [], []
41
+ a, b = self.a, self.b
42
+ if self.f(a) * self.f(b) > 0:
43
+ return [], []
44
+
45
+ intervals = []
46
+ guesses = []
47
+ for _ in range(self.max_iter):
48
+ fa, fb = self.f(a), self.f(b)
49
+ if abs(fb - fa) < 1e-12:
50
+ break
51
+ c = b - fb * (b - a) / (fb - fa)
52
+ intervals.append((a, b, c))
53
+ guesses.append(c)
54
+ if abs(self.f(c)) < self.tol:
55
+ break
56
+ if fa * self.f(c) < 0:
57
+ b = c
58
+ fb = self.f(b)
59
+ else:
60
+ a = c
61
+ fa = self.f(a)
62
+ return intervals, guesses
63
+
64
+ def _make_animation_closed(self, method='Bisection', interval_ms=750):
65
+ if method == 'Bisection':
66
+ intervals, guesses = self.bisect_intervals, self.bisect_guesses
67
+ title_method = "Bisection Method"
68
+ guess_label = "Midpoint"
69
+ else:
70
+ intervals, guesses = self.falsepos_intervals, self.falsepos_guesses
71
+ title_method = "False Position Method"
72
+ guess_label = "Point c"
73
+
74
+ if not guesses:
75
+ return None
76
+
77
+ total_steps = len(guesses)
78
+ all_points = [g for g in guesses] + [p for ab in intervals for p in ab[:2]]
79
+ min_g, max_g = min(all_points), max(all_points)
80
+
81
+ x_span = max_g - min_g if max_g > min_g else 1.0
82
+ x_pad = max(0.2 * x_span, 1.0)
83
+ x_min, x_max = min_g - x_pad, max_g + x_pad
84
+
85
+ x_vals = np.linspace(x_min, x_max, 400)
86
+ y_vals = self.f(x_vals)
87
+
88
+ fig, ax = plt.subplots(figsize=(10, 6))
89
+ ax.plot(x_vals, y_vals, label='f(x)', color='blue')
90
+ ax.axhline(0, color='gray', linestyle='--', alpha=0.7)
91
+
92
+ line, = ax.plot([], [], marker='o', linestyle='-', color='red', markersize=6, alpha=0.6, label='Previous guesses')
93
+ a_line = ax.axvline(0, color='red', linestyle=':', lw=1.5, alpha=0.9, label='a (left endpoint)')
94
+ b_line = ax.axvline(0, color='green', linestyle=':', lw=1.5, alpha=0.9, label='b (right endpoint)')
95
+ current_point, = ax.plot([], [], marker='o', color='gold', markersize=10, markeredgecolor='black', label=guess_label)
96
+ vertical_line, = ax.plot([], [], color='purple', linestyle=':', linewidth=2, label='Connector')
97
+
98
+ ax.set_title(f"{title_method} Animation", fontsize=14)
99
+ ax.set_xlabel("x", fontsize=12)
100
+ ax.set_ylabel("f(x)", fontsize=12)
101
+ ax.grid(True, linestyle=':', alpha=0.6)
102
+ ax.legend()
103
+ ax.set_xlim(x_min, x_max)
104
+
105
+ f_g = [self.f(g) for g in guesses]
106
+ y_min, y_max = min(f_g + [0]), max(f_g + [0])
107
+ y_span = y_max - y_min if y_max > y_min else 1.0
108
+ y_pad = max(0.2 * y_span, 1.0)
109
+ ax.set_ylim(y_min - y_pad, y_max + y_pad)
110
+
111
+ def update_closed(frame):
112
+ step = frame
113
+ shown_intervals = intervals[:step+1]
114
+ shown_guesses = guesses[:step+1]
115
+ f_guesses = [self.f(g) for g in shown_guesses]
116
+
117
+ a, b, c = shown_intervals[-1]
118
+ a_line.set_xdata([a])
119
+ b_line.set_xdata([b])
120
+
121
+ line.set_data(shown_guesses[:-1], f_guesses[:-1])
122
+ current_point.set_data([c], [self.f(c)])
123
+ vertical_line.set_data([c, c], [0, self.f(c)])
124
+
125
+ ax.set_title(f"{title_method} - Step {step}/{total_steps-1}", fontsize=14)
126
+ return line, current_point, vertical_line, a_line, b_line
127
+
128
+ anim = FuncAnimation(
129
+ fig, update_closed,
130
+ frames=total_steps,
131
+ interval=interval_ms,
132
+ blit=True,
133
+ repeat=False
134
+ )
135
+ plt.close(fig)
136
+ return HTML(anim.to_jshtml())
137
+
138
+ def show_toggle_closed(self, interval_ms=750):
139
+ bisect_anim_html = self._make_animation_closed('Bisection', interval_ms)
140
+ falsepos_anim_html = self._make_animation_closed('False Position', interval_ms)
141
+
142
+ if bisect_anim_html is None or falsepos_anim_html is None:
143
+ print("Cannot create animations. Check initial interval [a, b].")
144
+ return
145
+
146
+ bisect_content = widgets.HTML(value=bisect_anim_html.data)
147
+ falsepos_content = widgets.HTML(value=falsepos_anim_html.data)
148
+
149
+ tab_container = widgets.Tab()
150
+ tab_container.children = [bisect_content, falsepos_content]
151
+
152
+ tab_container.set_title(0, 'Bisection')
153
+ tab_container.set_title(1, 'False Position')
154
+
155
+ display(tab_container)
@@ -0,0 +1,169 @@
1
+ import numpy as np
2
+ import matplotlib.pyplot as plt
3
+ from matplotlib.animation import FuncAnimation
4
+ from IPython.display import HTML, display
5
+ import ipywidgets as widgets
6
+
7
+ class OptimizerGrad:
8
+
9
+ def __init__(self, f, grad, hess, x0, lr=0.1, tol=1e-4, max_iter=200):
10
+ self.f = f
11
+ self.grad = grad
12
+ self.hess = hess
13
+ self.x0 = np.asarray(x0, dtype=float)
14
+ self.lr = lr
15
+ self.tol = tol
16
+ self.max_iter = max_iter
17
+
18
+ self.newton_steps = self._run_newton()
19
+ self.gd_steps = self._run_gradient_descent()
20
+
21
+ self._define_plot_domain()
22
+
23
+
24
+ def _run_gradient_descent(self):
25
+ x = self.x0.copy()
26
+ steps = [x.copy()]
27
+ for _ in range(self.max_iter):
28
+ g = self.grad(x)
29
+ x_new = x - self.lr * g
30
+ steps.append(x_new.copy())
31
+ if np.linalg.norm(g) < self.tol:
32
+ break
33
+ x = x_new
34
+ return [tuple(s) for s in steps]
35
+
36
+ def _run_newton(self):
37
+ x = self.x0.copy()
38
+ steps = [x.copy()]
39
+ for _ in range(self.max_iter):
40
+ g = self.grad(x)
41
+ H = self.hess(x)
42
+ try:
43
+ p = np.linalg.solve(H, g)
44
+ except np.linalg.LinAlgError:
45
+ break
46
+ x_new = x - p
47
+ steps.append(x_new.copy())
48
+ if np.linalg.norm(g) < self.tol:
49
+ break
50
+ x = x_new
51
+ return [tuple(s) for s in steps]
52
+
53
+
54
+ def _define_plot_domain(self):
55
+ all_points = np.array(self.newton_steps + self.gd_steps)
56
+ x_min, y_min = np.min(all_points, axis=0)
57
+ x_max, y_max = np.max(all_points, axis=0)
58
+ dx, dy = (x_max - x_min) * 0.3, (y_max - y_min) * 0.3
59
+ x_min, x_max = x_min - dx, x_max + dx
60
+ y_min, y_max = y_min - dy, y_max + dy
61
+ self.x = np.linspace(x_min, x_max, 150)
62
+ self.y = np.linspace(y_min, y_max, 150)
63
+ self.X, self.Y = np.meshgrid(self.x, self.y)
64
+ self.Z = np.array([[self.f([xi, yi]) for xi in self.x] for yi in self.y])
65
+
66
+
67
+ def _animate_optimization(self, steps, method_name, interval_ms=500, show_vectors=False):
68
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
69
+ fig.subplots_adjust(wspace=0.3)
70
+
71
+ contour = ax1.contourf(self.X, self.Y, self.Z, levels=50, cmap='viridis')
72
+ ax1.contour(self.X, self.Y, self.Z, levels=15, colors='k', alpha=0.3, linewidths=0.5)
73
+ ax1.plot(self.x0[0], self.x0[1], 'r*', markersize=10, label='Initial Guess')
74
+ path_line, = ax1.plot([], [], 'r-', lw=1.5, alpha=0.7)
75
+ current_dot, = ax1.plot([], [], 'ro', markersize=6, label='Current Point')
76
+ ax1.legend(loc='lower left')
77
+ cbar = fig.colorbar(contour, ax=ax1)
78
+ cbar.set_label("f(x, y)", rotation=270, labelpad=15)
79
+
80
+ title_text = ax1.text(0.5, 1.03, '', transform=ax1.transAxes,
81
+ ha='center', fontsize=12)
82
+
83
+ indicator_box = ax1.annotate(
84
+ '',
85
+ xy=(0.02, 0.98),
86
+ xycoords='axes fraction',
87
+ textcoords='axes fraction',
88
+ ha='left', va='top', fontsize=10,
89
+ bbox=dict(boxstyle="round,pad=0.5", fc="white", alpha=0.8, ec="black")
90
+ )
91
+
92
+ quivers = []
93
+
94
+ grad_vals = np.array([[self.grad([xi, yi]) for xi in self.x] for yi in self.y])
95
+ Gx, Gy = grad_vals[:, :, 0], grad_vals[:, :, 1]
96
+ grad_mag = np.sqrt(Gx**2 + Gy**2)
97
+
98
+ if "Gradient" in method_name:
99
+ Gx_u = np.divide(Gx, grad_mag, out=np.zeros_like(Gx), where=grad_mag != 0)
100
+ Gy_u = np.divide(Gy, grad_mag, out=np.zeros_like(Gy), where=grad_mag != 0)
101
+
102
+ skip = (slice(None, None, 6), slice(None, None, 6))
103
+ Q = ax2.quiver(
104
+ self.X[skip], self.Y[skip],
105
+ Gx_u[skip], Gy_u[skip],
106
+ grad_mag[skip], cmap='plasma',
107
+ angles='xy', scale_units='xy', scale=15, width=0.005
108
+ )
109
+ ax2.set_title("Gradient Vector Field ∇f(x, y)")
110
+ fig.colorbar(Q, ax=ax2, label="‖∇f(x, y)‖")
111
+ else:
112
+ grad_contour = ax2.contourf(self.X, self.Y, grad_mag, levels=40, cmap='plasma')
113
+ fig.colorbar(grad_contour, ax=ax2, label="‖∇f(x, y)‖")
114
+ ax2.set_title("Gradient Magnitude Field ‖∇f(x, y)‖")
115
+
116
+ ax2.set_xlabel("x")
117
+ ax2.set_ylabel("y")
118
+
119
+ def update(i):
120
+ current_path_x = [step[0] for step in steps[:i+1]]
121
+ current_path_y = [step[1] for step in steps[:i+1]]
122
+ path_line.set_data(current_path_x, current_path_y)
123
+
124
+ current_x, current_y = steps[i]
125
+ current_dot.set_data([current_x], [current_y])
126
+ f_val = self.f([current_x, current_y])
127
+
128
+ for q in quivers:
129
+ q.remove()
130
+ quivers.clear()
131
+
132
+ if show_vectors and i > 0:
133
+ x_prev, y_prev = steps[i-1]
134
+ dx, dy = current_x - x_prev, current_y - y_prev
135
+ q1 = ax1.quiver(x_prev, y_prev, dx, dy, angles='xy', scale_units='xy',
136
+ scale=1, color='red', width=0.005)
137
+ q2 = ax2.quiver(x_prev, y_prev, dx, dy, angles='xy', scale_units='xy',
138
+ scale=1, color='black', width=0.005)
139
+ quivers.extend([q1, q2])
140
+
141
+ title_text.set_text(
142
+ f"{method_name} Step {i+1}/{len(steps)}\n"
143
+ f"({current_x:.4f}, {current_y:.4f}) f={f_val:.4f}"
144
+ )
145
+
146
+ grad_norm = np.linalg.norm(self.grad([current_x, current_y]))
147
+ indicator_box.set_text(f"‖∇f‖ = {grad_norm:.4e}")
148
+
149
+ return path_line, current_dot, title_text, indicator_box, *quivers
150
+
151
+ anim = FuncAnimation(fig, update, frames=len(steps),
152
+ interval=interval_ms, blit=True, repeat=False)
153
+ plt.close(fig)
154
+ return HTML(anim.to_jshtml())
155
+
156
+
157
+ def show_toggle_open(self, interval_ms=500):
158
+ gd_anim = self._animate_optimization(self.gd_steps, "Gradient Descent",
159
+ interval_ms, show_vectors=True)
160
+ newton_anim = self._animate_optimization(self.newton_steps, "Newton's Method",
161
+ interval_ms, show_vectors=True)
162
+
163
+ gd_tab = widgets.HTML(value=gd_anim.data)
164
+ newton_tab = widgets.HTML(value=newton_anim.data)
165
+ tab = widgets.Tab()
166
+ tab.children = [gd_tab, newton_tab]
167
+ tab.set_title(0, "Gradient Descent")
168
+ tab.set_title(1, "Newton's Method")
169
+ display(tab)