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.
- executable_engineering/__init__.py +32 -0
- executable_engineering/closedoptimization_module.py +282 -0
- executable_engineering/closedrootfinder_module.py +155 -0
- executable_engineering/gradientoptimization_module.py +169 -0
- executable_engineering/neuralnet_module.py +285 -0
- executable_engineering/openoptimization_module.py +231 -0
- executable_engineering/openrootfinder_module.py +157 -0
- executable_engineering-0.1.29.dist-info/METADATA +55 -0
- executable_engineering-0.1.29.dist-info/RECORD +12 -0
- executable_engineering-0.1.29.dist-info/WHEEL +5 -0
- executable_engineering-0.1.29.dist-info/licenses/LICENSE +21 -0
- executable_engineering-0.1.29.dist-info/top_level.txt +1 -0
|
@@ -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)
|