ftui_runtime/
sos_barrier.rs1#![forbid(unsafe_code)]
2
3include!("sos_barrier_coeffs.rs");
25
26#[derive(Debug, Clone, Copy, PartialEq)]
28pub struct BarrierResult {
29 pub value: f64,
31 pub is_safe: bool,
33 pub budget_remaining: f64,
35 pub change_rate: f64,
37}
38
39#[must_use]
50pub fn evaluate(budget_remaining: f64, change_rate: f64) -> BarrierResult {
51 let x1 = budget_remaining.clamp(0.0, 1.0);
52 let x2 = change_rate.clamp(0.0, 1.0);
53
54 let value = eval_polynomial(x1, x2);
55
56 BarrierResult {
57 value,
58 is_safe: value > 0.0,
59 budget_remaining: x1,
60 change_rate: x2,
61 }
62}
63
64#[inline]
71fn eval_polynomial(x1: f64, x2: f64) -> f64 {
72 let mut result = 0.0;
80 let mut offset = 0;
81 let mut x1_power = 1.0;
82
83 for i in 0..=BARRIER_DEGREE {
84 let row_len = BARRIER_DEGREE - i + 1;
85 let mut row_val = BARRIER_COEFFS[offset + row_len - 1];
87 for j in (0..row_len - 1).rev() {
88 row_val = row_val * x2 + BARRIER_COEFFS[offset + j];
89 }
90 result += x1_power * row_val;
91 x1_power *= x1;
92 offset += row_len;
93 }
94
95 result
96}
97
98#[must_use]
103pub fn safety_margin(budget_remaining: f64, change_rate: f64) -> f64 {
104 evaluate(budget_remaining, change_rate).value
105}
106
107#[must_use]
109pub fn is_admissible(budget_remaining: f64, change_rate: f64) -> bool {
110 evaluate(budget_remaining, change_rate).is_safe
111}
112
113#[cfg(test)]
114mod tests {
115 use super::*;
116
117 #[test]
120 fn full_budget_no_changes_is_safe() {
121 let r = evaluate(1.0, 0.0);
122 assert!(r.is_safe);
123 assert!(
124 r.value > 1.0,
125 "B(1,0) should be strongly positive: {}",
126 r.value
127 );
128 }
129
130 #[test]
131 fn high_budget_low_change_is_safe() {
132 let r = evaluate(0.8, 0.1);
133 assert!(r.is_safe);
134 }
135
136 #[test]
137 fn half_budget_moderate_change_is_safe() {
138 let r = evaluate(0.5, 0.2);
139 assert!(r.is_safe);
140 }
141
142 #[test]
143 fn low_budget_low_change_is_safe() {
144 let r = evaluate(0.3, 0.1);
145 assert!(r.is_safe);
146 }
147
148 #[test]
151 fn no_budget_high_change_is_unsafe() {
152 let r = evaluate(0.0, 0.8);
153 assert!(!r.is_safe);
154 assert!(r.value < 0.0);
155 }
156
157 #[test]
158 fn no_budget_max_change_is_unsafe() {
159 let r = evaluate(0.0, 1.0);
160 assert!(!r.is_safe);
161 assert!(r.value < -0.5);
162 }
163
164 #[test]
165 fn nearly_no_budget_very_high_change_is_unsafe() {
166 let r = evaluate(0.05, 0.95);
167 assert!(!r.is_safe);
168 }
169
170 #[test]
173 fn origin_is_boundary() {
174 let r = evaluate(0.0, 0.0);
175 assert!(r.value.abs() < 1e-10, "B(0,0) should be ~0: {}", r.value);
176 }
177
178 #[test]
181 fn negative_budget_clamped_to_zero() {
182 let r = evaluate(-0.5, 0.0);
183 assert_eq!(r.budget_remaining, 0.0);
184 }
185
186 #[test]
187 fn over_budget_clamped_to_one() {
188 let r = evaluate(1.5, 0.0);
189 assert_eq!(r.budget_remaining, 1.0);
190 }
191
192 #[test]
193 fn negative_change_rate_clamped() {
194 let r = evaluate(0.5, -0.1);
195 assert_eq!(r.change_rate, 0.0);
196 }
197
198 #[test]
199 fn over_change_rate_clamped() {
200 let r = evaluate(0.5, 1.5);
201 assert_eq!(r.change_rate, 1.0);
202 }
203
204 #[test]
207 fn safety_margin_matches_evaluate() {
208 let m = safety_margin(0.5, 0.2);
209 let r = evaluate(0.5, 0.2);
210 assert!((m - r.value).abs() < 1e-15);
211 }
212
213 #[test]
214 fn is_admissible_matches_evaluate() {
215 assert!(is_admissible(0.8, 0.1));
216 assert!(!is_admissible(0.0, 0.9));
217 }
218
219 #[test]
222 fn increasing_budget_increases_safety() {
223 let low = evaluate(0.2, 0.3);
224 let high = evaluate(0.8, 0.3);
225 assert!(
226 high.value > low.value,
227 "more budget should be safer: B(0.8,0.3)={} vs B(0.2,0.3)={}",
228 high.value,
229 low.value
230 );
231 }
232
233 #[test]
234 fn increasing_change_rate_decreases_safety() {
235 let low = evaluate(0.5, 0.1);
236 let high = evaluate(0.5, 0.8);
237 assert!(
238 low.value > high.value,
239 "more change should be less safe: B(0.5,0.1)={} vs B(0.5,0.8)={}",
240 low.value,
241 high.value
242 );
243 }
244
245 #[test]
248 fn coefficients_match_expected_count() {
249 assert_eq!(BARRIER_COEFFS.len(), BARRIER_N_TERMS);
250 assert_eq!(BARRIER_N_TERMS, 15); }
252
253 #[test]
254 fn degree_is_four() {
255 assert_eq!(BARRIER_DEGREE, 4);
256 }
257}