IBB-Helper 0.4.8.dev28__tar.gz → 0.4.8.dev30__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.
- {ibb_helper-0.4.8.dev28/src/IBB_Helper.egg-info → ibb_helper-0.4.8.dev30}/PKG-INFO +1 -1
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/pyproject.toml +1 -1
- ibb_helper-0.4.8.dev30/src/IBB_Helper/animate.py +249 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/combine_plots.py +14 -3
- ibb_helper-0.4.8.dev30/src/IBB_Helper/display.py +65 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/display_eigen.py +127 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/display_matrix.py +59 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/extend_plot.py +221 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/minimize.py +68 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/num_int.py +84 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/plot_2d.py +23 -10
- ibb_helper-0.4.8.dev30/src/IBB_Helper/plot_3d.py +142 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/plot_param_grid.py +156 -0
- ibb_helper-0.4.8.dev30/src/IBB_Helper/symbolic_BSpline.py +77 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30/src/IBB_Helper.egg-info}/PKG-INFO +1 -1
- ibb_helper-0.4.8.dev28/src/IBB_Helper/animate.py +0 -206
- ibb_helper-0.4.8.dev28/src/IBB_Helper/display.py +0 -59
- ibb_helper-0.4.8.dev28/src/IBB_Helper/display_eigen.py +0 -108
- ibb_helper-0.4.8.dev28/src/IBB_Helper/display_matrix.py +0 -47
- ibb_helper-0.4.8.dev28/src/IBB_Helper/extend_plot.py +0 -240
- ibb_helper-0.4.8.dev28/src/IBB_Helper/minimize.py +0 -64
- ibb_helper-0.4.8.dev28/src/IBB_Helper/num_int.py +0 -73
- ibb_helper-0.4.8.dev28/src/IBB_Helper/plot_3d.py +0 -136
- ibb_helper-0.4.8.dev28/src/IBB_Helper/plot_param_grid.py +0 -102
- ibb_helper-0.4.8.dev28/src/IBB_Helper/symbolic_BSpline.py +0 -60
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/LICENSE +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/README.md +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/setup.cfg +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/setup.py +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/__init__.py +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/SOURCES.txt +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/dependency_links.txt +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/requires.txt +0 -0
- {ibb_helper-0.4.8.dev28 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: IBB_Helper
|
|
3
|
-
Version: 0.4.8.
|
|
3
|
+
Version: 0.4.8.dev30
|
|
4
4
|
Summary: Helper functions for symbolic math, matrix visualization, and plotting
|
|
5
5
|
Author-email: "University of Stuttgart, Institute for Structural Mechanics (IBB)" <mvs@ibb.uni-stuttgart.de>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "IBB_Helper"
|
|
7
|
-
version = "0.4.8.
|
|
7
|
+
version = "0.4.8.dev30"
|
|
8
8
|
description = "Helper functions for symbolic math, matrix visualization, and plotting"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "BSD-3-Clause"
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sympy as sp
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import matplotlib.animation
|
|
5
|
+
from sympy import latex, Matrix
|
|
6
|
+
|
|
7
|
+
def animate(exprs, var,
|
|
8
|
+
# --- Labels & Text ---
|
|
9
|
+
labels=None, title="Animation", xlabel="x", ylabel="y",
|
|
10
|
+
# --- Styling ---
|
|
11
|
+
line_styles=None, colors=None, linewidth=2,
|
|
12
|
+
# --- Limits ---
|
|
13
|
+
xlim=None, ylim=None,
|
|
14
|
+
# --- Animation Config ---
|
|
15
|
+
animation_time=5000, frames=100, resolution=400, show=True):
|
|
16
|
+
|
|
17
|
+
"""
|
|
18
|
+
Animates 2D curves from symbolic expressions or numeric datasets using Matplotlib.
|
|
19
|
+
|
|
20
|
+
Parameters:
|
|
21
|
+
exprs : Expression, tuple, or list of expressions (symbolic, parametric, or numeric data)
|
|
22
|
+
var : Symbol or tuple defining variable/range, or array mode ('array', x_values)
|
|
23
|
+
|
|
24
|
+
# --- Labels ---
|
|
25
|
+
labels : Curve labels (strings or SymPy expressions, LaTeX supported) (default=None)
|
|
26
|
+
title : Plot title (default="Animation")
|
|
27
|
+
xlabel : X-axis label (default="x")
|
|
28
|
+
ylabel : Y-axis label (default="y")
|
|
29
|
+
|
|
30
|
+
# --- Styling ---
|
|
31
|
+
line_styles : Line styles for each curve (default=None -> solid)
|
|
32
|
+
colors : Colors for each curve (default=None -> Matplotlib cycle)
|
|
33
|
+
linewidth : Line width(s) as int or list (default=2)
|
|
34
|
+
|
|
35
|
+
# --- Limits ---
|
|
36
|
+
xlim : X-axis limits as (min, max) (default=None -> auto)
|
|
37
|
+
ylim : Y-axis limits as (min, max) (default=None -> auto)
|
|
38
|
+
|
|
39
|
+
# --- Animation Config ---
|
|
40
|
+
animation_time : Total animation duration in milliseconds (default=5000)
|
|
41
|
+
frames : Number of animation frames (default=100)
|
|
42
|
+
resolution : Number of evaluation points for symbolic expressions (default=400)
|
|
43
|
+
show : Whether to display the animation (default=True)
|
|
44
|
+
|
|
45
|
+
Returns:
|
|
46
|
+
matplotlib.animation.FuncAnimation
|
|
47
|
+
The animation object
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
# Configuration
|
|
51
|
+
plt.rcParams["animation.html"] = "jshtml"
|
|
52
|
+
plt.rcParams['figure.dpi'] = 150
|
|
53
|
+
plt.ioff()
|
|
54
|
+
|
|
55
|
+
# Label Formatting
|
|
56
|
+
def _smart_label(lbl):
|
|
57
|
+
"""Converts strings or SymPy objects to LaTeX formatted strings."""
|
|
58
|
+
if lbl is None:
|
|
59
|
+
return None
|
|
60
|
+
if isinstance(lbl, str):
|
|
61
|
+
# Check for LaTeX characters
|
|
62
|
+
if any(c in lbl for c in ['_', '^', '\\', '$']):
|
|
63
|
+
return f"${lbl}$" if '$' not in lbl else lbl
|
|
64
|
+
return lbl
|
|
65
|
+
# Convert SymPy expression to LaTeX
|
|
66
|
+
return f"${latex(lbl)}$"
|
|
67
|
+
|
|
68
|
+
# Data Preparation
|
|
69
|
+
def _prepare_data(raw_exprs, variable_def):
|
|
70
|
+
"""
|
|
71
|
+
Parses inputs (Symbolic/Numeric) and generates consistent (x, y) arrays.
|
|
72
|
+
"""
|
|
73
|
+
# Normalize inputs to lists
|
|
74
|
+
if not isinstance(raw_exprs, list):
|
|
75
|
+
expr_list = [raw_exprs]
|
|
76
|
+
else:
|
|
77
|
+
expr_list = raw_exprs
|
|
78
|
+
|
|
79
|
+
# Determine symbol and range
|
|
80
|
+
is_sympy_mode = True
|
|
81
|
+
|
|
82
|
+
if isinstance(variable_def, tuple):
|
|
83
|
+
if variable_def[0] == 'array':
|
|
84
|
+
is_sympy_mode = False
|
|
85
|
+
x_vals_sample = variable_def[1]
|
|
86
|
+
# If array mode, frames usually equals array length
|
|
87
|
+
nonlocal frames
|
|
88
|
+
frames = len(x_vals_sample)
|
|
89
|
+
x_sym = None
|
|
90
|
+
else:
|
|
91
|
+
x_sym = variable_def[0]
|
|
92
|
+
x_range = variable_def[1]
|
|
93
|
+
x_vals_sample = np.linspace(float(x_range[0]), float(x_range[1]), frames)
|
|
94
|
+
else:
|
|
95
|
+
x_sym = variable_def
|
|
96
|
+
x_range = (-1, 1)
|
|
97
|
+
x_vals_sample = np.linspace(float(x_range[0]), float(x_range[1]), frames)
|
|
98
|
+
|
|
99
|
+
prepared_data = []
|
|
100
|
+
|
|
101
|
+
for expr in expr_list:
|
|
102
|
+
# Handle SymPy Matrices
|
|
103
|
+
if isinstance(expr, Matrix):
|
|
104
|
+
expr = np.array(expr).astype(np.float64).flatten()
|
|
105
|
+
|
|
106
|
+
# Handle Parametric or Data Tuples (x, y)
|
|
107
|
+
if isinstance(expr, (tuple, list)) and len(expr) == 2:
|
|
108
|
+
x_comp, y_comp = expr
|
|
109
|
+
|
|
110
|
+
# Symbolic Parametric
|
|
111
|
+
if isinstance(x_comp, sp.Basic) or isinstance(y_comp, sp.Basic):
|
|
112
|
+
if is_sympy_mode:
|
|
113
|
+
f_x = sp.lambdify(x_sym, x_comp, modules='numpy')
|
|
114
|
+
f_y = sp.lambdify(x_sym, y_comp, modules='numpy')
|
|
115
|
+
x_data = f_x(x_vals_sample)
|
|
116
|
+
y_data = f_y(x_vals_sample)
|
|
117
|
+
else:
|
|
118
|
+
# Fallback if mixed types (shouldn't happen often)
|
|
119
|
+
x_data, y_data = x_comp, y_comp
|
|
120
|
+
else:
|
|
121
|
+
# Numeric Data or Constants
|
|
122
|
+
x_data, y_data = x_comp, y_comp
|
|
123
|
+
|
|
124
|
+
# specific check for constant arrays
|
|
125
|
+
is_x_array = hasattr(x_data, '__len__')
|
|
126
|
+
is_y_array = hasattr(y_data, '__len__')
|
|
127
|
+
|
|
128
|
+
if is_x_array and not is_y_array:
|
|
129
|
+
y_data = np.full_like(x_data, y_data)
|
|
130
|
+
elif not is_x_array and is_y_array:
|
|
131
|
+
x_data = np.full_like(y_data, x_data)
|
|
132
|
+
|
|
133
|
+
prepared_data.append((np.array(x_data), np.array(y_data)))
|
|
134
|
+
|
|
135
|
+
# Handle Single Expressions (y = f(x))
|
|
136
|
+
else:
|
|
137
|
+
expr = sp.sympify(expr)
|
|
138
|
+
|
|
139
|
+
# Constant function check
|
|
140
|
+
if is_sympy_mode and not expr.has(x_sym):
|
|
141
|
+
y_vals = np.full_like(x_vals_sample, float(expr))
|
|
142
|
+
else:
|
|
143
|
+
if is_sympy_mode:
|
|
144
|
+
f = sp.lambdify(x_sym, expr, modules='numpy')
|
|
145
|
+
try:
|
|
146
|
+
y_vals = f(x_vals_sample)
|
|
147
|
+
# Handle case where lambdify returns a scalar for vector input
|
|
148
|
+
if np.isscalar(y_vals):
|
|
149
|
+
y_vals = np.full_like(x_vals_sample, y_vals)
|
|
150
|
+
except Exception:
|
|
151
|
+
# Fallback for complex constant expressions
|
|
152
|
+
y_vals = np.full_like(x_vals_sample, float(expr))
|
|
153
|
+
else:
|
|
154
|
+
y_vals = np.array(expr).flatten()
|
|
155
|
+
|
|
156
|
+
y_vals = np.array(y_vals).flatten()
|
|
157
|
+
prepared_data.append((x_vals_sample, y_vals))
|
|
158
|
+
|
|
159
|
+
return prepared_data
|
|
160
|
+
|
|
161
|
+
# Animation Logic
|
|
162
|
+
def _create_animation_object(plot_data):
|
|
163
|
+
"""Sets up the figure, axes, and FuncAnimation object."""
|
|
164
|
+
|
|
165
|
+
# 1. Normalize Styling
|
|
166
|
+
n_plots = len(plot_data)
|
|
167
|
+
|
|
168
|
+
# Thickness
|
|
169
|
+
if isinstance(linewidth, (int, float)):
|
|
170
|
+
lw_list = [linewidth] * n_plots
|
|
171
|
+
else:
|
|
172
|
+
lw_list = linewidth
|
|
173
|
+
|
|
174
|
+
# Limits Calculation (if None)
|
|
175
|
+
nonlocal xlim, ylim
|
|
176
|
+
if xlim is None:
|
|
177
|
+
all_x = np.concatenate([d[0] for d in plot_data])
|
|
178
|
+
x_min, x_max = np.min(all_x), np.max(all_x)
|
|
179
|
+
dx = x_max - x_min if x_max != x_min else 1.0
|
|
180
|
+
xlim = (x_min - 0.05 * dx, x_max + 0.05 * dx)
|
|
181
|
+
|
|
182
|
+
if ylim is None:
|
|
183
|
+
all_y = np.concatenate([d[1] for d in plot_data])
|
|
184
|
+
y_min, y_max = np.min(all_y), np.max(all_y)
|
|
185
|
+
dy = y_max - y_min if y_max != y_min else 1.0
|
|
186
|
+
ylim = (y_min - 0.1 * dy, y_max + 0.1 * dy)
|
|
187
|
+
|
|
188
|
+
# 2. Setup Figure
|
|
189
|
+
fig, ax = plt.subplots(figsize=(10, 6))
|
|
190
|
+
|
|
191
|
+
# 3. Update Function
|
|
192
|
+
def _update_frame(frame):
|
|
193
|
+
ax.clear()
|
|
194
|
+
|
|
195
|
+
# Use 'frame + 1' to ensure we plot at least one point
|
|
196
|
+
idx = int(frame + 1)
|
|
197
|
+
|
|
198
|
+
for i, (x_data, y_data) in enumerate(plot_data):
|
|
199
|
+
# Safe Styling Retrieval
|
|
200
|
+
style = line_styles[i] if line_styles and i < len(line_styles) else 'solid'
|
|
201
|
+
color = colors[i] if colors and i < len(colors) else None
|
|
202
|
+
|
|
203
|
+
# Label Handling
|
|
204
|
+
raw_lbl = labels[i] if labels and i < len(labels) else None
|
|
205
|
+
lbl = _smart_label(raw_lbl)
|
|
206
|
+
|
|
207
|
+
# Slice data for animation effect
|
|
208
|
+
# Ensure we don't index out of bounds if frames > data length
|
|
209
|
+
curr_x = x_data[:min(idx, len(x_data))]
|
|
210
|
+
curr_y = y_data[:min(idx, len(y_data))]
|
|
211
|
+
|
|
212
|
+
ax.plot(curr_x, curr_y,
|
|
213
|
+
label=lbl,
|
|
214
|
+
linestyle=style,
|
|
215
|
+
color=color,
|
|
216
|
+
linewidth=lw_list[i] if i < len(lw_list) else 2)
|
|
217
|
+
|
|
218
|
+
# Apply Layout Settings
|
|
219
|
+
ax.set_xlim(xlim)
|
|
220
|
+
ax.set_ylim(ylim)
|
|
221
|
+
ax.set_title(_smart_label(title))
|
|
222
|
+
ax.set_xlabel(_smart_label(xlabel))
|
|
223
|
+
ax.set_ylabel(_smart_label(ylabel))
|
|
224
|
+
|
|
225
|
+
if labels:
|
|
226
|
+
ax.legend()
|
|
227
|
+
ax.grid(True, alpha=0.3)
|
|
228
|
+
|
|
229
|
+
# 4. Create Animation
|
|
230
|
+
interval = animation_time / frames
|
|
231
|
+
anim = matplotlib.animation.FuncAnimation(
|
|
232
|
+
fig, _update_frame, frames=frames, interval=interval, repeat=True
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
return anim
|
|
236
|
+
|
|
237
|
+
# Main Execution
|
|
238
|
+
|
|
239
|
+
# 1. Prepare Data
|
|
240
|
+
data = _prepare_data(exprs, var)
|
|
241
|
+
|
|
242
|
+
# 2. Create Animation
|
|
243
|
+
anim_obj = _create_animation_object(data)
|
|
244
|
+
|
|
245
|
+
# 3. Display
|
|
246
|
+
if not show:
|
|
247
|
+
plt.close()
|
|
248
|
+
|
|
249
|
+
return anim_obj
|
|
@@ -3,9 +3,20 @@ import plotly.graph_objects as go
|
|
|
3
3
|
import copy
|
|
4
4
|
import numpy as np
|
|
5
5
|
|
|
6
|
-
def combine_plots(plot_list,
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
def combine_plots(plot_list,
|
|
7
|
+
# --- Plot Labels ---
|
|
8
|
+
title=None, xlabel=None, ylabel=None, labels=None,
|
|
9
|
+
# --- Styling ---
|
|
10
|
+
colors=None, line_styles=None,
|
|
11
|
+
# --- Axis Options ---
|
|
12
|
+
swap_axes=False, grid=False,
|
|
13
|
+
# --- Limits ---
|
|
14
|
+
xlim=None, ylim=None,
|
|
15
|
+
# --- Display ---
|
|
16
|
+
show=True,
|
|
17
|
+
# --- Figure Size ---
|
|
18
|
+
width=800, height=700):
|
|
19
|
+
|
|
9
20
|
"""
|
|
10
21
|
Combines multiple Matplotlib Axes or Plotly Figures into a single plot while preserving styles.
|
|
11
22
|
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sympy as sp
|
|
3
|
+
from sympy import latex
|
|
4
|
+
import IPython.display as ipy
|
|
5
|
+
|
|
6
|
+
def display(obj, name=None, evalf=False, prec=5, mathrm=False):
|
|
7
|
+
"""
|
|
8
|
+
Converts vectors/numbers/matrices to LaTeX with optional simplification using internal helpers.
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
obj : Input (SymPy or NumPy object)
|
|
12
|
+
name : Name used for display
|
|
13
|
+
evalf : If True, evaluates to decimal form before display (default: False)
|
|
14
|
+
prec : Evaluate the given formula to an accuracy of prec digits (default: 5)
|
|
15
|
+
mathrm : Boolean to display as Roman text
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
None
|
|
19
|
+
"""
|
|
20
|
+
# Safe Rounding Helper
|
|
21
|
+
def _safe_round(x):
|
|
22
|
+
try:
|
|
23
|
+
x = sp.sympify(x)
|
|
24
|
+
return x.replace(
|
|
25
|
+
lambda a: isinstance(a, sp.Float),
|
|
26
|
+
lambda a: sp.Float(round(float(a), prec))
|
|
27
|
+
)
|
|
28
|
+
except (TypeError, ValueError):
|
|
29
|
+
return x
|
|
30
|
+
|
|
31
|
+
# Object Preparation
|
|
32
|
+
def _prepare_object(raw_obj):
|
|
33
|
+
# 1. Standardization: Convert to SymPy types
|
|
34
|
+
if isinstance(raw_obj, np.ndarray):
|
|
35
|
+
sym_obj = sp.Matrix(raw_obj)
|
|
36
|
+
elif isinstance(raw_obj, (sp.Matrix, list)):
|
|
37
|
+
sym_obj = sp.Matrix(raw_obj)
|
|
38
|
+
elif isinstance(raw_obj, sp.NDimArray):
|
|
39
|
+
sym_obj = raw_obj
|
|
40
|
+
else:
|
|
41
|
+
sym_obj = sp.sympify(raw_obj)
|
|
42
|
+
|
|
43
|
+
# 2. Evaluation: Apply rounding if requested
|
|
44
|
+
if evalf:
|
|
45
|
+
if hasattr(sym_obj, 'applyfunc'):
|
|
46
|
+
return sym_obj.applyfunc(_safe_round)
|
|
47
|
+
return _safe_round(sym_obj)
|
|
48
|
+
|
|
49
|
+
return sym_obj
|
|
50
|
+
|
|
51
|
+
# Rendering
|
|
52
|
+
def _render(processed_obj):
|
|
53
|
+
latex_str = latex(processed_obj)
|
|
54
|
+
|
|
55
|
+
if mathrm:
|
|
56
|
+
latex_str = rf"\mathrm{{{latex_str}}}"
|
|
57
|
+
|
|
58
|
+
if name is None:
|
|
59
|
+
ipy.display(ipy.Math(latex_str))
|
|
60
|
+
else:
|
|
61
|
+
ipy.display(ipy.Math(f"{name} = {latex_str}"))
|
|
62
|
+
|
|
63
|
+
# Main Execution
|
|
64
|
+
final_obj = _prepare_object(obj)
|
|
65
|
+
_render(final_obj)
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sympy as sp
|
|
3
|
+
from sympy import latex
|
|
4
|
+
import IPython.display as ipy
|
|
5
|
+
|
|
6
|
+
def display_eigen(A,
|
|
7
|
+
# --- Labeling & Formatting ---
|
|
8
|
+
name="Matrix", evalf=False, prec=5,
|
|
9
|
+
# --- Output Control ---
|
|
10
|
+
show="both", output="default", return_data=False, reverse=False):
|
|
11
|
+
|
|
12
|
+
"""
|
|
13
|
+
Computes and displays the eigenvalues and eigenvectors of a matrix A using internal sub-functions.
|
|
14
|
+
|
|
15
|
+
Parameters:
|
|
16
|
+
A : Square matrix (SymPy Matrix or NumPy ndarray)
|
|
17
|
+
name : Label used for the matrix (default="Matrix")
|
|
18
|
+
evalf : If True, show decimal values (default=False)
|
|
19
|
+
prec : Accuracy of decimal evaluation (default: 5)
|
|
20
|
+
show : What to display ("both", "eigvals", "eigvecs", or "none") (default="both")
|
|
21
|
+
output : How to display ("default", "compact", "Maple") (default="default")
|
|
22
|
+
return_data : If True, returns eigenvalues and eigenvectors (default=False)
|
|
23
|
+
reverse : If True, sorts eigenvalues in descending order (default=False)
|
|
24
|
+
|
|
25
|
+
Returns (if return_data=True):
|
|
26
|
+
- sp.Matrix: Column vector of eigenvalues
|
|
27
|
+
- sp.Matrix: Matrix of eigenvectors (stacked columns)
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
threshold = 10**(-10)
|
|
31
|
+
|
|
32
|
+
# Safe Evaluation
|
|
33
|
+
def _safe_eval(x):
|
|
34
|
+
try:
|
|
35
|
+
val = x.evalf(prec)
|
|
36
|
+
if abs(val) < threshold:
|
|
37
|
+
return sp.Integer(0)
|
|
38
|
+
return round(float(val), prec)
|
|
39
|
+
except (TypeError, ValueError):
|
|
40
|
+
return x
|
|
41
|
+
|
|
42
|
+
# Compute & Process Data
|
|
43
|
+
def _compute_eigen_data(matrix_in):
|
|
44
|
+
# Convert NumPy array if needed
|
|
45
|
+
if isinstance(matrix_in, np.ndarray):
|
|
46
|
+
matrix_in = sp.Matrix(matrix_in)
|
|
47
|
+
|
|
48
|
+
# Compute raw eigen data: list of (eigenval, multiplicity, [eigenvecs])
|
|
49
|
+
raw_data = sorted(matrix_in.eigenvects(), key=lambda x: x[0], reverse=reverse)
|
|
50
|
+
|
|
51
|
+
vals_clean = []
|
|
52
|
+
vecs_clean = []
|
|
53
|
+
|
|
54
|
+
for eigval, mult, vects in raw_data:
|
|
55
|
+
for i in range(mult):
|
|
56
|
+
v_raw = vects[i]
|
|
57
|
+
|
|
58
|
+
# Normalize vector by its largest element for cleaner display
|
|
59
|
+
max_val = max(v_raw, key=lambda x: abs(x.evalf()))
|
|
60
|
+
v_norm = v_raw / max_val
|
|
61
|
+
|
|
62
|
+
# Apply numerical formatting if requested
|
|
63
|
+
if evalf:
|
|
64
|
+
val_processed = _safe_eval(eigval)
|
|
65
|
+
vec_processed = v_norm.applyfunc(_safe_eval)
|
|
66
|
+
else:
|
|
67
|
+
val_processed = eigval
|
|
68
|
+
vec_processed = v_norm
|
|
69
|
+
|
|
70
|
+
vals_clean.append(val_processed)
|
|
71
|
+
vecs_clean.append(vec_processed)
|
|
72
|
+
|
|
73
|
+
return vals_clean, vecs_clean
|
|
74
|
+
|
|
75
|
+
# Render Output
|
|
76
|
+
def _render_display(vals, vecs):
|
|
77
|
+
# Header
|
|
78
|
+
if show == "both":
|
|
79
|
+
ipy.display(ipy.Math(f"\\text{{Eigenvalues and Eigenvectors of }} {name}:"))
|
|
80
|
+
|
|
81
|
+
if output == "default":
|
|
82
|
+
for i, (val, vec) in enumerate(zip(vals, vecs)):
|
|
83
|
+
latex_str = f"\\lambda_{{{i+1}}} = {latex(val)}, v_{{{i+1}}} = {latex(vec)} \\qquad"
|
|
84
|
+
ipy.display(ipy.Math(latex_str))
|
|
85
|
+
|
|
86
|
+
elif output == "compact":
|
|
87
|
+
for i, (val, vec) in enumerate(zip(vals, vecs)):
|
|
88
|
+
# Transpose vector for compact row view
|
|
89
|
+
latex_str = f"\\lambda_{{{i+1}}} = {latex(val)}, \\quad v_{{{i+1}}} = {latex(vec.T)}"
|
|
90
|
+
ipy.display(ipy.Math(latex_str))
|
|
91
|
+
|
|
92
|
+
elif output == "maple":
|
|
93
|
+
# Vector of eigenvalues
|
|
94
|
+
ipy.display(ipy.Math(f"\\lambda = {latex(sp.Matrix(vals))}"))
|
|
95
|
+
# Matrix of eigenvectors
|
|
96
|
+
V_matrix = sp.Matrix.hstack(*vecs)
|
|
97
|
+
ipy.display(ipy.Math(f"v = {latex(V_matrix)}"))
|
|
98
|
+
|
|
99
|
+
elif show == "eigvals":
|
|
100
|
+
ipy.display(ipy.Math(f"\\text{{Eigenvalues of }} {name}: \\quad \\lambda = {latex(sp.Matrix(vals))}"))
|
|
101
|
+
|
|
102
|
+
elif show == "eigvecs":
|
|
103
|
+
V_matrix = sp.Matrix.hstack(*vecs)
|
|
104
|
+
ipy.display(ipy.Math(f"\\text{{Eigenvectors of }} {name}: \\quad v = {latex(V_matrix)}"))
|
|
105
|
+
|
|
106
|
+
# Main Execution Flow
|
|
107
|
+
|
|
108
|
+
# Validation
|
|
109
|
+
show_opt = show.lower()
|
|
110
|
+
out_opt = output.lower()
|
|
111
|
+
if show_opt not in ["both", "eigvals", "eigvecs", "none"]:
|
|
112
|
+
raise ValueError(f"Invalid 'show' option: {show}")
|
|
113
|
+
if out_opt not in ["default", "compact", "maple"]:
|
|
114
|
+
raise ValueError(f"Invalid 'output' option: {output}")
|
|
115
|
+
|
|
116
|
+
# Computation
|
|
117
|
+
eig_vals, eig_vecs = _compute_eigen_data(A)
|
|
118
|
+
|
|
119
|
+
# Display
|
|
120
|
+
if show_opt != "none":
|
|
121
|
+
_render_display(eig_vals, eig_vecs)
|
|
122
|
+
|
|
123
|
+
# Return Data
|
|
124
|
+
if return_data:
|
|
125
|
+
return sp.Matrix(eig_vals), sp.Matrix.hstack(*eig_vecs)
|
|
126
|
+
|
|
127
|
+
return None
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import sympy as sp
|
|
3
|
+
from sympy import latex
|
|
4
|
+
from IPython.display import Math, display
|
|
5
|
+
|
|
6
|
+
def display_matrix(matrix, name="Matrix", r=8, c=8, evalf=False, prec=5, mathrm=True):
|
|
7
|
+
"""
|
|
8
|
+
Displays a truncated matrix with optional numerical evaluation and rational simplification.
|
|
9
|
+
|
|
10
|
+
Parameters:
|
|
11
|
+
matrix : Input matrix (NumPy array, SymPy Matrix, or list)
|
|
12
|
+
name : Display name (default: "Matrix")
|
|
13
|
+
r : Max rows to display (default: 8)
|
|
14
|
+
c : Max columns to display (default: 8)
|
|
15
|
+
evalf : If True, apply numerical evaluation (default: False)
|
|
16
|
+
prec : Evaluate the given formula to an accuracy of prec digits
|
|
17
|
+
mathrm : Boolean to display as Roman text (default: True)
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
None
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
# Matrix Preparation
|
|
24
|
+
def _prepare_matrix(raw_mat):
|
|
25
|
+
if isinstance(raw_mat, (np.ndarray, list)):
|
|
26
|
+
return sp.Matrix(raw_mat)
|
|
27
|
+
return raw_mat
|
|
28
|
+
|
|
29
|
+
# Processing (Truncation & Evaluation)
|
|
30
|
+
def _process_submatrix(full_mat):
|
|
31
|
+
# Truncate
|
|
32
|
+
sub = full_mat[:min(r, full_mat.rows), :min(c, full_mat.cols)]
|
|
33
|
+
|
|
34
|
+
# Evaluate
|
|
35
|
+
if evalf:
|
|
36
|
+
return sub.applyfunc(lambda x: x.evalf(prec))
|
|
37
|
+
return sub
|
|
38
|
+
|
|
39
|
+
# Rendering
|
|
40
|
+
def _render(processed_sub, original_rows, original_cols):
|
|
41
|
+
# Generate LaTeX with spacing adjustment
|
|
42
|
+
expr = latex(processed_sub).replace(r'\\', r'\\[5pt]')
|
|
43
|
+
|
|
44
|
+
if mathrm:
|
|
45
|
+
expr = rf"\mathrm{{{expr}}}"
|
|
46
|
+
|
|
47
|
+
# Display Main Equation
|
|
48
|
+
label = rf"\large {name} =" if name else r"\large"
|
|
49
|
+
display(Math(rf"{label} {expr}"))
|
|
50
|
+
|
|
51
|
+
# Display Truncation Message
|
|
52
|
+
if original_rows > r or original_cols > c:
|
|
53
|
+
msg = rf"\text{{... Truncated to first {r}x{c} out of {original_rows}x{original_cols} matrix}}"
|
|
54
|
+
display(Math(msg))
|
|
55
|
+
|
|
56
|
+
# Main Execution
|
|
57
|
+
sym_matrix = _prepare_matrix(matrix)
|
|
58
|
+
sub_matrix = _process_submatrix(sym_matrix)
|
|
59
|
+
_render(sub_matrix, sym_matrix.rows, sym_matrix.cols)
|