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 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539
// Copyright (c) 2021 Marco Boneberger
// Licensed under the EUPL-1.2-or-later
//! Contains the franka::RobotState types.
use std::time::Duration;
use crate::robot::errors::{FrankaErrorKind, FrankaErrors};
use crate::robot::types::{RobotMode, RobotStateIntern};
use nalgebra::{Matrix3, Vector3};
/// Describes the robot state.
#[derive(Debug, Clone, Default)]
#[allow(non_snake_case)]
pub struct RobotState {
/// ![^{O}T_{EE}](https://latex.codecogs.com/png.latex?^{O}T_{EE})
///
/// Measured end effector pose in base frame.
/// Pose is represented as a 4x4 matrix in column-major format.
pub O_T_EE: [f64; 16],
/// ![{^OT_{EE}}_{d}](http://latex.codecogs.com/png.latex?{^OT_{EE}}_{d})
///
/// Last desired end effector pose of motion generation in base frame.
/// Pose is represented as a 4x4 matrix in column-major format.
pub O_T_EE_d: [f64; 16],
/// ![^{F}T_{EE}](http://latex.codecogs.com/png.latex?^{F}T_{EE})
///
/// End effector frame pose in flange frame.
/// Pose is represented as a 4x4 matrix in column-major format.
/// # See
/// * [`F_T_NE`](`Self::F_T_NE`)
/// * [`NE_T_EE`](`Self::NE_T_EE`)
/// * [`Robot`](`crate::Robot`) for an explanation of the NE and EE frames.
pub F_T_EE: [f64; 16],
/// ![^{F}T_{NE}](https://latex.codecogs.com/png.latex?^{F}T_{NE})
///
/// Nominal end effector frame pose in flange frame.
/// Pose is represented as a 4x4 matrix in column-major format.
/// # See
/// * [`F_T_NE`](`Self::F_T_NE`)
/// * [`NE_T_EE`](`Self::NE_T_EE`)
/// * [`Robot`](`crate::Robot`) for an explanation of the NE and EE frames.
pub F_T_NE: [f64; 16],
/// ![^{NE}T_{EE}](https://latex.codecogs.com/png.latex?^{NE}T_{EE})
///
/// End effector frame pose in nominal end effector frame.
/// Pose is represented as a 4x4 matrix in column-major format.
/// # See
/// * [`F_T_NE`](`Self::F_T_NE`)
/// * [`NE_T_EE`](`Self::NE_T_EE`)
/// * [`Robot`](`crate::Robot`) for an explanation of the NE and EE frames.
pub NE_T_EE: [f64; 16],
/// ![^{EE}T_{K}](https://latex.codecogs.com/png.latex?^{EE}T_{K})
///
/// Stiffness frame pose in end effector frame.
/// Pose is represented as a 4x4 matrix in column-major format.
///
/// See also [K-Frame](`crate::Robot#stiffness-frame-k`)
pub EE_T_K: [f64; 16],
/// ![m_{EE}](https://latex.codecogs.com/png.latex?m_{EE})
///
/// Configured mass of the end effector.
pub m_ee: f64,
/// ![I_{EE}](https://latex.codecogs.com/png.latex?I_{EE})
///
/// Configured rotational inertia matrix of the end effector load with respect to center of mass.
pub I_ee: [f64; 9],
/// ![^{F}x_{C_{EE}}](https://latex.codecogs.com/png.latex?^{F}x_{C_{EE}})
///
/// Configured center of mass of the end effector load with respect to flange frame.
pub F_x_Cee: [f64; 3],
/// ![m_{load}](https://latex.codecogs.com/png.latex?m_{load})
///
/// Configured mass of the external load.
pub m_load: f64,
/// ![I_{load}](https://latex.codecogs.com/png.latex?I_{load})
///
/// Configured rotational inertia matrix of the external load with respect to center of mass.
pub I_load: [f64; 9],
/// ![^{F}x_{C_{load}}](https://latex.codecogs.com/png.latex?^{F}x_{C_{load}})
///
/// Configured center of mass of the external load with respect to flange frame.
pub F_x_Cload: [f64; 3],
/// ![m_{total}](https://latex.codecogs.com/png.latex?m_{total})
///
/// Sum of the mass of the end effector and the external load.
pub m_total: f64,
/// ![I_{total}](https://latex.codecogs.com/png.latex?I_{total})
///
/// Combined rotational inertia matrix of the end effector load and the external load with respect
/// to the center of mass.
pub I_total: [f64; 9],
/// ![^{F}x_{C_{total}}](https://latex.codecogs.com/png.latex?^{F}x_{C_{total}})
///
/// Combined center of mass of the end effector load and the external load with respect to flange
/// frame.
pub F_x_Ctotal: [f64; 3],
/// Elbow configuration.
///
/// The values of the array are:
/// - \[0\] Position of the 3rd joint in \[rad\].
/// - \[1\] Sign of the 4th joint. Can be +1 or -1.
pub elbow: [f64; 2],
/// Desired elbow configuration.
///
/// The values of the array are:
/// - \[0\] Position of the 3rd joint in \[rad\].
/// - \[1\] Sign of the 4th joint. Can be +1 or -1.
pub elbow_d: [f64; 2],
/// Commanded elbow configuration.
///
/// The values of the array are:
/// - \[0\] Position of the 3rd joint in \[rad\].
/// - \[1\] Sign of the 4th joint. Can be +1 or -1.
pub elbow_c: [f64; 2],
/// Commanded elbow velocity.
///
/// The values of the array are:
/// - \[0\] Velocity of the 3rd joint in \[rad/s\].
/// - \[1\] Sign of the 4th joint. Can be +1 or -1.
pub delbow_c: [f64; 2],
/// Commanded elbow acceleration.
///
/// The values of the array are:
/// - \[0\] Acceleration of the 3rd joint in \[rad/s^2\].
/// - \[1\] Sign of the 4th joint. Can be +1 or -1.
pub ddelbow_c: [f64; 2],
/// ![\tau_{J}](https://latex.codecogs.com/png.latex?\tau_{J})
///
/// Measured link-side joint torque sensor signals. Unit: \[Nm\]
pub tau_J: [f64; 7],
/// ![{\tau_J}_d](https://latex.codecogs.com/png.latex?{\tau_J}_d)
///
/// Desired link-side joint torque sensor signals without gravity. Unit: \[Nm\]
pub tau_J_d: [f64; 7],
/// ![\dot{\tau_{J}}](https://latex.codecogs.com/png.latex?\dot{\tau_{J}})
///
/// Derivative of measured link-side joint torque sensor signals. Unit: [Nm/s]
pub dtau_J: [f64; 7],
/// ![q](https://latex.codecogs.com/png.latex?q)
///
/// Measured joint position. Unit: \[rad\]
pub q: [f64; 7],
/// ![q_d](https://latex.codecogs.com/png.latex?q_d)
///
/// Desired joint position. Unit: \[rad\]
pub q_d: [f64; 7],
/// ![\dot{q}](https://latex.codecogs.com/png.latex?\dot{q})
///
/// Measured joint velocity. Unit: \[rad/s\]
pub dq: [f64; 7],
/// ![\dot{q}_d](https://latex.codecogs.com/png.latex?\dot{q}_d)
///
/// Desired joint velocity. Unit: \[rad/s\]
pub dq_d: [f64; 7],
/// ![\ddot{q}_d](https://latex.codecogs.com/png.latex?\ddot{q}_d)
///
/// Desired joint acceleration. Unit: \[rad/s^2\]
pub ddq_d: [f64; 7],
/// Indicates which contact level is activated in which joint. After contact disappears, value
/// turns to zero.
///
/// See [`Robot::set_collision_behavior`](`crate::Robot::set_collision_behavior`)
/// for setting sensitivity values.
pub joint_contact: [f64; 7],
/// Indicates which contact level is activated in which Cartesian dimension (x,y,z,R,P,Y).
/// After contact disappears, the value turns to zero.
///
/// See [`Robot::set_collision_behavior`](`crate::Robot::set_collision_behavior`)
/// for setting sensitivity values.
pub cartesian_contact: [f64; 6],
/// Indicates which contact level is activated in which joint. After contact disappears, the value
/// stays the same until a reset command is sent.
///
/// See [`Robot::set_collision_behavior`](`crate::Robot::set_collision_behavior`)
/// for setting sensitivity values.
///
/// See [`Robot::automatic_error_recovery`](`crate::Robot::automatic_error_recovery`)
/// for performing a reset after a collision.
pub joint_collision: [f64; 7],
/// Indicates which contact level is activated in which Cartesian dimension (x,y,z,R,P,Y).
/// After contact disappears, the value stays the same until a reset command is sent.
///
/// See [`Robot::set_collision_behavior`](`crate::Robot::set_collision_behavior`)
/// for setting sensitivity values.
///
/// See [`Robot::automatic_error_recovery`](`crate::Robot::automatic_error_recovery`)
/// for performing a reset after a collision.
pub cartesian_collision: [f64; 6],
/// ![\hat{\tau}_{\text{ext}}](https://latex.codecogs.com/png.latex?\hat{\tau}_{\text{ext}})
///
/// External torque, filtered. Unit: \[Nm\]
pub tau_ext_hat_filtered: [f64; 7],
/// ![^OF_{K,\text{ext}}](https://latex.codecogs.com/png.latex?^OF_{K,\text{ext}})
///
/// Estimated external wrench (force, torque) acting on stiffness frame, expressed
/// relative to the base frame. See also @ref k-frame "K frame".
/// Unit: \[N,N,N,Nm,Nm,Nm\].
pub O_F_ext_hat_K: [f64; 6],
/// ![^{K}F_{K,\text{ext}}](https://latex.codecogs.com/png.latex?^{K}F_{K,\text{ext}})
///
/// Estimated external wrench (force, torque) acting on stiffness frame,
/// expressed relative to the stiffness frame. See also @ref k-frame "K frame".
/// Unit: \[N,N,N,Nm,Nm,Nm\].
pub K_F_ext_hat_K: [f64; 6],
/// ![{^OdP_{EE}}_{d}](https://latex.codecogs.com/png.latex?{^OdP_{EE}}_{d})
///
/// Desired end effector twist in base frame.
/// Unit: [m/s,m/s,m/s,rad/s,rad/s,rad/s]
pub O_dP_EE_d: [f64; 6],
/// ![{^OddP_O}](https://latex.codecogs.com/png.latex?{^OddP_O)
///
/// Linear component of the acceleration of the robot's base, expressed in frame parallel to the
/// base frame, i.e. the base's translational acceleration. If the base is resting
/// this shows the direction of the gravity vector. It is hardcoded for now to `{0, 0, -9.81}`.
pub O_ddP_O: [f64; 3],
/// ![{^OT_{EE}}_{c}](https://latex.codecogs.com/png.latex?{^OT_{EE}}_{c})
///
/// Last commanded end effector pose of motion generation in base frame.
/// Pose is represented as a 4x4 matrix in column-major format.
pub O_T_EE_c: [f64; 16],
/// ![{^OdP_{EE}}_{c}](https://latex.codecogs.com/png.latex?{^OdP_{EE}}_{c})
///
/// Last commanded end effector twist in base frame.
/// Unit: [m/s,m/s,m/s,rad/s,rad/s,rad/s]
pub O_dP_EE_c: [f64; 6],
///![{^OddP_{EE}}_{c}](https://latex.codecogs.com/png.latex?{^OddP_{EE}}_{c})
///
/// Last commanded end effector acceleration in base frame.
/// Unit: [m/s^2,m/s^2,m/s^2,rad/s^2,rad/s^2,rad/s^2]
pub O_ddP_EE_c: [f64; 6],
/// ![\theta](https://latex.codecogs.com/png.latex?\theta)
///
/// Motor position. Unit: \[rad\]
pub theta: [f64; 7],
/// ![\dot{\theta}](https://latex.codecogs.com/png.latex?\dot{\theta})
///
/// Motor velocity. Unit: \[rad/s\]
pub dtheta: [f64; 7],
/// Current error state.
pub current_errors: FrankaErrors,
/// Contains the errors that aborted the previous motion
pub last_motion_errors: FrankaErrors,
/// Percentage of the last 100 control commands that were successfully received by the robot.
///
/// Shows a value of zero if no control or motion generator loop is currently running.
///
/// Range \[0,1\]
pub control_command_success_rate: f64,
/// Current robot mode.
pub robot_mode: RobotMode,
/// Strictly monotonically increasing timestamp since robot start.
///
/// Inside of control loops "time_step" parameter of Robot::control can be used
/// instead
pub time: Duration,
}
impl From<RobotStateIntern> for RobotState {
#[allow(non_snake_case)]
fn from(robot_state: RobotStateIntern) -> Self {
let O_T_EE = robot_state.O_T_EE;
let O_T_EE_d = robot_state.O_T_EE_d;
let F_T_NE = robot_state.F_T_NE;
let NE_T_EE = robot_state.NE_T_EE;
let F_T_EE = robot_state.F_T_EE;
let EE_T_K = robot_state.EE_T_K;
let m_ee = robot_state.m_ee;
let F_x_Cee = robot_state.F_x_Cee;
let I_ee = robot_state.I_ee;
let m_load = robot_state.m_load;
let F_x_Cload = robot_state.F_x_Cload;
let I_load = robot_state.I_load;
let m_total = robot_state.m_ee + robot_state.m_load;
let F_x_Ctotal = combine_center_of_mass(
robot_state.m_ee,
robot_state.F_x_Cee,
robot_state.m_load,
robot_state.F_x_Cload,
);
let I_total = combine_inertia_tensor(
robot_state.m_ee,
robot_state.F_x_Cee,
robot_state.I_ee,
robot_state.m_load,
robot_state.F_x_Cload,
robot_state.I_load,
m_total,
F_x_Ctotal,
);
let elbow = robot_state.elbow;
let elbow_d = robot_state.elbow_d;
let elbow_c = robot_state.elbow_c;
let delbow_c = robot_state.delbow_c;
let ddelbow_c = robot_state.ddelbow_c;
let tau_J = robot_state.tau_J;
let tau_J_d = robot_state.tau_J_d;
let dtau_J = robot_state.dtau_J;
let q = robot_state.q;
let dq = robot_state.dq;
let q_d = robot_state.q_d;
let dq_d = robot_state.dq_d;
let ddq_d = robot_state.ddq_d;
let joint_contact = robot_state.joint_contact;
let cartesian_contact = robot_state.cartesian_contact;
let joint_collision = robot_state.joint_collision;
let cartesian_collision = robot_state.cartesian_collision;
let tau_ext_hat_filtered = robot_state.tau_ext_hat_filtered;
let O_F_ext_hat_K = robot_state.O_F_ext_hat_K;
let K_F_ext_hat_K = robot_state.K_F_ext_hat_K;
let O_dP_EE_d = robot_state.O_dP_EE_d;
let O_ddP_O = robot_state.O_ddP_O;
let O_T_EE_c = robot_state.O_T_EE_c;
let O_dP_EE_c = robot_state.O_dP_EE_c;
let O_ddP_EE_c = robot_state.O_ddP_EE_c;
let theta = robot_state.theta;
let dtheta = robot_state.dtheta;
let control_command_success_rate = robot_state.control_command_success_rate;
let time = Duration::from_millis(robot_state.message_id);
let robot_mode = robot_state.robot_mode;
let current_errors = FrankaErrors::new(robot_state.errors, FrankaErrorKind::Error);
let last_motion_errors =
FrankaErrors::new(robot_state.errors, FrankaErrorKind::ReflexReason);
RobotState {
O_T_EE,
O_T_EE_d,
F_T_EE,
F_T_NE,
NE_T_EE,
EE_T_K,
m_ee,
I_ee,
F_x_Cee,
m_load,
I_load,
F_x_Cload,
m_total,
I_total,
F_x_Ctotal,
elbow,
elbow_d,
elbow_c,
delbow_c,
ddelbow_c,
tau_J,
tau_J_d,
dtau_J,
q,
q_d,
dq,
dq_d,
ddq_d,
joint_contact,
cartesian_contact,
joint_collision,
cartesian_collision,
tau_ext_hat_filtered,
O_F_ext_hat_K,
K_F_ext_hat_K,
O_dP_EE_d,
O_ddP_O,
O_T_EE_c,
O_dP_EE_c,
O_ddP_EE_c,
theta,
dtheta,
current_errors,
last_motion_errors,
control_command_success_rate,
robot_mode,
time,
}
}
}
#[allow(non_snake_case, clippy::too_many_arguments)]
fn combine_inertia_tensor(
m_ee: f64,
F_x_Cee: [f64; 3],
I_ee: [f64; 9],
m_load: f64,
F_x_Cload: [f64; 3],
I_load: [f64; 9],
m_total: f64,
F_x_Ctotal: [f64; 3],
) -> [f64; 9] {
let center_of_mass_ee = Vector3::from_column_slice(&F_x_Cee);
let center_of_mass_load = Vector3::from_column_slice(&F_x_Cload);
let center_of_mass_total = Vector3::from_column_slice(&F_x_Ctotal);
let mut inertia_ee = Matrix3::from_column_slice(&I_ee);
let mut inertia_load = Matrix3::from_column_slice(&I_load);
if m_ee == 0. {
inertia_ee = Matrix3::zeros();
}
if m_load == 0. {
inertia_load = Matrix3::zeros();
}
let inertia_ee_flange = inertia_ee
- m_ee
* (skew_symmetric_matrix_from_vector(¢er_of_mass_ee)
* skew_symmetric_matrix_from_vector(¢er_of_mass_ee));
let inertia_load_flange = inertia_load
- m_load
* (skew_symmetric_matrix_from_vector(¢er_of_mass_load)
* skew_symmetric_matrix_from_vector(¢er_of_mass_load));
let inertia_total_flange = inertia_ee_flange + inertia_load_flange;
let inertia_total: Matrix3<f64> = inertia_total_flange
+ m_total
* (skew_symmetric_matrix_from_vector(¢er_of_mass_total)
* skew_symmetric_matrix_from_vector(¢er_of_mass_total));
let mut I_total = [0.; 9];
for (i, &x) in inertia_total.as_slice().iter().enumerate() {
I_total[i] = x;
}
I_total
}
#[allow(non_snake_case)]
fn combine_center_of_mass(
m_ee: f64,
F_x_Cee: [f64; 3],
m_load: f64,
F_x_Cload: [f64; 3],
) -> [f64; 3] {
let mut F_x_Ctotal = [0.; 3];
if m_ee + m_load > 0. {
for i in 0..F_x_Ctotal.len() {
F_x_Ctotal[i] = (m_ee * F_x_Cee[i] + m_load * F_x_Cload[i]) / (m_ee + m_load);
}
}
F_x_Ctotal
}
fn skew_symmetric_matrix_from_vector(vector: &Vector3<f64>) -> Matrix3<f64> {
Matrix3::new(
0., -vector.z, vector.y, vector.z, 0., -vector.x, -vector.y, vector.x, 0.,
)
}
#[cfg(test)]
mod tests {
use crate::robot::robot_state::{combine_center_of_mass, combine_inertia_tensor};
#[test]
fn combine_com_test() {
let m_ee = 0.;
let m_load = 0.;
let f_x_ctotal = combine_center_of_mass(m_ee, [0.; 3], m_load, [0.; 3]);
f_x_ctotal
.iter()
.for_each(|&x| assert!(x.abs() < f64::EPSILON));
let m_ee = 0.73;
let m_load = 0.5;
let f_x_cee = [-0.01, 0., -0.03];
let i_ee = [0.001, 0.0, 0.0, 0.0, 0.0025, 0.0, 0.0, 0.0, 0.0017];
let i_load = [0.001, 0.0, 0.0, 0.0, 0.025, 0.0, 0.0, 0.0, 0.3];
let f_x_cload = [0.01, -0.2, 0.03];
let m_total = m_ee + m_load;
let f_x_ctotal = combine_center_of_mass(m_ee, f_x_cee, m_load, f_x_cload);
let i_total = combine_inertia_tensor(
m_ee, f_x_cee, i_ee, m_load, f_x_cload, i_load, m_total, f_x_ctotal,
);
let expected = [
1.49382113821138e-02,
1.18699186991870e-03,
-3.56097560975610e-04,
1.18699186991870e-03,
2.36869918699187e-02,
3.56097560975610e-03,
-3.56097560975610e-04,
3.56097560975610e-03,
3.13688617886179e-01,
];
i_total
.iter()
.zip(expected.iter())
.take(3)
.for_each(|(&x, &y)| assert!((x - y).abs() < 1e-14));
}
#[test]
fn inertia_zero_test() {
let m_ee = 0.;
let m_load = 0.0;
let f_x_cee = [-0.01, 0., -0.03];
let i_ee = [0.001, 0.0, 0.0, 0.0, 0.0025, 0.0, 0.0, 0.0, 0.0017];
let i_load = [0.001, 0.0, 0.0, 0.0, 0.025, 0.0, 0.0, 0.0, 0.3];
let f_x_cload = [0.01, -0.2, 0.03];
let m_total = m_ee + m_load;
let f_x_ctotal = combine_center_of_mass(m_ee, f_x_cee, m_load, f_x_cload);
let i_total = combine_inertia_tensor(
m_ee, f_x_cee, i_ee, m_load, f_x_cload, i_load, m_total, f_x_ctotal,
);
let expected = [0.; 9];
i_total
.iter()
.zip(expected.iter())
.take(3)
.for_each(|(&x, &y)| assert!((x - y).abs() < 1e-17));
let m_ee = 0.;
let m_load = 0.5;
let f_x_cee = [-0.01, 0., -0.03];
let i_ee = [0.001, 0.0, 0.0, 0.0, 0.0025, 0.0, 0.0, 0.0, 0.0017];
let i_load = [0.001, 0.0, 0.0, 0.0, 0.025, 0.0, 0.0, 0.0, 0.3];
let f_x_cload = [0.01, -0.2, 0.03];
let m_total = m_ee + m_load;
let f_x_ctotal = combine_center_of_mass(m_ee, f_x_cee, m_load, f_x_cload);
let i_total = combine_inertia_tensor(
m_ee, f_x_cee, i_ee, m_load, f_x_cload, i_load, m_total, f_x_ctotal,
);
let expected = i_load;
i_total
.iter()
.zip(expected.iter())
.take(3)
.for_each(|(&x, &y)| assert!((x - y).abs() < 1e-17));
let m_ee = 0.73;
let m_load = 0.0;
let f_x_cee = [-0.01, 0., -0.03];
let i_ee = [0.001, 0.0, 0.0, 0.0, 0.0025, 0.0, 0.0, 0.0, 0.0017];
let i_load = [0.001, 0.0, 0.0, 0.0, 0.025, 0.0, 0.0, 0.0, 0.3];
let f_x_cload = [0.01, -0.2, 0.03];
let m_total = m_ee + m_load;
let f_x_ctotal = combine_center_of_mass(m_ee, f_x_cee, m_load, f_x_cload);
let i_total = combine_inertia_tensor(
m_ee, f_x_cee, i_ee, m_load, f_x_cload, i_load, m_total, f_x_ctotal,
);
let expected = i_ee;
i_total
.iter()
.zip(expected.iter())
.take(3)
.for_each(|(&x, &y)| assert!((x - y).abs() < 1e-17));
}
}