Skip to main content

qae_kernel/
constraint.rs

1// SPDX-License-Identifier: BUSL-1.1
2//! Domain-agnostic constraint channel trait for the safety kernel.
3//!
4//! Constraint channels evaluate a single dimension of safety and return a
5//! margin in \[0, 1\]. Domain adapters compose multiple channels to cover
6//! all relevant safety dimensions for a given domain.
7
8use crate::KernelResult;
9
10/// A domain-agnostic constraint channel operating on a state vector.
11///
12/// Each channel evaluates a constraint dimension and returns a margin in [0, 1],
13/// where 0 means "at constraint boundary" and 1 means "maximum headroom".
14pub trait ConstraintChannel: Send + Sync {
15    /// Human-readable name of this constraint channel.
16    fn name(&self) -> &str;
17
18    /// Evaluate this constraint channel against the given state vector.
19    ///
20    /// Returns a margin in [0, 1].
21    fn evaluate(&self, state: &[f64]) -> KernelResult<f64>;
22
23    /// Names of state dimensions this channel operates on.
24    fn dimension_names(&self) -> Vec<String>;
25}
26
27#[cfg(test)]
28mod tests {
29    use super::*;
30
31    struct FixedChannel {
32        name: String,
33        margin: f64,
34        dims: Vec<String>,
35    }
36
37    impl ConstraintChannel for FixedChannel {
38        fn name(&self) -> &str {
39            &self.name
40        }
41
42        fn evaluate(&self, _state: &[f64]) -> KernelResult<f64> {
43            Ok(self.margin)
44        }
45
46        fn dimension_names(&self) -> Vec<String> {
47            self.dims.clone()
48        }
49    }
50
51    #[test]
52    fn fixed_channel_returns_margin() {
53        let ch = FixedChannel {
54            name: "test".into(),
55            margin: 0.75,
56            dims: vec!["x".into()],
57        };
58        assert_eq!(ch.name(), "test");
59        assert!((ch.evaluate(&[1.0]).unwrap() - 0.75).abs() < f64::EPSILON);
60        assert_eq!(ch.dimension_names(), vec!["x".to_string()]);
61    }
62
63    #[test]
64    fn channel_is_object_safe() {
65        let ch: Box<dyn ConstraintChannel> = Box::new(FixedChannel {
66            name: "boxed".into(),
67            margin: 0.5,
68            dims: vec![],
69        });
70        assert_eq!(ch.name(), "boxed");
71        assert!((ch.evaluate(&[]).unwrap() - 0.5).abs() < f64::EPSILON);
72    }
73}