Skip to main content

nabled_sim/
lib.rs

1//! Physical AI orchestration — compose-down only.
2//!
3//! Domain algorithms remain in their owner crates; this crate wires validated context,
4//! simulation steps, batch IK, closed-loop control, and estimation pipelines.
5
6#![allow(clippy::missing_errors_doc)]
7
8use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
9
10pub mod context;
11pub mod control_loop;
12pub mod estimation;
13pub mod manipulation;
14pub mod pipeline;
15pub mod sim;
16
17#[derive(Debug, Clone, PartialEq)]
18pub enum SimError {
19    DimensionMismatch,
20    InvalidInput(String),
21    Model(nabled_model::ModelError),
22    Kinematics(nabled_kinematics::KinematicsError),
23    Dynamics(nabled_dynamics::DynamicsError),
24    Control(nabled_control::ControlError),
25    Sensor(nabled_sensor::SensorError),
26    Stats(nabled_ml::stats::StatsError),
27}
28
29impl std::fmt::Display for SimError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            SimError::DimensionMismatch => write!(f, "input dimensions are incompatible"),
33            SimError::InvalidInput(message) => write!(f, "invalid input: {message}"),
34            SimError::Model(err) => write!(f, "{err}"),
35            SimError::Kinematics(err) => write!(f, "{err}"),
36            SimError::Dynamics(err) => write!(f, "{err}"),
37            SimError::Control(err) => write!(f, "{err}"),
38            SimError::Sensor(err) => write!(f, "{err}"),
39            SimError::Stats(err) => write!(f, "{err}"),
40        }
41    }
42}
43
44impl std::error::Error for SimError {
45    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
46        match self {
47            SimError::Model(err) => Some(err),
48            SimError::Kinematics(err) => Some(err),
49            SimError::Dynamics(err) => Some(err),
50            SimError::Control(err) => Some(err),
51            SimError::Sensor(err) => Some(err),
52            SimError::Stats(err) => Some(err),
53            SimError::DimensionMismatch | SimError::InvalidInput(_) => None,
54        }
55    }
56}
57
58impl IntoNabledError for SimError {
59    fn into_nabled_error(self) -> NabledError {
60        match self {
61            SimError::DimensionMismatch => NabledError::Shape(ShapeError::DimensionMismatch),
62            SimError::InvalidInput(message) => NabledError::InvalidInput(message),
63            SimError::Model(err) => err.into_nabled_error(),
64            SimError::Kinematics(err) => err.into_nabled_error(),
65            SimError::Dynamics(err) => err.into_nabled_error(),
66            SimError::Control(err) => err.into_nabled_error(),
67            SimError::Sensor(err) => err.into_nabled_error(),
68            SimError::Stats(err) => err.into_nabled_error(),
69        }
70    }
71}
72
73impl From<nabled_model::ModelError> for SimError {
74    fn from(value: nabled_model::ModelError) -> Self { Self::Model(value) }
75}
76
77impl From<nabled_kinematics::KinematicsError> for SimError {
78    fn from(value: nabled_kinematics::KinematicsError) -> Self { Self::Kinematics(value) }
79}
80
81impl From<nabled_dynamics::DynamicsError> for SimError {
82    fn from(value: nabled_dynamics::DynamicsError) -> Self { Self::Dynamics(value) }
83}
84
85impl From<nabled_control::ControlError> for SimError {
86    fn from(value: nabled_control::ControlError) -> Self { Self::Control(value) }
87}
88
89impl From<nabled_sensor::SensorError> for SimError {
90    fn from(value: nabled_sensor::SensorError) -> Self { Self::Sensor(value) }
91}
92
93impl From<nabled_ml::stats::StatsError> for SimError {
94    fn from(value: nabled_ml::stats::StatsError) -> Self { Self::Stats(value) }
95}
96
97#[cfg(test)]
98mod tests {
99    use std::error::Error;
100
101    use nabled_core::errors::{IntoNabledError, NabledError, ShapeError};
102
103    use super::*;
104
105    #[test]
106    fn sim_error_display_source_from_and_into_nabled() {
107        assert_eq!(SimError::DimensionMismatch.to_string(), "input dimensions are incompatible");
108        assert_eq!(
109            SimError::InvalidInput("bad grid".to_string()).to_string(),
110            "invalid input: bad grid"
111        );
112        assert!(SimError::DimensionMismatch.source().is_none());
113        assert!(SimError::InvalidInput("x".to_string()).source().is_none());
114
115        let model_err = nabled_model::ModelError::EmptyModel;
116        let sim_model: SimError = model_err.clone().into();
117        assert!(matches!(sim_model, SimError::Model(_)));
118        assert_eq!(sim_model.to_string(), model_err.to_string());
119        assert!(sim_model.source().is_some());
120
121        let kin_err = nabled_kinematics::KinematicsError::EmptyChain;
122        let sim_kin: SimError = kin_err.into();
123        assert!(matches!(sim_kin, SimError::Kinematics(_)));
124        assert_eq!(sim_kin.to_string(), "kinematic chain cannot be empty");
125
126        let dyn_err = nabled_dynamics::DynamicsError::NotImplemented;
127        let sim_dyn: SimError = dyn_err.into();
128        assert!(matches!(sim_dyn, SimError::Dynamics(_)));
129
130        let ctrl_err = nabled_control::ControlError::SingularSystem;
131        let sim_ctrl: SimError = ctrl_err.into();
132        assert!(matches!(sim_ctrl, SimError::Control(_)));
133
134        let sensor_err = nabled_sensor::SensorError::NumericalInstability;
135        let sim_sensor: SimError = sensor_err.into();
136        assert!(matches!(sim_sensor, SimError::Sensor(_)));
137
138        let stats_err = nabled_ml::stats::StatsError::InsufficientSamples;
139        let sim_stats: SimError = stats_err.into();
140        assert!(matches!(sim_stats, SimError::Stats(_)));
141
142        assert!(matches!(
143            SimError::DimensionMismatch.into_nabled_error(),
144            NabledError::Shape(ShapeError::DimensionMismatch)
145        ));
146        assert!(matches!(
147            SimError::InvalidInput("x".to_string()).into_nabled_error(),
148            NabledError::InvalidInput(_)
149        ));
150        assert!(matches!(
151            SimError::Model(nabled_model::ModelError::EmptyModel).into_nabled_error(),
152            NabledError::Shape(ShapeError::EmptyInput)
153        ));
154        assert!(matches!(
155            SimError::Kinematics(nabled_kinematics::KinematicsError::ConvergenceFailed)
156                .into_nabled_error(),
157            NabledError::ConvergenceFailed
158        ));
159        assert!(matches!(
160            SimError::Dynamics(nabled_dynamics::DynamicsError::DimensionMismatch)
161                .into_nabled_error(),
162            NabledError::Shape(ShapeError::DimensionMismatch)
163        ));
164        assert!(matches!(
165            SimError::Control(nabled_control::ControlError::SingularSystem).into_nabled_error(),
166            NabledError::SingularMatrix
167        ));
168        assert!(matches!(
169            SimError::Sensor(nabled_sensor::SensorError::EmptyInput).into_nabled_error(),
170            NabledError::Shape(ShapeError::EmptyInput)
171        ));
172        assert!(matches!(
173            SimError::Stats(nabled_ml::stats::StatsError::NumericalInstability).into_nabled_error(),
174            NabledError::NumericalInstability
175        ));
176    }
177}