IBB-Helper 0.4.8.dev29__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.
Files changed (25) hide show
  1. {ibb_helper-0.4.8.dev29/src/IBB_Helper.egg-info → ibb_helper-0.4.8.dev30}/PKG-INFO +1 -1
  2. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/pyproject.toml +1 -1
  3. ibb_helper-0.4.8.dev30/src/IBB_Helper/animate.py +249 -0
  4. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30/src/IBB_Helper.egg-info}/PKG-INFO +1 -1
  5. ibb_helper-0.4.8.dev29/src/IBB_Helper/animate.py +0 -206
  6. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/LICENSE +0 -0
  7. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/README.md +0 -0
  8. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/setup.cfg +0 -0
  9. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/setup.py +0 -0
  10. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/__init__.py +0 -0
  11. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/combine_plots.py +0 -0
  12. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/display.py +0 -0
  13. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/display_eigen.py +0 -0
  14. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/display_matrix.py +0 -0
  15. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/extend_plot.py +0 -0
  16. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/minimize.py +0 -0
  17. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/num_int.py +0 -0
  18. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/plot_2d.py +0 -0
  19. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/plot_3d.py +0 -0
  20. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/plot_param_grid.py +0 -0
  21. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper/symbolic_BSpline.py +0 -0
  22. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/SOURCES.txt +0 -0
  23. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/dependency_links.txt +0 -0
  24. {ibb_helper-0.4.8.dev29 → ibb_helper-0.4.8.dev30}/src/IBB_Helper.egg-info/requires.txt +0 -0
  25. {ibb_helper-0.4.8.dev29 → 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.dev29
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.dev29"
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IBB_Helper
3
- Version: 0.4.8.dev29
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
@@ -1,206 +0,0 @@
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
- from IPython.display import display, HTML
7
-
8
- def animate(exprs, var,
9
- # --- Plot Labels ---
10
- title="Animation", xlabel="x", ylabel="y", labels=None,
11
- # --- Styling ---
12
- colors=None, line_styles=None, linewidth=2, scaling=None,
13
- # --- Limits ---
14
- xlim=None, ylim=None,
15
- # --- Animation Settings ---
16
- animation_time=5000, frames=100, resolution=200, show=True,
17
- # --- Figure Size ---
18
- width=800, height=600):
19
-
20
- """
21
- Animates 2D curves from symbolic expressions or numeric datasets using Matplotlib.
22
-
23
- Parameters:
24
- exprs : Expression, tuple, or list of expressions to animate (symbolic, parametric, or numeric data)
25
- var : Symbol or tuple defining the variable and range, or array mode ('array', x_values)
26
- title : Plot title (string or SymPy expression, LaTeX supported) (default="Animation")
27
- xlabel : X-axis label (string or SymPy expression, LaTeX supported) (default="x")
28
- ylabel : Y-axis label (string or SymPy expression, LaTeX supported) (default="y")
29
- labels : Curve labels (strings or SymPy expressions, LaTeX supported) (default=None)
30
- colors : Colors for each curve (default=None → Matplotlib cycle)
31
- line_styles : Line styles for each curve (default=None → solid)
32
- linewidth : Line width(s) as int or list (default=2)
33
- scaling : Plot scaling behavior (e.g., 'constrained' for equal aspect ratio)
34
- xlim : X-axis limits as (min, max) (default=None → auto)
35
- ylim : Y-axis limits as (min, max) (default=None → auto)
36
- animation_time : Total animation duration in milliseconds (default=5000)
37
- frames : Number of animation frames (default=100)
38
- resolution : Number of evaluation points for symbolic spatial meshes (default=200)
39
- show : Whether to display the animation (default=True)
40
- width : Plot width in pixels (default=800)
41
- height : Plot height in pixels (default=600)
42
-
43
- Returns:
44
- matplotlib.animation.FuncAnimation
45
- The animation object
46
- """
47
-
48
- # Jupyter Configuration
49
- plt.rcParams["animation.html"] = "jshtml"
50
- plt.rcParams['figure.dpi'] = 100
51
- plt.ioff()
52
-
53
- # Default Handling for Mutable Arguments
54
- if colors is None:
55
- colors = ['black', 'blue', 'red', 'green']
56
- if line_styles is None:
57
- line_styles = ['solid'] * 10
58
- if labels is None:
59
- labels = []
60
-
61
- # Helper: Range Unpacker
62
- def _unpack_range(r_tuple):
63
- if len(r_tuple) == 3: return r_tuple[0], float(r_tuple[1]), float(r_tuple[2])
64
- elif len(r_tuple) == 2: return r_tuple[0], float(r_tuple[1][0]), float(r_tuple[1][1])
65
- raise ValueError(f"Invalid range format: {r_tuple}")
66
-
67
- # Parse Main Animation Variable
68
- t_sym, t_start, t_end = _unpack_range(var)
69
- t_vals = np.linspace(t_start, t_end, frames)
70
-
71
- # Process Expressions & Generate Frame Data
72
- all_curves_data = []
73
-
74
- if not isinstance(exprs, list): exprs = [exprs]
75
-
76
- for i, expr in enumerate(exprs):
77
-
78
- # Check for Deformation Mode: (x, y, (z, 0, h))
79
- if isinstance(expr, (tuple, list)) and len(expr) == 3 and isinstance(expr[2], (tuple, list)):
80
- x_e, y_e, spatial_info = expr
81
- z_sym, z_min, z_max = _unpack_range(spatial_info)
82
-
83
- # Create Spatial Mesh using resolution parameter
84
- z_vals = np.linspace(z_min, z_max, resolution)
85
-
86
- # Lambdify f(z, t)
87
- fx = sp.lambdify((z_sym, t_sym), x_e, modules='numpy')
88
- fy = sp.lambdify((z_sym, t_sym), y_e, modules='numpy')
89
-
90
- frame_x_list = []
91
- frame_y_list = []
92
-
93
- for t in t_vals:
94
- x_res = fx(z_vals, t)
95
- y_res = fy(z_vals, t)
96
-
97
- if not hasattr(x_res, '__len__'): x_res = np.full_like(z_vals, x_res)
98
- if not hasattr(y_res, '__len__'): y_res = np.full_like(z_vals, y_res)
99
-
100
- frame_x_list.append(x_res)
101
- frame_y_list.append(y_res)
102
-
103
- all_curves_data.append({
104
- 'type': 'deformation',
105
- 'x': frame_x_list,
106
- 'y': frame_y_list
107
- })
108
-
109
- # Standard Trace Mode
110
- else:
111
- if isinstance(expr, (tuple, list)) and len(expr) == 2:
112
- x_e, y_e = expr
113
- else:
114
- x_e, y_e = t_sym, expr
115
-
116
- fx = sp.lambdify(t_sym, x_e, modules='numpy')
117
- fy = sp.lambdify(t_sym, y_e, modules='numpy')
118
-
119
- full_x = fx(t_vals)
120
- full_y = fy(t_vals)
121
-
122
- if not hasattr(full_x, '__len__'): full_x = np.full_like(t_vals, full_x)
123
- if not hasattr(full_y, '__len__'): full_y = np.full_like(t_vals, full_y)
124
-
125
- all_curves_data.append({
126
- 'type': 'trace',
127
- 'x': full_x,
128
- 'y': full_y
129
- })
130
-
131
- # Auto-Scaling
132
- if xlim is None or ylim is None:
133
- flat_x, flat_y = [], []
134
- for c in all_curves_data:
135
- if c['type'] == 'deformation':
136
- flat_x.append(np.concatenate(c['x']))
137
- flat_y.append(np.concatenate(c['y']))
138
- else:
139
- flat_x.append(c['x'])
140
- flat_y.append(c['y'])
141
-
142
- flat_x = np.concatenate(flat_x)
143
- flat_y = np.concatenate(flat_y)
144
-
145
- if xlim is None:
146
- xmin, xmax = np.min(flat_x), np.max(flat_x)
147
- pad = (xmax - xmin) * 0.1 if xmax != xmin else 1.0
148
- xlim = (xmin - pad, xmax + pad)
149
-
150
- if ylim is None:
151
- ymin, ymax = np.min(flat_y), np.max(flat_y)
152
- pad = (ymax - ymin) * 0.1 if ymax != ymin else 1.0
153
- ylim = (ymin - pad, ymax + pad)
154
-
155
- # Plotting Setup
156
- fig, ax = plt.subplots(figsize=(width/100, height/100))
157
-
158
- if scaling == 'constrained' or (isinstance(scaling, list) and 'constrained' in scaling):
159
- ax.set_aspect('equal')
160
-
161
- ax.set_xlim(xlim)
162
- ax.set_ylim(ylim)
163
- ax.grid(True, alpha=0.3)
164
-
165
- def fmt_latex(l):
166
- return f"${latex(l)}$" if hasattr(l, 'free_symbols') or (isinstance(l, str) and '\\' in l) else str(l)
167
-
168
- ax.set_title(fmt_latex(title))
169
- ax.set_xlabel(fmt_latex(xlabel))
170
- ax.set_ylabel(fmt_latex(ylabel))
171
-
172
- # Initialize Lines
173
- lines = []
174
- for i in range(len(all_curves_data)):
175
- col = colors[i % len(colors)]
176
- sty = line_styles[i % len(line_styles)]
177
- lbl = labels[i] if i < len(labels) else None
178
-
179
- if isinstance(linewidth, list): lw = linewidth[i % len(linewidth)]
180
- else: lw = linewidth
181
-
182
- ln, = ax.plot([], [], color=col, linestyle=sty, linewidth=lw, label=fmt_latex(lbl) if lbl else None)
183
- lines.append(ln)
184
-
185
- if any(labels): ax.legend()
186
-
187
- # Animation Loop
188
- def update_frame(frame_idx):
189
- t_curr = t_vals[frame_idx]
190
- ax.set_title(f"{fmt_latex(title)} ($t={t_curr:.2f}$)")
191
-
192
- for i, data in enumerate(all_curves_data):
193
- if data['type'] == 'deformation':
194
- lines[i].set_data(data['x'][frame_idx], data['y'][frame_idx])
195
- else:
196
- lines[i].set_data(data['x'][:frame_idx+1], data['y'][:frame_idx+1])
197
-
198
- ani = matplotlib.animation.FuncAnimation(
199
- fig, update_frame, frames=frames, interval=animation_time/frames, blit=False
200
- )
201
-
202
- if show:
203
- display(HTML(ani.to_jshtml()))
204
- plt.close()
205
-
206
- return ani