dsfb_robotics/datasets/mod.rs
1//! Per-dataset residual adapters.
2//!
3//! Each adapter is a `no_std` + `no_alloc` pure function that
4//! converts a dataset-specific raw-sample type into a scalar residual
5//! norm `‖r(k)‖`. The scalar stream is then fed into the canonical
6//! [`crate::engine::DsfbRoboticsEngine`] / [`crate::observe`] pipeline
7//! unchanged.
8//!
9//! ## Dataset index (companion paper §10)
10//!
11//! | Module | Family | Residual form | Notes |
12//! |---|---|---|---|
13//! | `cwru` | PHM (bearing) | `\|E_{BPFI}(k) − μ_healthy\|` | Spectral-envelope deviation |
14//! | `ims` | PHM (bearing) | `\|HI(k) − HI_nominal\|` | Health-index trajectory |
15//! | `kuka_lwr` | Kinematics | `‖ddq − ddq_nominal‖` | Kinematic-residual variant, Simionato 7R |
16//! | `femto_st` | PHM (bearing) | `\|vib-HI(k) − HI_calib\|` | PRONOSTIA |
17//! | `panda_gaz` | Kinematics | `‖τ_meas − τ_pred(θ̂_panda)‖` | Literal Gaz-cpp model, Gaz 2019 |
18//! | `dlr_justin` | Kinematics | `‖τ_meas − τ_interp‖` | Literal Giacomuzzo Zenodo τ_interp |
19//! | `ur10_kufieta` | Kinematics | `‖τ_meas − τ_RNEA(URSim)‖` | Literal pinocchio RNEA, Polydoros 2015 |
20//! | `cheetah3` | Balancing | `combine(r_F, r_ξ)` | Quadruped MPC + CoM |
21//! | `icub_pushrecovery` | Balancing | `combine(r_W, r_ξ)` | Humanoid WBC + centroidal |
22
23pub mod cwru;
24pub mod ims;
25pub mod kuka_lwr;
26pub mod femto_st;
27pub mod panda_gaz;
28pub mod dlr_justin;
29pub mod ur10_kufieta;
30pub mod cheetah3;
31pub mod icub_pushrecovery;
32pub mod droid;
33pub mod openx;
34pub mod anymal_parkour;
35pub mod unitree_g1;
36pub mod aloha_static;
37pub mod icub3_sorrentino;
38pub mod mobile_aloha;
39pub mod so100;
40pub mod aloha_static_tape;
41pub mod aloha_static_screw_driver;
42pub mod aloha_static_pingpong_test;
43
44/// Stable dataset identifier, used in `paper-lock` subcommand dispatch
45/// and per-dataset audit artefacts.
46#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48pub enum DatasetId {
49 /// Case Western Reserve University bearing dataset.
50 Cwru,
51 /// IMS run-to-failure bearing dataset (NASA PCoE).
52 Ims,
53 /// KUKA LWR-IV+ joint-space identification (Simionato 7R / Jubien 2014 lineage).
54 KukaLwr,
55 /// FEMTO-ST PRONOSTIA accelerated bearing degradation (IEEE PHM 2012).
56 FemtoSt,
57 /// Franka Emika Panda dynamic identification (Gaz et al. 2019).
58 PandaGaz,
59 /// DLR-class 7-DoF Panda measurement-vs-model torque corpus (Giacomuzzo et al. 2024, Zenodo 12516500).
60 DlrJustin,
61 /// Universal Robots UR10 pick-and-place torque identification (Polydoros et al. IROS 2015).
62 Ur10Kufieta,
63 /// MIT Mini-Cheetah locomotion open logs (Katz–Di Carlo–Kim 2019; UMich-CURLY dataset).
64 Cheetah3,
65 /// ergoCub humanoid push-recovery experiment (Romualdi–Viceconte 2024, ami-iit).
66 IcubPushRecovery,
67 /// DROID distributed robot manipulation dataset (Khazatsky et al. 2024, Stanford/TRI).
68 Droid,
69 /// Open X-Embodiment cross-robot manipulation corpus (RT-X 2024 collaboration).
70 Openx,
71 /// ANYmal-C parkour locomotion in the wild (Miki et al., Science Robotics 2022).
72 AnymalParkour,
73 /// Unitree G1 humanoid teleoperation (Makolon0321 / `unitree_g1_block_stack`).
74 UnitreeG1,
75 /// ALOHA bimanual static teleoperation (Zhao et al. 2023, LeRobot
76 /// aloha_static_coffee corpus) — real physical ALOHA hardware.
77 AlohaStatic,
78 /// ergoCub Sorrentino balancing-torque-control (ami-iit RAL 2025).
79 Icub3Sorrentino,
80 /// Mobile ALOHA wipe-wine (Fu, Zhao, Finn 2024, Stanford).
81 MobileAloha,
82 /// SO-ARM100 pick-and-place (HuggingFace LeRobot, Apache-2.0).
83 So100,
84 /// ALOHA static tape attachment (LeRobot real bimanual).
85 AlohaStaticTape,
86 /// ALOHA static screw-driver tool-use (LeRobot real bimanual).
87 AlohaStaticScrewDriver,
88 /// ALOHA static ping-pong rhythmic transfer (LeRobot real bimanual).
89 AlohaStaticPingpongTest,
90}
91
92impl DatasetId {
93 /// Stable short identifier (kebab-style) for CLI + filesystem use.
94 #[inline]
95 #[must_use]
96 pub const fn slug(self) -> &'static str {
97 match self {
98 Self::Cwru => "cwru",
99 Self::Ims => "ims",
100 Self::KukaLwr => "kuka_lwr",
101 Self::FemtoSt => "femto_st",
102 Self::PandaGaz => "panda_gaz",
103 Self::DlrJustin => "dlr_justin",
104 Self::Ur10Kufieta => "ur10_kufieta",
105 Self::Cheetah3 => "cheetah3",
106 Self::IcubPushRecovery => "icub_pushrecovery",
107 Self::Droid => "droid",
108 Self::Openx => "openx",
109 Self::AnymalParkour => "anymal_parkour",
110 Self::UnitreeG1 => "unitree_g1",
111 Self::AlohaStatic => "aloha_static",
112 Self::Icub3Sorrentino => "icub3_sorrentino",
113 Self::MobileAloha => "mobile_aloha",
114 Self::So100 => "so100",
115 Self::AlohaStaticTape => "aloha_static_tape",
116 Self::AlohaStaticScrewDriver => "aloha_static_screw_driver",
117 Self::AlohaStaticPingpongTest => "aloha_static_pingpong_test",
118 }
119 }
120
121 /// Parse a slug back into a `DatasetId`. Returns `None` for unknowns.
122 ///
123 /// Safe-state policy: the `_ => None` arm is the explicit,
124 /// documented fallback state. Unknown slugs are treated as "not a
125 /// supported dataset" rather than coerced to a default or panicking.
126 /// Callers surface the `None` to the user as an EX_USAGE CLI error
127 /// (see the `paper-lock` CLI binary in `src/main.rs` and the
128 /// integration tests in `tests/paper_lock_binary.rs`), which is the
129 /// intended safe behaviour.
130 #[must_use]
131 pub fn from_slug(s: &str) -> Option<Self> {
132 debug_assert!(s.len() < 64, "slug unreasonably long");
133 match s {
134 "cwru" => Some(Self::Cwru),
135 "ims" => Some(Self::Ims),
136 "kuka_lwr" => Some(Self::KukaLwr),
137 "femto_st" => Some(Self::FemtoSt),
138 "panda_gaz" => Some(Self::PandaGaz),
139 "dlr_justin" => Some(Self::DlrJustin),
140 "ur10_kufieta" => Some(Self::Ur10Kufieta),
141 "cheetah3" => Some(Self::Cheetah3),
142 "icub_pushrecovery" => Some(Self::IcubPushRecovery),
143 "droid" => Some(Self::Droid),
144 "openx" => Some(Self::Openx),
145 "anymal_parkour" => Some(Self::AnymalParkour),
146 "unitree_g1" => Some(Self::UnitreeG1),
147 "aloha_static" => Some(Self::AlohaStatic),
148 "icub3_sorrentino" => Some(Self::Icub3Sorrentino),
149 "mobile_aloha" => Some(Self::MobileAloha),
150 "so100" => Some(Self::So100),
151 "aloha_static_tape" => Some(Self::AlohaStaticTape),
152 "aloha_static_screw_driver" => Some(Self::AlohaStaticScrewDriver),
153 "aloha_static_pingpong_test" => Some(Self::AlohaStaticPingpongTest),
154 // SAFE-STATE: unknown slug is the explicitly-named fallback
155 // state. Bind it to `unknown` so the arm is named (no
156 // wildcard catch-all), assert the input shape, and return
157 // None. Callers surface the None as EX_USAGE at the CLI
158 // boundary (see `crate::main` and `tests/paper_lock_binary.rs`).
159 unknown => {
160 debug_assert!(
161 unknown.len() < 64,
162 "slug input bound by callsite preconditions"
163 );
164 None
165 }
166 }
167 }
168
169 /// Residual-family tag, for table-of-contents emission.
170 #[inline]
171 #[must_use]
172 pub const fn family(self) -> DatasetFamily {
173 match self {
174 Self::Cwru | Self::Ims | Self::FemtoSt => DatasetFamily::Phm,
175 Self::KukaLwr
176 | Self::PandaGaz
177 | Self::DlrJustin
178 | Self::Ur10Kufieta
179 | Self::Droid
180 | Self::Openx
181 | Self::AlohaStatic
182 | Self::MobileAloha
183 | Self::So100
184 | Self::AlohaStaticTape
185 | Self::AlohaStaticScrewDriver
186 | Self::AlohaStaticPingpongTest => DatasetFamily::Kinematics,
187 Self::Cheetah3
188 | Self::IcubPushRecovery
189 | Self::AnymalParkour
190 | Self::UnitreeG1
191 | Self::Icub3Sorrentino => DatasetFamily::Balancing,
192 }
193 }
194}
195
196/// Family classification matching the companion paper's §10 grouping.
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
199pub enum DatasetFamily {
200 /// Prognostics / health monitoring (bearing degradation).
201 Phm,
202 /// Kinematic identification / manipulation residuals (arms, cobots).
203 Kinematics,
204 /// Balancing / whole-body control residuals (legged platforms).
205 Balancing,
206}
207
208impl DatasetFamily {
209 /// Stable label for table emission.
210 #[inline]
211 #[must_use]
212 pub const fn label(self) -> &'static str {
213 match self {
214 Self::Phm => "PHM",
215 Self::Kinematics => "Kinematics",
216 Self::Balancing => "Balancing",
217 }
218 }
219}
220
221#[cfg(test)]
222mod tests {
223 use super::*;
224
225 #[test]
226 fn slug_roundtrips_for_every_dataset() {
227 for id in [
228 DatasetId::Cwru,
229 DatasetId::Ims,
230 DatasetId::KukaLwr,
231 DatasetId::FemtoSt,
232 DatasetId::PandaGaz,
233 DatasetId::DlrJustin,
234 DatasetId::Ur10Kufieta,
235 DatasetId::Cheetah3,
236 DatasetId::IcubPushRecovery,
237 DatasetId::Droid,
238 DatasetId::Openx,
239 DatasetId::AnymalParkour,
240 DatasetId::UnitreeG1,
241 DatasetId::AlohaStatic,
242 ] {
243 let slug = id.slug();
244 assert_eq!(DatasetId::from_slug(slug), Some(id), "roundtrip failed for {slug}");
245 }
246 }
247
248 #[test]
249 fn unknown_slug_is_none() {
250 assert_eq!(DatasetId::from_slug("nope"), None);
251 assert_eq!(DatasetId::from_slug(""), None);
252 }
253
254 #[test]
255 fn kinematics_family_covers_four_arms() {
256 let arms = [DatasetId::KukaLwr, DatasetId::PandaGaz, DatasetId::DlrJustin, DatasetId::Ur10Kufieta];
257 for a in arms {
258 assert_eq!(a.family(), DatasetFamily::Kinematics);
259 }
260 }
261
262 #[test]
263 fn balancing_family_covers_two_platforms() {
264 assert_eq!(DatasetId::Cheetah3.family(), DatasetFamily::Balancing);
265 assert_eq!(DatasetId::IcubPushRecovery.family(), DatasetFamily::Balancing);
266 }
267
268 #[test]
269 fn phm_family_covers_three_bearing_datasets() {
270 assert_eq!(DatasetId::Cwru.family(), DatasetFamily::Phm);
271 assert_eq!(DatasetId::Ims.family(), DatasetFamily::Phm);
272 assert_eq!(DatasetId::FemtoSt.family(), DatasetFamily::Phm);
273 }
274}