executable-engineering 0.1.32__py3-none-any.whl → 0.1.33__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 +2 -1
- executable_engineering/linearsystems_module.py +113 -39
- {executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/METADATA +1 -1
- {executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/RECORD +7 -7
- {executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/WHEEL +0 -0
- {executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/licenses/LICENSE +0 -0
- {executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/top_level.txt +0 -0
|
@@ -5,10 +5,11 @@ from .closedoptimization_module import OptimizerClosed
|
|
|
5
5
|
from .openoptimization_module import OptimizerOpen
|
|
6
6
|
from .gradientoptimization_module import OptimizerGrad
|
|
7
7
|
from .numerical_error_module import python_internal_binary, decimal_to_binary
|
|
8
|
-
from .linearsystems_module import visual_solve_2d
|
|
8
|
+
from .linearsystems_module import visual_solve_2d, visualize_conditioning
|
|
9
9
|
|
|
10
10
|
__all__ = [
|
|
11
11
|
"visual_solve_2d",
|
|
12
|
+
"visualize_conditioning",
|
|
12
13
|
"init_weights",
|
|
13
14
|
"tanh",
|
|
14
15
|
"tanh_derivative",
|
|
@@ -1,76 +1,82 @@
|
|
|
1
1
|
import numpy as np
|
|
2
2
|
import plotly.graph_objects as go
|
|
3
|
+
from plotly.subplots import make_subplots
|
|
3
4
|
import plotly.colors as pcolors
|
|
4
5
|
|
|
5
|
-
def visual_solve_2d(A, b,
|
|
6
|
+
def visual_solve_2d(A, b, method='exact'):
|
|
6
7
|
"""
|
|
7
8
|
Solves and visualizes a 2D linear system Ax = b.
|
|
8
9
|
Plots the lines represented by the rows of A and b, and the solution point.
|
|
9
|
-
Falls back to the pseudoinverse for singular or non-square systems.
|
|
10
10
|
|
|
11
11
|
Args:
|
|
12
12
|
A: (N, 2) array-like of coefficients.
|
|
13
13
|
b: (N,) array-like of constants.
|
|
14
|
-
|
|
15
|
-
title: Plot title.
|
|
16
|
-
labels: Tuple of (x_label, y_label) for axes.
|
|
14
|
+
method: 'exact' (uses np.linalg.solve) or 'pseudo' (uses np.linalg.pinv).
|
|
17
15
|
"""
|
|
18
|
-
if labels is None:
|
|
19
|
-
labels = ("x", "y")
|
|
20
|
-
|
|
21
16
|
A = np.array(A, dtype=float)
|
|
22
17
|
b = np.array(b, dtype=float).flatten()
|
|
23
18
|
|
|
24
19
|
if A.shape[1] != 2:
|
|
25
20
|
raise ValueError("visual_solve_2d only supports 2D systems (A must have 2 columns).")
|
|
26
21
|
|
|
27
|
-
# 1. Solve the system
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
sol = np.linalg.pinv(A) @ b
|
|
22
|
+
# 1. Solve the system based on chosen method
|
|
23
|
+
sol = None
|
|
24
|
+
plot_star = False
|
|
31
25
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
26
|
+
if method == 'exact':
|
|
27
|
+
try:
|
|
28
|
+
if A.shape[0] == A.shape[1]:
|
|
29
|
+
sol = np.linalg.solve(A, b)
|
|
30
|
+
if np.allclose(A @ sol, b):
|
|
31
|
+
plot_star = True
|
|
32
|
+
sol_label = "Exact Solution"
|
|
33
|
+
except np.linalg.LinAlgError:
|
|
34
|
+
pass # No exact unique solution
|
|
35
|
+
|
|
36
|
+
elif method == 'pseudo':
|
|
37
|
+
sol = np.linalg.pinv(A) @ b
|
|
38
|
+
plot_star = True
|
|
39
|
+
|
|
40
|
+
if np.allclose(A @ sol, b):
|
|
41
|
+
sol_label = "Exact Solution (via Pseudoinverse)"
|
|
42
|
+
else:
|
|
43
|
+
sol_label = "Best Fit Solution"
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError("method must be 'exact' or 'pseudo'")
|
|
37
46
|
|
|
38
47
|
# 2. Determine plotting range
|
|
39
|
-
if
|
|
48
|
+
if sol is not None:
|
|
40
49
|
center_x = sol[0]
|
|
41
|
-
|
|
50
|
+
center_y = sol[1]
|
|
51
|
+
else:
|
|
52
|
+
# Fallback if no solution is found
|
|
53
|
+
center_x = 10
|
|
54
|
+
center_y = 10
|
|
42
55
|
|
|
56
|
+
x_margin = 10
|
|
57
|
+
x_range = (center_x - x_margin, center_x + x_margin)
|
|
43
58
|
x_vals = np.linspace(x_range[0], x_range[1], 100)
|
|
44
59
|
|
|
45
60
|
fig = go.Figure()
|
|
46
|
-
|
|
47
61
|
colors = pcolors.qualitative.Plotly
|
|
48
62
|
|
|
49
63
|
# 3. Plot each line
|
|
50
64
|
for i in range(A.shape[0]):
|
|
51
65
|
a1, a2 = A[i]
|
|
52
66
|
bi = b[i]
|
|
67
|
+
line_name = f"Row {i+1}"
|
|
53
68
|
|
|
54
69
|
if np.abs(a2) > 1e-10:
|
|
55
|
-
# y = (bi - a1*x) / a2
|
|
56
70
|
y_vals = (bi - a1 * x_vals) / a2
|
|
57
|
-
line_name = f"{a1:g}{labels[0]} + {a2:g}{labels[1]} = {bi:g}"
|
|
58
71
|
fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines', name=line_name, line=dict(color=colors[i % len(colors)])))
|
|
59
72
|
else:
|
|
60
|
-
# Vertical line: x = bi / a1
|
|
61
73
|
if np.abs(a1) > 1e-10:
|
|
62
74
|
x_val = bi / a1
|
|
63
|
-
|
|
64
|
-
fig.add_trace(go.Scatter(x=[x_val, x_val], y=[-100, 100], mode='lines', name=line_name, line=dict(color=colors[i % len(colors)])))
|
|
75
|
+
fig.add_trace(go.Scatter(x=[x_val, x_val], y=[center_y - 100, center_y + 100], mode='lines', name=line_name, line=dict(color=colors[i % len(colors)])))
|
|
65
76
|
|
|
66
|
-
# 4. Plot solution point
|
|
67
|
-
if
|
|
68
|
-
|
|
69
|
-
sol_label = "Exact Solution"
|
|
70
|
-
else:
|
|
71
|
-
sol_label = "Best Fit Solution"
|
|
72
|
-
|
|
73
|
-
hover_text = f"{labels[0]}={sol[0]:.4g}, {labels[1]}={sol[1]:.4g}"
|
|
77
|
+
# 4. Plot solution point if required
|
|
78
|
+
if plot_star and sol is not None:
|
|
79
|
+
hover_text = f"x={sol[0]:.4g}, y={sol[1]:.4g}"
|
|
74
80
|
fig.add_trace(go.Scatter(
|
|
75
81
|
x=[sol[0]],
|
|
76
82
|
y=[sol[1]],
|
|
@@ -81,15 +87,83 @@ def visual_solve_2d(A, b, x_range=None, title="Linear System", labels=None):
|
|
|
81
87
|
hovertext=hover_text
|
|
82
88
|
))
|
|
83
89
|
|
|
84
|
-
# Auto-adjust y-range nicely around the
|
|
90
|
+
# 5. Auto-adjust y-range nicely around the center
|
|
85
91
|
y_margin = (x_range[1] - x_range[0]) / 2.0
|
|
92
|
+
yaxis_dict = dict(range=[center_y - y_margin, center_y + y_margin], scaleanchor="x", scaleratio=1)
|
|
93
|
+
|
|
94
|
+
fig.update_layout(
|
|
95
|
+
xaxis=dict(range=[x_range[0], x_range[1]]),
|
|
96
|
+
yaxis=yaxis_dict,
|
|
97
|
+
width=700,
|
|
98
|
+
height=500,
|
|
99
|
+
margin=dict(l=20, r=20, t=50, b=20)
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
return fig
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def visualize_conditioning(A):
|
|
106
|
+
"""
|
|
107
|
+
Visualizes the effect of the condition number by applying the matrix transformation
|
|
108
|
+
and its inverse to the unit circle.
|
|
109
|
+
|
|
110
|
+
Args:
|
|
111
|
+
A: (2, 2) array-like matrix
|
|
112
|
+
"""
|
|
113
|
+
A = np.array(A, dtype=float)
|
|
114
|
+
if A.shape != (2, 2):
|
|
115
|
+
raise ValueError("visualize_conditioning requires a 2x2 matrix.")
|
|
116
|
+
|
|
117
|
+
# Create the unit circle
|
|
118
|
+
theta = np.linspace(0, 2*np.pi, 200)
|
|
119
|
+
circle = np.array([np.cos(theta), np.sin(theta)])
|
|
120
|
+
|
|
121
|
+
# Apply transformation A to the unit circle
|
|
122
|
+
transformed_A = A @ circle
|
|
123
|
+
|
|
124
|
+
# Check if invertible, if so, apply A^-1 to the unit circle
|
|
125
|
+
try:
|
|
126
|
+
A_inv = np.linalg.inv(A)
|
|
127
|
+
transformed_Ainv = A_inv @ circle
|
|
128
|
+
cond = np.linalg.cond(A)
|
|
129
|
+
subtitle = f"Condition Number: {cond:.2f}"
|
|
130
|
+
except np.linalg.LinAlgError:
|
|
131
|
+
transformed_Ainv = np.zeros_like(circle)
|
|
132
|
+
subtitle = "Matrix is singular (Condition Number: ∞)"
|
|
133
|
+
|
|
134
|
+
fig = make_subplots(
|
|
135
|
+
rows=1, cols=2,
|
|
136
|
+
subplot_titles=(f"Forward Transformation (A*x)", f"Inverse Transformation (A⁻¹*b)"),
|
|
137
|
+
horizontal_spacing=0.1
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
# Subplot 1: Forward transformation
|
|
141
|
+
fig.add_trace(go.Scatter(x=circle[0], y=circle[1], mode='lines', name='Unit Circle', line=dict(color='gray', dash='dash')), row=1, col=1)
|
|
142
|
+
fig.add_trace(go.Scatter(x=transformed_A[0], y=transformed_A[1], mode='lines', name='A * Circle', line=dict(color='blue')), row=1, col=1)
|
|
143
|
+
|
|
144
|
+
# Subplot 2: Inverse transformation
|
|
145
|
+
fig.add_trace(go.Scatter(x=circle[0], y=circle[1], mode='lines', name='Unit Circle', line=dict(color='gray', dash='dash'), showlegend=False), row=1, col=2)
|
|
146
|
+
fig.add_trace(go.Scatter(x=transformed_Ainv[0], y=transformed_Ainv[1], mode='lines', name='A⁻¹ * Circle', line=dict(color='red')), row=1, col=2)
|
|
147
|
+
|
|
148
|
+
# Update axes to be equal aspect ratio
|
|
149
|
+
max_A = np.max(np.abs(transformed_A)) * 1.1 if np.max(np.abs(transformed_A)) > 0 else 1
|
|
150
|
+
max_Ainv = np.max(np.abs(transformed_Ainv)) * 1.1 if np.max(np.abs(transformed_Ainv)) > 0 else 1
|
|
151
|
+
|
|
152
|
+
# Ensure they are at least 1.1 so the unit circle is visible
|
|
153
|
+
max_A = max(max_A, 1.1)
|
|
154
|
+
max_Ainv = max(max_Ainv, 1.1)
|
|
155
|
+
|
|
156
|
+
fig.update_xaxes(range=[-max_A, max_A], row=1, col=1)
|
|
157
|
+
fig.update_yaxes(range=[-max_A, max_A], scaleanchor="x", scaleratio=1, row=1, col=1)
|
|
158
|
+
|
|
159
|
+
fig.update_xaxes(range=[-max_Ainv, max_Ainv], row=1, col=2)
|
|
160
|
+
fig.update_yaxes(range=[-max_Ainv, max_Ainv], scaleanchor="x2", scaleratio=1, row=1, col=2)
|
|
86
161
|
|
|
87
162
|
fig.update_layout(
|
|
88
|
-
title=
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
template="plotly_white"
|
|
163
|
+
title=f"Visualizing Matrix Distortion ({subtitle})",
|
|
164
|
+
width=900,
|
|
165
|
+
height=500,
|
|
166
|
+
margin=dict(l=20, r=20, t=60, b=20)
|
|
93
167
|
)
|
|
94
168
|
|
|
95
169
|
return fig
|
{executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/METADATA
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: executable_engineering
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.33
|
|
4
4
|
Summary: Demonstrative tools for the Executable Engineering textbook
|
|
5
5
|
Home-page: https://github.com/themintlab/ExecutableEngineering/tree/main/executable_engineering
|
|
6
6
|
Author: Michael Welland
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
executable_engineering/__init__.py,sha256=
|
|
1
|
+
executable_engineering/__init__.py,sha256=jrrsfub0C6cM1R9r7Gw97tW5CxX5kNEoYkvxlHzSJjU,1166
|
|
2
2
|
executable_engineering/closedoptimization_module.py,sha256=i9KkdulyCZmriYKlx0CuUetjoMmxJm9ADWgStU3w85w,9273
|
|
3
3
|
executable_engineering/closedrootfinder_module.py,sha256=ozccVABOrTiqcdkkqwAWRNo04MsDC0Z_f_tfukl3q20,5706
|
|
4
4
|
executable_engineering/gradientoptimization_module.py,sha256=-2fErE9cemSEPeV9PlWOf4cF7Tr6DjilEw_NxvE-JYw,6601
|
|
5
|
-
executable_engineering/linearsystems_module.py,sha256=
|
|
5
|
+
executable_engineering/linearsystems_module.py,sha256=JNKcHPUxOBJ4xoFYZyUFwbEOGGD_gi6zRQfcJKYBb5E,6080
|
|
6
6
|
executable_engineering/neuralnet_module.py,sha256=Hu9QFoMyODg4RCEpHmT9zTwzjcVFYM8ZLYtbB3CbrsA,10102
|
|
7
7
|
executable_engineering/numerical_error_module.py,sha256=wxfYP8u29c19I2D6MxpQV3zDMV0DpS1QUqFpp3f6eGE,1204
|
|
8
8
|
executable_engineering/openoptimization_module.py,sha256=OCPyrynkTo1It50RoT7irnL72aQn6FexE_R1SfFYxlg,8590
|
|
9
9
|
executable_engineering/openrootfinder_module.py,sha256=rmXDi0XdrDYdLZdPFgYCihMg_4NF1D71i6kh8Kx-V2w,5802
|
|
10
|
-
executable_engineering-0.1.
|
|
11
|
-
executable_engineering-0.1.
|
|
12
|
-
executable_engineering-0.1.
|
|
13
|
-
executable_engineering-0.1.
|
|
14
|
-
executable_engineering-0.1.
|
|
10
|
+
executable_engineering-0.1.33.dist-info/licenses/LICENSE,sha256=Tg4nrt8rFhgpkPbIA8SWoCR3MlGLw0NufB8AE6-TH_o,1074
|
|
11
|
+
executable_engineering-0.1.33.dist-info/METADATA,sha256=bZoPtBOuuAakueT9uX15GfwOIJeQaEw3YDkewAiKjaM,1648
|
|
12
|
+
executable_engineering-0.1.33.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
13
|
+
executable_engineering-0.1.33.dist-info/top_level.txt,sha256=0U5ATV5WGTLvN9rRSjQ54TyL-ztTyXaN3FzZ_QMq4HM,23
|
|
14
|
+
executable_engineering-0.1.33.dist-info/RECORD,,
|
|
File without changes
|
{executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{executable_engineering-0.1.32.dist-info → executable_engineering-0.1.33.dist-info}/top_level.txt
RENAMED
|
File without changes
|