openscvx 0.3.2.dev170__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.

Potentially problematic release.


This version of openscvx might be problematic. Click here for more details.

Files changed (79) hide show
  1. openscvx/__init__.py +123 -0
  2. openscvx/_version.py +34 -0
  3. openscvx/algorithms/__init__.py +92 -0
  4. openscvx/algorithms/autotuning.py +24 -0
  5. openscvx/algorithms/base.py +351 -0
  6. openscvx/algorithms/optimization_results.py +215 -0
  7. openscvx/algorithms/penalized_trust_region.py +384 -0
  8. openscvx/config.py +437 -0
  9. openscvx/discretization/__init__.py +47 -0
  10. openscvx/discretization/discretization.py +236 -0
  11. openscvx/expert/__init__.py +23 -0
  12. openscvx/expert/byof.py +326 -0
  13. openscvx/expert/lowering.py +419 -0
  14. openscvx/expert/validation.py +357 -0
  15. openscvx/integrators/__init__.py +48 -0
  16. openscvx/integrators/runge_kutta.py +281 -0
  17. openscvx/lowered/__init__.py +30 -0
  18. openscvx/lowered/cvxpy_constraints.py +23 -0
  19. openscvx/lowered/cvxpy_variables.py +124 -0
  20. openscvx/lowered/dynamics.py +34 -0
  21. openscvx/lowered/jax_constraints.py +133 -0
  22. openscvx/lowered/parameters.py +54 -0
  23. openscvx/lowered/problem.py +70 -0
  24. openscvx/lowered/unified.py +718 -0
  25. openscvx/plotting/__init__.py +63 -0
  26. openscvx/plotting/plotting.py +756 -0
  27. openscvx/plotting/scp_iteration.py +299 -0
  28. openscvx/plotting/viser/__init__.py +126 -0
  29. openscvx/plotting/viser/animated.py +605 -0
  30. openscvx/plotting/viser/plotly_integration.py +333 -0
  31. openscvx/plotting/viser/primitives.py +355 -0
  32. openscvx/plotting/viser/scp.py +459 -0
  33. openscvx/plotting/viser/server.py +112 -0
  34. openscvx/problem.py +734 -0
  35. openscvx/propagation/__init__.py +60 -0
  36. openscvx/propagation/post_processing.py +104 -0
  37. openscvx/propagation/propagation.py +248 -0
  38. openscvx/solvers/__init__.py +51 -0
  39. openscvx/solvers/cvxpy.py +226 -0
  40. openscvx/symbolic/__init__.py +9 -0
  41. openscvx/symbolic/augmentation.py +630 -0
  42. openscvx/symbolic/builder.py +492 -0
  43. openscvx/symbolic/constraint_set.py +92 -0
  44. openscvx/symbolic/expr/__init__.py +222 -0
  45. openscvx/symbolic/expr/arithmetic.py +517 -0
  46. openscvx/symbolic/expr/array.py +632 -0
  47. openscvx/symbolic/expr/constraint.py +796 -0
  48. openscvx/symbolic/expr/control.py +135 -0
  49. openscvx/symbolic/expr/expr.py +720 -0
  50. openscvx/symbolic/expr/lie/__init__.py +87 -0
  51. openscvx/symbolic/expr/lie/adjoint.py +357 -0
  52. openscvx/symbolic/expr/lie/se3.py +172 -0
  53. openscvx/symbolic/expr/lie/so3.py +138 -0
  54. openscvx/symbolic/expr/linalg.py +279 -0
  55. openscvx/symbolic/expr/math.py +699 -0
  56. openscvx/symbolic/expr/spatial.py +209 -0
  57. openscvx/symbolic/expr/state.py +607 -0
  58. openscvx/symbolic/expr/stl.py +136 -0
  59. openscvx/symbolic/expr/variable.py +321 -0
  60. openscvx/symbolic/hashing.py +112 -0
  61. openscvx/symbolic/lower.py +760 -0
  62. openscvx/symbolic/lowerers/__init__.py +106 -0
  63. openscvx/symbolic/lowerers/cvxpy.py +1302 -0
  64. openscvx/symbolic/lowerers/jax.py +1382 -0
  65. openscvx/symbolic/preprocessing.py +757 -0
  66. openscvx/symbolic/problem.py +110 -0
  67. openscvx/symbolic/time.py +116 -0
  68. openscvx/symbolic/unified.py +420 -0
  69. openscvx/utils/__init__.py +20 -0
  70. openscvx/utils/cache.py +131 -0
  71. openscvx/utils/caching.py +210 -0
  72. openscvx/utils/printing.py +301 -0
  73. openscvx/utils/profiling.py +37 -0
  74. openscvx/utils/utils.py +100 -0
  75. openscvx-0.3.2.dev170.dist-info/METADATA +350 -0
  76. openscvx-0.3.2.dev170.dist-info/RECORD +79 -0
  77. openscvx-0.3.2.dev170.dist-info/WHEEL +5 -0
  78. openscvx-0.3.2.dev170.dist-info/licenses/LICENSE +201 -0
  79. openscvx-0.3.2.dev170.dist-info/top_level.txt +1 -0
