1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
use std::fmt;
/// Errors returned while constructing or evaluating a robot model.
#[derive(Debug)]
#[non_exhaustive]
pub enum Error {
/// The URDF file could not be read or parsed.
Urdf(urdf_rs::UrdfError),
/// The URDF describes an invalid or unsupported kinematic graph.
InvalidModel(String),
/// A joint uses a motion type not supported by this crate.
UnsupportedJointType {
/// Name of the joint using the unsupported type.
joint: String,
/// Unsupported URDF joint type.
joint_type: String,
},
/// A runtime-sized input or output has the wrong length.
WrongSliceLength {
/// Name of the rejected input or output.
slice: &'static str,
/// Required number of elements.
expected: usize,
/// Number of elements supplied by the caller.
actual: usize,
},
/// A runtime-sized numerical input contains a non-finite value.
NonFiniteInput {
/// Name of the rejected input.
input: &'static str,
},
/// Finite inputs produced an unrepresentable or inaccurate numerical result.
NumericalFailure {
/// Calculation that failed.
operation: &'static str,
},
/// A joint axis is too small to normalize.
InvalidJointAxis {
/// Name of the joint with the invalid axis.
joint: String,
},
/// No link with the requested name exists in the model.
UnknownLink {
/// Link name requested by the caller.
name: String,
},
/// An active joint degree-of-freedom index is out of range.
InvalidJointIndex {
/// Rejected active-DOF index.
index: usize,
},
/// A link identifier belongs to a different robot model.
InvalidLinkId,
/// A base-state component is invalid.
InvalidBaseState {
/// Name of the rejected component.
field: &'static str,
/// Constraint violated by the component.
reason: &'static str,
},
/// Inverse kinematics is not defined for floating-base robots.
FloatingBaseIkUnsupported,
/// One of the inverse-kinematics options is zero, negative, or non-finite.
InvalidIkOptions {
/// Name of the rejected option.
option: &'static str,
/// Constraint violated by the option.
reason: &'static str,
},
/// An inverse-kinematics input contains a non-finite value.
NonFiniteIkInput {
/// Name of the input containing a non-finite value.
input: &'static str,
},
/// The inverse-kinematics damped linear system could not be solved.
IkNumericalFailure {
/// One-based iteration at which the numerical solve failed.
iteration: usize,
},
/// A converged inverse-kinematics solution violates a joint limit.
IkJointLimitViolation {
/// Zero-based active degree-of-freedom index, matching the joint vector.
joint_index: usize,
/// Name of the joint.
joint: String,
/// Position produced by the solver.
position: f64,
/// Minimum permitted position.
lower: f64,
/// Maximum permitted position.
upper: f64,
},
/// Inverse kinematics did not reach the requested pose within its iteration budget.
IkNotConverged {
/// Number of joint updates attempted.
iterations: usize,
/// Final Euclidean translation error, in metres.
translation_error: f64,
/// Final rotation-vector norm, in radians.
rotation_error: f64,
},
/// A joint's articulated inertia cannot be inverted by forward dynamics.
ForwardDynamicsSingularJointInertia {
/// Zero-based active joint degree-of-freedom index.
joint_index: usize,
},
/// The floating base's articulated inertia cannot be inverted by forward dynamics.
ForwardDynamicsSingularBaseInertia,
/// The scaled floating-base inertia is too ill-conditioned for a reliable solve.
ForwardDynamicsIllConditionedBaseInertia,
}
/// Stable, coarse classification of errors for language bindings and callers.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ErrorCategory {
/// The caller supplied an invalid name, handle, buffer, option, or value.
InvalidInput,
/// A robot description could not be loaded or represented.
Model,
/// An iterative numerical calculation failed to produce a valid result.
Solver,
}
impl Error {
/// Returns a stable category suitable for mapping into a foreign-language API.
pub const fn category(&self) -> ErrorCategory {
match self {
Self::Urdf(_)
| Self::InvalidModel(_)
| Self::UnsupportedJointType { .. }
| Self::InvalidJointAxis { .. } => ErrorCategory::Model,
Self::WrongSliceLength { .. }
| Self::NonFiniteInput { .. }
| Self::UnknownLink { .. }
| Self::InvalidJointIndex { .. }
| Self::InvalidLinkId
| Self::InvalidBaseState { .. }
| Self::FloatingBaseIkUnsupported
| Self::InvalidIkOptions { .. }
| Self::NonFiniteIkInput { .. } => ErrorCategory::InvalidInput,
Self::IkNumericalFailure { .. }
| Self::NumericalFailure { .. }
| Self::IkJointLimitViolation { .. }
| Self::IkNotConverged { .. }
| Self::ForwardDynamicsSingularJointInertia { .. }
| Self::ForwardDynamicsSingularBaseInertia
| Self::ForwardDynamicsIllConditionedBaseInertia => ErrorCategory::Solver,
}
}
}
impl fmt::Display for Error {
/// Formats a human-readable description of the model or calculation error.
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Urdf(error) => write!(f, "failed to parse URDF: {error}"),
Self::InvalidModel(message) => write!(f, "invalid robot model: {message}"),
Self::UnsupportedJointType { joint, joint_type } => {
write!(f, "joint {joint} uses unsupported type {joint_type}")
}
Self::WrongSliceLength {
slice,
expected,
actual,
} => write!(f, "expected {expected} elements in {slice}, found {actual}"),
Self::NonFiniteInput { input } => {
write!(f, "{input} contains a non-finite value")
}
Self::NumericalFailure { operation } => write!(f, "numerical failure in {operation}"),
Self::InvalidJointAxis { joint } => write!(f, "joint {joint} has an invalid axis"),
Self::UnknownLink { name } => write!(f, "link {name} does not exist in the model"),
Self::InvalidJointIndex { index } => {
write!(f, "joint DOF index {index} does not exist in the model")
}
Self::InvalidLinkId => write!(f, "link identifier does not belong to this robot model"),
Self::InvalidBaseState { field, reason } => {
write!(f, "invalid base {field}: {reason}")
}
Self::FloatingBaseIkUnsupported => {
write!(f, "inverse kinematics does not support a floating base")
}
Self::InvalidIkOptions { option, reason } => {
write!(f, "invalid inverse-kinematics option {option}: {reason}")
}
Self::NonFiniteIkInput { input } => {
write!(f, "inverse-kinematics {input} contains a non-finite value")
}
Self::IkNumericalFailure { iteration } => write!(
f,
"inverse-kinematics linear solve failed at iteration {iteration}"
),
Self::IkJointLimitViolation {
joint_index,
joint,
position,
lower,
upper,
} => write!(
f,
"inverse-kinematics solution {position:.6e} for joint {joint_index} ({joint}) \
is outside URDF limits [{lower:.6e}, {upper:.6e}]"
),
Self::IkNotConverged {
iterations,
translation_error,
rotation_error,
} => write!(
f,
"inverse kinematics did not converge after {iterations} iterations \
(translation error {translation_error:.6e}, rotation error {rotation_error:.6e})"
),
Self::ForwardDynamicsSingularJointInertia { joint_index } => write!(
f,
"forward dynamics found singular articulated inertia at joint DOF {joint_index}"
),
Self::ForwardDynamicsSingularBaseInertia => write!(
f,
"forward dynamics found singular floating-base articulated inertia"
),
Self::ForwardDynamicsIllConditionedBaseInertia => write!(
f,
"forward dynamics found ill-conditioned scaled floating-base articulated inertia"
),
}
}
}
impl std::error::Error for Error {
/// Returns the underlying URDF error, when present.
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Urdf(error) => Some(error),
_ => None,
}
}
}
impl From<urdf_rs::UrdfError> for Error {
/// Wraps a URDF parser error in the crate's general error type.
fn from(value: urdf_rs::UrdfError) -> Self {
Self::Urdf(value)
}
}
/// Result type returned by robot-model construction and calculations.
pub type Result<T> = std::result::Result<T, Error>;
#[cfg(test)]
#[cfg_attr(coverage_nightly, coverage(off))]
mod tests {
use super::{Error, ErrorCategory};
#[test]
fn display_describes_each_library_error_and_has_no_source() {
let cases = [
(
Error::NumericalFailure { operation: "load aggregation" },
"numerical failure in load aggregation".to_owned(),
),
(
Error::ForwardDynamicsIllConditionedBaseInertia,
"forward dynamics found ill-conditioned scaled floating-base articulated inertia".to_owned(),
),
(
Error::InvalidModel("broken tree".to_owned()),
"invalid robot model: broken tree".to_owned(),
),
(
Error::UnsupportedJointType {
joint: "floating_base".to_owned(),
joint_type: "floating".to_owned(),
},
"joint floating_base uses unsupported type floating".to_owned(),
),
(
Error::WrongSliceLength {
slice: "q",
expected: 4,
actual: 3,
},
"expected 4 elements in q, found 3".to_owned(),
),
(
Error::NonFiniteInput { input: "q" },
"q contains a non-finite value".to_owned(),
),
(
Error::InvalidJointAxis {
joint: "shoulder".to_owned(),
},
"joint shoulder has an invalid axis".to_owned(),
),
(
Error::UnknownLink {
name: "tool".to_owned(),
},
"link tool does not exist in the model".to_owned(),
),
(
Error::InvalidJointIndex { index: 7 },
"joint DOF index 7 does not exist in the model".to_owned(),
),
(
Error::InvalidLinkId,
"link identifier does not belong to this robot model".to_owned(),
),
(
Error::InvalidBaseState {
field: "velocity",
reason: "must be finite",
},
"invalid base velocity: must be finite".to_owned(),
),
(
Error::FloatingBaseIkUnsupported,
"inverse kinematics does not support a floating base".to_owned(),
),
(
Error::InvalidIkOptions {
option: "damping",
reason: "must be positive",
},
"invalid inverse-kinematics option damping: must be positive".to_owned(),
),
(
Error::NonFiniteIkInput {
input: "target frame",
},
"inverse-kinematics target frame contains a non-finite value".to_owned(),
),
(
Error::IkNumericalFailure { iteration: 3 },
"inverse-kinematics linear solve failed at iteration 3".to_owned(),
),
(
Error::IkJointLimitViolation {
joint_index: 2,
joint: "elbow".to_owned(),
position: 1.5,
lower: -1.0,
upper: 1.0,
},
"inverse-kinematics solution 1.500000e0 for joint 2 (elbow) is outside URDF limits [-1.000000e0, 1.000000e0]".to_owned(),
),
(
Error::IkNotConverged {
iterations: 4,
translation_error: 0.25,
rotation_error: 0.5,
},
"inverse kinematics did not converge after 4 iterations (translation error 2.500000e-1, rotation error 5.000000e-1)".to_owned(),
),
(
Error::ForwardDynamicsSingularJointInertia { joint_index: 2 },
"forward dynamics found singular articulated inertia at joint DOF 2".to_owned(),
),
(
Error::ForwardDynamicsSingularBaseInertia,
"forward dynamics found singular floating-base articulated inertia".to_owned(),
),
];
for (error, expected) in cases {
assert_eq!(error.to_string(), expected);
assert!(std::error::Error::source(&error).is_none());
}
}
#[test]
fn categories_group_errors_by_caller_action() {
assert_eq!(
Error::InvalidModel("broken tree".to_owned()).category(),
ErrorCategory::Model
);
assert_eq!(
Error::WrongSliceLength {
slice: "q",
expected: 4,
actual: 3,
}
.category(),
ErrorCategory::InvalidInput
);
assert_eq!(
Error::IkNotConverged {
iterations: 4,
translation_error: 0.25,
rotation_error: 0.5,
}
.category(),
ErrorCategory::Solver
);
assert_eq!(
Error::ForwardDynamicsSingularBaseInertia.category(),
ErrorCategory::Solver
);
}
}