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
openscvx/config.py ADDED
@@ -0,0 +1,437 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+ import numpy as np
5
+
6
+ from openscvx.lowered.unified import UnifiedControl, UnifiedState
7
+
8
+
9
+ def get_affine_scaling_matrices(n, minimum, maximum):
10
+ S = np.diag(np.maximum(np.ones(n), abs(minimum - maximum) / 2))
11
+ c = (maximum + minimum) / 2
12
+ return S, c
13
+
14
+
15
+ @dataclass
16
+ class DiscretizationConfig:
17
+ def __init__(
18
+ self,
19
+ dis_type: str = "FOH",
20
+ custom_integrator: bool = False,
21
+ solver: str = "Tsit5",
22
+ args: Optional[dict] = None,
23
+ atol: float = 1e-3,
24
+ rtol: float = 1e-6,
25
+ ):
26
+ """
27
+ Configuration class for discretization settings.
28
+
29
+ This class defines the parameters required for discretizing system dynamics.
30
+
31
+ Main arguments:
32
+ These are the arguments most commonly used day-to-day.
33
+
34
+ Args:
35
+ dis_type (str): The type of discretization to use (e.g., "FOH" for
36
+ First-Order Hold). Defaults to "FOH".
37
+ custom_integrator (bool): This enables our custom fixed-step RK45
38
+ algorithm. This tends to be faster than Diffrax but unless you're
39
+ going for speed, it's recommended to stick with Diffrax for
40
+ robustness and other solver options. Defaults to False.
41
+ solver (str): Not used if custom_integrator is enabled. Any choice of
42
+ solver in Diffrax is valid, please refer here,
43
+ [How to Choose a Solver](https://docs.kidger.site/diffrax/usage/
44
+ how-to-choose-a-solver/). Defaults to "Tsit5".
45
+
46
+ Other arguments:
47
+ These arguments are less frequently used, and for most purposes you
48
+ shouldn't need to understand these.
49
+
50
+ Args:
51
+ args (Dict): Additional arguments to pass to the solver which can be
52
+ found [here](https://docs.kidger.site/diffrax/api/diffeqsolve/).
53
+ Defaults to an empty dictionary.
54
+ atol (float): Absolute tolerance for the solver. Defaults to 1e-3.
55
+ rtol (float): Relative tolerance for the solver. Defaults to 1e-6.
56
+ """
57
+ self.dis_type = dis_type
58
+ self.custom_integrator = custom_integrator
59
+ self.solver = solver
60
+ self.args = args if args is not None else {}
61
+ self.atol = atol
62
+ self.rtol = rtol
63
+
64
+
65
+ @dataclass
66
+ class DevConfig:
67
+ def __init__(self, profiling: bool = False, debug: bool = False, printing: bool = True):
68
+ """
69
+ Configuration class for development settings.
70
+
71
+ This class defines the parameters used for development and debugging
72
+ purposes.
73
+
74
+ Main arguments:
75
+ These are the arguments most commonly used day-to-day.
76
+
77
+ Args:
78
+ profiling (bool): Whether to enable profiling for performance
79
+ analysis. Defaults to False.
80
+ debug (bool): Disables all precompilation so you can place
81
+ breakpoints and inspect values. Defaults to False.
82
+ printing (bool): Whether to enable printing during development.
83
+ Defaults to True.
84
+ """
85
+ self.profiling = profiling
86
+ self.debug = debug
87
+ self.printing = printing
88
+
89
+
90
+ @dataclass
91
+ class ConvexSolverConfig:
92
+ def __init__(
93
+ self,
94
+ solver: str = "QOCO",
95
+ solver_args: Optional[dict] = None,
96
+ cvxpygen: bool = False,
97
+ cvxpygen_override: bool = False,
98
+ ):
99
+ """
100
+ Configuration class for convex solver settings.
101
+
102
+ This class defines the parameters required for configuring a convex solver.
103
+
104
+ These are the arguments most commonly used day-to-day. Generally I have
105
+ found [QOCO](https://qoco-org.github.io/qoco/index.html) to be the most
106
+ performant of the CVXPY solvers for these types of problems (I do have a
107
+ bias as the author is from my group) and can handle up to SOCP's.
108
+ [CLARABEL](https://clarabel.org/stable/) is also a great option with
109
+ feasibility checking and can handle a few more problem types.
110
+ [CVXPYGen](https://github.com/cvxgrp/cvxpygen) is also great if your
111
+ problem isn't too large. I have found qocogen to be the most performant
112
+ of the CVXPYGen solvers.
113
+
114
+ Args:
115
+ solver (str): The name of the CVXPY solver to use. A list of options
116
+ can be found [here](https://www.cvxpy.org/tutorial/solvers/
117
+ index.html). Defaults to "QOCO".
118
+ solver_args (dict, optional): Ensure you are using the correct
119
+ arguments for your solver as they are not all common. Additional
120
+ arguments to configure the solver, such as tolerances. Defaults
121
+ to {"abstol": 1e-6, "reltol": 1e-9}.
122
+ cvxpygen (bool): Whether to enable CVXPY code generation for the
123
+ solver. Defaults to False.
124
+ """
125
+ if solver_args is None:
126
+ solver_args = {"abstol": 1e-06, "reltol": 1e-09, "enforce_dpp": True}
127
+ self.solver = solver
128
+ self.solver_args = (
129
+ solver_args if solver_args is not None else {"abstol": 1e-6, "reltol": 1e-9}
130
+ )
131
+ self.cvxpygen = cvxpygen
132
+ self.cvxpygen_override = cvxpygen_override
133
+
134
+
135
+ @dataclass
136
+ class PropagationConfig:
137
+ def __init__(
138
+ self,
139
+ inter_sample: int = 30,
140
+ dt: float = 0.01,
141
+ solver: str = "Dopri8",
142
+ max_tau_len: int = 1000,
143
+ args: Optional[dict] = None,
144
+ atol: float = 1e-3,
145
+ rtol: float = 1e-6,
146
+ ):
147
+ """
148
+ Configuration class for propagation settings.
149
+
150
+ This class defines the parameters required for propagating the nonlinear
151
+ system dynamics using the optimal control sequence.
152
+
153
+ Main arguments:
154
+ These are the arguments most commonly used day-to-day.
155
+
156
+ Other arguments:
157
+ The solver should likely not be changed as it is a high accuracy 8th-order
158
+ Runge-Kutta method.
159
+
160
+ Args:
161
+ inter_sample (int): How dense the propagation within multishot
162
+ discretization should be. Defaults to 30.
163
+ dt (float): The time step for propagation. Defaults to 0.1.
164
+ solver (str): The numerical solver to use for propagation
165
+ (e.g., "Dopri8"). Defaults to "Dopri8".
166
+ max_tau_len (int): The maximum length of the time vector for
167
+ propagation. Defaults to 1000.
168
+ args (Dict, optional): Additional arguments to pass to the solver.
169
+ Defaults to an empty dictionary.
170
+ atol (float): Absolute tolerance for the solver. Defaults to 1e-3.
171
+ rtol (float): Relative tolerance for the solver. Defaults to 1e-6.
172
+ """
173
+ self.inter_sample = inter_sample
174
+ self.dt = dt
175
+ self.solver = solver
176
+ self.max_tau_len = max_tau_len
177
+ self.args = args if args is not None else {}
178
+ self.atol = atol
179
+ self.rtol = rtol
180
+
181
+
182
+ @dataclass(init=False)
183
+ class SimConfig:
184
+ # No class-level field declarations
185
+
186
+ def __init__(
187
+ self,
188
+ x: UnifiedState,
189
+ x_prop: UnifiedState,
190
+ u: UnifiedControl,
191
+ total_time: float,
192
+ save_compiled: bool = False,
193
+ ctcs_node_intervals: Optional[list] = None,
194
+ n_states: Optional[int] = None,
195
+ n_states_prop: Optional[int] = None,
196
+ n_controls: Optional[int] = None,
197
+ ):
198
+ """
199
+ Configuration class for simulation settings.
200
+
201
+ This class defines the parameters required for simulating a trajectory
202
+ optimization problem.
203
+
204
+ Main arguments:
205
+ These are the arguments most commonly used day-to-day.
206
+
207
+ Args:
208
+ x (State): State object, must have .min and .max attributes for bounds.
209
+ x_prop (State): Propagation state object, must have .min and .max
210
+ attributes for bounds.
211
+ u (Control): Control object, must have .min and .max attributes for
212
+ bounds.
213
+ total_time (float): The total simulation time.
214
+ idx_x_true (slice): Slice for true state indices.
215
+ idx_x_true_prop (slice): Slice for true propagation state indices.
216
+ idx_u_true (slice): Slice for true control indices.
217
+ idx_t (slice): Slice for time index.
218
+ idx_y (slice): Slice for constraint violation indices.
219
+ idx_y_prop (slice): Slice for propagation constraint violation
220
+ indices.
221
+ idx_s (slice): Slice for time dilation index.
222
+ save_compiled (bool): If True, save and reuse compiled solver
223
+ functions. Defaults to False.
224
+ ctcs_node_intervals (list, optional): Node intervals for CTCS
225
+ constraints.
226
+ n_states (int, optional): The number of state variables. Defaults to
227
+ `None` (inferred from x.max).
228
+ n_states_prop (int, optional): The number of propagation state
229
+ variables. Defaults to `None` (inferred from x_prop.max).
230
+ n_controls (int, optional): The number of control variables. Defaults
231
+ to `None` (inferred from u.max).
232
+
233
+ Note:
234
+ You can specify custom scaling for specific states/controls using
235
+ the `scaling_min` and `scaling_max` attributes on State, Control, and Time objects.
236
+ If not set, the default min/max bounds will be used for scaling.
237
+ """
238
+ # Assign core arguments to self
239
+ self.x = x
240
+ self.x_prop = x_prop
241
+ self.u = u
242
+ self.total_time = total_time
243
+ self.save_compiled = save_compiled
244
+ self.ctcs_node_intervals = ctcs_node_intervals
245
+ self.n_states = n_states
246
+ self.n_states_prop = n_states_prop
247
+ self.n_controls = n_controls
248
+
249
+ # Call post init logic
250
+ self.__post_init__()
251
+
252
+ def __post_init__(self):
253
+ self.n_states = len(self.x.max)
254
+ self.n_controls = len(self.u.max)
255
+
256
+ # State scaling
257
+ # Use scaling_min/max if provided, otherwise use regular min/max
258
+ min_x = np.array(self.x.min, dtype=float)
259
+ max_x = np.array(self.x.max, dtype=float)
260
+
261
+ # UnifiedState now always provides full-size scaling arrays when any state has scaling
262
+ if self.x.scaling_min is not None:
263
+ lower_x = np.array(self.x.scaling_min, dtype=float)
264
+ else:
265
+ lower_x = min_x
266
+
267
+ if self.x.scaling_max is not None:
268
+ upper_x = np.array(self.x.scaling_max, dtype=float)
269
+ else:
270
+ upper_x = max_x
271
+
272
+ S_x, c_x = get_affine_scaling_matrices(self.n_states, lower_x, upper_x)
273
+ self.S_x = S_x
274
+ self.c_x = c_x
275
+ self.inv_S_x = np.diag(1 / np.diag(self.S_x))
276
+
277
+ # Control scaling
278
+ # Use scaling_min/max if provided, otherwise use regular min/max
279
+ min_u = np.array(self.u.min, dtype=float)
280
+ max_u = np.array(self.u.max, dtype=float)
281
+
282
+ # UnifiedControl now always provides full-size scaling arrays when any control has scaling
283
+ if self.u.scaling_min is not None:
284
+ lower_u = np.array(self.u.scaling_min, dtype=float)
285
+ else:
286
+ lower_u = min_u
287
+
288
+ if self.u.scaling_max is not None:
289
+ upper_u = np.array(self.u.scaling_max, dtype=float)
290
+ else:
291
+ upper_u = max_u
292
+
293
+ S_u, c_u = get_affine_scaling_matrices(self.n_controls, lower_u, upper_u)
294
+ self.S_u = S_u
295
+ self.c_u = c_u
296
+ self.inv_S_u = np.diag(1 / np.diag(self.S_u))
297
+
298
+ # Properties for accessing slices from unified objects
299
+ @property
300
+ def time_slice(self):
301
+ """Slice for accessing time in the state vector."""
302
+ return self.x.time_slice
303
+
304
+ @property
305
+ def ctcs_slice(self):
306
+ """Slice for accessing CTCS augmented states."""
307
+ return self.x.ctcs_slice
308
+
309
+ @property
310
+ def ctcs_slice_prop(self):
311
+ """Slice for accessing CTCS augmented states in propagation."""
312
+ return self.x_prop.ctcs_slice
313
+
314
+ @property
315
+ def time_dilation_slice(self):
316
+ """Slice for accessing time dilation in the control vector."""
317
+ return self.u.time_dilation_slice
318
+
319
+ @property
320
+ def true_state_slice(self):
321
+ """Slice for accessing true (non-augmented) states."""
322
+ return self.x._true_slice
323
+
324
+ @property
325
+ def true_state_slice_prop(self):
326
+ """Slice for accessing true (non-augmented) propagation states."""
327
+ return self.x_prop._true_slice
328
+
329
+ @property
330
+ def true_control_slice(self):
331
+ """Slice for accessing true (non-augmented) controls."""
332
+ return self.u._true_slice
333
+
334
+
335
+ @dataclass
336
+ class ScpConfig:
337
+ def __init__(
338
+ self,
339
+ n: Optional[int] = None,
340
+ k_max: int = 200,
341
+ w_tr: float = 1.0,
342
+ lam_vc: float = 1.0,
343
+ ep_tr: float = 1e-4,
344
+ ep_vb: float = 1e-4,
345
+ ep_vc: float = 1e-8,
346
+ lam_cost: float = 0.0,
347
+ lam_vb: float = 0.0,
348
+ uniform_time_grid: bool = False,
349
+ cost_drop: int = -1,
350
+ cost_relax: float = 1.0,
351
+ w_tr_adapt: float = 1.0,
352
+ w_tr_max: Optional[float] = None,
353
+ w_tr_max_scaling_factor: Optional[float] = None,
354
+ ):
355
+ """
356
+ Configuration class for Sequential Convex Programming (SCP).
357
+
358
+ This class defines the parameters used to configure the SCP solver. You
359
+ will very likely need to modify the weights for your problem. Please
360
+ refer to my guide [here](https://haynec.github.io/openscvx/
361
+ hyperparameter_tuning) for more information.
362
+
363
+ Attributes:
364
+ n (int): The number of discretization nodes. Defaults to `None`.
365
+ k_max (int): The maximum number of SCP iterations. Defaults to 200.
366
+ w_tr (float): The trust region weight. Defaults to 1.0.
367
+ lam_vc (float): The penalty weight for virtual control. Defaults to 1.0.
368
+ ep_tr (float): The trust region convergence tolerance. Defaults to 1e-4.
369
+ ep_vb (float): The boundary constraint convergence tolerance.
370
+ Defaults to 1e-4.
371
+ ep_vc (float): The virtual constraint convergence tolerance.
372
+ Defaults to 1e-8.
373
+ lam_cost (float): The weight for original cost. Defaults to 0.0.
374
+ lam_vb (float): The weight for virtual buffer. This is only used if
375
+ there are nonconvex nodal constraints present. Defaults to 0.0.
376
+ uniform_time_grid (bool): Whether to use a uniform time grid.
377
+ Defaults to `False`.
378
+ cost_drop (int): The number of iterations to allow for cost
379
+ stagnation before termination. Defaults to -1 (disabled).
380
+ cost_relax (float): The relaxation factor for cost reduction.
381
+ Defaults to 1.0.
382
+ w_tr_adapt (float): The adaptation factor for the trust region
383
+ weight. Defaults to 1.0.
384
+ w_tr_max (float): The maximum allowable trust region weight.
385
+ Defaults to `None`.
386
+ w_tr_max_scaling_factor (float): The scaling factor for the maximum
387
+ trust region weight. Defaults to `None`.
388
+ """
389
+ self.n = n
390
+ self.k_max = k_max
391
+ self.w_tr = w_tr
392
+ self.lam_vc = lam_vc
393
+ self.ep_tr = ep_tr
394
+ self.ep_vb = ep_vb
395
+ self.ep_vc = ep_vc
396
+ self.lam_cost = lam_cost
397
+ self.lam_vb = lam_vb
398
+ self.uniform_time_grid = uniform_time_grid
399
+ self.cost_drop = cost_drop
400
+ self.cost_relax = cost_relax
401
+ self.w_tr_adapt = w_tr_adapt
402
+ self.w_tr_max = w_tr_max
403
+ self.w_tr_max_scaling_factor = w_tr_max_scaling_factor
404
+
405
+ def __post_init__(self):
406
+ keys_to_scale = ["w_tr", "lam_vc", "lam_cost", "lam_vb"]
407
+ # Handle lam_vc which might be scalar or array
408
+ scale_values = []
409
+ for key in keys_to_scale:
410
+ val = getattr(self, key)
411
+ if isinstance(val, np.ndarray):
412
+ scale_values.append(np.max(val))
413
+ else:
414
+ scale_values.append(val)
415
+ scale = max(scale_values)
416
+ for key in keys_to_scale:
417
+ val = getattr(self, key)
418
+ if isinstance(val, np.ndarray):
419
+ setattr(self, key, val / scale)
420
+ else:
421
+ setattr(self, key, val / scale)
422
+
423
+ if self.w_tr_max_scaling_factor is not None and self.w_tr_max is None:
424
+ self.w_tr_max = self.w_tr_max_scaling_factor * self.w_tr
425
+
426
+
427
+ @dataclass
428
+ class Config:
429
+ sim: SimConfig
430
+ scp: ScpConfig
431
+ cvx: ConvexSolverConfig
432
+ dis: DiscretizationConfig
433
+ prp: PropagationConfig
434
+ dev: DevConfig
435
+
436
+ def __post_init__(self):
437
+ pass
@@ -0,0 +1,47 @@
1
+ """Discretization methods for trajectory optimization.
2
+
3
+ This module provides implementations of discretization schemes that convert
4
+ continuous-time optimal control problems into discrete-time approximations
5
+ suitable for numerical optimization. Discretization is a critical step in
6
+ trajectory optimization that linearizes the nonlinear dynamics around a
7
+ reference trajectory.
8
+
9
+ Planned Architecture (ABC-based):
10
+
11
+ A base class will be introduced to enable pluggable discretization methods.
12
+ This will enable users to implement custom discretization methods.
13
+ Future discretizers will implement the Discretizer interface:
14
+
15
+ ```python
16
+ # discretization/base.py (planned):
17
+ class Discretizer(ABC):
18
+ def __init__(self, integrator: Integrator):
19
+ '''Initialize with a numerical integrator.'''
20
+ self.integrator = integrator
21
+
22
+ @abstractmethod
23
+ def discretize(self, dynamics, x, u, dt) -> tuple[A_d, B_d, C_d]:
24
+ '''Discretize continuous dynamics around trajectory (x, u).
25
+
26
+ Args:
27
+ dynamics: Continuous-time dynamics object
28
+ x: State trajectory
29
+ u: Control trajectory
30
+ dt: Time step
31
+
32
+ Returns:
33
+ A_d: Discretized state transition matrix
34
+ B_d: Discretized control influence matrix (current node)
35
+ C_d: Discretized control influence matrix (next node)
36
+ '''
37
+ ...
38
+ ```
39
+ """
40
+
41
+ from .discretization import calculate_discretization, dVdt, get_discretization_solver
42
+
43
+ __all__ = [
44
+ "calculate_discretization",
45
+ "get_discretization_solver",
46
+ "dVdt",
47
+ ]