sci-solver 0.1.0__tar.gz → 0.2.0__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 (77) hide show
  1. {sci_solver-0.1.0 → sci_solver-0.2.0}/CITATION.cff +1 -1
  2. {sci_solver-0.1.0 → sci_solver-0.2.0}/MANIFEST.in +1 -1
  3. {sci_solver-0.1.0 → sci_solver-0.2.0}/PKG-INFO +24 -5
  4. {sci_solver-0.1.0 → sci_solver-0.2.0}/README.md +23 -4
  5. {sci_solver-0.1.0 → sci_solver-0.2.0}/UPSTREAM.json +13 -2
  6. sci_solver-0.2.0/docs/api.md +121 -0
  7. sci_solver-0.2.0/docs/component-reuse.md +215 -0
  8. sci_solver-0.2.0/docs/fleet-partitions-small.md +119 -0
  9. sci_solver-0.2.0/docs/fleet-partitions.md +135 -0
  10. {sci_solver-0.1.0 → sci_solver-0.2.0}/docs/migration.md +31 -4
  11. sci_solver-0.2.0/docs/release-notes.md +36 -0
  12. {sci_solver-0.1.0 → sci_solver-0.2.0}/docs/releasing.md +11 -8
  13. {sci_solver-0.1.0 → sci_solver-0.2.0}/pyproject.toml +1 -1
  14. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/api.py +3 -2
  15. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/cli/vector_sci.py +3 -1
  16. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/config.py +4 -0
  17. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/contact_solver.py +63 -166
  18. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/matching_numerics.py +58 -21
  19. sci_solver-0.2.0/src/sci_solver/solvers/matching_operations.py +162 -0
  20. sci_solver-0.2.0/src/sci_solver/solvers/partitioned_matching.py +489 -0
  21. sci_solver-0.2.0/src/sci_solver/solvers/stage_timing.py +41 -0
  22. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver.egg-info/PKG-INFO +24 -5
  23. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver.egg-info/SOURCES.txt +24 -0
  24. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/test_api.py +38 -0
  25. sci_solver-0.2.0/tests/test_partitioned_matching.py +326 -0
  26. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/test_provenance.py +4 -2
  27. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/test_vector_solver.py +3 -0
  28. sci_solver-0.2.0/tools/benchmark_component_reuse.py +277 -0
  29. sci_solver-0.2.0/tools/benchmark_partitions.py +267 -0
  30. {sci_solver-0.1.0 → sci_solver-0.2.0}/tools/compare_upstream.py +10 -5
  31. sci_solver-0.2.0/tools/validate_component_reuse.py +108 -0
  32. sci_solver-0.2.0/validation/component-reuse-profile.csv +6 -0
  33. sci_solver-0.2.0/validation/component-reuse-validation.json +1792 -0
  34. sci_solver-0.2.0/validation/component-reuse.csv +37 -0
  35. sci_solver-0.2.0/validation/component-reuse.json.gz +0 -0
  36. sci_solver-0.2.0/validation/fleet-partitions-small-summary.json +93 -0
  37. sci_solver-0.2.0/validation/fleet-partitions-small-validation.json +1536 -0
  38. sci_solver-0.2.0/validation/fleet-partitions-small.csv +301 -0
  39. sci_solver-0.2.0/validation/fleet-partitions-small.json.gz +0 -0
  40. sci_solver-0.2.0/validation/fleet-partitions.csv +16 -0
  41. sci_solver-0.2.0/validation/fleet-partitions.json +53147 -0
  42. sci_solver-0.2.0/validation/fleet-small-inputs.json +7807 -0
  43. sci_solver-0.2.0/validation/partition-baseline-parity.json +41 -0
  44. sci_solver-0.2.0/validation/release-0.1.0.json +111 -0
  45. sci_solver-0.2.0/validation/release-0.2.0-local.json +116 -0
  46. sci_solver-0.1.0/docs/api.md +0 -62
  47. sci_solver-0.1.0/docs/release-notes.md +0 -14
  48. {sci_solver-0.1.0 → sci_solver-0.2.0}/LICENSE +0 -0
  49. {sci_solver-0.1.0 → sci_solver-0.2.0}/NOTICE +0 -0
  50. {sci_solver-0.1.0 → sci_solver-0.2.0}/examples/config.json +0 -0
  51. {sci_solver-0.1.0 → sci_solver-0.2.0}/examples/fleet.py +0 -0
  52. {sci_solver-0.1.0 → sci_solver-0.2.0}/examples/two_robots.py +0 -0
  53. {sci_solver-0.1.0 → sci_solver-0.2.0}/setup.cfg +0 -0
  54. {sci_solver-0.1.0 → sci_solver-0.2.0}/setup.py +0 -0
  55. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/__init__.py +0 -0
  56. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/cli/__init__.py +0 -0
  57. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/evaluation.py +0 -0
  58. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/__init__.py +0 -0
  59. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/_cubic_minima.c +0 -0
  60. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/base.py +0 -0
  61. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/collision_search.py +0 -0
  62. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/cubic_utils.py +0 -0
  63. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/reaction_matching.py +0 -0
  64. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver/solvers/vector_matching.py +0 -0
  65. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver.egg-info/dependency_links.txt +0 -0
  66. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver.egg-info/entry_points.txt +0 -0
  67. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver.egg-info/requires.txt +0 -0
  68. {sci_solver-0.1.0 → sci_solver-0.2.0}/src/sci_solver.egg-info/top_level.txt +0 -0
  69. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/data/fleet20_seed15.npz +0 -0
  70. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/data/fleet20_seed2.npz +0 -0
  71. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/data/fleet20_seed35.npz +0 -0
  72. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/data/fleet20_seed47.npz +0 -0
  73. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/data/fleet20_seed9.npz +0 -0
  74. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/test_collision_search.py +0 -0
  75. {sci_solver-0.1.0 → sci_solver-0.2.0}/tests/test_cubic_separation.py +0 -0
  76. {sci_solver-0.1.0 → sci_solver-0.2.0}/validation/local-validation.json +0 -0
  77. {sci_solver-0.1.0 → sci_solver-0.2.0}/validation/upstream-comparison.json +0 -0
