IBB-Helper 0.3.2__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.
@@ -0,0 +1,12 @@
1
+ __version__ = "0.3.2"
2
+ from .display_matrix import display_matrix
3
+ from .display import display
4
+ from .display_eigen import display_eigen
5
+ from .plot_2d import plot_2d
6
+ from .plot_3d import plot_3d
7
+ from .extend_plot import extend_plot
8
+ from .combine_plots import combine_plots
9
+ from .symbolic_BSpline import symbolic_BSpline
10
+ from .minimize import minimize
11
+ from .num_int import num_int
12
+ from .plot_param_grid import plot_param_grid
@@ -0,0 +1,239 @@
1
+ import matplotlib.pyplot as plt
2
+ import plotly.graph_objects as go
3
+ import copy
4
+ from sympy import latex
5
+
6
+ def combine_plots(plot_list, labels=None, line_styles=None, colors=None,
7
+ swap_axes=False, show=True, grid=False,
8
+ xlim=None, ylim=None, title=None, xlabel=None, ylabel=None):
9
+ """
10
+ Combines multiple individual matplotlib Axes or plotly Figures into a single figure.
11
+
12
+ This function intelligently detects the type of plot object (Matplotlib or Plotly)
13
+ and merges all data onto a new, single canvas. It preserves the original styling
14
+ of each plot by default, but allows for explicit overrides for the final output.
15
+
16
+ Parameters
17
+ ----------
18
+ plot_list : list
19
+ The list of plot objects to be combined. All plots must be of the same type.
20
+ - Syntax: `[ax1, ax2]` or `[fig1, fig2]`
21
+
22
+ labels : list[str], optional
23
+ A list of new labels to override the existing ones for the legend.
24
+ - Syntax: `['Plot A', 'Plot B']`
25
+
26
+ line_styles : list[str], optional
27
+ A list of new line styles to override the existing ones. Can also be used
28
+ to force a marker style (e.g., 'o', 'x').
29
+ - Syntax: `['solid', 'dashed', 'o', 'x']`
30
+
31
+ colors : list[str], optional
32
+ A list of new colors to override the existing ones.
33
+ - Syntax: `['red', 'blue', '#FF5733']`
34
+
35
+ swap_axes : bool, optional
36
+ If True, the x and y data for all plots will be swapped.
37
+ - Syntax: `True` or `False`
38
+
39
+ show : bool, optional
40
+ If True, displays the combined plot immediately. If False, the plot object
41
+ is returned without being shown.
42
+ - Syntax: `True` or `False`
43
+
44
+ grid : bool, optional
45
+ If True, displays a grid on the final combined plot.
46
+ - Syntax: `True` or `False`
47
+
48
+ xlim : tuple[float, float], optional
49
+ Sets the x-axis limits for the combined plot.
50
+ - Syntax: `(0, 10)`
51
+
52
+ ylim : tuple[float, float], optional
53
+ Sets the y-axis limits for the combined plot.
54
+ - Syntax: `(-5, 5)`
55
+
56
+ title : str, optional
57
+ The main title for the combined plot.
58
+ - Syntax: `"My Combined Plot"`
59
+
60
+ xlabel : str, optional
61
+ The x-axis label for the combined plot.
62
+ - Syntax: `"Time (s)"`
63
+
64
+ ylabel : str, optional
65
+ The y-axis label for the combined plot.
66
+ - Syntax: `"Value"`
67
+
68
+ Returns
69
+ -------
70
+ matplotlib.axes.Axes or plotly.graph_objects.Figure
71
+ The final combined plot object, either a Matplotlib Axes or a Plotly Figure,
72
+ depending on the input.
73
+ """
74
+
75
+ # === SMART LABEL HELPER ===
76
+ def smart_label(lbl):
77
+ """Converts label or SymPy expression into LaTeX-compatible string."""
78
+ if lbl is None:
79
+ return None
80
+ if isinstance(lbl, str):
81
+ if any(c in lbl for c in ['_', '^', '\\']):
82
+ return f"${lbl}$"
83
+ else:
84
+ return lbl
85
+ else:
86
+ return f"${latex(lbl)}$"
87
+
88
+ if not plot_list:
89
+ raise ValueError("plot_list cannot be empty")
90
+
91
+ first_plot = plot_list[0]
92
+
93
+ # Define marker symbols for internal check (same list used in plot_2d)
94
+ marker_symbols = ['o', 's', '^', 'x', '*', 'D', 'p', '+', 'v', '<', '>', '1', '2', '3', '4']
95
+
96
+ # === MATPLOTLIB AXES HANDLING ===
97
+ if hasattr(first_plot, 'lines'):
98
+ fig, ax = plt.subplots()
99
+
100
+ for i, plot_ax in enumerate(plot_list):
101
+ first_line = True
102
+
103
+ for line in plot_ax.lines:
104
+ x, y = line.get_data()
105
+
106
+ # --- 1. Retrieve Existing Properties (Default Transfer) ---
107
+ current_color = line.get_color()
108
+ current_marker = line.get_marker()
109
+ current_linestyle = line.get_linestyle()
110
+ current_markersize = line.get_markersize()
111
+ current_linewidth = line.get_linewidth()
112
+
113
+ # Normalize marker: Convert 'None' string to None
114
+ if current_marker in ['None', '']:
115
+ current_marker = None
116
+
117
+ # Normalize linestyle: Matplotlib stores '' for marker-only plots
118
+ if current_linestyle in ['None', '']:
119
+ current_linestyle = 'None'
120
+
121
+ # --- 2. Apply Overrides from append_plots Arguments (Only for First Line) ---
122
+ if first_line:
123
+ # Override Line Style / Marker
124
+ if line_styles and i < len(line_styles):
125
+ new_style = line_styles[i]
126
+
127
+ if new_style in marker_symbols:
128
+ # Case A: User forces a marker (e.g., 'o', 'x')
129
+ current_marker = new_style
130
+ current_linestyle = 'None' # Ensure linestyle is off
131
+ else:
132
+ # Case B: User forces a line style (e.g., '--', 'solid')
133
+ current_linestyle = new_style
134
+ current_marker = None # Clear the marker
135
+
136
+ # Override Color
137
+ if colors and i < len(colors):
138
+ current_color = colors[i]
139
+
140
+ # Apply label only to the first line of the current plot_ax
141
+ label_to_use = smart_label(labels[i]) if first_line and labels and i < len(labels) else None
142
+
143
+ # --- 3. Plot on the New Axes ---
144
+ if swap_axes:
145
+ ax.plot(y, x,
146
+ color=current_color,
147
+ linestyle=current_linestyle if current_linestyle != 'None' else '',
148
+ marker=current_marker if current_marker else '',
149
+ markersize=current_markersize,
150
+ linewidth=current_linewidth,
151
+ label=label_to_use)
152
+ else:
153
+ ax.plot(x, y,
154
+ color=current_color,
155
+ linestyle=current_linestyle if current_linestyle != 'None' else '',
156
+ marker=current_marker if current_marker else '',
157
+ markersize=current_markersize,
158
+ linewidth=current_linewidth,
159
+ label=label_to_use)
160
+
161
+ first_line = False
162
+
163
+ # --- 4. Final Axis/Figure Configuration (Matplotlib) ---
164
+ if xlim:
165
+ ax.set_xlim(xlim)
166
+ if ylim:
167
+ ax.set_ylim(ylim)
168
+ if title:
169
+ ax.set_title(smart_label(title))
170
+ if xlabel:
171
+ ax.set_xlabel(smart_label(xlabel))
172
+ if ylabel:
173
+ ax.set_ylabel(smart_label(ylabel))
174
+ if grid:
175
+ ax.grid()
176
+ if labels:
177
+ ax.legend()
178
+ if show:
179
+ plt.show()
180
+ else:
181
+ plt.close(fig)
182
+
183
+ return ax
184
+
185
+ # === PLOTLY FIGURE HANDLING ===
186
+ elif hasattr(first_plot, 'data'):
187
+ combined_fig = go.Figure()
188
+
189
+ for i, fig in enumerate(plot_list):
190
+ for trace in fig.data:
191
+ # Use deepcopy for safe cloning of the trace
192
+ new_trace = copy.deepcopy(trace)
193
+
194
+ # Apply colors if provided and trace supports it
195
+ if colors and i < len(colors):
196
+ if hasattr(new_trace, 'line'):
197
+ new_trace.line.color = colors[i]
198
+ if hasattr(new_trace, 'marker'):
199
+ new_trace.marker.color = colors[i]
200
+
201
+ # Apply line styles (dash) if provided
202
+ if line_styles and i < len(line_styles):
203
+ if isinstance(new_trace, go.Scatter) and hasattr(new_trace, 'line'):
204
+ new_trace.line.dash = line_styles[i]
205
+
206
+ # Apply labels/name
207
+ if labels and i < len(labels):
208
+ new_trace.name = labels[i]
209
+
210
+ # Handle Axis Swap (if requested)
211
+ if swap_axes and hasattr(new_trace, 'x') and hasattr(new_trace, 'y'):
212
+ new_trace.x, new_trace.y = new_trace.y, new_trace.x
213
+
214
+ combined_fig.add_trace(new_trace)
215
+
216
+ # Final Layout Configuration (Plotly)
217
+ if title:
218
+ combined_fig.update_layout(title=smart_label(title))
219
+
220
+ final_xlabel = smart_label(ylabel) if swap_axes and ylabel else smart_label(xlabel) if xlabel else None
221
+ final_ylabel = smart_label(xlabel) if swap_axes and xlabel else smart_label(ylabel) if ylabel else None
222
+
223
+ if final_xlabel or final_ylabel:
224
+ combined_fig.update_layout(xaxis_title=final_xlabel,
225
+ yaxis_title=final_ylabel)
226
+ if xlim:
227
+ combined_fig.update_xaxes(range=xlim if not swap_axes else ylim)
228
+ if ylim:
229
+ combined_fig.update_yaxes(range=ylim if not swap_axes else xlim)
230
+ if grid:
231
+ combined_fig.update_layout(xaxis_showgrid=True, yaxis_showgrid=True)
232
+
233
+ if show:
234
+ combined_fig.show()
235
+
236
+ return combined_fig
237
+
238
+ else:
239
+ raise TypeError("Unknown plot object type passed to combine_plots.")
@@ -0,0 +1,42 @@
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="obj", evalf=False, prec=5):
7
+ """
8
+ Converts vectors/numbers/matrices to LaTeX with optional simplification.
9
+
10
+ Parameters:
11
+ obj : Input (SymPy or NumPy object)
12
+ name : Name used for display
13
+ evalf : If T, evaluates to decimal form before display (default: F)
14
+ prec : Evaluate the given formula to an accuracy of prec digits (default: 5)
15
+ """
16
+
17
+ def safe_eval_entry(x):
18
+ try:
19
+ return sp.sympify(x).evalf(prec)
20
+ except (TypeError, ValueError):
21
+ return x
22
+
23
+ if evalf:
24
+ if isinstance(obj, np.ndarray):
25
+ obj = sp.Matrix(obj).applyfunc(safe_eval_entry)
26
+ elif isinstance(obj, (sp.Matrix, list)):
27
+ obj = sp.Matrix(obj).applyfunc(safe_eval_entry)
28
+ elif isinstance(obj, sp.NDimArray):
29
+ obj = obj.applyfunc(safe_eval_entry)
30
+ else:
31
+ obj = safe_eval_entry(obj)
32
+ else:
33
+ if isinstance(obj, np.ndarray):
34
+ obj = sp.Matrix(obj)
35
+ elif isinstance(obj, (sp.Matrix, list)):
36
+ obj = sp.Matrix(obj)
37
+ elif isinstance(obj, sp.NDimArray):
38
+ obj = obj
39
+ else:
40
+ obj = sp.sympify(obj)
41
+
42
+ ipy.display(ipy.Math(f"{name} = {latex(obj)}"))
@@ -0,0 +1,133 @@
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, name="Matrix", evalf=False, prec=5, return_data=False, show="both", output="default"):
7
+ """
8
+ Computes and displays the eigenvalues and eigenvectors of a matrix A with enhanced display options.
9
+
10
+ Parameters:
11
+ A : Square matrix (SymPy Matrix or NumPy ndarray)
12
+ name : Label used for the matrix (default="Matrix")
13
+ evalf : If True, show decimal values (default=False)
14
+ prec : Evaluate the given formula to an accuracy of prec digits (default: 5)
15
+ show : What to display ("both", "eigvals", "eigvecs", or "none") (default="both")
16
+ output : How to display ("default", "compact", "Maple") (default="default")
17
+ return_data: If True, also returns eigenvalues and eigenvectors (default=False)
18
+
19
+ Returns (if return_data=True):
20
+ - eigvals: List of eigenvalues
21
+ - eigvecs: List of corresponding eigenvectors (each as a SymPy Matrix)
22
+ """
23
+
24
+ threshold = 10**(-10)
25
+
26
+ # Track original input type
27
+ is_numpy_input = isinstance(A, np.ndarray)
28
+
29
+ def safe_eval(x):
30
+ try:
31
+ val = x.evalf(prec)
32
+ if abs(val) < threshold:
33
+ return sp.Integer(0)
34
+ return round(float(val), prec)
35
+ except (TypeError, ValueError):
36
+ return x
37
+
38
+ # Convert NumPy array to SymPy Matrix if needed
39
+ if isinstance(A, np.ndarray):
40
+ A = sp.Matrix(A)
41
+
42
+ # Compute eigenvalues and eigenvectors
43
+ eigen_data = A.eigenvects()
44
+
45
+ # To store raw data
46
+ eigvals = []
47
+ eigvecs = []
48
+
49
+ # Validate show parameter
50
+ show = show.lower()
51
+ valid_options = ["both", "eigvals", "eigvecs", "none"]
52
+ if show not in valid_options:
53
+ raise ValueError(f"show must be one of {valid_options}")
54
+
55
+ # Validate output parameter
56
+ output = output.lower()
57
+ valid_options_output = ["default", "compact", "maple"]
58
+ if output not in valid_options_output:
59
+ raise ValueError(f"output must be one of {valid_options_output}")
60
+
61
+
62
+ # Prepare data
63
+ counter = 1
64
+ for eigval, mult, vects in eigen_data:
65
+ for i in range(mult):
66
+ val_disp = safe_eval(eigval) if evalf else eigval
67
+ v_disp = vects[i].applyfunc(safe_eval) if evalf else vects[i]
68
+ eigvals.append(val_disp)
69
+ eigvecs.append(v_disp)
70
+ counter += 1
71
+
72
+ # Sort eigenvalues and eigenvectors
73
+ def sort_key(pair):
74
+ val = pair[0]
75
+ try:
76
+ # Try to get numerical value
77
+ if hasattr(val, 'evalf'):
78
+ num_val = complex(val.evalf())
79
+ else:
80
+ num_val = complex(val)
81
+
82
+ # For numpy inputs: simple ascending sort
83
+ if is_numpy_input:
84
+ return (num_val.real, num_val.imag)
85
+
86
+ # For sympy inputs: zeros first, then ascending
87
+ is_zero = abs(num_val) < threshold
88
+ return (not is_zero, num_val.real, num_val.imag)
89
+
90
+ except (TypeError, ValueError, AttributeError):
91
+ # Fallback for symbolic values
92
+ return (0, 0, 0)
93
+
94
+ paired = list(zip(eigvals, eigvecs))
95
+ paired.sort(key=sort_key)
96
+ eigvals, eigvecs = zip(*paired) if paired else ([], [])
97
+ eigvals = list(eigvals)
98
+ eigvecs = list(eigvecs)
99
+
100
+ # Build LaTeX string based on options
101
+ if show == "both":
102
+ latex_str = f"\\text{{Eigenvalues and Eigenvectors of }} {name}:"
103
+ ipy.display(ipy.Math(latex_str))
104
+ if output == "default":
105
+ latex_str = f""
106
+ for i, (val, vec) in enumerate(zip(eigvals, eigvecs)):
107
+ latex_str += f"\\lambda_{{{i+1}}} = {latex(val)}, v_{{{i+1}}} = {latex(vec)} \\qquad"
108
+ ipy.display(ipy.Math(latex_str))
109
+ elif output == "compact":
110
+ for i, (val, vec) in enumerate(zip(eigvals, eigvecs)):
111
+ latex_str = f"\\lambda_{{{i+1}}} = {latex(val)}, \\quad v_{{{i+1}}} = {latex(vec.T)}"
112
+ ipy.display(ipy.Math(latex_str))
113
+ elif output == "maple":
114
+ latex_str = f"\\lambda = {latex(sp.Matrix(eigvals))}"
115
+ ipy.display(ipy.Math(latex_str))
116
+ V = sp.Matrix.hstack(*[vec for vec in eigvecs])
117
+ latex_str = f"v = {latex(V)}"
118
+ ipy.display(ipy.Math(latex_str))
119
+
120
+ elif show == "eigvals":
121
+ # Default vector display for eigenvalues only
122
+ latex_str = f"\\text{{Eigenvalues of }} {name}: \\quad \\lambda = {latex(sp.Matrix(eigvals))}"
123
+ ipy.display(ipy.Math(latex_str))
124
+
125
+ elif show == "eigvecs":
126
+ # Default matrix display for eigenvectors only
127
+ V = sp.Matrix.hstack(*[vec for vec in eigvecs])
128
+ latex_str = f"\\text{{Eigenvectors of }} {name}: \\quad v = {latex(V)}"
129
+ ipy.display(ipy.Math(latex_str))
130
+
131
+ if return_data:
132
+ return eigvals, eigvecs
133
+ return None
@@ -0,0 +1,39 @@
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_matrix(matrix, name="Matrix", r=8, c=8, evalf=False, prec=5):
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 T, apply numerical evaluation (default: F)
16
+ prec : Evaluate the given formula to an accuracy of prec digits
17
+ """
18
+
19
+ # Convert to SymPy Matrix if needed
20
+ if isinstance(matrix, (np.ndarray, list)):
21
+ matrix = sp.Matrix(matrix)
22
+
23
+ # Truncate to r rows and c columns
24
+ submatrix = matrix[:min(r, matrix.rows), :min(c, matrix.cols)]
25
+
26
+ # Apply evaluation if needed
27
+ if evalf:
28
+ processed = submatrix.applyfunc(lambda x: x.evalf(prec))
29
+ else:
30
+ processed = submatrix
31
+
32
+ # Display matrix
33
+ ipy.display(ipy.Math(f"{name} = {latex(processed)}"))
34
+
35
+ m = matrix.rows
36
+ n = matrix.cols
37
+ # Show truncation message if applicable
38
+ if matrix.rows > r or matrix.cols > c:
39
+ ipy.display(ipy.Math(rf"\text{{... Truncated to first {r}x{c} out of {m}x{n} matrix}}"))