articulatearena 0.1.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 (59) hide show
  1. articulatearena-0.1.0/LICENSE +21 -0
  2. articulatearena-0.1.0/PKG-INFO +287 -0
  3. articulatearena-0.1.0/README.md +249 -0
  4. articulatearena-0.1.0/pyproject.toml +69 -0
  5. articulatearena-0.1.0/setup.cfg +4 -0
  6. articulatearena-0.1.0/src/articulatearena/__init__.py +101 -0
  7. articulatearena-0.1.0/src/articulatearena/cli.py +292 -0
  8. articulatearena-0.1.0/src/articulatearena/config.py +79 -0
  9. articulatearena-0.1.0/src/articulatearena/configs/default.yaml +55 -0
  10. articulatearena-0.1.0/src/articulatearena/general_equation/__init__.py +0 -0
  11. articulatearena-0.1.0/src/articulatearena/general_equation/assignment.py +38 -0
  12. articulatearena-0.1.0/src/articulatearena/general_equation/configuration.py +100 -0
  13. articulatearena-0.1.0/src/articulatearena/general_equation/exp_map.py +91 -0
  14. articulatearena-0.1.0/src/articulatearena/general_equation/tree_edit.py +103 -0
  15. articulatearena-0.1.0/src/articulatearena/new_equation/E.py +121 -0
  16. articulatearena-0.1.0/src/articulatearena/new_equation/__init__.py +0 -0
  17. articulatearena-0.1.0/src/articulatearena/new_equation/continuous.py +156 -0
  18. articulatearena-0.1.0/src/articulatearena/new_equation/inner_product.py +142 -0
  19. articulatearena-0.1.0/src/articulatearena/new_equation/material_motion.py +152 -0
  20. articulatearena-0.1.0/src/articulatearena/new_equation/representation.py +193 -0
  21. articulatearena-0.1.0/src/articulatearena/new_equation/skeleton.py +82 -0
  22. articulatearena-0.1.0/src/articulatearena/new_equation/tree.py +335 -0
  23. articulatearena-0.1.0/src/articulatearena/previous_metrics/__init__.py +0 -0
  24. articulatearena-0.1.0/src/articulatearena/previous_metrics/component_scores.py +158 -0
  25. articulatearena-0.1.0/src/articulatearena/read/__init__.py +0 -0
  26. articulatearena-0.1.0/src/articulatearena/read/case_loader.py +113 -0
  27. articulatearena-0.1.0/src/articulatearena/read/config_loader.py +99 -0
  28. articulatearena-0.1.0/src/articulatearena/read/inertia_loader.py +150 -0
  29. articulatearena-0.1.0/src/articulatearena/read/urdf_loader.py +181 -0
  30. articulatearena-0.1.0/src/articulatearena/types.py +200 -0
  31. articulatearena-0.1.0/src/articulatearena/validation.py +41 -0
  32. articulatearena-0.1.0/src/articulatearena/write/__init__.py +0 -0
  33. articulatearena-0.1.0/src/articulatearena/write/report.py +131 -0
  34. articulatearena-0.1.0/src/articulatearena.egg-info/PKG-INFO +287 -0
  35. articulatearena-0.1.0/src/articulatearena.egg-info/SOURCES.txt +57 -0
  36. articulatearena-0.1.0/src/articulatearena.egg-info/dependency_links.txt +1 -0
  37. articulatearena-0.1.0/src/articulatearena.egg-info/entry_points.txt +2 -0
  38. articulatearena-0.1.0/src/articulatearena.egg-info/requires.txt +11 -0
  39. articulatearena-0.1.0/src/articulatearena.egg-info/top_level.txt +1 -0
  40. articulatearena-0.1.0/tests/test_axis_perturbation.py +80 -0
  41. articulatearena-0.1.0/tests/test_compactification.py +274 -0
  42. articulatearena-0.1.0/tests/test_config.py +106 -0
  43. articulatearena-0.1.0/tests/test_configuration.py +62 -0
  44. articulatearena-0.1.0/tests/test_errors_validation.py +89 -0
  45. articulatearena-0.1.0/tests/test_inertia_loader.py +52 -0
  46. articulatearena-0.1.0/tests/test_inner_products.py +161 -0
  47. articulatearena-0.1.0/tests/test_joint_metric.py +146 -0
  48. articulatearena-0.1.0/tests/test_limit_wrap.py +112 -0
  49. articulatearena-0.1.0/tests/test_matcher.py +156 -0
  50. articulatearena-0.1.0/tests/test_material_motion.py +155 -0
  51. articulatearena-0.1.0/tests/test_metric_properties.py +136 -0
  52. articulatearena-0.1.0/tests/test_numerical_sanity.py +184 -0
  53. articulatearena-0.1.0/tests/test_previous_metrics.py +113 -0
  54. articulatearena-0.1.0/tests/test_report.py +123 -0
  55. articulatearena-0.1.0/tests/test_training_loss.py +52 -0
  56. articulatearena-0.1.0/tests/test_tree_edit.py +48 -0
  57. articulatearena-0.1.0/tests/test_tree_metric.py +212 -0
  58. articulatearena-0.1.0/tests/test_types.py +207 -0
  59. articulatearena-0.1.0/tests/test_urdf_endpoint.py +111 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yumeng He
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,287 @@
1
+ Metadata-Version: 2.4
2
+ Name: articulatearena
3
+ Version: 0.1.0
4
+ Summary: A quotient metric on articulated kinematics via Lie-algebra endpoint twists
5
+ Author-email: Yumeng He <heyumeng0928@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://heyumeng.com/ArticulateArena-web/
8
+ Project-URL: Documentation, https://heyumeng.com/ArticulateArena-web/docs.html
9
+ Project-URL: Repository, https://github.com/YumengHe/ArticulateMetrics
10
+ Project-URL: Issues, https://github.com/YumengHe/ArticulateMetrics/issues
11
+ Project-URL: Paper, https://arxiv.org/abs/2609.33931
12
+ Keywords: articulated objects,kinematics,metric,evaluation,URDF,se(3),screw theory,robotics
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3 :: Only
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering
23
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Requires-Python: >=3.10
26
+ Description-Content-Type: text/markdown
27
+ License-File: LICENSE
28
+ Requires-Dist: numpy>=1.20.0
29
+ Requires-Dist: scipy>=1.7.0
30
+ Requires-Dist: pyyaml>=6.0
31
+ Provides-Extra: mesh
32
+ Requires-Dist: trimesh>=3.9.0; extra == "mesh"
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
35
+ Requires-Dist: build>=1.0; extra == "dev"
36
+ Requires-Dist: twine>=5.0; extra == "dev"
37
+ Dynamic: license-file
38
+
39
+ # ArticulateArena
40
+
41
+ [![PyPI](https://img.shields.io/pypi/v/articulatearena.svg)](https://pypi.org/project/articulatearena/)
42
+ [![Python](https://img.shields.io/pypi/pyversions/articulatearena.svg)](https://pypi.org/project/articulatearena/)
43
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/YumengHe/ArticulateMetrics/blob/main/LICENSE)
44
+ [![arXiv](https://img.shields.io/badge/arXiv-2609.33931-b31b1b.svg)](https://arxiv.org/abs/2609.33931)
45
+
46
+ A quotient metric on articulated kinematics via Lie-algebra endpoint twists.
47
+
48
+ `articulatearena` is the reference implementation of the metric **E** from the
49
+ paper *ArticulateArena: A Metric for Articulated Kinematics*. It gives a single
50
+ principled distance between a predicted articulated object and its ground
51
+ truth, in place of the hand-combined type / axis / origin / limit scores used by
52
+ earlier evaluation protocols.
53
+
54
+ A one-degree-of-freedom joint J = (ξ, [l⁻, l⁺]) with unit screw twist
55
+ ξ ∈ se(3) and limits l⁻, l⁺ is represented by its **unordered pair of endpoint
56
+ twists** {l⁻ξ, l⁺ξ}. The joint metric is the quotient distance of
57
+ se(3) × se(3) under the swap of the two endpoints:
58
+
59
+ ```
60
+ E(J1, J2) = min{ ‖z1⁻ − z2⁻‖² + ‖z1⁺ − z2⁺‖², ‖z1⁻ − z2⁺‖² + ‖z1⁺ − z2⁻‖² }^(1/2)
61
+ ```
62
+
63
+ where ‖·‖ is an se(3) norm that the caller injects. Because it is a quotient of
64
+ a flat space by a finite group of isometries, E is a genuine metric (symmetric,
65
+ with the triangle inequality), it is invariant to the sign of the axis and to
66
+ which limit is called "lower", and it is zero exactly when the two joints
67
+ produce the same motion.
68
+
69
+ The package provides:
70
+
71
+ - the joint metric `E` and a smooth, swap-invariant training surrogate;
72
+ - three injected se(3) norms: the dimensionless **split norm** (E_α), the
73
+ **kinetic-energy norm** of the moving link (E_B, in meters), and a general
74
+ Riemannian norm;
75
+ - the **radial compactification** ϕ that extends E continuously to unbounded
76
+ (continuous) joints (E_ϕ);
77
+ - two object-level lifts: an optimal-assignment **skeleton distance** with a
78
+ topology penalty, and a motion-aware **tree edit distance** (E_tree) with a
79
+ certified assignment relaxation;
80
+ - loaders for YAML configs and URDF files (axes resolved to the world frame by
81
+ forward kinematics), a JSON / Markdown report writer, and a command-line tool;
82
+ - the component scores of the earlier evaluation template, kept for baseline
83
+ comparisons.
84
+
85
+ Links: [project page](https://heyumeng.com/ArticulateArena-web/) ·
86
+ [documentation](https://heyumeng.com/ArticulateArena-web/docs.html) ·
87
+ [paper](https://arxiv.org/abs/2609.33931) ·
88
+ [source](https://github.com/YumengHe/ArticulateMetrics)
89
+
90
+ ## Installation
91
+
92
+ ```bash
93
+ pip install articulatearena
94
+ ```
95
+
96
+ Python 3.10 or newer. The core depends only on `numpy`, `scipy` and `pyyaml`.
97
+ Two optional extras exist:
98
+
99
+ ```bash
100
+ pip install "articulatearena[mesh]" # trimesh, for mesh-derived link inertia (kinetic-energy norm)
101
+ pip install "articulatearena[dev]" # pytest, build, twine
102
+ ```
103
+
104
+ From a source checkout:
105
+
106
+ ```bash
107
+ git clone https://github.com/YumengHe/ArticulateMetrics.git
108
+ cd ArticulateMetrics
109
+ pip install -e ".[dev]"
110
+ pytest
111
+ ```
112
+
113
+ ## Quickstart
114
+
115
+ Build the endpoint pairs of two joints, pick an se(3) norm, and evaluate
116
+ `joint_metric`. The distance is `0` exactly when the two joints induce the same
117
+ motion.
118
+
119
+ ```python
120
+ import numpy as np
121
+ from articulatearena import joint_metric, make_endpoint_pair, make_split_norm
122
+
123
+ xi = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]) # unit screw twist (omega; nu)
124
+ gt = make_endpoint_pair(xi, 0.0, 1.0) # joint limits [a, b]
125
+ pred = make_endpoint_pair(xi, 0.0, 1.2)
126
+
127
+ norm = make_split_norm(alpha=1.0) # the norm is injected, never hardcoded
128
+ E = joint_metric(gt.u.data, gt.v.data, pred.u.data, pred.v.data, norm)
129
+ print(E) # 0.2 (only the upper endpoint moved by 0.2 along the axis)
130
+ ```
131
+
132
+ Reversing the axis and negating the limits describes the same motion, and E
133
+ sees that without any special casing:
134
+
135
+ ```python
136
+ flipped = make_endpoint_pair(-xi, -1.0, 0.0)
137
+ print(joint_metric(gt.u.data, gt.v.data, flipped.u.data, flipped.v.data, norm)) # 0.0
138
+ ```
139
+
140
+ To score real URDF joints, load them through the reader layer instead of
141
+ constructing twists by hand. The loader resolves each joint's axis into the
142
+ world frame with forward kinematics at the zero configuration:
143
+
144
+ ```python
145
+ from articulatearena import load_joint, joint_to_endpoint_pair
146
+
147
+ joint = load_joint("sample_data/45168/mobility.urdf", "joint_0")
148
+ pair = joint_to_endpoint_pair(joint) # unordered {a*xi, b*xi}
149
+ ```
150
+
151
+ Continuous (unbounded) joints have no finite endpoint pair. Compare them, or
152
+ mix them with bounded joints, through the compactified metric E_ϕ:
153
+
154
+ ```python
155
+ import math
156
+ from articulatearena import compactified_joint_metric, continuous_joint_repr, radial_compactify
157
+
158
+ kappa = math.pi # benchmark setting
159
+
160
+ # two bounded joints: E after mapping all four endpoints into the unit ball
161
+ E_phi = compactified_joint_metric(gt.u.data, gt.v.data, pred.u.data, pred.v.data, norm, kappa)
162
+
163
+ # a bounded joint against a continuous joint about the same axis
164
+ gt_ball = [radial_compactify(z, kappa, norm) for z in (gt.u.data, gt.v.data)]
165
+ u2, v2 = continuous_joint_repr(xi, norm) # antipodal boundary pair, already in the closed ball
166
+ E_phi = joint_metric(*gt_ball, u2, v2, norm)
167
+ ```
168
+
169
+ Whole kinematic trees are compared with the assignment relaxation of the tree
170
+ edit distance, `assignment_tree_distance`, or the exact `exact_tree_distance`
171
+ when a certificate is wanted. See the
172
+ [documentation](https://heyumeng.com/ArticulateArena-web/docs.html) for the
173
+ full API and a tutorial on the underlying concepts.
174
+
175
+ ## Command line
176
+
177
+ The `articulatearena` command loads a config, runs the pure metric functions
178
+ and writes a JSON report:
179
+
180
+ ```bash
181
+ articulatearena # built-in demo joints, report under ./out
182
+ articulatearena configs/default.yaml # the URDF case named in the config (run from the repo root)
183
+ articulatearena my_config.yaml --data joints.json --output out
184
+ ```
185
+
186
+ `--data` points at a JSON file with top-level `prediction` and `ground_truth`
187
+ lists, each joint being `{"xi_hat": [6 numbers], "a": lower, "b": upper}`.
188
+
189
+ The config groups the knobs that the paper treats as caller-supplied
190
+ (`inner_product.form` and `split_norm_alpha`, `compactification.kappa`,
191
+ `skeleton.lambda_topology`, the aggregation form and its smoothing ε) plus an
192
+ optional evaluation case (`prediction` / `ground_truth`, each a URDF path and a
193
+ joint name). The packaged default lives at
194
+ [`src/articulatearena/configs/default.yaml`](https://github.com/YumengHe/ArticulateMetrics/blob/main/src/articulatearena/configs/default.yaml).
195
+ Anything left `null` has to be provided before use. `split_norm_alpha` is a
196
+ characteristic inverse length. Set it to match your length units before
197
+ comparing across methods.
198
+
199
+ In a source checkout the same entry point runs without installing:
200
+
201
+ ```bash
202
+ python scripts/run.py configs/default.yaml
203
+ ```
204
+
205
+ ## Package layout
206
+
207
+ ```
208
+ articulatearena/
209
+ types.py, config.py, validation.py # typed data models, flat Config, input checks
210
+ new_equation/ # the paper's formulas
211
+ representation.py # screw twist, endpoint pair {a*xi, b*xi}, revolute wrap
212
+ E.py # joint_metric E and the smooth training_loss
213
+ inner_product.py # split / kinetic-energy / Riemannian norms
214
+ continuous.py # radial compactification, continuous joints, E_phi
215
+ tree.py # motion-aware tree edit distance and its relaxation
216
+ skeleton.py # assignment skeleton distance with topology penalty
217
+ material_motion.py # finite-displacement material discrepancy (calibration)
218
+ general_equation/ # background math: se(3) exp map, Hungarian assignment,
219
+ # Zhang-Shasha tree edit, L2 configuration difference
220
+ previous_metrics/ # baseline component scores of the earlier template
221
+ read/ # config, URDF, evaluation-case and mesh-inertia loaders
222
+ write/ # JSON / Markdown report generation
223
+ cli.py # the `articulatearena` command
224
+ configs/default.yaml # packaged default configuration
225
+ ```
226
+
227
+ All metric functions are pure: norms, weights and tolerances are passed in by
228
+ the caller and never hardcoded, and nothing in the metric layer reads or writes
229
+ files.
230
+
231
+ ## Scope and conventions
232
+
233
+ - One-DOF joints only: revolute, prismatic, continuous and helical. Multi-DOF
234
+ joints are out of scope.
235
+ - Twists are `(6,)` float64 arrays with the angular part first. Revolute and
236
+ helical joints use a unit angular part, prismatic joints a unit linear part.
237
+ - Coordinate frames and units are the caller's responsibility. All inputs
238
+ must share one reference frame, and `alpha` must match the length unit.
239
+ - `E` is not normalized. Only compare values produced under the same norm and
240
+ the same `alpha` or inertia.
241
+
242
+ ## Repository contents beyond the package
243
+
244
+ The [GitHub repository](https://github.com/YumengHe/ArticulateMetrics) also
245
+ holds material that is not part of the wheel:
246
+
247
+ - [`examples/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/examples):
248
+ end-to-end runs on real PartNet-Mobility objects.
249
+ - [`scripts/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/scripts):
250
+ the benchmark driver for the baseline component scores and a generator of
251
+ controlled error cases.
252
+ - [`render/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/render):
253
+ URDF and joint-motion renderers used for the figures (see its README).
254
+ - [`ablation/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/ablation):
255
+ the parameter ablations of the paper.
256
+ - [`docs/IMPLEMENTATION.md`](https://github.com/YumengHe/ArticulateMetrics/blob/main/docs/IMPLEMENTATION.md):
257
+ formula coverage and test strategy, and
258
+ [`docs/RELEASING.md`](https://github.com/YumengHe/ArticulateMetrics/blob/main/docs/RELEASING.md)
259
+ for the release procedure.
260
+
261
+ ## Tests
262
+
263
+ ```bash
264
+ pytest
265
+ ```
266
+
267
+ Every formula has a dedicated test class with hand-computed values, metric
268
+ properties (identity, symmetry, triangle inequality, swap invariance) and edge
269
+ cases. `examples/run_real_cases.py` adds real-data invariants: zero
270
+ self-distance, zero assignment cost against an identical copy, and monotone
271
+ growth of E under axis perturbation.
272
+
273
+ ## Citation
274
+
275
+ ```bibtex
276
+ @article{he2026articulatearena,
277
+ title = {ArticulateArena: A Metric for Articulated Kinematics},
278
+ author = {He, Yumeng and She, Yongfei and Chen, Huanyu and Yuan, Chun and Li, Peihao
279
+ and Masterjohn, Joseph and Yang, Yin and Jiang, Ying and Jiang, Chenfanfu},
280
+ journal = {arXiv preprint arXiv:2609.33931},
281
+ year = {2026}
282
+ }
283
+ ```
284
+
285
+ ## License
286
+
287
+ MIT. See [LICENSE](https://github.com/YumengHe/ArticulateMetrics/blob/main/LICENSE).
@@ -0,0 +1,249 @@
1
+ # ArticulateArena
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/articulatearena.svg)](https://pypi.org/project/articulatearena/)
4
+ [![Python](https://img.shields.io/pypi/pyversions/articulatearena.svg)](https://pypi.org/project/articulatearena/)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/YumengHe/ArticulateMetrics/blob/main/LICENSE)
6
+ [![arXiv](https://img.shields.io/badge/arXiv-2609.33931-b31b1b.svg)](https://arxiv.org/abs/2609.33931)
7
+
8
+ A quotient metric on articulated kinematics via Lie-algebra endpoint twists.
9
+
10
+ `articulatearena` is the reference implementation of the metric **E** from the
11
+ paper *ArticulateArena: A Metric for Articulated Kinematics*. It gives a single
12
+ principled distance between a predicted articulated object and its ground
13
+ truth, in place of the hand-combined type / axis / origin / limit scores used by
14
+ earlier evaluation protocols.
15
+
16
+ A one-degree-of-freedom joint J = (ξ, [l⁻, l⁺]) with unit screw twist
17
+ ξ ∈ se(3) and limits l⁻, l⁺ is represented by its **unordered pair of endpoint
18
+ twists** {l⁻ξ, l⁺ξ}. The joint metric is the quotient distance of
19
+ se(3) × se(3) under the swap of the two endpoints:
20
+
21
+ ```
22
+ E(J1, J2) = min{ ‖z1⁻ − z2⁻‖² + ‖z1⁺ − z2⁺‖², ‖z1⁻ − z2⁺‖² + ‖z1⁺ − z2⁻‖² }^(1/2)
23
+ ```
24
+
25
+ where ‖·‖ is an se(3) norm that the caller injects. Because it is a quotient of
26
+ a flat space by a finite group of isometries, E is a genuine metric (symmetric,
27
+ with the triangle inequality), it is invariant to the sign of the axis and to
28
+ which limit is called "lower", and it is zero exactly when the two joints
29
+ produce the same motion.
30
+
31
+ The package provides:
32
+
33
+ - the joint metric `E` and a smooth, swap-invariant training surrogate;
34
+ - three injected se(3) norms: the dimensionless **split norm** (E_α), the
35
+ **kinetic-energy norm** of the moving link (E_B, in meters), and a general
36
+ Riemannian norm;
37
+ - the **radial compactification** ϕ that extends E continuously to unbounded
38
+ (continuous) joints (E_ϕ);
39
+ - two object-level lifts: an optimal-assignment **skeleton distance** with a
40
+ topology penalty, and a motion-aware **tree edit distance** (E_tree) with a
41
+ certified assignment relaxation;
42
+ - loaders for YAML configs and URDF files (axes resolved to the world frame by
43
+ forward kinematics), a JSON / Markdown report writer, and a command-line tool;
44
+ - the component scores of the earlier evaluation template, kept for baseline
45
+ comparisons.
46
+
47
+ Links: [project page](https://heyumeng.com/ArticulateArena-web/) ·
48
+ [documentation](https://heyumeng.com/ArticulateArena-web/docs.html) ·
49
+ [paper](https://arxiv.org/abs/2609.33931) ·
50
+ [source](https://github.com/YumengHe/ArticulateMetrics)
51
+
52
+ ## Installation
53
+
54
+ ```bash
55
+ pip install articulatearena
56
+ ```
57
+
58
+ Python 3.10 or newer. The core depends only on `numpy`, `scipy` and `pyyaml`.
59
+ Two optional extras exist:
60
+
61
+ ```bash
62
+ pip install "articulatearena[mesh]" # trimesh, for mesh-derived link inertia (kinetic-energy norm)
63
+ pip install "articulatearena[dev]" # pytest, build, twine
64
+ ```
65
+
66
+ From a source checkout:
67
+
68
+ ```bash
69
+ git clone https://github.com/YumengHe/ArticulateMetrics.git
70
+ cd ArticulateMetrics
71
+ pip install -e ".[dev]"
72
+ pytest
73
+ ```
74
+
75
+ ## Quickstart
76
+
77
+ Build the endpoint pairs of two joints, pick an se(3) norm, and evaluate
78
+ `joint_metric`. The distance is `0` exactly when the two joints induce the same
79
+ motion.
80
+
81
+ ```python
82
+ import numpy as np
83
+ from articulatearena import joint_metric, make_endpoint_pair, make_split_norm
84
+
85
+ xi = np.array([0.0, 0.0, 1.0, 0.0, 0.0, 0.0]) # unit screw twist (omega; nu)
86
+ gt = make_endpoint_pair(xi, 0.0, 1.0) # joint limits [a, b]
87
+ pred = make_endpoint_pair(xi, 0.0, 1.2)
88
+
89
+ norm = make_split_norm(alpha=1.0) # the norm is injected, never hardcoded
90
+ E = joint_metric(gt.u.data, gt.v.data, pred.u.data, pred.v.data, norm)
91
+ print(E) # 0.2 (only the upper endpoint moved by 0.2 along the axis)
92
+ ```
93
+
94
+ Reversing the axis and negating the limits describes the same motion, and E
95
+ sees that without any special casing:
96
+
97
+ ```python
98
+ flipped = make_endpoint_pair(-xi, -1.0, 0.0)
99
+ print(joint_metric(gt.u.data, gt.v.data, flipped.u.data, flipped.v.data, norm)) # 0.0
100
+ ```
101
+
102
+ To score real URDF joints, load them through the reader layer instead of
103
+ constructing twists by hand. The loader resolves each joint's axis into the
104
+ world frame with forward kinematics at the zero configuration:
105
+
106
+ ```python
107
+ from articulatearena import load_joint, joint_to_endpoint_pair
108
+
109
+ joint = load_joint("sample_data/45168/mobility.urdf", "joint_0")
110
+ pair = joint_to_endpoint_pair(joint) # unordered {a*xi, b*xi}
111
+ ```
112
+
113
+ Continuous (unbounded) joints have no finite endpoint pair. Compare them, or
114
+ mix them with bounded joints, through the compactified metric E_ϕ:
115
+
116
+ ```python
117
+ import math
118
+ from articulatearena import compactified_joint_metric, continuous_joint_repr, radial_compactify
119
+
120
+ kappa = math.pi # benchmark setting
121
+
122
+ # two bounded joints: E after mapping all four endpoints into the unit ball
123
+ E_phi = compactified_joint_metric(gt.u.data, gt.v.data, pred.u.data, pred.v.data, norm, kappa)
124
+
125
+ # a bounded joint against a continuous joint about the same axis
126
+ gt_ball = [radial_compactify(z, kappa, norm) for z in (gt.u.data, gt.v.data)]
127
+ u2, v2 = continuous_joint_repr(xi, norm) # antipodal boundary pair, already in the closed ball
128
+ E_phi = joint_metric(*gt_ball, u2, v2, norm)
129
+ ```
130
+
131
+ Whole kinematic trees are compared with the assignment relaxation of the tree
132
+ edit distance, `assignment_tree_distance`, or the exact `exact_tree_distance`
133
+ when a certificate is wanted. See the
134
+ [documentation](https://heyumeng.com/ArticulateArena-web/docs.html) for the
135
+ full API and a tutorial on the underlying concepts.
136
+
137
+ ## Command line
138
+
139
+ The `articulatearena` command loads a config, runs the pure metric functions
140
+ and writes a JSON report:
141
+
142
+ ```bash
143
+ articulatearena # built-in demo joints, report under ./out
144
+ articulatearena configs/default.yaml # the URDF case named in the config (run from the repo root)
145
+ articulatearena my_config.yaml --data joints.json --output out
146
+ ```
147
+
148
+ `--data` points at a JSON file with top-level `prediction` and `ground_truth`
149
+ lists, each joint being `{"xi_hat": [6 numbers], "a": lower, "b": upper}`.
150
+
151
+ The config groups the knobs that the paper treats as caller-supplied
152
+ (`inner_product.form` and `split_norm_alpha`, `compactification.kappa`,
153
+ `skeleton.lambda_topology`, the aggregation form and its smoothing ε) plus an
154
+ optional evaluation case (`prediction` / `ground_truth`, each a URDF path and a
155
+ joint name). The packaged default lives at
156
+ [`src/articulatearena/configs/default.yaml`](https://github.com/YumengHe/ArticulateMetrics/blob/main/src/articulatearena/configs/default.yaml).
157
+ Anything left `null` has to be provided before use. `split_norm_alpha` is a
158
+ characteristic inverse length. Set it to match your length units before
159
+ comparing across methods.
160
+
161
+ In a source checkout the same entry point runs without installing:
162
+
163
+ ```bash
164
+ python scripts/run.py configs/default.yaml
165
+ ```
166
+
167
+ ## Package layout
168
+
169
+ ```
170
+ articulatearena/
171
+ types.py, config.py, validation.py # typed data models, flat Config, input checks
172
+ new_equation/ # the paper's formulas
173
+ representation.py # screw twist, endpoint pair {a*xi, b*xi}, revolute wrap
174
+ E.py # joint_metric E and the smooth training_loss
175
+ inner_product.py # split / kinetic-energy / Riemannian norms
176
+ continuous.py # radial compactification, continuous joints, E_phi
177
+ tree.py # motion-aware tree edit distance and its relaxation
178
+ skeleton.py # assignment skeleton distance with topology penalty
179
+ material_motion.py # finite-displacement material discrepancy (calibration)
180
+ general_equation/ # background math: se(3) exp map, Hungarian assignment,
181
+ # Zhang-Shasha tree edit, L2 configuration difference
182
+ previous_metrics/ # baseline component scores of the earlier template
183
+ read/ # config, URDF, evaluation-case and mesh-inertia loaders
184
+ write/ # JSON / Markdown report generation
185
+ cli.py # the `articulatearena` command
186
+ configs/default.yaml # packaged default configuration
187
+ ```
188
+
189
+ All metric functions are pure: norms, weights and tolerances are passed in by
190
+ the caller and never hardcoded, and nothing in the metric layer reads or writes
191
+ files.
192
+
193
+ ## Scope and conventions
194
+
195
+ - One-DOF joints only: revolute, prismatic, continuous and helical. Multi-DOF
196
+ joints are out of scope.
197
+ - Twists are `(6,)` float64 arrays with the angular part first. Revolute and
198
+ helical joints use a unit angular part, prismatic joints a unit linear part.
199
+ - Coordinate frames and units are the caller's responsibility. All inputs
200
+ must share one reference frame, and `alpha` must match the length unit.
201
+ - `E` is not normalized. Only compare values produced under the same norm and
202
+ the same `alpha` or inertia.
203
+
204
+ ## Repository contents beyond the package
205
+
206
+ The [GitHub repository](https://github.com/YumengHe/ArticulateMetrics) also
207
+ holds material that is not part of the wheel:
208
+
209
+ - [`examples/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/examples):
210
+ end-to-end runs on real PartNet-Mobility objects.
211
+ - [`scripts/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/scripts):
212
+ the benchmark driver for the baseline component scores and a generator of
213
+ controlled error cases.
214
+ - [`render/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/render):
215
+ URDF and joint-motion renderers used for the figures (see its README).
216
+ - [`ablation/`](https://github.com/YumengHe/ArticulateMetrics/tree/main/ablation):
217
+ the parameter ablations of the paper.
218
+ - [`docs/IMPLEMENTATION.md`](https://github.com/YumengHe/ArticulateMetrics/blob/main/docs/IMPLEMENTATION.md):
219
+ formula coverage and test strategy, and
220
+ [`docs/RELEASING.md`](https://github.com/YumengHe/ArticulateMetrics/blob/main/docs/RELEASING.md)
221
+ for the release procedure.
222
+
223
+ ## Tests
224
+
225
+ ```bash
226
+ pytest
227
+ ```
228
+
229
+ Every formula has a dedicated test class with hand-computed values, metric
230
+ properties (identity, symmetry, triangle inequality, swap invariance) and edge
231
+ cases. `examples/run_real_cases.py` adds real-data invariants: zero
232
+ self-distance, zero assignment cost against an identical copy, and monotone
233
+ growth of E under axis perturbation.
234
+
235
+ ## Citation
236
+
237
+ ```bibtex
238
+ @article{he2026articulatearena,
239
+ title = {ArticulateArena: A Metric for Articulated Kinematics},
240
+ author = {He, Yumeng and She, Yongfei and Chen, Huanyu and Yuan, Chun and Li, Peihao
241
+ and Masterjohn, Joseph and Yang, Yin and Jiang, Ying and Jiang, Chenfanfu},
242
+ journal = {arXiv preprint arXiv:2609.33931},
243
+ year = {2026}
244
+ }
245
+ ```
246
+
247
+ ## License
248
+
249
+ MIT. See [LICENSE](https://github.com/YumengHe/ArticulateMetrics/blob/main/LICENSE).
@@ -0,0 +1,69 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "articulatearena"
7
+ dynamic = ["version"]
8
+ description = "A quotient metric on articulated kinematics via Lie-algebra endpoint twists"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ authors = [{ name = "Yumeng He", email = "heyumeng0928@gmail.com" }]
13
+ requires-python = ">=3.10"
14
+ keywords = [
15
+ "articulated objects",
16
+ "kinematics",
17
+ "metric",
18
+ "evaluation",
19
+ "URDF",
20
+ "se(3)",
21
+ "screw theory",
22
+ "robotics",
23
+ ]
24
+ classifiers = [
25
+ "Development Status :: 4 - Beta",
26
+ "Intended Audience :: Science/Research",
27
+ "Operating System :: OS Independent",
28
+ "Programming Language :: Python :: 3",
29
+ "Programming Language :: Python :: 3 :: Only",
30
+ "Programming Language :: Python :: 3.10",
31
+ "Programming Language :: Python :: 3.11",
32
+ "Programming Language :: Python :: 3.12",
33
+ "Programming Language :: Python :: 3.13",
34
+ "Topic :: Scientific/Engineering",
35
+ "Topic :: Scientific/Engineering :: Mathematics",
36
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
37
+ ]
38
+ dependencies = [
39
+ "numpy>=1.20.0",
40
+ "scipy>=1.7.0",
41
+ "pyyaml>=6.0",
42
+ ]
43
+
44
+ [project.optional-dependencies]
45
+ # Mesh-derived link inertia for the kinetic-energy norm (read/inertia_loader.py).
46
+ mesh = ["trimesh>=3.9.0"]
47
+ dev = ["pytest>=7.0.0", "build>=1.0", "twine>=5.0"]
48
+
49
+ [project.urls]
50
+ Homepage = "https://heyumeng.com/ArticulateArena-web/"
51
+ Documentation = "https://heyumeng.com/ArticulateArena-web/docs.html"
52
+ Repository = "https://github.com/YumengHe/ArticulateMetrics"
53
+ Issues = "https://github.com/YumengHe/ArticulateMetrics/issues"
54
+ Paper = "https://arxiv.org/abs/2609.33931"
55
+
56
+ [project.scripts]
57
+ articulatearena = "articulatearena.cli:run"
58
+
59
+ [tool.setuptools.packages.find]
60
+ where = ["src"]
61
+
62
+ [tool.setuptools.package-data]
63
+ articulatearena = ["configs/*.yaml"]
64
+
65
+ [tool.setuptools.dynamic]
66
+ version = { attr = "articulatearena.__version__" }
67
+
68
+ [tool.pytest.ini_options]
69
+ testpaths = ["tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+