openscvx 0.3.2.dev315__tar.gz → 0.3.2.dev317__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.
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/_docs.yml +1 -1
- {openscvx-0.3.2.dev315/openscvx.egg-info → openscvx-0.3.2.dev317}/PKG-INFO +1 -1
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/UnderTheHood/vectorization_and_vmapping.md +2 -5
- openscvx-0.3.2.dev317/docs/UsersGuide/00_introduction.md +44 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/01_hello_world_brachistochrone.md +320 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/02_drone_racing_constraints.md +289 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/03_obstacle_avoidance_vmap.md +424 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/04_viewpoint_constraints.md +356 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/05_visualization.md +447 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/06_logic.md +12 -0
- openscvx-0.3.2.dev317/docs/UsersGuide/07_lie.md +9 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/getting-started.md +9 -5
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/mkdocs.yml +17 -23
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/_version.py +3 -3
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/algorithms/optimization_results.py +22 -25
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/config.py +0 -8
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/discretization/discretization.py +16 -15
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/integrators/runge_kutta.py +9 -9
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/unified.py +2 -2
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/plotting.py +2 -2
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/scp_iteration.py +1 -1
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/problem.py +18 -16
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/propagation/propagation.py +19 -9
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/solvers/base.py +2 -2
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/arithmetic.py +31 -15
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/array.py +11 -11
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/constraint.py +13 -13
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/control.py +6 -4
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/expr.py +23 -15
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/lie/adjoint.py +19 -9
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/lie/se3.py +6 -5
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/lie/so3.py +6 -5
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/linalg.py +15 -11
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/logic.py +5 -5
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/math.py +43 -31
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/spatial.py +9 -7
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/state.py +13 -12
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/stl.py +10 -7
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/variable.py +15 -7
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/vmap.py +3 -3
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/lower.py +2 -2
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/lowerers/cvxpy.py +2 -2
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/lowerers/jax.py +5 -5
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/preprocessing.py +3 -1
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/utils/caching.py +1 -1
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/utils/printing.py +2 -1
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317/openscvx.egg-info}/PKG-INFO +1 -1
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx.egg-info/SOURCES.txt +14 -18
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/scripts/gen_example_pages.py +2 -2
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/scripts/gen_ref_pages.py +2 -2
- openscvx-0.3.2.dev315/docs/Usage/advanced_problem_setup.md +0 -249
- openscvx-0.3.2.dev315/docs/Usage/api.md +0 -121
- openscvx-0.3.2.dev315/docs/Usage/api_constraints.md +0 -41
- openscvx-0.3.2.dev315/docs/Usage/api_control.md +0 -10
- openscvx-0.3.2.dev315/docs/Usage/api_integrators.md +0 -25
- openscvx-0.3.2.dev315/docs/Usage/api_state.md +0 -26
- openscvx-0.3.2.dev315/docs/Usage/api_trajoptproblem.md +0 -48
- openscvx-0.3.2.dev315/docs/Usage/api_variable.md +0 -6
- openscvx-0.3.2.dev315/docs/Usage/basic_problem_setup.md +0 -273
- openscvx-0.3.2.dev315/docs/Usage/tutorial_6dof_los_guidance.md +0 -436
- openscvx-0.3.2.dev315/docs/Usage/tutorial_6dof_obstacle_avoidance.md +0 -294
- openscvx-0.3.2.dev315/docs/Usage/tutorials.md +0 -46
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/assets/logo.svg +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/release-drafter.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/branch-name.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/docs.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/lint.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/nightly.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/release-drafter.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/release.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/tests-integration.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.github/workflows/tests-unit.yml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/.gitignore +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/CONTRIBUTING.md +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/LICENSE +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/README.md +0 -0
- {openscvx-0.3.2.dev315/docs/Overview → openscvx-0.3.2.dev317/docs/Foundations}/constraint_reformulation.md +0 -0
- {openscvx-0.3.2.dev315/docs/Overview → openscvx-0.3.2.dev317/docs/Foundations}/control_parameterization.md +0 -0
- {openscvx-0.3.2.dev315/docs/Overview → openscvx-0.3.2.dev317/docs/Foundations}/discretization.md +0 -0
- {openscvx-0.3.2.dev315/docs/Overview → openscvx-0.3.2.dev317/docs/Foundations}/ocp.md +0 -0
- {openscvx-0.3.2.dev315/docs/Overview → openscvx-0.3.2.dev317/docs/Foundations}/scvx.md +0 -0
- {openscvx-0.3.2.dev315/docs/Overview → openscvx-0.3.2.dev317/docs/Foundations}/time_dilation.md +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/UnderTheHood/lowering_architecture.md +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/favicon.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/images/ct-scvx_dark.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/images/ct-scvx_light.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/images/ctcs_dark.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/images/ctcs_light.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/images/problem_class_dark.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/images/problem_class_light.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/assets/logo.svg +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/citation.md +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/examples.md +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/index.md +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/javascripts/mathjax.js +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/abstract/brachistochrone.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/arm/three_link_arm.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/car/dubins_car.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/car/dubins_car_conditional.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/car/dubins_car_disjoint.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/car/dubins_car_stljax.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/cinema_vp.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/cinema_vp_realtime_base.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/dr_double_integrator.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/dr_vp.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/dr_vp_nodal.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/dr_vp_polytope.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/drone_racing.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/obstacle_avoidance.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/obstacle_avoidance_nodal.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/obstacle_avoidance_realtime_base.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/drone/obstacle_avoidance_vmap.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/plotting.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/plotting_viser.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/realtime/cinema_vp_realtime.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/realtime/drone_racing_realtime.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/realtime/dubins_car_realtime.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/realtime/obstacle_avoidance_realtime.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/rocket/3DoF_pdg.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/examples/spacecraft/proxops_cw.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/ctlos_cine.gif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/ctlos_dr.gif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/dtlos_cine.gif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/dtlos_dr.gif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/openscvx_logo.svg +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/openscvx_logo_square.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/oscvx_structure_full_dark.svg +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/figures/video_preview.png +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/1-background.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/1-background@1x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/1-background@2x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/1-background@3x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/1-background@4x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/2-mars.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/2-mars@1x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/2-mars@2x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/2-mars@3x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/2-mars@4x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/3-moon.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/3-moon@1x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/3-moon@2x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/3-moon@3x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/3-moon@4x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/4-sat1.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/4-sat1@1x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/4-sat1@2x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/4-sat1@3x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/4-sat1@4x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/5-space.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/5-space@1x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/5-space@2x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/5-space@3x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/5-space@4x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/6-earth.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/6-earth@1x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/6-earth@2x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/6-earth@3x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/images/layers/6-earth@4x.avif +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/javascripts/parallax.js +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/logo.svg +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/stylesheets/custom.css +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/assets/stylesheets/parallax.css +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/home.html +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/main.html +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/partials/parallax/hero.html +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/material/overrides/partials/parallax.html +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/algorithms/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/algorithms/autotuning.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/algorithms/base.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/algorithms/penalized_trust_region.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/discretization/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/expert/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/expert/byof.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/expert/lowering.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/expert/validation.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/init/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/init/interpolation.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/integrators/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/cvxpy_constraints.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/cvxpy_variables.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/dynamics.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/jax_constraints.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/parameters.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/lowered/problem.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/viser/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/viser/animated.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/viser/plotly_integration.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/viser/primitives.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/viser/scp.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/plotting/viser/server.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/propagation/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/propagation/post_processing.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/solvers/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/solvers/ptr_solver.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/augmentation.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/builder.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/constraint_set.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/expr/lie/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/hashing.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/lowerers/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/problem.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/time.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/symbolic/unified.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/utils/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/utils/cache.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/utils/profiling.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx/utils/utils.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx.egg-info/dependency_links.txt +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx.egg-info/requires.txt +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/openscvx.egg-info/top_level.txt +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/pyproject.toml +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/setup.cfg +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/brachistochrone_analytical.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/__init__.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_arithmetic.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_array.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_constraint.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_expr.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_lie.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_linalg.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_logic.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_math.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_node_reference.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_parameters.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_scaling.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_spatial.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_variable.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/expr/test_vmap.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/test_augmentation.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/test_hashing.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/test_lower_cvxpy.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/test_lower_jax.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/test_preprocessing.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/symbolic/test_unified.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_brachistochrone.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_cvxpygen_optional.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_discretization.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_examples.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_expert.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_init.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_integrators.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_plotting.py +0 -0
- {openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/tests/test_propagation.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openscvx
|
|
3
|
-
Version: 0.3.2.
|
|
3
|
+
Version: 0.3.2.dev317
|
|
4
4
|
Summary: A general Python-based successive convexification implementation which uses a JAX backend.
|
|
5
5
|
Author-email: Chris Hayner and Griffin Norris <haynec@uw.edu>
|
|
6
6
|
License: Apache Software License
|
{openscvx-0.3.2.dev315 → openscvx-0.3.2.dev317}/docs/UnderTheHood/vectorization_and_vmapping.md
RENAMED
|
@@ -511,8 +511,5 @@ for state in problem.states:
|
|
|
511
511
|
|
|
512
512
|
## See Also
|
|
513
513
|
|
|
514
|
-
- [
|
|
515
|
-
- [
|
|
516
|
-
- [API: Control](../Usage/api_control.md) - Control class documentation
|
|
517
|
-
- [API: Problem](../Usage/api_problem.md) - Main problem class
|
|
518
|
-
- [Discretization](../Overview/discretization.md) - How discretization works in OpenSCvx
|
|
514
|
+
- [Hello world tutorial](../UsersGuide/01_hello_world_brachistochrone.md) - How to define problems
|
|
515
|
+
- [Discretization](../Foundations/discretization.md) - How discretization works in OpenSCvx
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Users Guide
|
|
2
|
+
|
|
3
|
+
Welcome to the OpenSCvx Users Guide. This section aims to provides a progressive and useful introduction to trajectory optimization with OpenSCvx, starting from first principles and building toward complex, representative problems.
|
|
4
|
+
|
|
5
|
+
## Learning Path
|
|
6
|
+
|
|
7
|
+
The tutorials are designed to be read in order. Each builds on concepts from the previous, introducing new features in the context of increasingly realistic problems.
|
|
8
|
+
|
|
9
|
+
| Tutorial | Problem | You Will Learn |
|
|
10
|
+
|----------|---------|----------------|
|
|
11
|
+
| [01 Hello Brachistochrone](01_hello_world_brachistochrone.md) | Minimum-time descent curve | Core API: states, controls, dynamics, time, CTCS constraints, solving |
|
|
12
|
+
| [02 Drone Racing](02_drone_racing_constraints.md) | Racing through gates | Nodal constraints, `.at()`, `.over()`, `.convex()`, keyframe initialization |
|
|
13
|
+
| [03 Obstacle Avoidance](03_obstacle_avoidance_vmap.md) | 6-DOF navigation | Quaternion dynamics, spatial utilities, `ox.Parameter`, `ox.Vmap` |
|
|
14
|
+
| [04 Viewpoint Constraints](04_viewpoint_constraints.md) | Perception-constrained racing | Custom symbolic functions, Vmap with custom functions, attitude initialization |
|
|
15
|
+
| [05 Visualization](05_visualization.md) | — | 2D plots with Plotly, 3D interactive visualization with viser, Plotly-in-viser |
|
|
16
|
+
| [06 Dubin's Car](06_logic.md) | Conditional path planning | Conditional statements, signal temporal logic (STL) |
|
|
17
|
+
| [07 Multi-Link Arms](07_lie.md) | Articulated robot control | Lie algebra, propagated states |
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
If you're new to OpenSCvx, start with [Hello Brachistochrone](01_hello_world_brachistochrone.md). By the end of that tutorial you will have solved your first trajectory optimization problem and understand the core workflow:
|
|
22
|
+
|
|
23
|
+
1. Define states and controls with `ox.State` and `ox.Control`
|
|
24
|
+
2. Specify dynamics as a dictionary of symbolic expressions
|
|
25
|
+
3. Add constraints using `ox.ctcs()` for continuous enforcement
|
|
26
|
+
4. Create a `Problem`, initialize, solve, and post-process
|
|
27
|
+
|
|
28
|
+
From there, each subsequent tutorial introduces new capabilities while reinforcing the fundamentals.
|
|
29
|
+
|
|
30
|
+
## Interactive Notebooks
|
|
31
|
+
|
|
32
|
+
Some tutorials include Google Colab notebooks for interactive learning:
|
|
33
|
+
|
|
34
|
+
- [03 6-DOF Obstacle Avoidance ](https://colab.research.google.com/drive/1xLPC_UJWC35oPRIAY3vkxi8WEYnHCysQ?usp=sharing)
|
|
35
|
+
- [04 Viewpoint Constraints ](https://colab.research.google.com/drive/1b3NEx288h4r4HuvCOj-fexmt90PPhKUw?usp=sharing)
|
|
36
|
+
|
|
37
|
+
These notebooks let you run the examples without local setup and experiment with parameters in real-time.
|
|
38
|
+
|
|
39
|
+
## Beyond the Tutorials
|
|
40
|
+
|
|
41
|
+
After completing the tutorials, explore:
|
|
42
|
+
|
|
43
|
+
- [Examples](../Examples/abstract/brachistochrone.md) — Complete problem implementations across domains
|
|
44
|
+
- [API Reference](../Reference/problem.md) — Detailed documentation for all classes and functions
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
# 01 Hello Brachistochrone
|
|
2
|
+
|
|
3
|
+
In this _hello world_ tutorial we will introduce the reader to the fundamental concepts and API needed to define and solve a problem using OpenSCvx as well as how the results can be accessed.
|
|
4
|
+
Our example of choice in this endeavor is the _Brachistochrone problem_, which we briefly describe below before delving into the implementation.
|
|
5
|
+
Finally, we will lay out some further reading and tease the next tutorial for the interested reader.
|
|
6
|
+
|
|
7
|
+
This tutorial covers:
|
|
8
|
+
|
|
9
|
+
- Problem creation
|
|
10
|
+
- Variable instantiation
|
|
11
|
+
- Dynamics and constraint definition
|
|
12
|
+
- Solving the problem
|
|
13
|
+
- Accessing results
|
|
14
|
+
|
|
15
|
+
## The Brachistochrone Problem
|
|
16
|
+
|
|
17
|
+
The [Brachistochrone problem](https://en.wikipedia.org/wiki/Brachistochrone_curve) is concerned with finding the fastest path between 2 points under the influence of gravity and without friction.
|
|
18
|
+
Bernouli originally posed the problem as:
|
|
19
|
+
|
|
20
|
+
> _Given two points A and B in a vertical plane, what is the curve traced out by a point acted on only by gravity, which starts at A and reaches B in the shortest time._
|
|
21
|
+
|
|
22
|
+
As with most good things in life, this can be written as an optimal control problem in the Mayer form as:
|
|
23
|
+
|
|
24
|
+
$$
|
|
25
|
+
\begin{align}
|
|
26
|
+
\min_{\mathbf{x}, \mathbf{u}, t_f}\ &t_f & \\
|
|
27
|
+
\mathrm{s.t.}\ &\dot{\mathbf{x}}(t) = f(\mathbf{x}(t), \mathbf{u}(t)) & \forall t\in[0, t_f], \quad &\textrm{dynamics} \\
|
|
28
|
+
&\mathbf{x}_{\min} \leq \mathbf{x}(t) \leq \mathbf{x}_{\max} & \forall t\in[0, t_f], \quad &\textrm{state bounds} \\
|
|
29
|
+
&\mathbf{u}_{\min} \leq \mathbf{u}(t) \leq \mathbf{u}_{\max} & \forall t\in[0, t_f], \quad &\textrm{control bounds} \\
|
|
30
|
+
&\mathbf{x}(0) = \mathbf{x}_{\mathrm{init}}, & & \textrm{initial}\\
|
|
31
|
+
&\mathbf{p}(t_f) = \mathbf{p}_{\mathrm{final}} & & \textrm{terminal}
|
|
32
|
+
\end{align}
|
|
33
|
+
$$
|
|
34
|
+
|
|
35
|
+
where the state $\mathbf{x} = [x, y, v]^\top$ consists of 2D position $\mathbf{p} = [x, y]^\top$ and speed $v$, the control $\mathbf{u} = \theta$ is the angle from vertical, and the dynamics are given by:
|
|
36
|
+
|
|
37
|
+
$$
|
|
38
|
+
f(\mathbf{x}, \mathbf{u}) = \begin{bmatrix} v \sin(\theta) \\ -v \cos(\theta) \\ g \cos(\theta) \end{bmatrix}
|
|
39
|
+
$$
|
|
40
|
+
|
|
41
|
+
Note that the terminal velocity is unconstrained.
|
|
42
|
+
|
|
43
|
+
This problem is particularly interesting to us because of two main reasons:
|
|
44
|
+
|
|
45
|
+
1. It is a very nice, small toy problem we can use to introduce the core concepts of OpenSCvx. It is quick to formulate and quick to solve.
|
|
46
|
+
2. _It has an analytical solution._ The Brachistochrone problem was solved by none other than Isaac Newton in 1697, who showed that the optimal path is given by a cycloid, the curve traced by a point on the rim of a rolling wheel:
|
|
47
|
+
|
|
48
|
+
$$
|
|
49
|
+
\begin{align}
|
|
50
|
+
x(\phi) &= r(\phi - \sin\phi) \\
|
|
51
|
+
y(\phi) &= r(1 - \cos\phi)
|
|
52
|
+
\end{align}
|
|
53
|
+
$$
|
|
54
|
+
|
|
55
|
+
where $\phi \in [0, \phi_f]$ is the curve parameter and $r$ is the radius of the generating circle, determined by the boundary conditions. The minimum time of descent is:
|
|
56
|
+
|
|
57
|
+
$$
|
|
58
|
+
t_f^* = \sqrt{\frac{r}{g}} \phi_f
|
|
59
|
+
$$
|
|
60
|
+
|
|
61
|
+
Because of these advantageous properties the Brachistochrone problem is extensively leveraged as a [unit test](https://github.com/OpenSCvx/OpenSCvx/blob/main/tests/test_brachistochrone.py).
|
|
62
|
+
We would highly recommend that anyone setting out to develop some kind of optimization software do the same.
|
|
63
|
+
It may not be necessary _nor_ sufficient, but a lot of things have to be working properly to solve Brachistochrone problem.
|
|
64
|
+
|
|
65
|
+
## Creating an OpenSCvx Problem
|
|
66
|
+
|
|
67
|
+
During the development of OpenSCvx we spent a great deal of time trying to make the library as easy to work with as possible.
|
|
68
|
+
If it isn't easy to use, people will just write their own.
|
|
69
|
+
For that reason, we took inspiration from fantastic libraries such as [NumPy](https://github.com/numpy/numpy), [JAX](https://github.com/jax-ml/jax), and [CVXPY](https://github.com/cvxpy/cvxpy) and developed a symbolic expression system.
|
|
70
|
+
|
|
71
|
+
This not only allows us to keep the syntax close to the mathematical notation but also enables us to do lots of preprocessing, validation, canonicalization, and augmentation under the hood without the user ever having to know about it if they choose not to.
|
|
72
|
+
It also means that we can keep the syntax similar to popular libraries such as Numpy and JAX to reduce the learning curve for new users.
|
|
73
|
+
|
|
74
|
+
Before we begin defining our problem we can take care of our imports as well as some high-level parameters.
|
|
75
|
+
Typically, OpenSCvx is imported as `ox` for brevity. This will be the standard throughout these tutorials
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
import openscvx as ox
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Next, we can define our number of discrete decision nodes `n`, our initial guess for the completion time `total_time`, and define the gravitational acceleration `g`
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
n = 2
|
|
85
|
+
total_time = 2.0
|
|
86
|
+
g = 9.81
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Note that `total_time` is an _initial guess_ for the time. We will solve this as a free final-time problem and find the optimal solution.
|
|
90
|
+
|
|
91
|
+
### Variable Definition
|
|
92
|
+
|
|
93
|
+
Now, we can start implementing the Brachistochrone problem, starting by creating the necessary state and control variables.
|
|
94
|
+
Each `ox.State` can be instantiated with a name and shape such as
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
position = ox.State("position", shape=(2,)) # 2D position [x, y]
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Then, we need to define the bounds. In this example we arbitrarily choose a 10 by 10 box for our position
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
position.max = [10.0, 10.0]
|
|
104
|
+
position.min = [0.0, 0.0]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Finally, we must define the initial and final conditions. In this case we will find the Brachistochrone curve from $[0, 10]$ to $[10, 5]$ and set the values accordingly:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
position.initial = np.array([0.0, 10.0])
|
|
111
|
+
position.final = [10.0, 5.0]
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
All of these values can be either raw Python lists, NumPy or JAX arrays.
|
|
115
|
+
We can then similarly define the velocity state and it's bounds, resulting in the following code defining both states:
|
|
116
|
+
|
|
117
|
+
```python
|
|
118
|
+
# Define state components
|
|
119
|
+
position = ox.State("position", shape=(2,)) # 2D position [x, y]
|
|
120
|
+
position.max = np.array([10.0, 10.0])
|
|
121
|
+
position.min = np.array([0.0, 0.0])
|
|
122
|
+
position.initial = np.array([0.0, 10.0])
|
|
123
|
+
position.final = [10.0, 5.0]
|
|
124
|
+
|
|
125
|
+
velocity = ox.State("velocity", shape=(1,)) # Scalar speed
|
|
126
|
+
velocity.max = np.array([10.0])
|
|
127
|
+
velocity.min = np.array([0.0])
|
|
128
|
+
velocity.initial = np.array([0.0])
|
|
129
|
+
velocity.final = [("free", 10.0)]
|
|
130
|
+
|
|
131
|
+
# Define list of all states (needed for Problem and constraints)
|
|
132
|
+
states = [position, velocity]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
The `.final` value of Velocity is defined as a tuple `("free", 10.0)`. This sets the corresponding value as "free" for the optimizer to choose.
|
|
136
|
+
This is how we can encode that the final velocity is unconstrained. The numerical value is necessary as an initial guess.
|
|
137
|
+
When only a value is specified as in the case of `position`, the values are assumed to be fixed.
|
|
138
|
+
|
|
139
|
+
To summarize, we can use the following syntax to define the initial and final conditions:
|
|
140
|
+
|
|
141
|
+
- Fixed value: `value` or `("fixed", value)`
|
|
142
|
+
- Free variable: `("free", guess)` - Can be optimized within bounds
|
|
143
|
+
- Minimize: `("minimize", guess)` - Variable to be minimized
|
|
144
|
+
- Maximize: `("maximize", guess)` - Variable to be maximized
|
|
145
|
+
|
|
146
|
+
For our control `theta` we can follow a similar syntax to define the symbolic `ox.Control` object:
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
# Define control
|
|
150
|
+
theta = ox.Control("theta", shape=(1,)) # Angle from vertical
|
|
151
|
+
theta.max = np.array([100.5 * jnp.pi / 180])
|
|
152
|
+
theta.min = np.array([0.0])
|
|
153
|
+
theta.guess = np.linspace(5 * jnp.pi / 180, 100.5 * jnp.pi / 180, n).reshape(-1, 1)
|
|
154
|
+
|
|
155
|
+
controls = [theta]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Here, we do _not_ specify an initial or final value for the control but we _do_ need to specify an initial guess.
|
|
159
|
+
The initial guess must be of shape $(n_u \times n_{\mathrm{nodes}})$, providing an initial guess for each control at every node.
|
|
160
|
+
Technically, we can also provide a `.guess` for states. However, these default to a linear interpolation between initial and final conditions which is sufficient in most cases.
|
|
161
|
+
|
|
162
|
+
### Dynamics
|
|
163
|
+
|
|
164
|
+
Dynamics are defined as a dictionary mapping state names to their time derivatives using symbolic expressions:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
# Define dynamics as dictionary mapping state names to their derivatives
|
|
168
|
+
dynamics = {
|
|
169
|
+
"position": ox.Concat(
|
|
170
|
+
velocity[0] * ox.Sin(theta[0]), # x_dot
|
|
171
|
+
-velocity[0] * ox.Cos(theta[0]), # y_dot
|
|
172
|
+
),
|
|
173
|
+
"velocity": g * ox.Cos(theta[0]),
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
!!! Note
|
|
178
|
+
Every state passed to the problem must have a matching element in the dynamics dictionary under the same name.
|
|
179
|
+
|
|
180
|
+
The symbolic expressions support standard Python operators:
|
|
181
|
+
|
|
182
|
+
- Arithmetic: `+`, `-`, `*`, `/`, `**`
|
|
183
|
+
- Matrix multiplication: `@`
|
|
184
|
+
- Comparisons: `<=`, `>=`, `==` (for constraint definitions, see below)
|
|
185
|
+
- Indexing: `[...]`
|
|
186
|
+
- Transpose: `.T`
|
|
187
|
+
|
|
188
|
+
Common symbolic functions include:
|
|
189
|
+
|
|
190
|
+
- `ox.Concat()`: Concatenation
|
|
191
|
+
- `ox.Sin()`/`ox.Cos()`: Trigonometric functions
|
|
192
|
+
- `ox.linalg.Norm()`: Vector/matrix norms
|
|
193
|
+
|
|
194
|
+
!!! Note
|
|
195
|
+
Under the hood, symbolic expressions are compiled using JAX, so use `jax.numpy` for numerical constants and functions when needed.
|
|
196
|
+
|
|
197
|
+
### Constraints (Continuous)
|
|
198
|
+
|
|
199
|
+
We already defined the box constraints when we instantiated the variables.
|
|
200
|
+
These are enforced at the discrete decision nodes automatically.
|
|
201
|
+
However, OpenSCvx offers another way to enforce constraints: we can also enforce the constraints _between_ the discrete nodes.
|
|
202
|
+
We call this continuous-time constraint-satisfaction (CTCS).
|
|
203
|
+
While the full description of CTCS is beyond the scope of this tutorial, what's important is that this is handled internally without user interaction.
|
|
204
|
+
|
|
205
|
+
We can define the boundary constraints as inequalities _i.e._ `state.min <= state` and `state <= state.max`.
|
|
206
|
+
To mark these as CTCS constraints we simply wrap them in `ox.ctcs(...)`
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
# Generate box constraints for all states
|
|
210
|
+
constraints = []
|
|
211
|
+
for state in states:
|
|
212
|
+
constraints.extend(
|
|
213
|
+
[
|
|
214
|
+
ox.ctcs(state <= state.max),
|
|
215
|
+
ox.ctcs(state.min <= state)
|
|
216
|
+
]
|
|
217
|
+
)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
This style of constraint definition as a list should feel familiar to CVXPY users.
|
|
221
|
+
Note that we do not need to specify continuous box constraints for the controls.
|
|
222
|
+
This is because, by default, the controls are interpolated using first-order hold.
|
|
223
|
+
Therefore, by constraining the discrete nodes to lie within the bounds we can trivially see that the continuous case is guaranteed as well.
|
|
224
|
+
|
|
225
|
+
We will explore the various forms of constraints supported by OpenSCvx more in the [next tutorial](02_drone_racing_constraints.md)
|
|
226
|
+
|
|
227
|
+
### Time
|
|
228
|
+
|
|
229
|
+
The last remaining piece we need is `Time`. OpenSCvx allows for free final-time problems with non-constant spacing.
|
|
230
|
+
We can define our time object similar to a `State` object, including the initial and final values as well as the min. and max. bounds.
|
|
231
|
+
Similar to the states the `final` value is defined as a tuple indicating that the final time is to be minimized as well as providing an initial guess for the total time.
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
time = ox.Time(
|
|
235
|
+
initial=0.0,
|
|
236
|
+
final=("minimize", total_time),
|
|
237
|
+
min=0.0,
|
|
238
|
+
max=total_time,
|
|
239
|
+
)
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
OpenSCvx treats the `Time` object like any other state; it can be used to formulate time-dependent dynamics or constraints.
|
|
243
|
+
This is a more advanced subject for later tutorials.
|
|
244
|
+
|
|
245
|
+
### Defining the Problem
|
|
246
|
+
|
|
247
|
+
Now we have everything we need to instantiate the `Problem` with our `dynamics`, `states`, `controls`, `time`, `constraints`, as well as the number of nodes `N`
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
problem = ox.Problem(
|
|
251
|
+
dynamics=dynamics,
|
|
252
|
+
states=states,
|
|
253
|
+
controls=controls,
|
|
254
|
+
time=time,
|
|
255
|
+
constraints=constraints,
|
|
256
|
+
N=n,
|
|
257
|
+
)
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
### Solving the Problem
|
|
261
|
+
|
|
262
|
+
Solving the problem is split into three steps:
|
|
263
|
+
|
|
264
|
+
1. `problem.initialize()` initializes the problem, validating, preprocessing, canonicalizing, and augmenting the symbolic expressions before lowering them to JAX and CVXPY code.
|
|
265
|
+
2. `problem.solve()` iteratively solves the OCP and generates the discrete state and control solution at the decision nodes.
|
|
266
|
+
3. `problem.post_process()` propagates the nodal solution at high temporal fidelity. This lets us generate high resolution trajectories decoupled from the number of optimization nodes.
|
|
267
|
+
|
|
268
|
+
```python
|
|
269
|
+
problem.initialize()
|
|
270
|
+
results = problem.solve()
|
|
271
|
+
results = problem.post_process()
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Both `.solve()` and `.post_process()` return an `OptimizationResults` object with the former containing only the nodal solution while the latter also includes the high-fidelity trajectories.
|
|
275
|
+
|
|
276
|
+
### Accessing the results
|
|
277
|
+
|
|
278
|
+
But how can we easily access the results? Could we _somehow_ leverage the symbolic variables to make this easy?
|
|
279
|
+
|
|
280
|
+
Fear not; once a problem has been solved and post-processed we can access the results using the exact same variable names we defined earlier for our states and controls.
|
|
281
|
+
We can do so both for the discrete decision nodes in `results.nodes` and the high-resolution `results.trajectory` _e.g._
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
pos_nodes = results.nodes["position"]
|
|
285
|
+
theta_traj = results.trajectory["theta"]
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### Visualizing the Results
|
|
289
|
+
|
|
290
|
+
OpenSCvx provides built-in plotting utilities for quick visualization of your results. The simplest way to see your solution is with `plot_states()` and `plot_controls()`:
|
|
291
|
+
|
|
292
|
+
```python
|
|
293
|
+
from openscvx.plotting import plot_states, plot_controls
|
|
294
|
+
|
|
295
|
+
# Plot all state trajectories in a subplot grid
|
|
296
|
+
plot_states(results, ["position", "velocity"]).show()
|
|
297
|
+
|
|
298
|
+
# Plot control trajectories
|
|
299
|
+
plot_controls(results, ["theta"]).show()
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
These functions create interactive Plotly figures showing:
|
|
303
|
+
|
|
304
|
+
- **Green lines**: High-fidelity propagated trajectory
|
|
305
|
+
- **Cyan markers**: Discrete optimization nodes
|
|
306
|
+
- **Red dashed lines**: Variable bounds
|
|
307
|
+
|
|
308
|
+
For the Brachistochrone problem, you'll see the position trace out the characteristic cycloid curve, while the control angle `theta` smoothly varies from near-vertical at the start to nearly horizontal at the end.
|
|
309
|
+
|
|
310
|
+
For more advanced visualization options including 3D interactive plots, see [Tutorial 05: Visualization](05_visualization.md).
|
|
311
|
+
|
|
312
|
+
## Further Reading
|
|
313
|
+
|
|
314
|
+
- [Complete Brachistochrone Example](../Examples/abstract/brachistochrone.md)
|
|
315
|
+
- [Drone Racing: Constraints and 3DoF Dynamics](02_drone_racing_constraints.md)
|
|
316
|
+
- [Visualization: 2D Plots and 3D Interactive](05_visualization.md)
|
|
317
|
+
|
|
318
|
+
At this point you are well-equipped to go out and start constructing trajectory optimization problems.
|
|
319
|
+
If you are so-inclined you can dive into the [API reference documentation](../Reference/problem.md) or the [examples](../Examples/drone/drone_racing.md) and figure the rest out yourself.
|
|
320
|
+
For the interested reader, we will continue our guided tour of the various features in OpenSCvx by examining a slightly more interesting example which shows off different kinds of constraints we can define.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# 02 Drone Racing: Constraints and 3-DOF Dynamics
|
|
2
|
+
|
|
3
|
+
In this tutorial we build on the concepts from [Hello Brachistochrone](01_hello_world_brachistochrone.md) to tackle a more interesting problem: time-optimal drone racing through gates.
|
|
4
|
+
We will introduce 3D double-integrator dynamics and explore the various constraint types available in OpenSCvx.
|
|
5
|
+
|
|
6
|
+
This tutorial covers:
|
|
7
|
+
|
|
8
|
+
- 3DoF double-integrator (point mass) dynamics
|
|
9
|
+
- Nodal constraints with `.at()`
|
|
10
|
+
- Convex constraint marking with `.convex()`
|
|
11
|
+
- Keyframe-based initialization with `ox.init.linspace()`
|
|
12
|
+
|
|
13
|
+
## The Drone Racing Problem
|
|
14
|
+
|
|
15
|
+
We consider a simplified drone racing scenario where a point-mass drone must fly through a sequence of gates in minimum time, returning to its starting position (loop closure).
|
|
16
|
+
|
|
17
|
+
$$
|
|
18
|
+
\begin{align}
|
|
19
|
+
\min_{\mathbf{x}, \mathbf{u}, t_f}\ &t_f & \\
|
|
20
|
+
\mathrm{s.t.}\ &\dot{\mathbf{x}}(t) = f(\mathbf{x}(t), \mathbf{u}(t)) & \forall t\in[0, t_f], \quad &\textrm{dynamics} \\
|
|
21
|
+
&\mathbf{x}_{\min} \leq \mathbf{x}(t) \leq \mathbf{x}_{\max} & \forall t\in[0, t_f], \quad &\textrm{state bounds} \\
|
|
22
|
+
&\mathbf{u}_{\min} \leq \mathbf{u}(t) \leq \mathbf{u}_{\max} & \forall t\in[0, t_f], \quad &\textrm{control bounds} \\
|
|
23
|
+
&\lVert A_i \mathbf{p}(t_i) - \mathbf{c}_i \rVert_\infty \leq 1 & \forall i \in [1, N_{\mathrm{gates}}], \quad &\textrm{gate constraints} \\
|
|
24
|
+
&\mathbf{x}(0) = \mathbf{x}_{\mathrm{init}}, & & \textrm{initial}\\
|
|
25
|
+
&\mathbf{p}(t_f) = \mathbf{p}_{\mathrm{init}} & & \textrm{loop closure}
|
|
26
|
+
\end{align}
|
|
27
|
+
$$
|
|
28
|
+
|
|
29
|
+
where the state $\mathbf{x} = [\mathbf{p}^\top, \mathbf{v}^\top]^\top$ consists of 3D position and velocity, and the control $\mathbf{u} = \mathbf{f}$ is the force vector. The double-integrator dynamics are:
|
|
30
|
+
|
|
31
|
+
$$
|
|
32
|
+
f(\mathbf{x}, \mathbf{u}) = \begin{bmatrix} \mathbf{v} \\ \frac{1}{m}\mathbf{f} + \mathbf{g} \end{bmatrix}
|
|
33
|
+
$$
|
|
34
|
+
|
|
35
|
+
The gate constraints use an infinity-norm formulation: the drone must pass through an ellipsoidal region defined by the matrix $A_i$ centered at $\mathbf{c}_i$ at specific times $t_i$.
|
|
36
|
+
|
|
37
|
+
## Implementation
|
|
38
|
+
|
|
39
|
+
### Variables
|
|
40
|
+
|
|
41
|
+
The state and control definitions follow the same pattern as the Brachistochrone problem, now in 3D:
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
import openscvx as ox
|
|
45
|
+
|
|
46
|
+
# 3D position and velocity states
|
|
47
|
+
position = ox.State("position", shape=(3,))
|
|
48
|
+
position.max = np.array([200.0, 100, 50])
|
|
49
|
+
position.min = np.array([-200.0, -100, 15])
|
|
50
|
+
position.initial = np.array([10.0, 0, 20])
|
|
51
|
+
position.final = [10.0, 0, 20] # Loop closure: return to start
|
|
52
|
+
|
|
53
|
+
velocity = ox.State("velocity", shape=(3,))
|
|
54
|
+
velocity.max = np.array([100, 100, 100])
|
|
55
|
+
velocity.min = np.array([-100, -100, -100])
|
|
56
|
+
velocity.initial = np.array([0, 0, 0])
|
|
57
|
+
velocity.final = [("free", 0), ("free", 0), ("free", 0)]
|
|
58
|
+
|
|
59
|
+
# 3D force control
|
|
60
|
+
force = ox.Control("force", shape=(3,))
|
|
61
|
+
f_max = 4.179 * 9.81
|
|
62
|
+
force.max = np.array([f_max, f_max, f_max])
|
|
63
|
+
force.min = np.array([-f_max, -f_max, -f_max])
|
|
64
|
+
force.guess = np.repeat(np.array([[0.0, 0, 10]]), n, axis=0)
|
|
65
|
+
|
|
66
|
+
states = [position, velocity]
|
|
67
|
+
controls = [force]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Note that `position.final` equals `position.initial`. This enforces loop closure, requiring the drone to return to its starting position.
|
|
71
|
+
|
|
72
|
+
### Dynamics
|
|
73
|
+
|
|
74
|
+
The double-integrator dynamics are straightforward: velocity is the derivative of position, and acceleration (force/mass plus gravity) is the derivative of velocity:
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
m = 1.0 # Mass
|
|
78
|
+
g_const = -9.81 # Gravity (negative z)
|
|
79
|
+
|
|
80
|
+
dynamics = {
|
|
81
|
+
"position": velocity,
|
|
82
|
+
"velocity": (1 / m) * force + np.array([0, 0, g_const]),
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
This is simpler than the Brachistochrone dynamics because there's no angle—we directly control the force vector.
|
|
87
|
+
|
|
88
|
+
### Path Constraints
|
|
89
|
+
|
|
90
|
+
As before, we enforce box constraints on states continuously:
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
constraints = []
|
|
94
|
+
for state in states:
|
|
95
|
+
constraints.extend([
|
|
96
|
+
ox.ctcs(state <= state.max),
|
|
97
|
+
ox.ctcs(state.min <= state)
|
|
98
|
+
])
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
With our states now in 3D it becomes clear to us why such constraints are also referred to as _path constraints_; we are enforcing the entire path to follow the constraints, not just the discrete nodes.
|
|
102
|
+
|
|
103
|
+
### Discrete Constraints: Gate Passage
|
|
104
|
+
|
|
105
|
+
The key new concept is **nodal constraints**, constraints enforced at specific nodes rather than continuously.
|
|
106
|
+
Gate passage constraints are a perfect example: the drone must be within each gate at a specific node.
|
|
107
|
+
|
|
108
|
+
We use the `.at([node])` method to specify which nodes the constraint applies to:
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
# Gate passage constraint at a specific node
|
|
112
|
+
gate_constraint = (
|
|
113
|
+
ox.linalg.Norm(A_gate @ position - c_gate, ord="inf") <= 1.0
|
|
114
|
+
).at([node])
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
The infinity norm $\lVert \cdot \rVert_\infty$ defines a box-shaped region, which when combined with the scaling matrix `A_gate` creates an ellipsoidal gate region.
|
|
118
|
+
|
|
119
|
+
It should be noted that by default constraints are interpreted as nodal constraints, however they are applied to _all_ nodes when not otherwise noted. Writing
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
gate_constraint = (
|
|
123
|
+
ox.linalg.Norm(A_gate @ position - c_gate, ord="inf") <= 1.0
|
|
124
|
+
)
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
would result in all nodes being constrained to lie within the gate.
|
|
128
|
+
|
|
129
|
+
There is a similar syntax for defining CTCS constraints: we can use the `.over((k, j))` method to define a continuous interval between nodes where a constraint should be enforced:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
# Enforce altitude constraint continuously between nodes 0 and 5
|
|
133
|
+
altitude_constraint = (position[2] >= 15.0).over((0, 5))
|
|
134
|
+
|
|
135
|
+
# Enforce obstacle avoidance only during the approach phase
|
|
136
|
+
obstacle_center = np.array([50, -20, 20])
|
|
137
|
+
safe_distance = 5.0
|
|
138
|
+
diff = position - obstacle_center
|
|
139
|
+
obstacle_constraint = (diff.T @ diff >= safe_distance**2).over((5, 10))
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
The `.over()` method also accepts optional parameters for the penalty function type (`penalty`), grouping index (`idx`), and whether to also check nodally (`check_nodally`).
|
|
143
|
+
|
|
144
|
+
!!! note
|
|
145
|
+
You can also directly instantiate `NodalConstraint` or `CTCS` objects if you prefer:
|
|
146
|
+
|
|
147
|
+
```python
|
|
148
|
+
from openscvx.symbolic.expr.constraint import NodalConstraint, CTCS
|
|
149
|
+
|
|
150
|
+
# Equivalent to (position[2] >= 15.0).at([5, 10])
|
|
151
|
+
altitude_nodal = NodalConstraint(position[2] >= 15.0, nodes=[5, 10])
|
|
152
|
+
|
|
153
|
+
# Equivalent to (position[2] >= 15.0).over((0, 15))
|
|
154
|
+
altitude_ctcs = CTCS(position[2] >= 15.0, nodes=(0, 15))
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
#### Marking Convex Constraints
|
|
158
|
+
|
|
159
|
+
When a constraint is convex, we can mark it with `.convex()`:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
gate_constraint = (
|
|
163
|
+
ox.linalg.Norm(A_gate @ position - c_gate, ord="inf") <= 1.0
|
|
164
|
+
).convex().at([node])
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
This tells OpenSCvx that no successive convexification is needed for this constraint, it is then lowered directly as a CVXPY constraint when the problem is lowered.
|
|
168
|
+
This helps improve numerical performance as the solver is operating directly on the true constraint, not a linearized version thereof.
|
|
169
|
+
|
|
170
|
+
#### Full Gate Setup
|
|
171
|
+
|
|
172
|
+
Here's the complete gate constraint setup for multiple gates:
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
n_gates = 10
|
|
176
|
+
gate_centers = [
|
|
177
|
+
np.array([59.436, 0.000, 20.0]),
|
|
178
|
+
np.array([92.964, -23.750, 25.524]),
|
|
179
|
+
# ... more gate centers
|
|
180
|
+
]
|
|
181
|
+
|
|
182
|
+
# Assign nodes to gates (evenly spaced)
|
|
183
|
+
nodes_per_gate = 2
|
|
184
|
+
gate_nodes = np.arange(nodes_per_gate, n, nodes_per_gate)
|
|
185
|
+
|
|
186
|
+
# Add gate constraints
|
|
187
|
+
for node, center in zip(gate_nodes, gate_centers):
|
|
188
|
+
A_gate_scaled = A_gate @ center # Pre-scaled center
|
|
189
|
+
gate_constraint = (
|
|
190
|
+
ox.linalg.Norm(A_gate @ position - A_gate_scaled, ord="inf") <= 1.0
|
|
191
|
+
).convex().at([node])
|
|
192
|
+
constraints.append(gate_constraint)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Keyframe Initialization
|
|
196
|
+
|
|
197
|
+
For problems with waypoints or gates, a linear interpolation between start and end isn't a good initial guess.
|
|
198
|
+
In fact, the start and end positions are identical to enforce loop closure. We need a more customized initial guess.
|
|
199
|
+
|
|
200
|
+
In this drone racing example the gate ordering is not free. There is a specific order in which the gates must be traversed.
|
|
201
|
+
We can use this _a priori_ knowledge to construct our initial guess; placing the required nodes directly in the center of the corresponding gate and linearly interpolating at the nodes in between gates.
|
|
202
|
+
|
|
203
|
+
To facilitate such initial guesses, OpenSCvx provides `ox.init.linspace()` for keyframe-based initialization:
|
|
204
|
+
|
|
205
|
+
```python
|
|
206
|
+
position.guess = ox.init.linspace(
|
|
207
|
+
keyframes=[position.initial] + gate_centers + [position.final],
|
|
208
|
+
nodes=[0] + list(gate_nodes) + [n - 1],
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
This lets us specify lists of key values, or `keyframes` to borrow an animation term and the corresponding nodes. At the nodes in between the keyframes the values will just be linearly interpolated.
|
|
213
|
+
This creates an initial trajectory that passes through each gate center at the appropriate node, giving the solver a much better starting point.
|
|
214
|
+
|
|
215
|
+
OpenSCvx also provides similar `ox.init.slerp(...)` and `ox.init.nlerp(...)` functions for _spherical linear interpolation_ (SLERP) and _normalized linear interpolation_ (NLERP) for quaternion initialization.
|
|
216
|
+
|
|
217
|
+
### Problem Definition and Solution
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
time = ox.Time(
|
|
221
|
+
initial=0.0,
|
|
222
|
+
final=("minimize", total_time),
|
|
223
|
+
min=0.0,
|
|
224
|
+
max=total_time,
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
problem = ox.Problem(
|
|
228
|
+
dynamics=dynamics,
|
|
229
|
+
states=states,
|
|
230
|
+
controls=controls,
|
|
231
|
+
time=time,
|
|
232
|
+
constraints=constraints,
|
|
233
|
+
N=n,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
problem.initialize()
|
|
237
|
+
results = problem.solve()
|
|
238
|
+
results = problem.post_process()
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Visualizing 3D Trajectories
|
|
242
|
+
|
|
243
|
+
With our drone now flying in 3D, simple time series plots don't capture the full picture. The `plot_projections_2d()` function shows XY, XZ, and YZ plane views:
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from openscvx.plotting import plot_states, plot_projections_2d
|
|
247
|
+
|
|
248
|
+
# Time series of states
|
|
249
|
+
plot_states(results, ["position", "velocity"]).show()
|
|
250
|
+
|
|
251
|
+
# 2D projections colored by velocity
|
|
252
|
+
plot_projections_2d(
|
|
253
|
+
results,
|
|
254
|
+
var_name="position",
|
|
255
|
+
velocity_var_name="velocity"
|
|
256
|
+
).show()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For fully interactive 3D visualization with animated playback, gates, and thrust vectors, OpenSCvx integrates with [viser](https://viser.studio/). See [Tutorial 05: Visualization](05_visualization.md) for details on building rich 3D visualizations.
|
|
260
|
+
|
|
261
|
+
## Constraint Types Summary
|
|
262
|
+
|
|
263
|
+
| Type | Syntax | Use Case |
|
|
264
|
+
|------|--------|----------|
|
|
265
|
+
| Continuous (all nodes) | `ox.ctcs(expr)` | Path constraints enforced between all nodes |
|
|
266
|
+
| Continuous (interval) | `expr.over((start, end))` | Path constraints over a specific interval |
|
|
267
|
+
| Nodal (specific) | `expr.at([nodes])` | Waypoints, gate passage, events |
|
|
268
|
+
| Nodal (all) | `expr` | Constraint at every node |
|
|
269
|
+
| Convex | `expr.convex()` | Mark convex constraints for direct CVXPY lowering |
|
|
270
|
+
| Combined | `expr.convex().at([nodes])` | Convex nodal constraints |
|
|
271
|
+
|
|
272
|
+
!!! note "Cross-Node Constraints"
|
|
273
|
+
OpenSCvx also supports **cross-node constraints** that couple values at different nodes within a single constraint expression. These are created by using `.at(k)` on variables (not constraints):
|
|
274
|
+
|
|
275
|
+
```python
|
|
276
|
+
# Rate limit: position change between consecutive nodes
|
|
277
|
+
rate_limit = position.at(5) - position.at(4) <= max_step
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Cross-node constraints are automatically detected and handled differently from nodal constraints—they operate on the full trajectory arrays rather than being evaluated node-by-node. We will cover these in a more advanced tutorial.
|
|
281
|
+
|
|
282
|
+
## Further Reading
|
|
283
|
+
|
|
284
|
+
- [Complete Drone Racing Example](../Examples/drone/dr_double_integrator.md)
|
|
285
|
+
- [Full 6-DOF Drone Racing](../Examples/drone/drone_racing.md) — adds attitude dynamics
|
|
286
|
+
- [API Reference: Constraints](../Reference/symbolic/expr/constraint.md)
|
|
287
|
+
- [Obstacle Avoidance: 6-DOF Dynamics, Parameters, and Vmap](03_obstacle_avoidance_vmap.md)
|
|
288
|
+
- [Viewpoint Constraints: Custom Functions and Perception](04_viewpoint_constraints.md)
|
|
289
|
+
- [Visualization: 2D Plots and 3D Interactive](05_visualization.md)
|