executable-engineering 0.1.29__tar.gz
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-0.1.29/LICENSE +21 -0
- executable_engineering-0.1.29/PKG-INFO +55 -0
- executable_engineering-0.1.29/README.md +21 -0
- executable_engineering-0.1.29/executable_engineering/__init__.py +32 -0
- executable_engineering-0.1.29/executable_engineering/closedoptimization_module.py +282 -0
- executable_engineering-0.1.29/executable_engineering/closedrootfinder_module.py +155 -0
- executable_engineering-0.1.29/executable_engineering/gradientoptimization_module.py +169 -0
- executable_engineering-0.1.29/executable_engineering/neuralnet_module.py +285 -0
- executable_engineering-0.1.29/executable_engineering/openoptimization_module.py +231 -0
- executable_engineering-0.1.29/executable_engineering/openrootfinder_module.py +157 -0
- executable_engineering-0.1.29/executable_engineering.egg-info/PKG-INFO +55 -0
- executable_engineering-0.1.29/executable_engineering.egg-info/SOURCES.txt +16 -0
- executable_engineering-0.1.29/executable_engineering.egg-info/dependency_links.txt +1 -0
- executable_engineering-0.1.29/executable_engineering.egg-info/requires.txt +10 -0
- executable_engineering-0.1.29/executable_engineering.egg-info/top_level.txt +1 -0
- executable_engineering-0.1.29/pyproject.toml +3 -0
- executable_engineering-0.1.29/setup.cfg +4 -0
- executable_engineering-0.1.29/setup.py +36 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Michael J Welland
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: executable_engineering
|
|
3
|
+
Version: 0.1.29
|
|
4
|
+
Summary: Tools for ENGPHYS 3NM4
|
|
5
|
+
Home-page: https://github.com/themintlab/ExecutableEngineering/tree/main/executable_engineering
|
|
6
|
+
Author: Michael Welland
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.9
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: numpy
|
|
15
|
+
Requires-Dist: matplotlib
|
|
16
|
+
Requires-Dist: networkx
|
|
17
|
+
Requires-Dist: ipywidgets
|
|
18
|
+
Requires-Dist: IPython
|
|
19
|
+
Requires-Dist: pyppeteer
|
|
20
|
+
Requires-Dist: nbconvert
|
|
21
|
+
Requires-Dist: scipy
|
|
22
|
+
Requires-Dist: plotly
|
|
23
|
+
Requires-Dist: pandas
|
|
24
|
+
Dynamic: author
|
|
25
|
+
Dynamic: classifier
|
|
26
|
+
Dynamic: description
|
|
27
|
+
Dynamic: description-content-type
|
|
28
|
+
Dynamic: home-page
|
|
29
|
+
Dynamic: license
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
Dynamic: requires-dist
|
|
32
|
+
Dynamic: requires-python
|
|
33
|
+
Dynamic: summary
|
|
34
|
+
|
|
35
|
+
# executable_engineering
|
|
36
|
+
|
|
37
|
+
`executable_engineering` is the interactive numerical methods support package used by this book.
|
|
38
|
+
|
|
39
|
+
## Local development
|
|
40
|
+
|
|
41
|
+
From the repository root:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
python -m pip install -r requirements-book.txt
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
That installs the in-repo package in editable mode so notebook changes use the local source.
|
|
48
|
+
|
|
49
|
+
## Standalone package checks
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
python -m pip install -e ./executable_engineering
|
|
53
|
+
( cd /tmp && python -c "from executable_engineering import RootFinderOpen, RootFinderClosed" )
|
|
54
|
+
python -m build ./executable_engineering
|
|
55
|
+
```
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# executable_engineering
|
|
2
|
+
|
|
3
|
+
`executable_engineering` is the interactive numerical methods support package used by this book.
|
|
4
|
+
|
|
5
|
+
## Local development
|
|
6
|
+
|
|
7
|
+
From the repository root:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
python -m pip install -r requirements-book.txt
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
That installs the in-repo package in editable mode so notebook changes use the local source.
|
|
14
|
+
|
|
15
|
+
## Standalone package checks
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
python -m pip install -e ./executable_engineering
|
|
19
|
+
( cd /tmp && python -c "from executable_engineering import RootFinderOpen, RootFinderClosed" )
|
|
20
|
+
python -m build ./executable_engineering
|
|
21
|
+
```
|
|
@@ -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)
|