@@ -0,0 +1,299 @@
1
+ import numpy as np
2
+ import plotly.graph_objects as go
3
+ from plotly.subplots import make_subplots
4
+
5
+ from openscvx.algorithms import OptimizationResults
6
+
7
+ from .plotting import _get_var
8
+
9
+
10
+ def plot_scp_iterations(
11
+ result: OptimizationResults,
12
+ state_names: list[str] | None = None,
13
+ control_names: list[str] | None = None,
14
+ cmap_name: str = "viridis",
15
+ show_propagation: bool = True,
16
+ ):
17
+ """Plot all SCP iterations overlaid with colormap-based coloring.
18
+
19
+ Shows the evolution of states and controls across SCP iterations. Early
20
+ iterations are dark, later iterations are bright (following the colormap).
21
+ This makes convergence visible at a glance.
22
+
23
+ Args:
24
+ result: Optimization results containing iteration history
25
+ state_names: Optional list of state names to include. If None, plots all states.
26
+ control_names: Optional list of control names to include. If None, plots all controls.
27
+ cmap_name: Matplotlib colormap name (default: "viridis")
28
+ show_propagation: If True, show multi-shot propagation lines (default: True)
29
+
30
+ Returns:
31
+ Plotly figure with all iterations overlaid
32
+
33
+ Example:
34
+ >>> results = problem.solve()
35
+ >>> plot_scp_iterations(results, ["position", "velocity"]).show()
36
+ """
37
+ import matplotlib.pyplot as plt
38
+
39
+ if not result.X:
40
+ raise ValueError("No iteration history available in result.X")
41
+
42
+ # Derive dimensions from result data
43
+ n_x = result.X[0].shape[1]
44
+ n_u = result.U[0].shape[1]
45
+
46
+ # Find time slice by looking for "time" state
47
+ time_slice = None
48
+ for state in result._states:
49
+ if state.name.lower() == "time":
50
+ time_slice = state._slice
51
+ break
52
+
53
+ # Extract multi-shot propagation trajectories
54
+ V_history = result.discretization_history if result.discretization_history else []
55
+ X_prop_history = []
56
+ if V_history and show_propagation:
57
+ i4 = n_x + n_x * n_x + 2 * n_x * n_u
58
+ for V in V_history:
59
+ pos_traj = []
60
+ for i_multi in range(V.shape[1]):
61
+ pos_traj.append(V[:, i_multi].reshape(-1, i4)[:, :n_x])
62
+ X_prop_history.append(np.array(pos_traj))
63
+
64
+ n_iterations = len(result.X)
65
+ if X_prop_history:
66
+ n_iterations = min(n_iterations, len(X_prop_history))
67
+
68
+ # Filter states and controls (exclude ctcs_aug and time)
69
+ states = [
70
+ s for s in result._states if "ctcs_aug" not in s.name.lower() and s.name.lower() != "time"
71
+ ]
72
+ controls = list(result._controls) if result._controls else []
73
+
74
+ state_filter = set(state_names) if state_names else None
75
+ control_filter = set(control_names) if control_names else None
76
+
77
+ if state_filter and control_filter is None:
78
+ controls = []
79
+ if control_filter and state_filter is None:
80
+ states = []
81
+ if state_filter:
82
+ states = [s for s in states if s.name in state_filter]
83
+ if not states:
84
+ available = {s.name for s in result._states if "ctcs_aug" not in s.name.lower()}
85
+ raise ValueError(
86
+ f"No states matched filter {state_names}. Available: {sorted(available)}"
87
+ )
88
+ if control_filter:
89
+ controls = [c for c in controls if c.name in control_filter]
90
+ if not controls:
91
+ available = {c.name for c in result._controls}
92
+ raise ValueError(
93
+ f"No controls matched filter {control_names}. Available: {sorted(available)}"
94
+ )
95
+
96
+ if not states and not controls:
97
+ raise ValueError("No states or controls to plot")
98
+
99
+ # Expand multi-dimensional variables to individual components
100
+ def expand_variables(variables):
101
+ expanded = []
102
+ for var in variables:
103
+ s = var._slice
104
+ start = s.start if isinstance(s, slice) else s
105
+ stop = s.stop if isinstance(s, slice) else start + 1
106
+ n_comp = (stop or start + 1) - (start or 0)
107
+
108
+ for i in range(n_comp):
109
+ expanded.append(
110
+ {
111
+ "name": f"{var.name}_{i}" if n_comp > 1 else var.name,
112
+ "idx": start + i,
113
+ "parent": var.name,
114
+ "comp": i,
115
+ }
116
+ )
117
+ return expanded
118
+
119
+ expanded_states = expand_variables(states)
120
+ expanded_controls = expand_variables(controls)
121
+
122
+ # Grid layout
123
+ n_states = len(expanded_states)
124
+ n_controls = len(expanded_controls)
125
+ n_state_cols = min(7, n_states) if n_states > 0 else 1
126
+ n_control_cols = min(3, n_controls) if n_controls > 0 else 1
127
+ n_state_rows = (n_states + n_state_cols - 1) // n_state_cols if n_states > 0 else 0
128
+ n_control_rows = (n_controls + n_control_cols - 1) // n_control_cols if n_controls > 0 else 0
129
+ total_rows = n_state_rows + n_control_rows
130
+ max_cols = max(n_state_cols, n_control_cols)
131
+
132
+ subplot_titles = [s["name"] for s in expanded_states] + [c["name"] for c in expanded_controls]
133
+ fig = make_subplots(
134
+ rows=total_rows,
135
+ cols=max_cols,
136
+ subplot_titles=subplot_titles,
137
+ vertical_spacing=0.08,
138
+ horizontal_spacing=0.05,
139
+ )
140
+
141
+ # Get colormap
142
+ cmap = plt.get_cmap(cmap_name)
143
+
144
+ def iter_color(iter_idx):
145
+ rgba = cmap(iter_idx / max(n_iterations - 1, 1))
146
+ return f"rgb({int(rgba[0] * 255)},{int(rgba[1] * 255)},{int(rgba[2] * 255)})"
147
+
148
+ # Plot all iterations
149
+ for iter_idx in range(n_iterations):
150
+ X_nodes = result.X[iter_idx]
151
+ U_iter = result.U[iter_idx]
152
+ color = iter_color(iter_idx)
153
+ legend_group = f"iter_{iter_idx}"
154
+ show_legend_for_iter = True # Show legend for first trace of this iteration
155
+
156
+ t_nodes = (
157
+ X_nodes[:, time_slice].flatten()
158
+ if time_slice is not None
159
+ else np.linspace(0, result.t_final, X_nodes.shape[0])
160
+ )
161
+
162
+ # States
163
+ for state_idx, state in enumerate(expanded_states):
164
+ row = (state_idx // n_state_cols) + 1
165
+ col = (state_idx % n_state_cols) + 1
166
+ idx = state["idx"]
167
+
168
+ # Multi-shot propagation lines
169
+ if X_prop_history and iter_idx < len(X_prop_history):
170
+ pos_traj = X_prop_history[iter_idx]
171
+ for j in range(pos_traj.shape[1]):
172
+ segment_times = pos_traj[:, j, time_slice].flatten()
173
+ segment_states = pos_traj[:, j, idx]
174
+ fig.add_trace(
175
+ go.Scatter(
176
+ x=segment_times,
177
+ y=segment_states,
178
+ mode="lines",
179
+ line={"color": color, "width": 1.5},
180
+ legendgroup=legend_group,
181
+ showlegend=show_legend_for_iter,
182
+ name=f"Iter {iter_idx}" if show_legend_for_iter else None,
183
+ hoverinfo="skip",
184
+ ),
185
+ row=row,
186
+ col=col,
187
+ )
188
+ show_legend_for_iter = False
189
+
190
+ # Nodes
191
+ fig.add_trace(
192
+ go.Scatter(
193
+ x=t_nodes,
194
+ y=X_nodes[:, idx],
195
+ mode="markers",
196
+ marker={"color": color, "size": 5},
197
+ legendgroup=legend_group,
198
+ showlegend=show_legend_for_iter,
199
+ name=f"Iter {iter_idx}" if show_legend_for_iter else None,
200
+ hovertemplate=f"iter {iter_idx}<br>t=%{{x:.2f}}<br>y=%{{y:.3g}}<extra></extra>",
201
+ ),
202
+ row=row,
203
+ col=col,
204
+ )
205
+ show_legend_for_iter = False
206
+
207
+ # Controls
208
+ for control_idx, control in enumerate(expanded_controls):
209
+ row = n_state_rows + (control_idx // n_control_cols) + 1
210
+ col = (control_idx % n_control_cols) + 1
211
+ idx = control["idx"]
212
+
213
+ fig.add_trace(
214
+ go.Scatter(
215
+ x=t_nodes,
216
+ y=U_iter[:, idx],
217
+ mode="markers",
218
+ marker={"color": color, "size": 5},
219
+ legendgroup=legend_group,
220
+ showlegend=show_legend_for_iter,
221
+ name=f"Iter {iter_idx}" if show_legend_for_iter else None,
222
+ hovertemplate=f"iter {iter_idx}<br>t=%{{x:.2f}}<br>y=%{{y:.3g}}<extra></extra>",
223
+ ),
224
+ row=row,
225
+ col=col,
226
+ )
227
+ show_legend_for_iter = False
228
+
229
+ # Add bounds (once, using final iteration's time range)
230
+ t_nodes_final = (
231
+ result.X[-1][:, time_slice].flatten()
232
+ if time_slice is not None
233
+ else np.linspace(0, result.t_final, result.X[-1].shape[0])
234
+ )
235
+ t_min, t_max = t_nodes_final.min(), t_nodes_final.max()
236
+
237
+ for state_idx, state in enumerate(expanded_states):
238
+ row = (state_idx // n_state_cols) + 1
239
+ col = (state_idx % n_state_cols) + 1
240
+ parent = _get_var(result, state["parent"], result._states)
241
+ comp_idx = state["comp"]
242
+
243
+ for bound_val, bound_attr in [(parent.min, "min"), (parent.max, "max")]:
244
+ if bound_val is not None and np.isfinite(bound_val[comp_idx]):
245
+ fig.add_trace(
246
+ go.Scatter(
247
+ x=[t_min, t_max],
248
+ y=[bound_val[comp_idx], bound_val[comp_idx]],
249
+ mode="lines",
250
+ line={"color": "red", "width": 1.5, "dash": "dot"},
251
+ showlegend=False,
252
+ hoverinfo="skip",
253
+ ),
254
+ row=row,
255
+ col=col,
256
+ )
257
+
258
+ for control_idx, control in enumerate(expanded_controls):
259
+ row = n_state_rows + (control_idx // n_control_cols) + 1
260
+ col = (control_idx % n_control_cols) + 1
261
+ parent = _get_var(result, control["parent"], result._controls)
262
+ comp_idx = control["comp"]
263
+
264
+ for bound_val in [parent.min, parent.max]:
265
+ if bound_val is not None and np.isfinite(bound_val[comp_idx]):
266
+ fig.add_trace(
267
+ go.Scatter(
268
+ x=[t_min, t_max],
269
+ y=[bound_val[comp_idx], bound_val[comp_idx]],
270
+ mode="lines",
271
+ line={"color": "red", "width": 1.5, "dash": "dot"},
272
+ showlegend=False,
273
+ hoverinfo="skip",
274
+ ),
275
+ row=row,
276
+ col=col,
277
+ )
278
+
279
+ # Layout
280
+ fig.update_layout(
281
+ title_text="SCP Iterations",
282
+ template="plotly_dark",
283
+ showlegend=True,
284
+ legend={
285
+ "title": "Iterations",
286
+ "yanchor": "top",
287
+ "y": 0.99,
288
+ "xanchor": "left",
289
+ "x": 1.02,
290
+ "bgcolor": "rgba(0, 0, 0, 0.5)",
291
+ "itemclick": "toggle",
292
+ "itemdoubleclick": "toggleothers",
293
+ },
294
+ )
295
+
296
+ for col_idx in range(1, max_cols + 1):
297
+ fig.update_xaxes(title_text="Time (s)", row=total_rows, col=col_idx)
298
+
299
+ return fig
@@ -0,0 +1,126 @@
1
+ """Composable 3D visualization primitives using viser.
2
+
3
+ This module provides building blocks for creating interactive 3D trajectory
4
+ visualizations. The design philosophy is to give you useful primitives that
5
+ you can mix and match - not a monolithic plotting function that tries to
6
+ handle every case.
7
+
8
+ **Basic Pattern**:
9
+ 1. Create a ``viser.ViserServer`` (use our helper, or make your own!)
10
+ 2. Add static scene elements (obstacles, gates, ground planes, etc.)
11
+ 3. Add animated elements - each returns ``(handle, update_callback)``
12
+ 4. Wire up animation controls with your list of update callbacks
13
+ 5. Call ``server.sleep_forever()`` to keep the visualization running
14
+
15
+ **Example - Building Your Own Visualization**::
16
+
17
+ from openscvx.plotting import viser
18
+
19
+ # Step 1: Create server (or just use viser.ViserServer() directly!)
20
+ server = viser.create_server(positions)
21
+
22
+ # Step 2: Add static elements
23
+ viser.add_gates(server, gate_vertices)
24
+ viser.add_ellipsoid_obstacles(server, centers, radii)
25
+ viser.add_ghost_trajectory(server, positions, colors)
26
+
27
+ # Step 3: Add animated elements (collect the update callbacks)
28
+ _, update_trail = viser.add_animated_trail(server, positions, colors)
29
+ _, update_marker = viser.add_position_marker(server, positions)
30
+ _, update_thrust = viser.add_thrust_vector(server, positions, thrust)
31
+
32
+ # Step 4: Wire up animation controls
33
+ viser.add_animation_controls(
34
+ server, time_array,
35
+ [update_trail, update_marker, update_thrust]
36
+ )
37
+
38
+ # Step 5: Keep server running
39
+ server.sleep_forever()
40
+
41
+ **Available Primitives**:
42
+ - Server: ``create_server``, ``compute_velocity_colors``, ``compute_grid_size``
43
+ - Static: ``add_gates``, ``add_ellipsoid_obstacles``, ``add_glideslope_cone``,
44
+ ``add_ghost_trajectory``
45
+ - Animated: ``add_animated_trail``, ``add_position_marker``, ``add_thrust_vector``,
46
+ ``add_attitude_frame``, ``add_viewcone``, ``add_target_marker(s)``
47
+ - Plotly: ``add_animated_plotly_marker``, ``add_animated_vector_norm_plot``
48
+ - SCP iteration: ``add_scp_animation_controls``, ``add_scp_iteration_nodes``, etc.
49
+
50
+ For problem-specific examples (drones with viewcones, rockets with glideslope
51
+ constraints, etc.), see ``examples/plotting_viser.py``.
52
+ """
53
+
54
+ # Server setup
55
+ # Animated components
56
+ from .animated import (
57
+ UpdateCallback,
58
+ add_animated_trail,
59
+ add_animation_controls,
60
+ add_attitude_frame,
61
+ add_position_marker,
62
+ add_target_marker,
63
+ add_target_markers,
64
+ add_thrust_vector,
65
+ add_viewcone,
66
+ )
67
+
68
+ # Plotly integration
69
+ from .plotly_integration import (
70
+ add_animated_plotly_marker,
71
+ add_animated_plotly_vline,
72
+ add_animated_vector_norm_plot,
73
+ )
74
+
75
+ # Static primitives
76
+ from .primitives import (
77
+ add_ellipsoid_obstacles,
78
+ add_gates,
79
+ add_ghost_trajectory,
80
+ add_glideslope_cone,
81
+ )
82
+
83
+ # SCP iteration visualization
84
+ from .scp import (
85
+ add_scp_animation_controls,
86
+ add_scp_ghost_iterations,
87
+ add_scp_iteration_attitudes,
88
+ add_scp_iteration_nodes,
89
+ add_scp_propagation_lines,
90
+ extract_propagation_positions,
91
+ )
92
+ from .server import compute_grid_size, compute_velocity_colors, create_server
93
+
94
+ __all__ = [
95
+ # Server
96
+ "create_server",
97
+ "compute_velocity_colors",
98
+ "compute_grid_size",
99
+ # Static primitives
100
+ "add_gates",
101
+ "add_ellipsoid_obstacles",
102
+ "add_glideslope_cone",
103
+ "add_ghost_trajectory",
104
+ # Animated components
105
+ "UpdateCallback",
106
+ "add_animated_trail",
107
+ "add_position_marker",
108
+ "add_target_marker",
109
+ "add_target_markers",
110
+ "add_thrust_vector",
111
+ "add_attitude_frame",
112
+ "add_viewcone",
113
+ # Animation controls
114
+ "add_animation_controls",
115
+ # Plotly integration
116
+ "add_animated_plotly_marker",
117
+ "add_animated_plotly_vline",
118
+ "add_animated_vector_norm_plot",
119
+ # SCP visualization
120
+ "add_scp_iteration_nodes",
121
+ "add_scp_iteration_attitudes",
122
+ "add_scp_ghost_iterations",
123
+ "extract_propagation_positions",
124
+ "add_scp_propagation_lines",
125
+ "add_scp_animation_controls",
126
+ ]