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.
- articulatearena-0.1.0/LICENSE +21 -0
- articulatearena-0.1.0/PKG-INFO +287 -0
- articulatearena-0.1.0/README.md +249 -0
- articulatearena-0.1.0/pyproject.toml +69 -0
- articulatearena-0.1.0/setup.cfg +4 -0
- articulatearena-0.1.0/src/articulatearena/__init__.py +101 -0
- articulatearena-0.1.0/src/articulatearena/cli.py +292 -0
- articulatearena-0.1.0/src/articulatearena/config.py +79 -0
- articulatearena-0.1.0/src/articulatearena/configs/default.yaml +55 -0
- articulatearena-0.1.0/src/articulatearena/general_equation/__init__.py +0 -0
- articulatearena-0.1.0/src/articulatearena/general_equation/assignment.py +38 -0
- articulatearena-0.1.0/src/articulatearena/general_equation/configuration.py +100 -0
- articulatearena-0.1.0/src/articulatearena/general_equation/exp_map.py +91 -0
- articulatearena-0.1.0/src/articulatearena/general_equation/tree_edit.py +103 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/E.py +121 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/__init__.py +0 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/continuous.py +156 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/inner_product.py +142 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/material_motion.py +152 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/representation.py +193 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/skeleton.py +82 -0
- articulatearena-0.1.0/src/articulatearena/new_equation/tree.py +335 -0
- articulatearena-0.1.0/src/articulatearena/previous_metrics/__init__.py +0 -0
- articulatearena-0.1.0/src/articulatearena/previous_metrics/component_scores.py +158 -0
- articulatearena-0.1.0/src/articulatearena/read/__init__.py +0 -0
- articulatearena-0.1.0/src/articulatearena/read/case_loader.py +113 -0
- articulatearena-0.1.0/src/articulatearena/read/config_loader.py +99 -0
- articulatearena-0.1.0/src/articulatearena/read/inertia_loader.py +150 -0
- articulatearena-0.1.0/src/articulatearena/read/urdf_loader.py +181 -0
- articulatearena-0.1.0/src/articulatearena/types.py +200 -0
- articulatearena-0.1.0/src/articulatearena/validation.py +41 -0
- articulatearena-0.1.0/src/articulatearena/write/__init__.py +0 -0
- articulatearena-0.1.0/src/articulatearena/write/report.py +131 -0
- articulatearena-0.1.0/src/articulatearena.egg-info/PKG-INFO +287 -0
- articulatearena-0.1.0/src/articulatearena.egg-info/SOURCES.txt +57 -0
- articulatearena-0.1.0/src/articulatearena.egg-info/dependency_links.txt +1 -0
- articulatearena-0.1.0/src/articulatearena.egg-info/entry_points.txt +2 -0
- articulatearena-0.1.0/src/articulatearena.egg-info/requires.txt +11 -0
- articulatearena-0.1.0/src/articulatearena.egg-info/top_level.txt +1 -0
- articulatearena-0.1.0/tests/test_axis_perturbation.py +80 -0
- articulatearena-0.1.0/tests/test_compactification.py +274 -0
- articulatearena-0.1.0/tests/test_config.py +106 -0
- articulatearena-0.1.0/tests/test_configuration.py +62 -0
- articulatearena-0.1.0/tests/test_errors_validation.py +89 -0
- articulatearena-0.1.0/tests/test_inertia_loader.py +52 -0
- articulatearena-0.1.0/tests/test_inner_products.py +161 -0
- articulatearena-0.1.0/tests/test_joint_metric.py +146 -0
- articulatearena-0.1.0/tests/test_limit_wrap.py +112 -0
- articulatearena-0.1.0/tests/test_matcher.py +156 -0
- articulatearena-0.1.0/tests/test_material_motion.py +155 -0
- articulatearena-0.1.0/tests/test_metric_properties.py +136 -0
- articulatearena-0.1.0/tests/test_numerical_sanity.py +184 -0
- articulatearena-0.1.0/tests/test_previous_metrics.py +113 -0
- articulatearena-0.1.0/tests/test_report.py +123 -0
- articulatearena-0.1.0/tests/test_training_loss.py +52 -0
- articulatearena-0.1.0/tests/test_tree_edit.py +48 -0
- articulatearena-0.1.0/tests/test_tree_metric.py +212 -0
- articulatearena-0.1.0/tests/test_types.py +207 -0
- 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
|
+
[](https://pypi.org/project/articulatearena/)
|
|
42
|
+
[](https://pypi.org/project/articulatearena/)
|
|
43
|
+
[](https://github.com/YumengHe/ArticulateMetrics/blob/main/LICENSE)
|
|
44
|
+
[](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
|
+
[](https://pypi.org/project/articulatearena/)
|
|
4
|
+
[](https://pypi.org/project/articulatearena/)
|
|
5
|
+
[](https://github.com/YumengHe/ArticulateMetrics/blob/main/LICENSE)
|
|
6
|
+
[](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"]
|