1use std::os::raw::c_int;
2
3#[no_mangle]
5pub extern "C" fn flux_eisenstein_norm(a: c_int, b: c_int) -> i64 {
6 let a = a as i64;
7 let b = b as i64;
8 a * a - a * b + b * b
9}
10
11#[no_mangle]
13pub extern "C" fn flux_laman_edges(vertices: c_int) -> c_int {
14 if vertices < 2 { return 0; }
15 2 * vertices - 3
16}
17
18#[no_mangle]
20pub extern "C" fn flux_is_rigid(vertices: c_int, edges: c_int) -> bool {
21 if vertices < 2 {
22 return edges == 0;
23 }
24 edges == 2 * vertices - 3
25}
26
27#[no_mangle]
29pub extern "C" fn flux_holonomy_check(transforms: *const i64, len: usize) -> bool {
30 if transforms.is_null() || len == 0 {
31 return true;
32 }
33 let slice = unsafe { std::slice::from_raw_parts(transforms, len) };
34 slice.iter().sum::<i64>() == 0
36}
37
38#[no_mangle]
40pub extern "C" fn flux_manhattan_distance(a: *const c_int, b: *const c_int, len: usize) -> i64 {
41 if a.is_null() || b.is_null() || len == 0 {
42 return 0;
43 }
44 let sa = unsafe { std::slice::from_raw_parts(a, len) };
45 let sb = unsafe { std::slice::from_raw_parts(b, len) };
46 sa.iter().zip(sb.iter())
47 .map(|(&x, &y)| (x as i64 - y as i64).abs())
48 .sum()
49}
50
51#[no_mangle]
53pub extern "C" fn flux_pythagorean48_encode(x: f64, y: f64) -> c_int {
54 let ix = x.round() as i32;
56 let iy = y.round() as i32;
57 let radius = (ix * ix + iy * iy) as i32;
58 if radius == 0 {
59 return 0;
60 }
61 let angle = (iy as f64).atan2(ix as f64);
63 let sector = ((angle + std::f64::consts::PI) / (2.0 * std::f64::consts::PI / 48.0)).round() as i32;
64 radius * 48 + sector
65}
66
67#[no_mangle]
69pub extern "C" fn flux_constraint_check(values: *const f64, bounds: *const f64, len: usize) -> c_int {
70 if values.is_null() || bounds.is_null() || len == 0 {
71 return 0;
72 }
73 let sv = unsafe { std::slice::from_raw_parts(values, len) };
74 let sb = unsafe { std::slice::from_raw_parts(bounds, len) };
75 sv.iter().zip(sb.iter())
76 .filter(|(&v, &b)| v > b)
77 .count() as c_int
78}
79
80#[no_mangle]
82pub extern "C" fn flux_spline_interpolate(control_points: *const f64, t: f64, n_points: usize) -> f64 {
83 if control_points.is_null() || n_points == 0 {
84 return 0.0;
85 }
86 if n_points == 1 {
87 return unsafe { *control_points };
88 }
89 let slice = unsafe { std::slice::from_raw_parts(control_points, n_points) };
90 let t_clamped = t.clamp(0.0, 1.0);
91 let idx = t_clamped * (n_points - 1) as f64;
92 let lo = idx.floor() as usize;
93 let hi = (lo + 1).min(n_points - 1);
94 let frac = idx - lo as f64;
95 slice[lo] * (1.0 - frac) + slice[hi] * frac
96}
97
98#[no_mangle]
100pub extern "C" fn flux_deadband_filter(value: f64, baseline: f64, threshold: f64) -> f64 {
101 if (value - baseline).abs() > threshold {
102 value
103 } else {
104 baseline
105 }
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111
112 #[test]
114 fn test_eisenstein_norm_zero() {
115 assert_eq!(flux_eisenstein_norm(0, 0), 0);
116 }
117
118 #[test]
119 fn test_eisenstein_norm_unit() {
120 assert_eq!(flux_eisenstein_norm(1, 0), 1);
121 assert_eq!(flux_eisenstein_norm(0, 1), 1);
122 assert_eq!(flux_eisenstein_norm(1, 1), 1);
123 }
124
125 #[test]
126 fn test_eisenstein_norm_general() {
127 assert_eq!(flux_eisenstein_norm(2, 3), 7);
129 assert_eq!(flux_eisenstein_norm(-1, 2), 7);
131 }
132
133 #[test]
135 fn test_laman_edges_small() {
136 assert_eq!(flux_laman_edges(0), 0);
137 assert_eq!(flux_laman_edges(1), 0);
138 assert_eq!(flux_laman_edges(2), 1);
139 assert_eq!(flux_laman_edges(3), 3);
140 assert_eq!(flux_laman_edges(4), 5);
141 }
142
143 #[test]
145 fn test_is_rigid_laman() {
146 assert!(flux_is_rigid(4, 5)); assert!(flux_is_rigid(2, 1)); assert!(!flux_is_rigid(4, 4)); assert!(!flux_is_rigid(4, 6)); }
151
152 #[test]
153 fn test_is_rigid_trivial() {
154 assert!(flux_is_rigid(0, 0));
155 assert!(flux_is_rigid(1, 0));
156 assert!(!flux_is_rigid(1, 1));
157 }
158
159 #[test]
161 fn test_holonomy_trivial() {
162 assert!(flux_holonomy_check(std::ptr::null(), 0));
163 }
164
165 #[test]
166 fn test_holonomy_zero_sum() {
167 let transforms: [i64; 4] = [1, 2, -3, 0];
168 assert!(flux_holonomy_check(transforms.as_ptr(), 4));
169 }
170
171 #[test]
172 fn test_holonomy_nonzero() {
173 let transforms: [i64; 3] = [1, 2, 3];
174 assert!(!flux_holonomy_check(transforms.as_ptr(), 3));
175 }
176
177 #[test]
179 fn test_manhattan_identity() {
180 let a: [i32; 3] = [1, 2, 3];
181 let b: [i32; 3] = [1, 2, 3];
182 assert_eq!(flux_manhattan_distance(a.as_ptr(), b.as_ptr(), 3), 0);
183 }
184
185 #[test]
186 fn test_manhattan_basic() {
187 let a: [i32; 3] = [0, 0, 0];
188 let b: [i32; 3] = [1, 2, 3];
189 assert_eq!(flux_manhattan_distance(a.as_ptr(), b.as_ptr(), 3), 6);
190 }
191
192 #[test]
194 fn test_pythagorean48_origin() {
195 assert_eq!(flux_pythagorean48_encode(0.0, 0.0), 0);
196 }
197
198 #[test]
199 fn test_pythagorean48_unit_x() {
200 let idx = flux_pythagorean48_encode(1.0, 0.0);
201 assert!(idx > 0);
203 }
204
205 #[test]
207 fn test_constraint_check_none_violated() {
208 let values: [f64; 3] = [1.0, 2.0, 3.0];
209 let bounds: [f64; 3] = [5.0, 5.0, 5.0];
210 assert_eq!(flux_constraint_check(values.as_ptr(), bounds.as_ptr(), 3), 0);
211 }
212
213 #[test]
214 fn test_constraint_check_some_violated() {
215 let values: [f64; 4] = [1.0, 6.0, 3.0, 10.0];
216 let bounds: [f64; 4] = [5.0, 5.0, 5.0, 5.0];
217 assert_eq!(flux_constraint_check(values.as_ptr(), bounds.as_ptr(), 4), 2);
218 }
219
220 #[test]
222 fn test_spline_single_point() {
223 let pts: [f64; 1] = [42.0];
224 assert!((flux_spline_interpolate(pts.as_ptr(), 0.5, 1) - 42.0).abs() < 1e-10);
225 }
226
227 #[test]
228 fn test_spline_two_points() {
229 let pts: [f64; 2] = [0.0, 10.0];
230 assert!((flux_spline_interpolate(pts.as_ptr(), 0.0, 2) - 0.0).abs() < 1e-10);
231 assert!((flux_spline_interpolate(pts.as_ptr(), 1.0, 2) - 10.0).abs() < 1e-10);
232 assert!((flux_spline_interpolate(pts.as_ptr(), 0.5, 2) - 5.0).abs() < 1e-10);
233 }
234
235 #[test]
236 fn test_spline_three_points() {
237 let pts: [f64; 3] = [0.0, 10.0, 20.0];
238 assert!((flux_spline_interpolate(pts.as_ptr(), 0.25, 3) - 5.0).abs() < 1e-10);
239 }
240
241 #[test]
243 fn test_deadband_within_threshold() {
244 assert!((flux_deadband_filter(10.0, 10.0, 0.5) - 10.0).abs() < 1e-10);
245 assert!((flux_deadband_filter(10.3, 10.0, 0.5) - 10.0).abs() < 1e-10);
246 }
247
248 #[test]
249 fn test_deadband_outside_threshold() {
250 assert!((flux_deadband_filter(11.0, 10.0, 0.5) - 11.0).abs() < 1e-10);
251 assert!((flux_deadband_filter(8.0, 10.0, 1.0) - 8.0).abs() < 1e-10);
252 }
253
254 #[test]
255 fn test_deadband_negative() {
256 assert!((flux_deadband_filter(-1.0, 0.0, 0.5) - (-1.0)).abs() < 1e-10);
257 }
258}