@@ -5,7 +5,7 @@ title: "SCI: Sequential Contact Insertion"
5
5
  authors:
6
6
  - family-names: Kamm
7
7
  given-names: Jan
8
- version: 0.1.0
8
+ version: 0.2.0
9
9
  license: Apache-2.0
10
10
  repository-code: "https://github.com/jankammeth/sci"
11
11
  abstract: "Continuous-time robot fleet planning with sequential contact insertion."
@@ -3,5 +3,5 @@ recursive-include docs *.md
3
3
  recursive-include examples *.py *.json
4
4
  recursive-include tests *.py *.npz
5
5
  recursive-include tools *.py
6
- recursive-include validation *.json
6
+ recursive-include validation *.json *.json.gz *.csv
7
7
  include src/sci_solver/solvers/_cubic_minima.c
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sci-solver
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Sequential Contact Insertion for continuous-time robot fleet planning.
5
5
  Author: Jan Kamm
6
6
  License-Expression: Apache-2.0
@@ -38,13 +38,13 @@ checks separation over the entire trajectory.
38
38
 
39
39
  ## Installation
40
40
 
41
- Python 3.10 or newer is required. After the first PyPI release:
41
+ Python 3.10 or newer is required. Install from [PyPI](https://pypi.org/project/sci-solver/):
42
42
 
43
43
  ```sh
44
44
  pip install sci-solver
45
45
  ```
46
46
 
47
- To install directly from GitHub now:
47
+ To install directly from GitHub:
48
48
 
49
49
  ```sh
50
50
  python -m pip install git+https://github.com/jankammeth/sci.git
@@ -116,6 +116,21 @@ and diagnostics; it is not a usable collision-free plan. Tolerances are absolute
116
116
  and should be chosen for the physical scale of the problem. The reference default
117
117
  separation allowance is zero; the examples explicitly use `1e-6`.
118
118
 
119
+ Starting with 0.2.0, the default `sci_fleet_mode="components"` uses union-find to
120
+ group robots connected by contacts and solves one system per group. Robots with
121
+ no contacts follow their unconstrained trajectories. Unchanged groups reuse their
122
+ solutions and checks. All modes check continuous separation across the entire fleet.
123
+
124
+ Select `SolverConfig(sci_fleet_mode="full")` to reproduce the earlier full-fleet
125
+ formulation, or `sci_fleet_mode="active"` to solve one system containing all
126
+ participating robots. The default uses the same component algorithm for every
127
+ fleet size; it has no robot-count threshold. Small contacting fleets can incur
128
+ extra bookkeeping overhead; the benchmarks report those cases as well.
129
+ [The component reuse benchmark](docs/component-reuse.md) compares the optimized
130
+ driver with its prototype and full-fleet mode on saved 2-, 4-, and 20-robot cases,
131
+ including a detailed Newton breakdown. Unchanged components retain their solved
132
+ states and checks; graph rebuilding remains simple union-find.
133
+
119
134
  ## Development and provenance
120
135
 
121
136
  ```sh
@@ -127,10 +142,14 @@ ruff check .
127
142
  The numerical implementation is preserved from
128
143
  [`contact-solver` at `641a234`](https://github.com/jankammeth/contact-solver/tree/641a2341a6a59938e285b8ff872bd484713e0605).
129
144
  [UPSTREAM.json](UPSTREAM.json) records source paths, SHA-256 hashes, and every
130
- extraction edit. Six core files are byte-identical. The collision wrapper changes
145
+ extraction edit. Four core files remain byte-identical. The collision wrapper changes
131
146
  only the package name in its fallback warning; the C kernel adds equivalent
132
147
  stack workspace allocation for Microsoft's C compiler. Numerical formulas and
133
- control flow are unchanged.
148
+ control flow in the explicit full-fleet mode are retained; its driver records
149
+ stage timings and dispatches the default component solver. Contact initialization,
150
+ recovery, and validation are shared with the component driver; Newton has optional
151
+ fine stage profiling. Original extraction hashes are retained alongside current
152
+ hashes and change descriptions.
134
153
  [The migration guide](docs/migration.md) describes the extraction and comparison
135
154
  procedure. The paper and historical experiment infrastructure remain separate.
136
155
 
@@ -10,13 +10,13 @@ checks separation over the entire trajectory.
10
10
 
11
11
  ## Installation
12
12
 
13
- Python 3.10 or newer is required. After the first PyPI release:
13
+ Python 3.10 or newer is required. Install from [PyPI](https://pypi.org/project/sci-solver/):
14
14
 
15
15
  ```sh
16
16
  pip install sci-solver
17
17
  ```
18
18
 
19
- To install directly from GitHub now:
19
+ To install directly from GitHub:
20
20
 
21
21
  ```sh
22
22
  python -m pip install git+https://github.com/jankammeth/sci.git
@@ -88,6 +88,21 @@ and diagnostics; it is not a usable collision-free plan. Tolerances are absolute
88
88
  and should be chosen for the physical scale of the problem. The reference default
89
89
  separation allowance is zero; the examples explicitly use `1e-6`.
90
90
 
91
+ Starting with 0.2.0, the default `sci_fleet_mode="components"` uses union-find to
92
+ group robots connected by contacts and solves one system per group. Robots with
93
+ no contacts follow their unconstrained trajectories. Unchanged groups reuse their
94
+ solutions and checks. All modes check continuous separation across the entire fleet.
95
+
96
+ Select `SolverConfig(sci_fleet_mode="full")` to reproduce the earlier full-fleet
97
+ formulation, or `sci_fleet_mode="active"` to solve one system containing all
98
+ participating robots. The default uses the same component algorithm for every
99
+ fleet size; it has no robot-count threshold. Small contacting fleets can incur
100
+ extra bookkeeping overhead; the benchmarks report those cases as well.
101
+ [The component reuse benchmark](docs/component-reuse.md) compares the optimized
102
+ driver with its prototype and full-fleet mode on saved 2-, 4-, and 20-robot cases,
103
+ including a detailed Newton breakdown. Unchanged components retain their solved
104
+ states and checks; graph rebuilding remains simple union-find.
105
+
91
106
  ## Development and provenance
92
107
 
93
108
  ```sh
@@ -99,10 +114,14 @@ ruff check .
99
114
  The numerical implementation is preserved from
100
115
  [`contact-solver` at `641a234`](https://github.com/jankammeth/contact-solver/tree/641a2341a6a59938e285b8ff872bd484713e0605).
101
116
  [UPSTREAM.json](UPSTREAM.json) records source paths, SHA-256 hashes, and every
102
- extraction edit. Six core files are byte-identical. The collision wrapper changes
117
+ extraction edit. Four core files remain byte-identical. The collision wrapper changes
103
118
  only the package name in its fallback warning; the C kernel adds equivalent
104
119
  stack workspace allocation for Microsoft's C compiler. Numerical formulas and
105
- control flow are unchanged.
120
+ control flow in the explicit full-fleet mode are retained; its driver records
121
+ stage timings and dispatches the default component solver. Contact initialization,
122
+ recovery, and validation are shared with the component driver; Newton has optional
123
+ fine stage profiling. Original extraction hashes are retained alongside current
124
+ hashes and change descriptions.
106
125
  [The migration guide](docs/migration.md) describes the extraction and comparison
107
126
  procedure. The paper and historical experiment infrastructure remain separate.
108
127
 
@@ -10,7 +10,14 @@
10
10
  "src/sci_solver/solvers/contact_solver.py": {
11
11
  "upstream_path": "src/contact_solver/solvers/contact_solver.py",
12
12
  "sha256": "9ae40bc11838b8fc0ca76f439f21af878d27669f3d3440bfd8519196e624b7a1",
13
- "extracted_sha256": "9ae40bc11838b8fc0ca76f439f21af878d27669f3d3440bfd8519196e624b7a1"
13
+ "extracted_sha256": "9ae40bc11838b8fc0ca76f439f21af878d27669f3d3440bfd8519196e624b7a1",
14
+ "current_sha256": "08214666e733cfe2fe8c3f5b249ab08ec4c6ee6475cc6c771914cd46a46f6cc6",
15
+ "post_extraction_changes": [
16
+ "Active-robot and connected-component matching drivers; components become the default in 0.2.0, with explicit full mode retained.",
17
+ "Disjoint stage timings and system sizes for benchmark comparisons.",
18
+ "Share pure contact initialization, recovery and validation operations with lightweight components; allow selected-coordinate evaluation.",
19
+ "Configuration objects without a fleet-mode field inherit the public SolverConfig default."
20
+ ]
14
21
  },
15
22
  "src/sci_solver/solvers/collision_search.py": {
16
23
  "upstream_path": "src/contact_solver/solvers/collision_search.py",
@@ -31,7 +38,11 @@
31
38
  "src/sci_solver/solvers/matching_numerics.py": {
32
39
  "upstream_path": "src/contact_solver/solvers/matching_numerics.py",
33
40
  "sha256": "e10f5dbf0747163754355cdffd62b9067b2dea82a31d72222e368f67f7a8cd66",
34
- "extracted_sha256": "e10f5dbf0747163754355cdffd62b9067b2dea82a31d72222e368f67f7a8cd66"
41
+ "extracted_sha256": "e10f5dbf0747163754355cdffd62b9067b2dea82a31d72222e368f67f7a8cd66",
42
+ "current_sha256": "67abc420c4b84ea8947a798b3913597baee9aaa9b5c9ad069dac82811b400889",
43
+ "post_extraction_changes": [
44
+ "Optional Newton-stage timing and LSMR iteration diagnostics; numerical algorithm and tolerances unchanged."
45
+ ]
35
46
  },
36
47
  "src/sci_solver/solvers/reaction_matching.py": {
37
48
  "upstream_path": "src/contact_solver/solvers/reaction_matching.py",
@@ -0,0 +1,121 @@
1
+ # API and numerical options
2
+
3
+ `solve(initial_positions, final_positions, *, time_horizon, min_distance,
4
+ initial_velocities=None, final_velocities=None, options=None)` returns a `Solution`.
5
+ Endpoint arrays have shape `(N, d)` with `N >= 2` and `d >= 2`.
6
+
7
+ ## Results
8
+
9
+ | Member | Meaning |
10
+ | --- | --- |
11
+ | `success` | Matching checks and continuous separation both passed. |
12
+ | `sample(times, derivative=0)` | Array with shape `(N, len(times), d)`; derivative orders 0–3. |
13
+ | `trajectory` | Knot times, relative cubic coefficients, and barycenter coefficients. |
14
+ | `contacts` | Pair indices, times, normals, relative velocities, and reaction masses. |
15
+ | `acceleration_cost` | Integrated sum of squared robot accelerations. |
16
+ | `metrics` | Status, reason, residuals, separation, backend, timing, and recovery details. |
17
+
18
+ Times may be unordered but must be finite and inside the horizon. At a contact,
19
+ the third derivative uses the segment to its right; the final endpoint uses the
20
+ last segment. Orders 0–2 are continuous up to the matching tolerance.
21
+
22
+ ## Options
23
+
24
+ Pass `SolverConfig(...)` through `options`. Numerical tolerances retain the
25
+ reference defaults; equation systems use contact components by default since 0.2.0.
26
+
27
+ | Option | Default | Meaning |
28
+ | --- | --- | --- |
29
+ | `sci_detection_tol` | `0.0` | Allowed absolute separation deficit. |
30
+ | `sci_residual_tol` | `1e-7` | Matching and endpoint residual tolerance. |
31
+ | `sci_mass_tol` | `1e-9` | Allowed negative reaction mass within tolerance. |
32
+ | `sci_min_contact_gap` | `0.0` | Minimum knot gap; zero still requires strict ordering. |
33
+ | `sci_root_max_iterations` | `100` | Newton iterations per matching solve. |
34
+ | `sci_reaction_recovery` | `True` | Enable condensed recovery when matching fails. |
35
+ | `timeout` | `300.0` | Cooperative solve deadline in seconds. |
36
+ | `use_analytical_jacobian` | `True` | Use the analytical matching Jacobian. |
37
+ | `sci_fleet_mode` | `"components"` | `"full"`, `"active"`, or `"components"`; see below. |
38
+ | `sci_profile_newton` | `False` | Record detailed Newton-stage times and LSMR iteration counts. |
39
+
40
+ Statuses are `success`, `unresolved`, `root_failure`, and `invalid_solution`.
41
+ `metrics["convergence_reason"]` supplies details. Unsuccessful iterates remain
42
+ available for diagnosis. The deadline is checked between numerical operations;
43
+ it does not interrupt an in-progress linear solve.
44
+
45
+ For direct control, use `Config`, `ProblemConfig`, and `ContactSolver`, followed by
46
+ `set_initial_states`, `set_final_states`, and `generate_trajectories`. This retains
47
+ the reference interface. Its `ProblemConfig.timestep` is a legacy configuration
48
+ field used by automatic horizon selection when the horizon is omitted; it does
49
+ not discretize contact matching or collision checking. The convenience `solve`
50
+ function requires an explicit horizon.
51
+
52
+ ## Contact components
53
+
54
+ `components` is the default for `solve`, `ContactSolver`, and the CLI, including
55
+ saved configurations that omit `sci_fleet_mode`. `full` uses all robots in every
56
+ contact block. `active` eliminates robots with no
57
+ current contacts and solves one system for the remaining robots. `components`
58
+ also partitions the contact graph and solves one system per connected group.
59
+ The graph is rebuilt with union-find after every insertion or removal; there is
60
+ no persistent graph cache. Groups whose robot and contact identities have not
61
+ changed retain their solution, trajectory, and validation checks. Changed groups,
62
+ including merges and splits, are rebuilt. Every group uses the same Newton and
63
+ recovery routines and its own endpoint barycenter. Uninvolved robots follow their unconstrained
64
+ endpoint cubics. Collision search still covers all pairs and can activate robots
65
+ or join components on later iterations.
66
+
67
+ Component trajectories are joined by translating cubic coefficients onto the
68
+ union of their knots. Independent components can have simultaneous contacts.
69
+ Minimum knot gaps and ordering constraints apply within each component. If a
70
+ new edge joins components whose existing contact times coincide or violate the
71
+ configured gap, the solver returns `unresolved` with reason
72
+ `merged_component_knot_gap`; it does not move already solved contact times to
73
+ make the merge fit the current Hermite representation.
74
+
75
+ `Solution`, sampling, contact indices, and CLI exports retain their fleet-wide
76
+ shape. For low-level diagnostics in the reduced modes, `ContactSolver.system`
77
+ is a tuple of local matching systems, `solution` concatenates their local vectors,
78
+ and `matching_components` associates each local system/vector/trajectory with
79
+ its original robot IDs. `metrics["system_size"]` is the sum of local system sizes;
80
+ `partition_history` records robot groups, contact counts, sizes, and `rebuilt` and
81
+ `reused` groups after each contact-set change. `component_builds` and
82
+ `component_reuses` total these events. Each root record reports a solve actually
83
+ performed; reused components do not add root records.
84
+
85
+ `metrics["timing"]` includes `stage_seconds` and `stage_calls` for partitioning,
86
+ assembly, Newton, recovery, reconstruction, validation, and collision search.
87
+ Stages do not overlap. Partitioning includes union-find, index grouping, and
88
+ sorting; assembly includes local systems and initial vectors; recovery includes its own
89
+ assembly/reconstruction/checks. `other` accounts for remaining solve overhead.
90
+ Final output sampling and export of fleet-indexed contact vectors are separately
91
+ reported as `postprocess_time`; `contact_export_seconds` is the export portion in
92
+ reduced modes. `wall_time` includes postprocessing.
93
+ Use these stages for comparisons: the legacy `matching_seconds` in full mode
94
+ also contains active-set bookkeeping, while reduced modes report Newton plus
95
+ recovery there. Timings are wall-clock seconds, not CPU-time measurements.
96
+
97
+ When `sci_profile_newton=True`, Newton root records additionally contain
98
+ `newton_timing.stage_seconds`, `newton_timing.stage_calls`, and `lsmr_iterations`.
99
+ These nested stages separate residual/Jacobian evaluation, band assembly, band
100
+ solve, sparse assembly/scaling, LSMR, line-search residual evaluation, and other
101
+ Newton work. They are subdivisions of the outer `newton` stage, not additional
102
+ solve time. Recovery retains its own outer timer. Use profiling separately from
103
+ baseline timing comparisons because instrumentation itself adds overhead.
104
+
105
+ See [the component reuse benchmark](component-reuse.md) for current measured
106
+ costs and [the original fleet benchmark](fleet-partitions.md) for the prototype.
107
+
108
+ ## Saved problems
109
+
110
+ ```sh
111
+ sci-solve --states endpoints.npz --config examples/config.json \
112
+ --output outputs/metrics.json --trajectories outputs/trajectories.npz
113
+ ```
114
+
115
+ Input NPZ keys are `initial_positions`, `final_positions`, and optionally
116
+ `initial_velocities`, `final_velocities`. JSON configuration uses `problem` and
117
+ `solver` objects; unknown fields are rejected. `Config.to_json` and
118
+ `Config.from_json` support roundtrips without a YAML dependency.
119
+
120
+ The CLI exits zero only for a successful solve. Sampled output includes times,
121
+ positions, velocities, accelerations, contact states, and contact pairs.
@@ -0,0 +1,215 @@
1
+ # Component reuse benchmark
2
+
3
+ This report records the benchmark before the 0.2.0 release. At that time, full
4
+ mode remained the default. Version 0.2.0 adopts the optimized component method;
5
+ the measurements and source hashes below retain their original meaning.
6
+
7
+ The three general improvements reduce component-driver overhead without any
8
+ fleet-size threshold. The selected small interacting cases improve by 11–24%
9
+ against the prototype, but remain 9–24% slower than full-fleet solves. At N=20,
10
+ three cases improve by 22–30%; the other two change little. The optimized
11
+ components are 3–10 times faster than full-fleet solves on these five N=20 cases.
12
+ Union-find is a small fraction of runtime. Newton evaluation and sparse fallback
13
+ are the useful next profiling targets.
14
+
15
+ ## Changes
16
+
17
+ 1. Components hold a matching system, vector, endpoint data, and trajectory.
18
+ They no longer construct nested `Config` and `ContactSolver` objects. Contact
19
+ initialization, recovery, and validation use shared numerical functions.
20
+ 2. Initialization reads only the selected relative coordinates. Reconstruction
21
+ assembles relative cubic coefficients directly. Fleet-indexed public contact
22
+ vectors are exported once at the end. When a single component already has the
23
+ full fleet layout, its trajectory is returned directly; this is a representation
24
+ identity that applies at every N.
25
+ 3. Unchanged robot/contact groups retain their solution, trajectory, and checks.
26
+ Stable contact identities distinguish retained contacts from newly inserted
27
+ ones, including after recovery reorders contacts. Merges, splits, insertions,
28
+ and removals rebuild affected groups. No component state persists across solves.
29
+
30
+ Union-find is still rebuilt after every contact-set update. No dependencies,
31
+ matching equations, numerical tolerances, or solver selection thresholds were
32
+ added. The default remains `full`; no full experiment campaign was rerun.
33
+
34
+ These measurements evaluate the three changes together, not their isolated
35
+ contributions. Stage timings and component reuse counts explain where work was
36
+ removed, but are not a three-way ablation.
37
+
38
+ ## Protocol
39
+
40
+ Measured on 2026-10-17, macOS arm64, Python 3.14.7, NumPy 2.5.3, SciPy 1.18.1,
41
+ native collision backend, and numerical-library thread limits of one. The input
42
+ settings match the prior benchmarks: minimum distance 0.5, separation allowance
43
+ 0.001, residual tolerance 1e-7, recovery enabled, and a 30-second deadline.
44
+
45
+ The selection was fixed before timing: N=2 uses the first free seed and first two
46
+ interacting seeds; N=4 uses the first seed at each observed contact count 0–3;
47
+ N=20 uses all five previously benchmarked regression cases. Endpoints and
48
+ configuration are embedded in the raw report.
49
+
50
+ `Before` is SCI commit `5e7a8e3c667f70c7230db2024d9525254eb409f2`, archived
51
+ before editing and imported as a separate package. `Full` and `After` use the
52
+ current sources, whose hashes are recorded. All run in the same process with one
53
+ warmup per case/variant, then 21 repetitions for N=2/4 and seven for N=20. Case
54
+ and variant order are shuffled deterministically. This gives 546 timed solves.
55
+
56
+ Detailed Newton profiling is disabled in this comparison. A separate pass uses
57
+ three profiled repetitions per N=20 case. Reported times are medians; stage
58
+ medians need not sum exactly to the median total. Quartiles are in the CSV.
59
+
60
+ ## Solve times
61
+
62
+ Milliseconds. `Reduction` compares optimized components against the prototype.
63
+ Solve time excludes final output sampling and contact-vector export.
64
+
65
+ | N | Seed | Final contacts | Full | Components before | Components after | Reduction |
66
+ | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
67
+ | 2 | 0 | 0 | 0.117 | 0.129 | 0.100 | 22.9% |
68
+ | 2 | 2 | 1 | 0.825 | 1.212 | 0.925 | 23.7% |
69
+ | 2 | 10 | 1 | 0.837 | 1.208 | 0.915 | 24.3% |
70
+ | 4 | 0 | 1 | 0.869 | 1.220 | 1.078 | 11.7% |
71
+ | 4 | 1 | 0 | 0.128 | 0.136 | 0.106 | 22.4% |
72
+ | 4 | 8 | 3 | 3.638 | 4.917 | 4.001 | 18.6% |
73
+ | 4 | 40 | 2 | 1.951 | 2.616 | 2.336 | 10.7% |
74
+ | 20 | 2 | 3 | 82.280 | 19.562 | 19.436 | 0.6% |
75
+ | 20 | 9 | 13 | 1610.075 | 545.170 | 537.159 | 1.5% |
76
+ | 20 | 15 | 8 | 101.151 | 24.667 | 17.956 | 27.2% |
77
+ | 20 | 35 | 7 | 1101.601 | 169.983 | 118.573 | 30.2% |
78
+ | 20 | 47 | 6 | 135.752 | 17.466 | 13.587 | 22.2% |
79
+
80
+ Seed 2 has overlapping interquartile ranges (before 19.353–20.522 ms,
81
+ after 19.160–20.046 ms); its apparent 0.6% gain should be treated as inconclusive.
82
+ Seed 9 improves little because most time is inside Newton, not component setup.
83
+ The small-case selection is representative of contact counts, not an estimate
84
+ over the entire previous 100-case set.
85
+
86
+ The CSV also records the complete `generate_trajectories` call, including final
87
+ contact export and sampled output. Its before/after reduction is 8–17% on the
88
+ small interacting cases, and 0.7%, 1.4%, 24.8%, 29.9%, and 19.2% on the five N=20
89
+ cases respectively. Thus the improvement remains when moved export work is
90
+ included. For N=20 the export itself takes 0.066–0.106 ms. Configuration and cost
91
+ evaluation are outside both timers.
92
+
93
+ For N=2 seed 2, component assembly falls from 0.171 to 0.076 ms and reconstruction
94
+ from 0.221 to 0.053 ms. Newton stays at about 0.473 ms in all three variants.
95
+ The remaining component/full gap is 0.100 ms. For N=4 seed 40, the gap is 0.386 ms
96
+ over two contact updates, with reconstruction contributing 0.290 ms in components
97
+ versus 0.069 ms in full mode. The overhead scales with components, knots, and
98
+ updates; it is not a constant tax determined only by the fleet size.
99
+
100
+ ## N=20 outer stages
101
+
102
+ Accumulated milliseconds per optimized component solve, from the unprofiled
103
+ seven-repeat comparison. Stages are disjoint.
104
+
105
+ | Stage | Seed 2 | Seed 9 | Seed 15 | Seed 35 | Seed 47 |
106
+ | --- | ---: | ---: | ---: | ---: | ---: |
107
+ | Partition: union-find, grouping, sorting | 0.028 | 0.135 | 0.059 | 0.064 | 0.049 |
108
+ | Component assembly | 0.604 | 3.122 | 0.878 | 0.980 | 0.903 |
109
+ | Newton | 16.189 | 516.226 | 11.606 | 111.093 | 7.882 |
110
+ | Reaction recovery | 0.000 | 7.170 | 0.000 | 1.185 | 0.000 |
111
+ | Trajectory reconstruction | 0.840 | 3.204 | 1.854 | 1.886 | 1.654 |
112
+ | Validation | 0.637 | 2.609 | 1.250 | 1.107 | 1.130 |
113
+ | Collision search | 0.562 | 2.705 | 1.224 | 1.187 | 1.020 |
114
+ | Other bookkeeping | 0.600 | 2.095 | 1.128 | 1.156 | 0.989 |
115
+ | Total solve | 19.436 | 537.159 | 17.956 | 118.573 | 13.587 |
116
+
117
+ There are respectively 5, 15, 8, 9, and 8 partition calls: approximately
118
+ 5.6–9.0 microseconds per call. Partitioning is 0.025–0.362% of solve time and
119
+ includes more than union-find alone. Graph rebuilding costs
120
+ O(m alpha(A)) plus O(A log A) deterministic ordering, for m contacts and A active
121
+ robots. It is not constant, but is negligible on these cases.
122
+
123
+ Actual component solves and reuse events explain the savings:
124
+
125
+ | Seed | Newton calls before | Newton calls after | Unchanged groups reused | Function evaluations before → after |
126
+ | ---: | ---: | ---: | ---: | ---: |
127
+ | 2 | 5 | 5 | 0 | 143 → 143 |
128
+ | 9 | 32 | 15 | 17 | 658 → 641 |
129
+ | 15 | 22 | 8 | 14 | 96 → 82 |
130
+ | 35 | 20 | 9 | 11 | 598 → 470 |
131
+ | 47 | 17 | 9 | 8 | 78 → 70 |
132
+
133
+ Recovery evaluations are included in the last column, separately timed above.
134
+ The changes also alter floating-point warm starts by avoiding global coordinate
135
+ round trips; all numerical comparisons and physical checks pass below. The
136
+ seed-35 gain should not be attributed entirely to its 11 skipped initial residual
137
+ evaluations: its total evaluation count also falls substantially.
138
+
139
+ ## Inside Newton
140
+
141
+ Milliseconds accumulated across all Newton calls in each solve, measured in the
142
+ separate three-repeat profiling pass. These are subdivisions of Newton, not
143
+ additional time to add to the outer table. Recovery has its own timer.
144
+
145
+ | Newton stage | Seed 2 | Seed 9 | Seed 15 | Seed 35 | Seed 47 |
146
+ | --- | ---: | ---: | ---: | ---: | ---: |
147
+ | Residual and Jacobian evaluation | 5.723 | 44.742 | 5.557 | 14.398 | 3.791 |
148
+ | Band matrix assembly | 0.989 | 17.833 | 0.955 | 3.013 | 0.576 |
149
+ | Banded linear solve | 1.088 | 28.689 | 1.155 | 3.352 | 0.789 |
150
+ | Sparse assembly and scaling | 0.000 | 12.852 | 0.000 | 3.455 | 0.000 |
151
+ | Sparse LSMR solve | 0.000 | 332.378 | 0.000 | 55.412 | 0.000 |
152
+ | Line-search residual evaluations | 6.827 | 71.132 | 2.647 | 28.192 | 1.732 |
153
+ | Other Newton work | 1.593 | 7.558 | 1.237 | 4.734 | 1.057 |
154
+ | Total Newton | 16.273 | 514.929 | 11.608 | 112.604 | 7.986 |
155
+ | Total profiled solve | 19.377 | 535.144 | 17.900 | 120.019 | 13.723 |
156
+
157
+ | Seed | Newton iterations | Residual/Jacobian calls | Line-search residual calls | LSMR calls | LSMR iterations |
158
+ | ---: | ---: | ---: | ---: | ---: | ---: |
159
+ | 2 | 37 | 42 | 101 | 0 | 0 |
160
+ | 9 | 106 | 121 | 482 | 14 | 15,232 |
161
+ | 15 | 34 | 42 | 40 | 0 | 0 |
162
+ | 35 | 82 | 91 | 369 | 10 | 2,880 |
163
+ | 47 | 29 | 38 | 32 | 0 | 0 |
164
+
165
+ Seed 9 spends 62% of its complete profiled solve in LSMR and another 22% in
166
+ residual/Jacobian and line-search evaluation. For seed 35 those shares are 46%
167
+ and 35%. The easier cases use no LSMR; evaluation accounts for 40–65% of their
168
+ total solve time. Banded linear solves take only 3–6% across these cases.
169
+
170
+ The next general optimization target is therefore numerical evaluation and the
171
+ fallback path. For the hard cases, inspect why fallback needs so many iterations
172
+ before changing the linear solver or safeguards. For the easier cases, reducing
173
+ repeated residual/Jacobian allocations and polynomial work is more promising
174
+ than a more elaborate graph algorithm. These are directions justified by the
175
+ profile, not measured speedup claims. The current results do not justify an
176
+ N-dependent policy.
177
+
178
+ ## Validation and reproduction
179
+
180
+ All 546 timed runs and 15 profiled runs converge with valid matching checks and
181
+ continuous separation. The largest relative cost difference from paired full
182
+ mode is 1.19e-11; the largest position-coordinate difference over 201 times is
183
+ 6.31e-7. Separate independent polynomial checks pass for all 12 cases in
184
+ `full`, `active`, and `components` (36 checks): endpoints, continuity, jerk
185
+ balance, contact geometry, reaction signs, and continuous separation.
186
+
187
+ Profiling on/off produces exactly equal knot times and trajectory coefficients
188
+ on all five N=20 cases. The refactored full mode matches the preserved version
189
+ exactly across the existing 29-case comparison: identical records and zero
190
+ difference in all 174 arrays, including sampled derivatives and failed solves.
191
+ The complete test suite passes 172 tests; lint and whitespace checks pass.
192
+
193
+ From the SCI checkout, prepare an archive of `5e7a8e3` at `SCI_REFERENCE` with
194
+ the same native collision extension built for the current Python interpreter.
195
+ Then run:
196
+
197
+ ```sh
198
+ PYTHONPATH=src python tools/benchmark_component_reuse.py \
199
+ --reference-source "$SCI_REFERENCE"
200
+
201
+ PYTHONPATH="$SCI_REFERENCE/src" python tools/compare_upstream.py \
202
+ --worker sci_solver --output /tmp/sci-reuse-reference-full.json
203
+ PYTHONPATH=src python tools/compare_upstream.py \
204
+ --worker sci_solver --output /tmp/sci-reuse-current-full.json
205
+ PYTHONPATH=src python tools/validate_component_reuse.py \
206
+ --full-before /tmp/sci-reuse-reference-full.json \
207
+ --full-after /tmp/sci-reuse-current-full.json
208
+ PYTHONPATH=src python -m pytest -q
209
+ ruff check .
210
+ ```
211
+
212
+ - [Raw runs and source hashes](../validation/component-reuse.json.gz)
213
+ - [Per-case timing CSV and quartiles](../validation/component-reuse.csv)
214
+ - [Detailed Newton profiling CSV](../validation/component-reuse-profile.csv)
215
+ - [Validation records](../validation/component-reuse-validation.json)
@@ -0,0 +1,119 @@
1
+ # Fleet reductions at N=2 and N=4
2
+
3
+ The original component prototype was slower than the full-fleet solver for
4
+ these small cases. The subsequent [component reuse benchmark](component-reuse.md)
5
+ measures a general optimization of that implementation. The results below are
6
+ historical measurements; they do not prescribe fleet-size dispatch. No solver
7
+ code, defaults, experiment campaigns, or manuscript data were changed for this
8
+ original comparison.
9
+
10
+ ## Inputs and protocol
11
+
12
+ All 50 saved fleet-study seeds for N=2 and all 50 for N=4 are included, without
13
+ filtering by outcome. The exact endpoints, velocities, horizons, minimum distance,
14
+ and separation allowance are copied into the self-contained
15
+ [input snapshot](../validation/fleet-small-inputs.json). It records the original
16
+ table, input, and configuration hashes. The selected settings are minimum
17
+ distance 0.5 and separation allowance 0.001; matching uses the current defaults
18
+ including reaction recovery and a 30-second deadline.
19
+
20
+ Each case/mode receives one warmup and 21 timed repetitions. Case and mode order
21
+ are deterministically shuffled within every repetition; execution is sequential
22
+ in one process. The native collision backend is used, with numerical-library
23
+ thread limits set to one. Environment: macOS arm64, Python 3.14.7, NumPy 2.5.3,
24
+ SciPy 1.18.1. The solver implementation is unchanged from the N=20 comparison.
25
+
26
+ Reported times are the median across cases of each case's median over 21 runs.
27
+ They include planning, reconstruction within planning, and collision search,
28
+ and exclude final output sampling. Setup and complete wall time are also logged.
29
+ Input loading is outside the solve timer. Contact-free and interacting cases are
30
+ reported separately because 74 of the 100 cases need no matching equations.
31
+
32
+ ## Results
33
+
34
+ Times in milliseconds:
35
+
36
+ | N | Cases | Count | Full | Active | Components |
37
+ | --- | --- | ---: | ---: | ---: | ---: |
38
+ | 2 | Contact-free | 41 | 0.115 | 0.121 | 0.122 |
39
+ | 2 | Interacting | 9 | 0.818 | 1.196 | 1.189 |
40
+ | 4 | Contact-free | 33 | 0.121 | 0.129 | 0.129 |
41
+ | 4 | Interacting | 17 | 0.873 | 1.225 | 1.214 |
42
+
43
+ On the interacting cases, the median paired slowdown for components is 45.5%
44
+ at N=2 and 39.9% at N=4. Components are slower on every interacting case.
45
+ Contact-free cases show a smaller median paired slowdown of 5.4% and 6.7%.
46
+ Summing all 50 per-case median times gives:
47
+
48
+ | N | Full sum (ms) | Active sum (ms) | Components sum (ms) |
49
+ | --- | ---: | ---: | ---: |
50
+ | 2 | 12.095 | 15.632 | 15.596 |
51
+ | 4 | 23.054 | 30.556 | 30.765 |
52
+
53
+ Thus the component implementation takes 28.9% and 33.4% more total time across
54
+ the two respective 50-case sets. This is a ratio of summed per-case medians,
55
+ distinct from the median paired slowdown above. All 6,300 timed solves succeeded.
56
+ The largest relative cost difference from the paired full solve is 4.27e-15;
57
+ the largest coordinate difference over 201 comparison times is 1.21e-7.
58
+
59
+ ## Why it is slower
60
+
61
+ Median accumulated stages over the interacting cases, in microseconds:
62
+
63
+ | N | Mode | Partition including UFDS | Assembly | Newton | Reconstruction | Validation | Collision search |
64
+ | --- | --- | ---: | ---: | ---: | ---: | ---: | ---: |
65
+ | 2 | Full | 0 | 69.5 | 468.6 | 41.5 | 111.3 | 77.0 |
66
+ | 2 | Active | 1.9 | 170.4 | 472.0 | 208.1 | 145.4 | 77.7 |
67
+ | 2 | Components | 4.0 | 170.0 | 470.9 | 208.1 | 145.0 | 75.8 |
68
+ | 4 | Full | 0 | 77.2 | 489.5 | 41.6 | 117.1 | 87.4 |
69
+ | 4 | Active | 1.9 | 174.0 | 470.7 | 215.0 | 152.0 | 84.8 |
70
+ | 4 | Components | 4.3 | 173.4 | 468.0 | 213.0 | 151.5 | 86.4 |
71
+
72
+ Recovery was unused. Other bookkeeping is separately logged, and medians need
73
+ not add to the median total. Partition timing includes union-find, grouping,
74
+ and sorting, so it bounds union-find alone from above. It is zero on all
75
+ contact-free runs and about four microseconds per interacting solve.
76
+
77
+ With N=2 there are no variables to eliminate once the two robots interact.
78
+ At N=4 the smaller Newton systems save little time. In both sizes, the extra
79
+ local system assembly, trajectory reconstruction, and bookkeeping outweigh that
80
+ saving. Graph construction contributes only about 1% of the added time in the
81
+ typical interacting case.
82
+
83
+ These measurements identify avoidable setup and reconstruction overhead in the
84
+ prototype. They do not establish a crossover for intermediate fleet sizes.
85
+ The subsequent implementation removes duplicate work and reuses unchanged
86
+ components without introducing any fleet-size thresholds.
87
+
88
+ ## Validation and reproduction
89
+
90
+ All 100 cases and three modes also passed separate physical checks outside the
91
+ timing runs: independent polynomial separation, endpoints, continuity, jerk
92
+ balance, contact geometry, and multiplier signs. This gives 300 independent
93
+ case/mode checks. All 6,300 timed records were checked for convergence, residuals,
94
+ continuous separation, paired cost/position differences, and timing accounting.
95
+ [Validation details](../validation/fleet-partitions-small-validation.json) record
96
+ the checker and solver source hashes.
97
+
98
+ From the SCI repository:
99
+
100
+ ```sh
101
+ PYTHONPATH=src python tools/benchmark_partitions.py \
102
+ --cases validation/fleet-small-inputs.json --repeats 21 --warmups 1 \
103
+ --output validation/fleet-partitions-small.json
104
+ gzip -n validation/fleet-partitions-small.json
105
+ ```
106
+
107
+ The original five-case N=20 benchmark remains the default when `--cases` is
108
+ omitted. The new input snapshot is portable and needs no local paper run files.
109
+
110
+ - [Raw runs, compressed JSON](../validation/fleet-partitions-small.json.gz): every
111
+ run, stage timing, root record, environment, and source/input hash.
112
+ - [Per-case CSV](../validation/fleet-partitions-small.csv): medians and quartiles
113
+ for all 300 case/mode combinations.
114
+ - [Aggregate summary](../validation/fleet-partitions-small-summary.json): the
115
+ grouped figures and paired slowdown calculations used above.
116
+
117
+ The benchmark's recorded Git commit is its pre-change base; source hashes
118
+ identify the benchmark script used in the working tree. Only benchmark tooling,
119
+ inputs, results, and documentation changed in this comparison.