gato-mpc 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. gato_mpc-0.1.1/CHANGELOG.md +393 -0
  2. gato_mpc-0.1.1/CITATION.cff +31 -0
  3. gato_mpc-0.1.1/CMakeLists.txt +221 -0
  4. gato_mpc-0.1.1/LICENSE +21 -0
  5. gato_mpc-0.1.1/MANIFEST.in +15 -0
  6. gato_mpc-0.1.1/PKG-INFO +529 -0
  7. gato_mpc-0.1.1/README.md +470 -0
  8. gato_mpc-0.1.1/_build_resources.py +52 -0
  9. gato_mpc-0.1.1/docs/README.md +24 -0
  10. gato_mpc-0.1.1/docs/archaeology.md +55 -0
  11. gato_mpc-0.1.1/docs/constraints.md +374 -0
  12. gato_mpc-0.1.1/docs/consumer_contract.md +232 -0
  13. gato_mpc-0.1.1/docs/development.md +187 -0
  14. gato_mpc-0.1.1/docs/figure-refresh-2026-10-01.md +257 -0
  15. gato_mpc-0.1.1/docs/status.md +61 -0
  16. gato_mpc-0.1.1/docs/website.md +19 -0
  17. gato_mpc-0.1.1/examples/cpp/CMakeLists.txt +44 -0
  18. gato_mpc-0.1.1/examples/cpp/batched_solve.cu +121 -0
  19. gato_mpc-0.1.1/examples/go2_description/README.md +8 -0
  20. gato_mpc-0.1.1/examples/go2_description/go2.urdf +761 -0
  21. gato_mpc-0.1.1/examples/iiwa_description/5674K57_flange_mount_ball_transfer.stl +0 -0
  22. gato_mpc-0.1.1/examples/iiwa_description/assembled_ee.stl +0 -0
  23. gato_mpc-0.1.1/examples/iiwa_description/case_shell.stl +0 -0
  24. gato_mpc-0.1.1/examples/iiwa_description/iiwa14.urdf +431 -0
  25. gato_mpc-0.1.1/examples/iiwa_description/iiwa_base.stl +0 -0
  26. gato_mpc-0.1.1/examples/iiwa_description/iiwa_grid.urdf +216 -0
  27. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_1.stl +0 -0
  28. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_2.stl +0 -0
  29. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_3.stl +0 -0
  30. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_4.stl +0 -0
  31. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_5.stl +0 -0
  32. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_6.stl +0 -0
  33. gato_mpc-0.1.1/examples/iiwa_description/iiwa_link_7.stl +0 -0
  34. gato_mpc-0.1.1/examples/iiwa_description/kuka_to_sensor_mount.stl +0 -0
  35. gato_mpc-0.1.1/examples/iiwa_description/sensor_to_case_mount.stl +0 -0
  36. gato_mpc-0.1.1/examples/indy7_description/indy7.urdf +378 -0
  37. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_0.stl +0 -0
  38. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_1.stl +0 -0
  39. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_2.stl +0 -0
  40. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_3.stl +0 -0
  41. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_4.stl +0 -0
  42. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_5.stl +0 -0
  43. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_6.stl +0 -0
  44. gato_mpc-0.1.1/examples/indy7_description/meshes/collision/Indy7_eye_6.stl +0 -0
  45. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_0.stl +0 -0
  46. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_1.stl +0 -0
  47. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_2.stl +0 -0
  48. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_3.stl +0 -0
  49. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_4.stl +0 -0
  50. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_5.stl +0 -0
  51. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_6.stl +0 -0
  52. gato_mpc-0.1.1/examples/indy7_description/meshes/visual/Indy7_eye_6.stl +0 -0
  53. gato_mpc-0.1.1/gato/bsqp/bsqp.cuh +1398 -0
  54. gato_mpc-0.1.1/gato/bsqp/kernels/admm.cuh +532 -0
  55. gato_mpc-0.1.1/gato/bsqp/kernels/bdsv.cuh +285 -0
  56. gato_mpc-0.1.1/gato/bsqp/kernels/contact_debug.cuh +154 -0
  57. gato_mpc-0.1.1/gato/bsqp/kernels/line_search.cuh +162 -0
  58. gato_mpc-0.1.1/gato/bsqp/kernels/merit.cuh +370 -0
  59. gato_mpc-0.1.1/gato/bsqp/kernels/pcg.cuh +81 -0
  60. gato_mpc-0.1.1/gato/bsqp/kernels/schur_linsys.cuh +580 -0
  61. gato_mpc-0.1.1/gato/bsqp/kernels/setup_kkt.cuh +484 -0
  62. gato_mpc-0.1.1/gato/bsqp/kernels/sim.cuh +115 -0
  63. gato_mpc-0.1.1/gato/bsqp/rowgroups.cuh +1156 -0
  64. gato_mpc-0.1.1/gato/constants.h +73 -0
  65. gato_mpc-0.1.1/gato/dynamics/go2/grid.cuh +50068 -0
  66. gato_mpc-0.1.1/gato/dynamics/go2/limits.cuh +58 -0
  67. gato_mpc-0.1.1/gato/dynamics/grid_plant_step.cuh +376 -0
  68. gato_mpc-0.1.1/gato/dynamics/iiwa14/grid.cuh +35699 -0
  69. gato_mpc-0.1.1/gato/dynamics/iiwa14/limits.cuh +43 -0
  70. gato_mpc-0.1.1/gato/dynamics/indy7/grid.cuh +34585 -0
  71. gato_mpc-0.1.1/gato/dynamics/indy7/limits.cuh +40 -0
  72. gato_mpc-0.1.1/gato/dynamics/integrator.cuh +290 -0
  73. gato_mpc-0.1.1/gato/dynamics/manifold.cuh +89 -0
  74. gato_mpc-0.1.1/gato/dynamics/plant.cuh +1131 -0
  75. gato_mpc-0.1.1/gato/settings.h +79 -0
  76. gato_mpc-0.1.1/gato/types.cuh +84 -0
  77. gato_mpc-0.1.1/gato/utils/cuda.cuh +42 -0
  78. gato_mpc-0.1.1/gato/utils/dlpack.h +366 -0
  79. gato_mpc-0.1.1/gato/utils/linalg.cuh +84 -0
  80. gato_mpc-0.1.1/pyproject.toml +137 -0
  81. gato_mpc-0.1.1/python/bindings.cu +765 -0
  82. gato_mpc-0.1.1/python/gato/__init__.py +56 -0
  83. gato_mpc-0.1.1/python/gato/_arrays.py +98 -0
  84. gato_mpc-0.1.1/python/gato/_cache.py +362 -0
  85. gato_mpc-0.1.1/python/gato/_grid.py +75 -0
  86. gato_mpc-0.1.1/python/gato/_registry.json +47 -0
  87. gato_mpc-0.1.1/python/gato/builder.py +367 -0
  88. gato_mpc-0.1.1/python/gato/certificate.py +117 -0
  89. gato_mpc-0.1.1/python/gato/common.py +233 -0
  90. gato_mpc-0.1.1/python/gato/config.py +133 -0
  91. gato_mpc-0.1.1/python/gato/controller.py +258 -0
  92. gato_mpc-0.1.1/python/gato/envs.py +121 -0
  93. gato_mpc-0.1.1/python/gato/estimators.py +709 -0
  94. gato_mpc-0.1.1/python/gato/fingerprint.py +128 -0
  95. gato_mpc-0.1.1/python/gato/gait.py +271 -0
  96. gato_mpc-0.1.1/python/gato/hypotheses.py +183 -0
  97. gato_mpc-0.1.1/python/gato/interface.py +1145 -0
  98. gato_mpc-0.1.1/python/gato/linsys_autotune.py +170 -0
  99. gato_mpc-0.1.1/python/gato/mpc_gato.py +673 -0
  100. gato_mpc-0.1.1/python/gato/paths.py +69 -0
  101. gato_mpc-0.1.1/python/gato/policy.py +84 -0
  102. gato_mpc-0.1.1/python/gato/rowkinds.py +31 -0
  103. gato_mpc-0.1.1/python/gato/worlds.py +293 -0
  104. gato_mpc-0.1.1/python/gato_mpc.egg-info/PKG-INFO +529 -0
  105. gato_mpc-0.1.1/python/gato_mpc.egg-info/SOURCES.txt +167 -0
  106. gato_mpc-0.1.1/python/gato_mpc.egg-info/dependency_links.txt +1 -0
  107. gato_mpc-0.1.1/python/gato_mpc.egg-info/requires.txt +41 -0
  108. gato_mpc-0.1.1/python/gato_mpc.egg-info/top_level.txt +1 -0
  109. gato_mpc-0.1.1/setup.cfg +4 -0
  110. gato_mpc-0.1.1/setup.py +10 -0
  111. gato_mpc-0.1.1/test/conftest.py +238 -0
  112. gato_mpc-0.1.1/test/cuda/bdsv_factor_solve.cu +364 -0
  113. gato_mpc-0.1.1/test/cuda/bdsv_vs_pcg.cu +312 -0
  114. gato_mpc-0.1.1/test/cuda/ee_rows.cu +184 -0
  115. gato_mpc-0.1.1/test/cuda/gamma_parity.cu +155 -0
  116. gato_mpc-0.1.1/test/cuda/pcg_vs_cpu.cu +182 -0
  117. gato_mpc-0.1.1/test/cuda/plant_shim.cuh +16 -0
  118. gato_mpc-0.1.1/test/dynamics_fingerprint.json +1845 -0
  119. gato_mpc-0.1.1/test/expected_skips.txt +6 -0
  120. gato_mpc-0.1.1/test/expected_tests.txt +397 -0
  121. gato_mpc-0.1.1/test/gpu-proof-policy.yaml +29 -0
  122. gato_mpc-0.1.1/test/oracles/__init__.py +0 -0
  123. gato_mpc-0.1.1/test/oracles/mechanisms.py +445 -0
  124. gato_mpc-0.1.1/test/receipt_modules.txt +31 -0
  125. gato_mpc-0.1.1/test/test_anchor.py +201 -0
  126. gato_mpc-0.1.1/test/test_api_smoke.py +185 -0
  127. gato_mpc-0.1.1/test/test_arrays.py +117 -0
  128. gato_mpc-0.1.1/test/test_bindings_shapes.py +103 -0
  129. gato_mpc-0.1.1/test/test_build.py +51 -0
  130. gato_mpc-0.1.1/test/test_codegen.py +81 -0
  131. gato_mpc-0.1.1/test/test_common.py +68 -0
  132. gato_mpc-0.1.1/test/test_contact_rows.py +245 -0
  133. gato_mpc-0.1.1/test/test_controller.py +271 -0
  134. gato_mpc-0.1.1/test/test_cpp_interface.py +60 -0
  135. gato_mpc-0.1.1/test/test_distribution_artifacts.py +80 -0
  136. gato_mpc-0.1.1/test/test_dynamics_fingerprint.py +50 -0
  137. gato_mpc-0.1.1/test/test_exact_hessian.py +193 -0
  138. gato_mpc-0.1.1/test/test_examples.py +91 -0
  139. gato_mpc-0.1.1/test/test_f_ext.py +600 -0
  140. gato_mpc-0.1.1/test/test_fig7_validation.py +94 -0
  141. gato_mpc-0.1.1/test/test_floating_go2.py +502 -0
  142. gato_mpc-0.1.1/test/test_floating_rowgroups.py +285 -0
  143. gato_mpc-0.1.1/test/test_floating_worlds.py +559 -0
  144. gato_mpc-0.1.1/test/test_gait.py +94 -0
  145. gato_mpc-0.1.1/test/test_kernel_gates.py +48 -0
  146. gato_mpc-0.1.1/test/test_limits_closed_loop.py +56 -0
  147. gato_mpc-0.1.1/test/test_linsys_autotune.py +108 -0
  148. gato_mpc-0.1.1/test/test_manifold.py +101 -0
  149. gato_mpc-0.1.1/test/test_mechanism_oracles.py +520 -0
  150. gato_mpc-0.1.1/test/test_mpc_initialization.py +78 -0
  151. gato_mpc-0.1.1/test/test_packaging.py +101 -0
  152. gato_mpc-0.1.1/test/test_parity_golden.py +160 -0
  153. gato_mpc-0.1.1/test/test_policy.py +47 -0
  154. gato_mpc-0.1.1/test/test_receipt_contract.py +76 -0
  155. gato_mpc-0.1.1/test/test_register_robot.py +110 -0
  156. gato_mpc-0.1.1/test/test_row_masks.py +220 -0
  157. gato_mpc-0.1.1/test/test_rowgroups.py +844 -0
  158. gato_mpc-0.1.1/test/test_smoke_solve.py +96 -0
  159. gato_mpc-0.1.1/test/test_worlds.py +225 -0
  160. gato_mpc-0.1.1/tools/autotune_linsys.py +124 -0
  161. gato_mpc-0.1.1/tools/build.sh +65 -0
  162. gato_mpc-0.1.1/tools/check_installed_package.py +93 -0
  163. gato_mpc-0.1.1/tools/common.sh +14 -0
  164. gato_mpc-0.1.1/tools/docker.sh +65 -0
  165. gato_mpc-0.1.1/tools/gen_dynamics_fingerprint.py +142 -0
  166. gato_mpc-0.1.1/tools/install.sh +109 -0
  167. gato_mpc-0.1.1/tools/native_identity.py +88 -0
  168. gato_mpc-0.1.1/tools/parity_baseline.py +111 -0
  169. gato_mpc-0.1.1/tools/regen_grid.py +65 -0
