executable-engineering 0.1.31__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.
@@ -5,9 +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
-
8
+ from .linearsystems_module import visual_solve_2d, visualize_conditioning
9
9
 
10
10
  __all__ = [
11
+ "visual_solve_2d",
12
+ "visualize_conditioning",
11
13
  "init_weights",
12
14
  "tanh",
13
15
  "tanh_derivative",
@@ -0,0 +1,169 @@
1
+ import numpy as np
2
+ import plotly.graph_objects as go
3
+ from plotly.subplots import make_subplots
4
+ import plotly.colors as pcolors
5
+
6
+ def visual_solve_2d(A, b, method='exact'):
7
+ """
8
+ Solves and visualizes a 2D linear system Ax = b.
9
+ Plots the lines represented by the rows of A and b, and the solution point.
10
+
11
+ Args:
12
+ A: (N, 2) array-like of coefficients.
13
+ b: (N,) array-like of constants.
14
+ method: 'exact' (uses np.linalg.solve) or 'pseudo' (uses np.linalg.pinv).
15
+ """
16
+ A = np.array(A, dtype=float)
17
+ b = np.array(b, dtype=float).flatten()
18
+
19
+ if A.shape[1] != 2:
20
+ raise ValueError("visual_solve_2d only supports 2D systems (A must have 2 columns).")
21
+
22
+ # 1. Solve the system based on chosen method
23
+ sol = None
24
+ plot_star = False
25
+
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'")
46
+
47
+ # 2. Determine plotting range
48
+ if sol is not None:
49
+ center_x = sol[0]
50
+ center_y = sol[1]
51
+ else:
52
+ # Fallback if no solution is found
53
+ center_x = 10
54
+ center_y = 10
55
+
56
+ x_margin = 10
57
+ x_range = (center_x - x_margin, center_x + x_margin)
58
+ x_vals = np.linspace(x_range[0], x_range[1], 100)
59
+
60
+ fig = go.Figure()
61
+ colors = pcolors.qualitative.Plotly
62
+
63
+ # 3. Plot each line
64
+ for i in range(A.shape[0]):
65
+ a1, a2 = A[i]
66
+ bi = b[i]
67
+ line_name = f"Row {i+1}"
68
+
69
+ if np.abs(a2) > 1e-10:
70
+ y_vals = (bi - a1 * x_vals) / a2
71
+ fig.add_trace(go.Scatter(x=x_vals, y=y_vals, mode='lines', name=line_name, line=dict(color=colors[i % len(colors)])))
72
+ else:
73
+ if np.abs(a1) > 1e-10:
74
+ x_val = bi / a1
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)])))
76
+
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}"
80
+ fig.add_trace(go.Scatter(
81
+ x=[sol[0]],
82
+ y=[sol[1]],
83
+ mode='markers',
84
+ marker=dict(color='black', size=12, symbol='star'),
85
+ name=sol_label,
86
+ hoverinfo='text',
87
+ hovertext=hover_text
88
+ ))
89
+
90
+ # 5. Auto-adjust y-range nicely around the center
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)
161
+
162
+ fig.update_layout(
163
+ title=f"Visualizing Matrix Distortion ({subtitle})",
164
+ width=900,
165
+ height=500,
166
+ margin=dict(l=20, r=20, t=60, b=20)
167
+ )
168
+
169
+ return fig
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: executable_engineering
3
- Version: 0.1.31
4
- Summary: Tools for ENGPHYS 3NM4
3
+ Version: 0.1.33
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
7
7
  License: MIT
@@ -1,13 +1,14 @@
1
- executable_engineering/__init__.py,sha256=sKydh1swA1LaNjxLVe9N7NG-uGNYhXHiHzcnf5uw5E8,1044
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=JNKcHPUxOBJ4xoFYZyUFwbEOGGD_gi6zRQfcJKYBb5E,6080
5
6
  executable_engineering/neuralnet_module.py,sha256=Hu9QFoMyODg4RCEpHmT9zTwzjcVFYM8ZLYtbB3CbrsA,10102
6
7
  executable_engineering/numerical_error_module.py,sha256=wxfYP8u29c19I2D6MxpQV3zDMV0DpS1QUqFpp3f6eGE,1204
7
8
  executable_engineering/openoptimization_module.py,sha256=OCPyrynkTo1It50RoT7irnL72aQn6FexE_R1SfFYxlg,8590
8
9
  executable_engineering/openrootfinder_module.py,sha256=rmXDi0XdrDYdLZdPFgYCihMg_4NF1D71i6kh8Kx-V2w,5802
9
- executable_engineering-0.1.31.dist-info/licenses/LICENSE,sha256=Tg4nrt8rFhgpkPbIA8SWoCR3MlGLw0NufB8AE6-TH_o,1074
10
- executable_engineering-0.1.31.dist-info/METADATA,sha256=8_qdrwmMDcHZIxc2hdKXPMJv5Q8fAx32hcDi-Zr-NZU,1611
11
- executable_engineering-0.1.31.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
12
- executable_engineering-0.1.31.dist-info/top_level.txt,sha256=0U5ATV5WGTLvN9rRSjQ54TyL-ztTyXaN3FzZ_QMq4HM,23
13
- executable_engineering-0.1.31.dist-info/RECORD,,
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,,