@@ -0,0 +1,393 @@
1
+ # Changelog
2
+
3
+ Installs are source-tree only (no tag, no PyPI release); the version is `gato.__version__`.
4
+
5
+ ## 0.1.1 — 2026-10-13: the pip-install path
6
+
7
+ - Published on PyPI as `gato-mpc` (the name `gato` belongs to an unrelated
8
+ package); the import name stays `gato`.
9
+
10
+ - `gato.register_robot(name, urdf, N=[...])` generates a robot's dynamics headers
11
+ into a per-host cache (`GATO_CACHE_DIR` or the platform user cache, e.g.
12
+ `~/.cache/gato`); the first `gato.BSQP(..., plant_type=name)` for a horizon
13
+ compiles that module once — with a logged warning naming the one-time cost —
14
+ and every later process loads it from the cache. Module keys cover the URDF
15
+ bytes and options, the grid-rbd provenance, GATO's native sources, the GPU
16
+ architecture and the toolchain, so nothing stale is ever loaded. The three
17
+ vendored robots (Indy7, iiwa14, Go2) are pre-registered: any horizon compiles
18
+ from the bundled headers without a `register_robot` call.
19
+ - Wheels ship GATO's native sources (`gato/_native`: CMakeLists, headers, the
20
+ bindings TU, the vendored robot headers and URDFs) and never compiled modules;
21
+ `pip install` needs no checkout and no submodule. `gato.robot_urdf(name)` is
22
+ the URDF path that works in both a checkout and a wheel; the bundled
23
+ descriptions include the collision meshes, so a copy of a vendored URDF can
24
+ be registered as a new robot (visual-only meshes are left out).
25
+ - `solve()` and `MPCController.step()` accept numpy, torch or JAX arrays and
26
+ return the same kind. CUDA tensors take a device-pointer path (inputs read in
27
+ place, `xu` returned on the device through DLPack, no host round trip) that is
28
+ bit-identical to the host path, cold and warm-started; CPU tensors go through
29
+ the host path. `MPC_GATO` and the controller keep their host warm-start buffers.
30
+ - `[test]` extra now lists torch and jax (CPU builds suffice for the host tier).
31
+ - C++/CUDA tier documented as a first-class interface: `python -m gato.paths`
32
+ prints the include directories, defines and flags for any (robot, horizon,
33
+ variant) — vendored, cached or registered; `examples/cpp/` holds a real
34
+ batched-solve template on `BSQP<T>` with a standalone CMake that works from
35
+ a wheel (the headers and the example ship in it). The old `examples/bsqp.cu`
36
+ demo is replaced; the in-tree `build/bsqp` target builds the new source.
37
+
38
+ ## 0.1.0 — 2026-10-13: wheel-based dependency, closed-loop limits gate, declared API surface
39
+
40
+ Everything below this heading down to the 2026-10-05 entry ships in 0.1.0; the
41
+ earlier entries were the unreleased 0.0.2 line.
42
+
43
+ - Closed-loop joint-limit enforcement: `examples/09_mpc_with_limits.py` and
44
+ `test/test_limits_closed_loop.py` grade the SIMULATED plant, not the planner:
45
+ with the ADMM box mechanism and a ±1 rad/s velocity box the Indy7 figure-8
46
+ peaks at 1.03–1.04 rad/s (1.6–2.0 unenforced) at N ≥ 32; horizons of 16 or
47
+ fewer knots cannot brake in time (measured, `docs/constraints.md`).
48
+ `MPC_GATO` loops now record the winning plan's `row_max_violation`.
49
+ - `MPCController.step()` contract (fixed iteration budget, no deadline-aware
50
+ exit, caller owns the schedule) and a stable/experimental API list:
51
+ `docs/consumer_contract.md` §4 and §8.
52
+ - Version 0.1.0 (was 0.0.2): the install contract changed (below), so this is a
53
+ minor bump under the pre-1.0 convention. Development status stays Alpha.
54
+
55
+ ### GRiD and GLASS from the released `grid-rbd` wheel
56
+
57
+ - GATO no longer carries `external/GRiD` and `external/GLASS` submodules. The code
58
+ generator, URDFParser, the global `glass::` headers and GRiD's collision headers
59
+ all come from `grid-rbd==0.5.1` (PyPI); `python/gato/_grid.py` pins that version
60
+ and the GLASS commit it must bundle (`9e57178`, unchanged), and refuses any other
61
+ install at codegen, CMake configure and test time. The Go2 URDF the Go2 headers
62
+ are generated from is vendored at `examples/go2_description/` (the wheel ships
63
+ no robot assets). A fresh clone needs no submodule to build.
64
+ - Robot headers regenerated with the 0.5.1 generator: it adds per-kernel
65
+ `init_grid_kernel_attr_*_checked` registration helpers and makes the
66
+ collision-check hit flag an atomic exchange; dynamics, costs and sphere sets
67
+ are unchanged and the numerical goldens still pass.
68
+ - Receipts: the fingerprint drops the two gitlinks and covers the pin instead;
69
+ native modules embed a content identity of the wheel's headers keyed by logical
70
+ name (independent of the install location). pytest-gpu-proof 0.4.1.
71
+
72
+ ## 2026-10-05 — Dependency integration
73
+
74
+ - Pin GRiD 8dccbfa and GLASS 9e57178 and regenerate all three robot headers.
75
+ This includes upstream dynamics synchronization changes and collision APIs;
76
+ collision sphere counts and controller defaults remain unchanged.
77
+ - Codegen validation now requires matching GRiD and consumer GLASS pins.
78
+ Collision generation rejects spherizer fallbacks independently of warning
79
+ wording, preserving the same geometry when optional dependencies are missing.
80
+ October 5 timing artifacts describe the preceding dependency revisions;
81
+ no performance improvement is inferred from this update.
82
+
83
+ ## 2026-10-04 — Audit evidence and reproducibility
84
+
85
+ - Signed GPU receipts now require the full test inventory, wider source coverage
86
+ and content identities matching all receipt-profile native modules. Missing
87
+ receipts and partial test selections fail closed; numerical goldens are unchanged.
88
+ - A frozen Fig-7 holdout records arm-arrival outcomes separately from applied
89
+ effort, velocity and position limits, including a torque-clamped stop arm.
90
+ See the [figure refresh](docs/figure-refresh-2026-10-01.md) for interpretation.
91
+ - The optional CPU comparison baseline builds in its own pinned Pinocchio 3.8
92
+ environment, with a two-arm numerical check and the matching Python launcher.
93
+ - An opt-in quiet-window lane pairs controller-call wall time with internal
94
+ solver time. Documentation distinguishes simulated task quality, feasibility
95
+ and timing boundaries; no new performance claim follows from correctness runs.
96
+
97
+ ## 2026-10-03 — Fig-7 refresh: two task settings, identified-weight hypothesis batch
98
+
99
+ - `reproduce_fig7_pickplace.py --task {stop,pass-through}` presets. `stop` (default): the EE
100
+ reference ramps to each goal over 1.5 s (minimum jerk) and the success gates must hold 100 ms,
101
+ with the paper's exploration sampler — 15/100/99/98 % at B = 1/8/32/128 on 100 corrected
102
+ scenarios. `pass-through`: the paper's protocol verbatim with the identified-weight sampler —
103
+ 26/83/97/95 % (the paper's sampler: 6/62/84/82 %). `--success-plot` renders both panels.
104
+ - `gato.estimators.IdentifiedWrenchSampler` and `MPC_GATO(estimator="wid")`: the hypothesis
105
+ batch is the least-squares identified payload weight, a zero row and bounded Fibonacci-sphere
106
+ perturbations with an adaptive radius (`inertial_rows=True` adds the full identified wrench
107
+ and its blends — off by default: they fling the arm). The API default stays `estimator="fe"`.
108
+ - `MPC_GATO.run_mpc_goals(settle_time=, goal_ramp=)`: optional dwell on the success gates and a
109
+ minimum-jerk EE reference between goals (both 0 by default = the paper's loop); exposed on
110
+ `reproduce_fig7_pickplace.py` as `--settle-time`, `--goal-ramp`, `--qd-cost`, `--estimator`.
111
+ - Fig-3 Indy7 lane re-run in an exclusive window (October 3); Fig-7 findings and the reasons
112
+ the gate stays instantaneous are in `docs/figure-refresh-2026-10-01.md`.
113
+
114
+ ## 2026-10-02 — Fig-3 harness: `--robot indy7` + fig8 driver stop-condition fix
115
+
116
+ - `MPC_GATO.run_mpc_fig8` stopped `6N` goal steps early instead of `N`: a goal of exactly
117
+ sim-steps + N knots produced 148 control steps at N = 32 and none at N ≥ 64. The long
118
+ MPCGPU goal file (1202 steps) hid it on the iiwa14 lane; the wipe task padded around it.
119
+ Fixed (`eepos_offset + N > steps`), regression test `test_mpc_gato_run_mpc_fig8_uses_whole_goal`.
120
+ No committed number changes: every existing caller's goal is far longer than its sim time.
121
+ - The FAIR fig8 harness is robot-parameterized: `iiwa_fig8_shared.robot(name)` carries URDF,
122
+ registry EE frame, q0 and whether MPCGPU's goal file applies; `sweep_batch_iiwa_fig8.py`,
123
+ `track_iiwa_fig8_gato.py`, `baselines/track_iiwa_fig8_bt.py` and `reproduce_fig3_fair.py`
124
+ take `--robot {iiwa14,indy7}`. iiwa14 outputs are unchanged (same goal hashes as every
125
+ recorded run); Indy7 uses the same fig8 at `INDY7_START_CONFIGS["ready"]`, a synthesized
126
+ goal (MPCGPU has no Indy7 trajfile) and `_indy7`-suffixed CSV/figure names. Indy7 numbers
127
+ are recorded in `docs/figure-refresh-2026-10-01.md` once measured in a quiet window.
128
+
129
+ ## 2026-10-01 — Release-hygiene batch (no numeric change: all 42 goldens bit-identical)
130
+
131
+ - Cooperative row kinds are described once. `gato/bsqp/rowgroups.cuh` lost the per-kind
132
+ EE_POS / COLLISION / CONTACT_POS sections (scratch sizers, `has_*_rows`, `*_eval*`, six
133
+ fold/value wrappers, the three telemetry branches and the collision-only AL dual branch):
134
+ the `CooperativeRows<T, KIND>` trait now owns the FK call, the scratch carve and its sizes,
135
+ the Jacobian layout, the bound slot and the state index, and `dispatch_cooperative_kind`
136
+ routes the telemetry, AL dual-update and ADMM kernels to it. setup_kkt / merit call
137
+ `cooperative_row_grad_hess<KIND>` / `cooperative_row_cost_value<KIND>` directly
138
+ (`__noinline__` kept: the cicc expansion cliff). −180 lines across rowgroups.cuh, admm.cuh,
139
+ setup_kkt.cuh, merit.cuh (the handoff's −250 estimate predated the 09-26 trait landing). Gated bitwise FIRST: four new arm `collision_{al,admm}` goldens
140
+ (indy7 / iiwa14 N16, sphere parked on the reach goal so the rows bind) join the existing
141
+ masked-CONTACT_POS and ADMM/EE cases — 38 → 42 goldens, all unchanged by the refactor.
142
+ - `python/bindings.cu` 720 → 632 lines: one `DenseArray` alias, `checked_ptr` /
143
+ `optional_ptr` for the per-solve and per-joint setters, one `state_dict` behind the four
144
+ dual/ADMM getters, `per_iter_array` for the (iters, B) stats. Same Python surface; the
145
+ per-joint vector size errors now print the expected count instead of the constant's name.
146
+ - Scratch sizers follow the tree's snake_case `*_smem_ct` convention:
147
+ `stepValueFloating_TempMemCt` → `step_value_floating_smem_ct`, `stepGradFloating_TempMemCt`
148
+ → `step_grad_floating_smem_ct`, `simStep_TempMemCt` → `sim_step_smem_ct`,
149
+ `linearizedDynamics_TempMemCt` → `linearized_dynamics_smem_ct`, `integratorError_TempMemCt`
150
+ → `integrator_error_smem_ct` (integrator.cuh / grid_plant_step.cuh and their kernel callers).
151
+ - Retired `examples/archive/` (the June indy7 fig3 chain, the pinocchio-sim baseline, the
152
+ bdsv timing session; nothing imported it — recover from git history). The bundled Fig-4
153
+ data moved next to its only consumer: `examples/paper-figures/gato_hparam_batch_results.pkl`
154
+ (`reproduce_fig4_hparam.py --replot`).
155
+ - New `docs/development.md`: install, capped builds, test tiers and goldens, the signed
156
+ receipt and its local verify command, the quiet-window timing rule. Linked from
157
+ `docs/README.md` and the README Tests section.
158
+
159
+ ## 2026-10-01 — Main integration candidate and figure refresh
160
+
161
+ - Pinned GRiD `main` (0a14c0f); all vendored headers regenerate byte-identically. Merged the
162
+ remote README citation and funding edits. Quick start, package URL and the GRiD submodule
163
+ branch now point at `main`.
164
+ - Seed A/B timing: the benchmark's seed change explains nearly all of the September gap;
165
+ about 3% (B8) and 4% (B128) remain unattributed (docs/figure-refresh-2026-10-01.md).
166
+ - Fixed Fig-4, which plotted nothing: best-merit curves are now padded past early SQP stops
167
+ instead of truncated to the shortest. Refreshed Fig-3, Fig-4 and Fig-5 with the current code;
168
+ Fig-3's MPCGPU lane now imports MPCGPU's own timing-harness output. Results and limits:
169
+ `docs/figure-refresh-2026-10-01.md`. Fig-7 / Table I stay as published.
170
+ - Timing launchers no longer need ripgrep.
171
+ - Fixed a solver bug: the merit's terminal-knot cost read the unwritten control slot with
172
+ zero weight, so NaN or Inf left in shared memory by an earlier kernel (for example a
173
+ diverging tiny-rho solve) made 0 * NaN = NaN. That solve's initial merit became NaN and its
174
+ line search never accepted a step, nondeterministically and only after such a kernel. The
175
+ slot is now zeroed; all 38 goldens are bit-identical and a regression test replays the
176
+ triggering sequence. Found because Fig-4 batches up to 128 with rho down to 1e-8.
177
+
178
+ ## 2026-09-27 — Checkpoint follow-up fixes (no new timing claims)
179
+
180
+ - Fixed floating effort-vector uploads/checks to use actuator width, not stored
181
+ configuration width; default/fc KKT gates cover the contract and scalar reset.
182
+ - Fixed spherical-payload initialization: axis-angle is integrated on the
183
+ manifold into a unit quaternion, and augmented robot q/v offsets are separate.
184
+ Both simulation task loops share this assembly. New pick-place data is marked
185
+ `unit-quaternion-pendulum-v2`; historical pools remain preserved, not relabeled
186
+ as corrected results. Added fixed-pacing traces and per-goal event diagnostics.
187
+ - Added explicit controller reset seeds and restored the batch benchmark's
188
+ historical zero-tail initial guess. Hold remains an explicit comparison mode;
189
+ correctness-only raw/controller parity and frozen reference provenance guard
190
+ future comparisons. Added a guarded seed A/B timing suite; execution deferred.
191
+ - Recorded the overnight checkpoint and its limitations; reconciled preferred
192
+ citation metadata with the README's ICRA entry. No production tuning changes.
193
+ - Fresh lean-install validation exposed a missing SciPy codegen dependency:
194
+ trimesh fell back to a different collision sphere cover. SciPy is now a base
195
+ build dependency, fallback geometry is rejected, and old codegen cache keys
196
+ are invalidated. No upstream sources or vendored headers were changed.
197
+
198
+ ## 2026-09-27 — Merge-readiness documentation and example validation
199
+
200
+ - Added a documentation index, feature-status boundaries, API migration notes
201
+ and a bounded merge checklist. Corrected standing, ADMM dispatch, receipt
202
+ coverage and paper-protocol claims; restored main's citation/acknowledgement
203
+ in the README. Quick start builds one module with capped concurrency.
204
+ - Fixed the gym example's indentation error and added introductory syntax
205
+ gates. The basic MPC example now uses fixed simulation pacing. Added a short
206
+ masked-contact AL/barrier/ADMM gate suitable for race instrumentation.
207
+ - Added a GATO-only, opt-in quiet-window checkpoint runner with a GPU-free
208
+ dry run, unique result paths and source/binary/environment provenance. It
209
+ samples Fig-3 and Fig-7, not a full research reproduction or autotune run.
210
+ - Corrected the distribution contract: Git checkout plus pinned submodules
211
+ is the supported native build; the sdist does not bundle external trees.
212
+ Excluded local working notes from sdists and added a packaging canary gate.
213
+
214
+ ## 2026-09-26 — GRiD integration and cooperative-row cleanup
215
+
216
+ - GRiD pinned to `65fd051`: public HTTPS nested dependencies, declared trimesh
217
+ dependency, and updated integrator/plant surfaces. Regenerated all three robot
218
+ headers. GATO's floating adapter continues to select Euler/semi-implicit Euler
219
+ by enum name; its integration scheme is unchanged.
220
+ - Added masked floating CONTACT_POS goldens for AL and ADMM before consolidating
221
+ the cooperative EE, collision and contact fold/merit/ADMM implementations.
222
+ Traits retain each evaluator's scratch layout, Jacobian layout, bounds, masks
223
+ and dual-state indexing. Existing noinline boundaries are preserved.
224
+ - Consolidated binding input uploads and row-state downloads, named owned
225
+ scratch counts consistently, and shared the plant's linalg arena padding.
226
+ - Corrected notebook package shadowing, documented contact-wipe reproduction and
227
+ the local-only stale data archives, and added package project URLs. Stance
228
+ documentation now distinguishes moving prediction targets from measured slip.
229
+
230
+ ## 2026-09-26 — Closed-loop foot rows: non-PD regularization bump, re-anchored stance targets; release hygiene
231
+
232
+ - Solver: a non-PD direct factor (`stats.pcg_iters == 2`, the f32 Cholesky of the Schur
233
+ system failing once stiff row groups are folded into Q) now multiplies the trust-region
234
+ rho by `NON_PD_RHO_FACTOR` (settings.h, 100: 1e-3 -> 0.1 in one step) independent of the
235
+ AL mode's frozen adaptation. Measured on the go2 stance rows at AL rho 1e3: every
236
+ iteration non-PD before, none after the first iteration of a solve now. The two
237
+ exact-Hessian goldens moved (each hits one non-PD factor mid-solve) and end at a lower
238
+ merit; re-baselined.
239
+ - `GaitProgrammer`: stance targets are RE-ANCHORED to the measured feet every tick (the
240
+ no-slip contact semantics) instead of frozen at touchdown; the static stand with stance
241
+ rows now holds height within 2 mm at 2-5 mm residual (gate:
242
+ `test_fc_mpc_stands_with_foot_rows`). The swing itself remains open: lifting a foot without
243
+ a support-polygon base plan tips the model (docs/constraints.md, closed-loop section).
244
+ - Bug fix: masked-off COLLISION knots no longer fold gradient/Hessian/merit (the row-mask
245
+ contract already promised it; the fold and value sites lacked the gate).
246
+ - `test_build` compiles in its own CMake tree (it used to reconfigure the shared `build/`
247
+ to the test plant).
248
+ - Release hygiene: `gato.rowkinds` (the row-kind / mechanism ids, shared by the API, the KKT
249
+ certificate — now covering LIN_U / COLLISION / CONTACT_POS — and the tests);
250
+ `gato.fingerprint` / `gato.worlds` / `gato.gait` reachable as attributes; the seven
251
+ constraint docstrings that duplicated docs/constraints.md now summarize and point there;
252
+ `add_contact_pos_rows` defaults to knots 1..N-1; batch accessors, the four limit enables,
253
+ the mechanism/rho boilerplate and six copies of the test reach problem deduplicated;
254
+ dangling references to internal notes removed from tracked code and docs; dead
255
+ `tools/clean.sh` and unused helpers deleted (`test/expected_skips.txt` stays: it is the CI
256
+ verifier's empty allow-list of skips, i.e. the zero-skip gate); README/docs corrected
257
+ (import forms, submodule story, Fig-3 reproduction, contact-frame order, telemetry
258
+ semantics); `docs/baselines.md` (orphaned, self-contradictory) removed and
259
+ `docs/archaeology.md` trimmed to the provenance table; sdist now ships docs/ and the
260
+ changelog; packaging metadata (authors, license, classifiers) completed.
261
+
262
+ ## 2026-09-26 — CL-4: contact-frame POSITION rows (stance/swing feet), solver-level gates
263
+
264
+ - Row kind `CONTACT_POS` (`BSQP.add_contact_pos_rows`, `set_row_group_targets`,
265
+ `contact_positions`): per-knot residual rows `p_f(q_k) - tgt_k` on the baked contact
266
+ frames over GRiD's new `contact_frame_positions[_gradient]` surface, folded at every
267
+ active knot (AL / relaxed barrier / ADMM / telemetry), with a per-knot target table on
268
+ the row-group descriptor and the CL-4 masks selecting (knot, foot) rows. The dedicated
269
+ cooperative carve of setup_kkt / merit is shared with the collision rows (sized when
270
+ either kind is registered; defaults untouched — goldens bitwise).
271
+ - `GaitProgrammer.install_foot_rows`: a stance group (footholds frozen at touchdown, at
272
+ ground height) and a swing group (swing-curve tracking to the planned landing);
273
+ `apply(t, q)` retargets and re-masks both every tick beside the fc pins and the fn
274
+ reference. Default AL rho 100 (loop-stable; see below).
275
+ - Gates (test_contact_rows.py): device residual == pinocchio, per-knot target table,
276
+ AL/ADMM move an arm EE to a violated target within 3 mm on the active knots only,
277
+ masks select knots, go2 keeps its feet < 1 mm under a lateral goal, solver-only S3
278
+ (FR lifts ~3 cm at knot 12 with the stance feet held), programmer plumbing.
279
+ - OPEN (measured, docs/constraints.md, closed-loop section): the closed loop. The stiffness that holds feet within
280
+ mm (AL rho 1e3) makes the loop's SQP reject every line-search step on 26/150 ticks of a
281
+ static stand (base sags 4 cm); every ADMM variant collapses; AL rho 100 stands with a
282
+ 1.8 cm residual. S2 weight shift / S3 lift-a-foot closed-loop gates are not shipped.
283
+ - `pyproject`: mujoco pinned `<3.14` (3.14.0 segfaults inside MjSpec compile of the go2
284
+ URDF with the welded root; CI cpu-lane at 2b03a38, reproduced locally 6/6).
285
+
286
+ ## 2026-09-26 — GRiD pin 3af782e → 5904dbd (contact-frame positions), GLASS e83b086 → 8ce68a2
287
+
288
+ - GRiD `modernizing-tests` tip: the contact-frame position surface GATO asked for on
289
+ 2026-09-20 (`grid_plant::contact_frame_positions[_gradient]`, world positions of the
290
+ baked contact origins + tangent Jacobians, receipt-covered upstream @8aac88e) and the raw
291
+ `grid_plant::multi_target_position[_gradient]` evaluators (GATO nit 2). The three plants
292
+ are regenerated (`tools/regen_grid.py`); `gato.build` idempotency key recorded for go2.
293
+ GLASS moved by docs/figures only (headers byte-identical).
294
+ - iiwa14 goldens RE-BASELINED: URDFParser 29d3d78 no longer rationalizes transform
295
+ coefficients, so iiwa14's emitted transforms carry ±1.6e-15 terms the old parser snapped
296
+ to 0 (indy7/go2: bit-identical). The golden problems are unconverged 10-iteration solves
297
+ that amplify any ULP change to O(1) (a 1e-7 input nudge moves xu by ~1%), so the drift was
298
+ attributed with the referee gates (dynamics fingerprint, f_ext, exact Hessian, row groups
299
+ vs the pinocchio-pinned tables: 54 passed) and a clean compute-sanitizer memcheck of
300
+ iiwa14 N8 + both go2 modules. Sensitivity note in test_parity_golden.py.
301
+
302
+ ## 2026-09-21 — D14: the Fig-3 CPU baseline pin is public
303
+
304
+ - `examples/benchmarks/baselines/sqpcpu` now points at `A2R-Lab/sqpcpu` (the public
305
+ fork of the upstream sqpcpu), branch `fig3-fair-sigma` = upstream master + the two
306
+ fair-comparison commits GATO pins. The `update = none` opt-in gating is gone: a plain
307
+ recursive clone and `tools/install.sh` fetch it like every other submodule.
308
+
309
+ ## 2026-09-20 — CL-4 groundwork: gait oracle, per-knot row masks, per-knot fc reference
310
+
311
+ - `gato.gait.GaitSchedule`: the fixed-gait ORACLE (trot/bound/pace/walk/stand; period,
312
+ duty, phase offsets) → rolling per-knot stance masks, fc pin masks, per-knot fn
313
+ references, Raibert-lite footholds, swing curves, base references. Pure numpy.
314
+ - Row groups carry a per-knot row-activity mask (`BSQP.set_row_group_mask(g, mask)`,
315
+ `get_row_groups()[g]["active"]`): an inactive (knot, row) is treated like a knot
316
+ below the group's window (ADMM keeps its proximal fold, everything else contributes
317
+ nothing) and its AL dual is reset. All-True is bitwise the previous behaviour.
318
+ - `set_fc_ref` accepts an (N, n_fc) per-knot table (a 1-D wrench still broadcasts,
319
+ bitwise); the device buffer is per knot.
320
+
321
+ ## 2026-09-20 — Wave F: contact forces on the floating base (go2 fc-on-feet)
322
+
323
+ - `bsqpN16_go2_fc`: the fc variant now builds on the floating base (was a
324
+ `static_assert`). The fc tail (one world-aligned `[n; f]` wrench per baked foot
325
+ frame, `FC_SIZE = 24`) enters the grid step as the mapped per-body wrench; the
326
+ linearization gains the fc columns of B and the dfext/dq chain term, composed from
327
+ the grid step's full-force B block; the floating cost composition carries the fc
328
+ regularization/reference terms. Gated against pinocchio central FD at 25 N
329
+ wrenches, bitwise the default module at fc = 0. `go2 16 fc` joins the receipt
330
+ profile (+2 goldens).
331
+ - API: `BSQP.contact_frames`, `BSQP.fc_slots(frame, part)`, module attr
332
+ `NUM_CONTACT_FRAMES`, `MuJoCoWorld.last_contact["fn_by_body"]`.
333
+ - Fixed: `debug_contact_dynamics` returned silently wrong numbers on floating
334
+ modules (fixed-base composition) — it now raises there. Kernel launches past the
335
+ 48 KB dynamic-smem default are opted in at every launch site via one helper
336
+ (the Schur assembly kernel had no opt-in: 64 KB on go2-fc failed to launch);
337
+ the setup_kkt/merit collision sizers under-allocated the dedicated carve by the
338
+ temp tail (latent — every generated robot's carve fit the tail). setup_kkt's
339
+ terminal cost blocks overlay the dead dynamics scratch (offsets only, −10 KB:
340
+ the device opt-in maximum is ~99 KB, go2 default sat at 94 KB).
341
+ - Fixed (found by memcheck once that slack was gone): the fixed-base setup_kkt and
342
+ merit kernels sized their dynamics scratch from the plant adapter's arena alone,
343
+ omitting the integrator layer's own qdd|dqdd / qdd|err prefix (114 floats on
344
+ indy7 N8) — the ID-gradient inner wrote past the launch by up to that much, hidden
345
+ for months by the prepended terminal chain. `linearizedDynamics_TempMemCt` /
346
+ `integratorError_TempMemCt` / `simStep_TempMemCt` (integrator.cuh) are now the
347
+ sizers. Results are bitwise unchanged (goldens).
348
+
349
+ ## 2026-09-20 — audit waves 0–2
350
+
351
+ Breaking (clean-break API, no shims):
352
+ - `gato.SolverParams` is the ONE solver configuration; `BSQP(model, B, N, dt, params=...)`
353
+ (field keywords accepted). The constructor's second default set and `kkt_tol` are gone;
354
+ defaults = the paper/MPC values (`max_sqp_iters=1`, `mu=10`, `rho=0.01`, `qd_cost=1e-2`, …).
355
+ - `BSQP.solve(x, ref, xu_warm=None)` is stateless in the trajectory (None = hold-at-x cold
356
+ start; pass `res.xu` to warm-start); caller arrays are never mutated; `SolveResult.xu` is
357
+ a fresh array. `BSQP.XU_B` / `clear_cost_weights_per_knot` removed
358
+ (`set_cost_weights_per_knot(None)` clears).
359
+ - `MPC_GATO(params=..., linsys=..., bdsv_threshold=..., reseed_threshold=..., variant=...)`
360
+ replaces the `solver_params` dict; `StepResult.u` is the ACTUATED control (`StepResult.fc`
361
+ carries wrench slots on fc variants).
362
+ - Module variants have their own names: `bsqpN{N}_{plant}[_fc|_eh].so`, side by side;
363
+ `BSQP(..., variant=)`, `gato.available(variant)`, `gato.build(contact_forces=, exact_hessian=)`,
364
+ `-DMODULES="plant:N[:variant]"`, `./tools/build.sh --variant`. The `.so`-swap workflow is gone.
365
+ - `enable_limit_*` after an appended row group raises (used to drop the group silently).
366
+ - C++: `BSQP` constructor drops `dt`/`kkt_tol`; every hand-written device/host function is
367
+ snake_case; raw bindings constructor has 14 args.
368
+ - `gato.config.{STANDARD_BATCH_SIZES, EXPERIMENT_BATCH_SIZES, BATCH_COLORS}` moved to
369
+ `examples/paper-figures/_common.py`.
370
+
371
+ Fixed:
372
+ - Strided / broadcast / f64 numpy inputs were copied through their strides (garbage reads):
373
+ every binding array parameter is now `c_style|forcecast`.
374
+ - `TrajectoryReference.done()` fired 5N knots early (knot/element unit mix).
375
+ - Max-dynamic-shared-memory attribute was latched at the first request; a later larger carve
376
+ (collision after exact-Hessian) launched over-ceiling. Post-launch checks on every launch.
377
+ - Dead L2 persisting carve-out reservation removed; pinocchio no longer required to construct
378
+ a solver; `gato.build` no longer silently overridden by a cached receipt-profile configure.
379
+
380
+ Infrastructure:
381
+ - pytest-gpu-proof 0.4.0 (schema 3, restricted signer, weekly CI cron); receipt module
382
+ profile `test/receipt_modules.txt` (arms × 6 horizons + go2 N16 + fc/eh arms N16); golden
383
+ bitwise gate (`test/golden/`, 36 cases); kernel harnesses run from pytest; distribution
384
+ contract (pure-python wheel, buildable sdist) gated; HTTPS submodules; one `.venv`.
385
+ - GRiD → 3af782e, GLASS → e83b086; regen emits only the consumed families (headers −21%).
386
+ - CUDA: GLASS banded block movers in the Schur assembly, shared gamma/BDSV/ADMM helpers,
387
+ shared-memory layout structs (sizer == carve), dead solver state removed; all bitwise.
388
+
389
+ ## 0.0.2 (2026-06 → 2026-08)
390
+ Modernization: GRiD/GLASS pins, runtime batch size, `gato.build`, pytest + signed receipts,
391
+ constraint row-group layer (boxes, EE rows, cones, collision; barrier/ADMM/AL), exact-Hessian
392
+ SO-SQP, contact-force controls (fc), floating base (go2), hybrid pcg/bdsv + autotune plumbing,
393
+ MuJoCo worlds, wipe contact task, paper figure reproduction.
@@ -0,0 +1,31 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use GATO, please cite the paper below."
3
+ title: "GATO: GPU-Accelerated and Batched Trajectory Optimization for Scalable Edge Model Predictive Control"
4
+ type: software
5
+ repository-code: "https://github.com/A2R-Lab/GATO"
6
+ license: MIT
7
+ authors:
8
+ - family-names: Du
9
+ given-names: Alexander
10
+ - family-names: Adabag
11
+ given-names: Emre
12
+ - family-names: Bravo
13
+ given-names: Gabriel
14
+ - family-names: Plancher
15
+ given-names: Brian
16
+ preferred-citation:
17
+ type: conference-paper
18
+ title: "GATO: GPU-Accelerated and Batched Trajectory Optimization for Scalable Edge Model Predictive Control"
19
+ authors:
20
+ - family-names: Du
21
+ given-names: Alexander
22
+ - family-names: Adabag
23
+ given-names: Emre
24
+ - family-names: Bravo
25
+ given-names: Gabriel
26
+ - family-names: Plancher
27
+ given-names: Brian
28
+ year: 2026
29
+ month: 6
30
+ collection-title: "IEEE International Conference on Robotics and Automation (ICRA)"
31
+ url: "https://arxiv.org/abs/2510.07625"
@@ -0,0 +1,221 @@
1
+ #
2
+ # cmake -S . -B build [-DPLANT="indy7;iiwa14"] [-DKNOTS="8;16;32;64;128"]
3
+ # cmake --build build --parallel 4
4
+ #
5
+
6
+ cmake_minimum_required(VERSION 3.22)
7
+ project(GATO LANGUAGES CXX CUDA)
8
+
9
+ set(CMAKE_CXX_STANDARD 17)
10
+ set(CMAKE_CXX_STANDARD_REQUIRED True)
11
+ set(CMAKE_CUDA_STANDARD 17)
12
+ set(CMAKE_CUDA_STANDARD_REQUIRED True)
13
+
14
+ if(NOT CMAKE_BUILD_TYPE)
15
+ set(CMAKE_BUILD_TYPE Release)
16
+ endif()
17
+
18
+ option(GATO_FAST_MATH "Compile CUDA with -use_fast_math (disable for tight-tolerance validation)" ON)
19
+ option(GATO_BUILD_DEMO "Build the bsqp C++ demo executable" ON)
20
+ option(GATO_EXACT_HESSIAN "Compile the exact-Hessian (SO-SQP) stage-projection path; OFF keeps the device tree preprocessor-identical to the GN-only solver" OFF)
21
+ option(GATO_CONTACT_FORCES "Append a per-knot contact wrench to the control vector (f_c extends u; a module-ABI change, so it builds as the separate _fc module variant); OFF keeps the baseline solver bit-identical" OFF)
22
+
23
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra")
24
+ # NOTE: -G is an nvcc flag and lives in the CUDA debug flags only; ASan on a CUDA
25
+ # pybind module is fraught, so host debug flags stay plain.
26
+ set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -fPIC -fno-omit-frame-pointer")
27
+ set(CMAKE_CXX_FLAGS_RELEASE "-O3 -fPIC -DNDEBUG")
28
+ set(CMAKE_CUDA_FLAGS_DEBUG "${CMAKE_CUDA_FLAGS_DEBUG} -G -g")
29
+ set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS_RELEASE} -O3")
30
+ if(GATO_FAST_MATH)
31
+ set(CMAKE_CUDA_FLAGS_RELEASE "${CMAKE_CUDA_FLAGS_RELEASE} -use_fast_math")
32
+ endif()
33
+
34
+ # "native" (CMake 3.24+) detects the local GPU at configure time and compiles
35
+ # only for that architecture. On older CMake (e.g. 3.22 on Ubuntu 22.04), fall
36
+ # back to a broad list of recent arches. Override at configure time with: cmake
37
+ # -DCMAKE_CUDA_ARCHITECTURES="75;86;87;89;100;120" ..
38
+ if(NOT DEFINED CMAKE_CUDA_ARCHITECTURES)
39
+ if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.24")
40
+ set(CMAKE_CUDA_ARCHITECTURES native)
41
+ else()
42
+ # No silent fallback to a stale arch list (the old 75;86;89 default could
43
+ # not even target the sm_120 boxes this repo is developed on): older CMake
44
+ # cannot detect the GPU, so the caller must say which one to build for.
45
+ message(FATAL_ERROR "CMake < 3.24 cannot detect the CUDA arch ('native'); pass "
46
+ "-DCMAKE_CUDA_ARCHITECTURES=<sm> explicitly (e.g. 120 for RTX 50xx, 89 for RTX 40xx)")
47
+ endif()
48
+ endif()
49
+ message(STATUS "Targeting CUDA Architectures: ${CMAKE_CUDA_ARCHITECTURES}")
50
+
51
+ find_package(Python3 REQUIRED COMPONENTS Interpreter Development)
52
+ find_package(pybind11 CONFIG REQUIRED)
53
+ find_package(CUDAToolkit REQUIRED)
54
+ # Headers from the pinned grid-rbd wheel: GLASS (global `glass::`, distinct from
55
+ # grid.cuh's vendored `grid::glass`) and GRiD's collision primitives. gato._grid
56
+ # refuses an unpinned install, so a build never silently compiles against the wrong GLASS.
57
+ # `gato` is importable from the configured python (editable checkout or installed wheel).
58
+ execute_process(COMMAND ${Python3_EXECUTABLE} -m gato._grid
59
+ OUTPUT_VARIABLE GATO_GRID_INCLUDE_DIRS OUTPUT_STRIP_TRAILING_WHITESPACE
60
+ COMMAND_ERROR_IS_FATAL ANY)
61
+ message(STATUS "grid-rbd include dirs: ${GATO_GRID_INCLUDE_DIRS}")
62
+
63
+ include_directories(${CMAKE_SOURCE_DIR}/gato)
64
+
65
+ # --- Executable Targets ---
66
+ if(GATO_BUILD_DEMO)
67
+ add_executable(bsqp examples/cpp/batched_solve.cu)
68
+ target_compile_definitions(bsqp PRIVATE
69
+ KNOT_POINTS=16
70
+ GATO_PLANT_NAME=indy7
71
+ GATO_PLANT_NAME_STR="indy7"
72
+ GATO_PLANT_HEADER="dynamics/plant.cuh")
73
+ if(GATO_EXACT_HESSIAN)
74
+ target_compile_definitions(bsqp PRIVATE USE_EXACT_HESSIAN=1)
75
+ endif()
76
+ if(GATO_CONTACT_FORCES)
77
+ target_compile_definitions(bsqp PRIVATE GATO_CONTACT_FORCES=1)
78
+ endif()
79
+ # The per-robot dir supplies grid.cuh + limits.cuh to the generic plant header.
80
+ target_include_directories(bsqp PRIVATE ${CMAKE_SOURCE_DIR}/gato/dynamics/indy7
81
+ ${GATO_GRID_INCLUDE_DIRS})
82
+ target_link_libraries(bsqp PRIVATE CUDA::cudart)
83
+ endif()
84
+
85
+ # --- Python Bindings ---
86
+
87
+ # horizon length and plant options (configurable via -D at configure time).
88
+ # Plants are discovered by convention: gato/dynamics/<name>/{grid.cuh,limits.cuh}
89
+ # (generated) + the shared gato/dynamics/plant.cuh adapter — adding a robot
90
+ # requires NO CMake edits.
91
+ set(KNOTS_DEFAULT "8;16;32;64;128")
92
+ set(PLANT_DEFAULT "indy7;iiwa14")
93
+ # Where the built modules land. Default = the python package (import gato picks
94
+ # them up). Point it elsewhere for side-by-side variant builds (e.g. an
95
+ # exact-Hessian A/B) so a variant configure can't clobber the default modules.
96
+ set(GATO_MODULE_OUTPUT_DIR "${CMAKE_SOURCE_DIR}/python/gato" CACHE PATH "Output directory for the bsqp python modules")
97
+ # Where <plant>/{grid.cuh,limits.cuh} live: the checkout's vendored robots by default;
98
+ # gato.register_robot points this at its per-host cache for user robots.
99
+ set(GATO_PLANT_ROOT "${CMAKE_SOURCE_DIR}/gato/dynamics" CACHE PATH "Directory holding <plant>/{grid.cuh,limits.cuh}")
100
+ set(KNOTS "${KNOTS_DEFAULT}" CACHE STRING "Semicolon-separated horizon lengths (e.g., 32;64;128)")
101
+ set(PLANT "${PLANT_DEFAULT}" CACHE STRING "Semicolon-separated plant targets (e.g., indy7;iiwa14)")
102
+ # Explicit per-plant horizons: 'plant:N,N;plant:N' (overrides the PLANT x KNOTS
103
+ # cross-product — needed whenever plants have DIFFERENT supported horizons,
104
+ # e.g. go2 is N16-only while the arms carry the full ladder).
105
+ set(MODULES "" CACHE STRING "Explicit module list 'indy7:8,16;go2:16;indy7:16:fc' = plant:knots[:variant] (overrides PLANT x KNOTS)")
106
+ # Module VARIANTS are separate ABIs with their own module names (plan 2.A,
107
+ # 2026-09-20): bsqpN{N}_{plant} (default) | _fc (GATO_CONTACT_FORCES: contact-
108
+ # force controls appended to u) | _eh (GATO_EXACT_HESSIAN: SO-SQP path). They
109
+ # all land in GATO_MODULE_OUTPUT_DIR side by side — no more .so-swap. The
110
+ # global options set the variant for a PLANT x KNOTS cross-product; an explicit
111
+ # MODULES / receipt-profile entry carries its own variant per target.
112
+ if(GATO_CONTACT_FORCES AND GATO_EXACT_HESSIAN)
113
+ message(FATAL_ERROR "GATO_CONTACT_FORCES and GATO_EXACT_HESSIAN are separate module variants — build one at a time (or list them per target in MODULES)")
114
+ endif()
115
+ set(GATO_DEFAULT_VARIANT "default")
116
+ if(GATO_CONTACT_FORCES)
117
+ set(GATO_DEFAULT_VARIANT "fc")
118
+ elseif(GATO_EXACT_HESSIAN)
119
+ set(GATO_DEFAULT_VARIANT "eh")
120
+ endif()
121
+ # The receipt profile = exactly test/receipt_modules.txt (the set the signed
122
+ # gpu-proof receipt attests); run_gpu_proof.sh preflights the same file.
123
+ option(GATO_RECEIPT_PROFILE "Configure exactly the receipt module set (test/receipt_modules.txt)" OFF)
124
+ # Normalize to semicolon lists (accept commas/spaces)
125
+ string(REPLACE "," ";" KNOTS "${KNOTS}")
126
+ string(REPLACE " " ";" KNOTS "${KNOTS}")
127
+ string(REPLACE "," ";" PLANT "${PLANT}")
128
+ string(REPLACE " " ";" PLANT "${PLANT}")
129
+
130
+ # Resolve the module set into a list of "plant:knot" pairs.
131
+ set(GATO_MODULE_PAIRS "")
132
+ if(GATO_RECEIPT_PROFILE)
133
+ file(STRINGS "${CMAKE_SOURCE_DIR}/test/receipt_modules.txt" _receipt_lines REGEX "^[a-z0-9_]+[ \t]+[0-9]+")
134
+ foreach(_line ${_receipt_lines})
135
+ string(REGEX MATCH "^([a-z0-9_]+)[ \t]+([0-9]+)[ \t]*([a-z]*)" _m "${_line}")
136
+ set(_var "${CMAKE_MATCH_3}")
137
+ if(NOT _var)
138
+ set(_var "default")
139
+ endif()
140
+ list(APPEND GATO_MODULE_PAIRS "${CMAKE_MATCH_1}:${CMAKE_MATCH_2}:${_var}")
141
+ endforeach()
142
+ message(STATUS "Receipt profile: ${GATO_MODULE_PAIRS}")
143
+ elseif(MODULES)
144
+ string(REPLACE " " ";" _entries "${MODULES}")
145
+ foreach(_entry ${_entries})
146
+ string(REGEX MATCH "^([a-z0-9_]+):([0-9,]+)(:([a-z]+))?$" _m "${_entry}")
147
+ if(NOT _m)
148
+ message(FATAL_ERROR "MODULES entry '${_entry}' is not 'plant:N,N[:variant]'")
149
+ endif()
150
+ set(_var "${CMAKE_MATCH_4}")
151
+ if(NOT _var)
152
+ set(_var "${GATO_DEFAULT_VARIANT}")
153
+ endif()
154
+ string(REPLACE "," ";" _ks "${CMAKE_MATCH_2}")
155
+ foreach(_k ${_ks})
156
+ list(APPEND GATO_MODULE_PAIRS "${CMAKE_MATCH_1}:${_k}:${_var}")
157
+ endforeach()
158
+ endforeach()
159
+ message(STATUS "Explicit modules: ${GATO_MODULE_PAIRS}")
160
+ else()
161
+ foreach(plant ${PLANT})
162
+ foreach(knot ${KNOTS})
163
+ list(APPEND GATO_MODULE_PAIRS "${plant}:${knot}:${GATO_DEFAULT_VARIANT}")
164
+ endforeach()
165
+ endforeach()
166
+ message(STATUS "Configuring Python modules for PLANT='${PLANT}' KNOTS='${KNOTS}'")
167
+ endif()
168
+
169
+ foreach(pair ${GATO_MODULE_PAIRS})
170
+ string(REPLACE ":" ";" _pk "${pair}")
171
+ list(GET _pk 0 plant)
172
+ list(GET _pk 1 knot)
173
+ list(GET _pk 2 variant)
174
+ if(NOT variant MATCHES "^(default|fc|eh)$")
175
+ message(FATAL_ERROR "module ${pair}: variant must be default|fc|eh")
176
+ endif()
177
+ foreach(gen_header grid.cuh limits.cuh)
178
+ if(NOT EXISTS "${GATO_PLANT_ROOT}/${plant}/${gen_header}")
179
+ message(FATAL_ERROR "Plant '${plant}': missing ${GATO_PLANT_ROOT}/${plant}/${gen_header} "
180
+ "(generate it with gato.register_robot / gato.build / tools/regen_grid.py)")
181
+ endif()
182
+ endforeach()
183
+ block()
184
+ set(VARIANT_SUFFIX "")
185
+ if(NOT variant STREQUAL "default")
186
+ set(VARIANT_SUFFIX "_${variant}")
187
+ endif()
188
+ set(MODULE_NAME "bsqpN${knot}_${plant}${VARIANT_SUFFIX}")
189
+ message(STATUS "Configuring Python module: ${MODULE_NAME}")
190
+ pybind11_add_module(${MODULE_NAME} SHARED python/bindings.cu)
191
+ # Bind the module to actual native inputs, not their mtimes. Reconfigure
192
+ # after source changes (tools/build.sh does this) before signing a receipt.
193
+ execute_process(COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/native_identity.py
194
+ --plant ${plant} --plant-root ${GATO_PLANT_ROOT} OUTPUT_VARIABLE _native_id
195
+ OUTPUT_STRIP_TRAILING_WHITESPACE COMMAND_ERROR_IS_FATAL ANY)
196
+ target_compile_definitions(${MODULE_NAME} PRIVATE GATO_NATIVE_SOURCE_ID="${_native_id}")
197
+
198
+ # The per-robot dir supplies grid.cuh + limits.cuh to the generic plant
199
+ # header; the wheel's collision dir supplies grid_collision_geometry.cuh
200
+ # (SDF primitives grid.cuh includes when built with collision_res).
201
+ target_include_directories(${MODULE_NAME} PRIVATE ${CMAKE_SOURCE_DIR}/gato
202
+ ${GATO_PLANT_ROOT}/${plant}
203
+ ${GATO_GRID_INCLUDE_DIRS})
204
+
205
+ target_compile_definitions(${MODULE_NAME} PRIVATE
206
+ KNOT_POINTS=${knot}
207
+ GATO_PLANT_NAME=${plant}
208
+ GATO_MODULE_VARIANT=${VARIANT_SUFFIX}
209
+ GATO_PLANT_HEADER="dynamics/plant.cuh")
210
+ if(variant STREQUAL "eh")
211
+ target_compile_definitions(${MODULE_NAME} PRIVATE USE_EXACT_HESSIAN=1)
212
+ elseif(variant STREQUAL "fc")
213
+ target_compile_definitions(${MODULE_NAME} PRIVATE GATO_CONTACT_FORCES=1)
214
+ endif()
215
+
216
+ set_target_properties(
217
+ ${MODULE_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY
218
+ ${GATO_MODULE_OUTPUT_DIR})
219
+ target_link_libraries(${MODULE_NAME} PRIVATE CUDA::cudart)
220
+ endblock()
221
+ endforeach()