1use super::variables::Value;
11use crate::error::{AlgorithmError, Result};
12use crate::raster::{gaussian_blur, median_filter};
13
14#[cfg(not(feature = "std"))]
15use alloc::{boxed::Box, string::String, vec::Vec};
16
17pub type BuiltinFn = fn(&[Value]) -> Result<Value>;
19
20pub struct FunctionRegistry {
22 functions: Vec<(&'static str, BuiltinFn, usize)>, }
24
25impl Default for FunctionRegistry {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl FunctionRegistry {
32 pub fn new() -> Self {
34 let mut registry = Self {
35 functions: Vec::new(),
36 };
37
38 registry.register("sqrt", fn_sqrt, 1);
40 registry.register("abs", fn_abs, 1);
41 registry.register("floor", fn_floor, 1);
42 registry.register("ceil", fn_ceil, 1);
43 registry.register("round", fn_round, 1);
44 registry.register("log", fn_log, 1);
45 registry.register("log10", fn_log10, 1);
46 registry.register("log2", fn_log2, 1);
47 registry.register("exp", fn_exp, 1);
48 registry.register("sin", fn_sin, 1);
49 registry.register("cos", fn_cos, 1);
50 registry.register("tan", fn_tan, 1);
51 registry.register("asin", fn_asin, 1);
52 registry.register("acos", fn_acos, 1);
53 registry.register("atan", fn_atan, 1);
54 registry.register("sinh", fn_sinh, 1);
55 registry.register("cosh", fn_cosh, 1);
56 registry.register("tanh", fn_tanh, 1);
57
58 registry.register("atan2", fn_atan2, 2);
60 registry.register("pow", fn_pow, 2);
61 registry.register("hypot", fn_hypot, 2);
62
63 registry.register("min", fn_min, 0);
65 registry.register("max", fn_max, 0);
66
67 registry.register("mean", fn_mean, 1);
69 registry.register("median", fn_median, 1);
70 registry.register("mode", fn_mode, 1);
71 registry.register("stddev", fn_stddev, 1);
72 registry.register("variance", fn_variance, 1);
73 registry.register("sum", fn_sum, 1);
74 registry.register("product", fn_product, 1);
75
76 registry.register("percentile", fn_percentile, 2);
78
79 registry.register("gaussian", fn_gaussian, 2);
81 registry.register("median_filter", fn_median_filt, 2);
82
83 registry.register("and", fn_and, 2);
85 registry.register("or", fn_or, 2);
86 registry.register("not", fn_not, 1);
87 registry.register("xor", fn_xor, 2);
88
89 registry.register("eq", fn_eq, 2);
91 registry.register("ne", fn_ne, 2);
92 registry.register("lt", fn_lt, 2);
93 registry.register("le", fn_le, 2);
94 registry.register("gt", fn_gt, 2);
95 registry.register("ge", fn_ge, 2);
96
97 registry.register("to_number", fn_to_number, 1);
99 registry.register("to_bool", fn_to_bool, 1);
100
101 registry.register("clamp", fn_clamp, 3);
103 registry.register("select", fn_select, 3);
104
105 registry
106 }
107
108 pub fn register(&mut self, name: &'static str, func: BuiltinFn, arity: usize) {
110 self.functions.push((name, func, arity));
111 }
112
113 pub fn lookup(&self, name: &str) -> Option<(BuiltinFn, usize)> {
115 self.functions
116 .iter()
117 .find(|(n, _, _)| *n == name)
118 .map(|(_, f, a)| (*f, *a))
119 }
120
121 pub fn exists(&self, name: &str) -> bool {
123 self.functions.iter().any(|(n, _, _)| *n == name)
124 }
125
126 pub fn function_names(&self) -> Vec<&'static str> {
128 self.functions.iter().map(|(n, _, _)| *n).collect()
129 }
130}
131
132fn apply_unary_fn<F>(value: &Value, f: F) -> Result<Value>
136where
137 F: Fn(f64) -> f64,
138{
139 match value {
140 Value::Number(x) => Ok(Value::Number(f(*x))),
141 Value::Raster(raster) => {
142 use oxigeo_core::types::RasterDataType;
143 let width = raster.width();
144 let height = raster.height();
145 let mut result =
146 oxigeo_core::buffer::RasterBuffer::zeros(width, height, RasterDataType::Float32);
147
148 for y in 0..height {
149 for x in 0..width {
150 let pixel = raster
151 .get_pixel(x, y)
152 .map_err(crate::error::AlgorithmError::Core)?;
153 let new_val = f(pixel);
154 result
155 .set_pixel(x, y, new_val)
156 .map_err(crate::error::AlgorithmError::Core)?;
157 }
158 }
159
160 Ok(Value::Raster(Box::new(result)))
161 }
162 _ => Err(AlgorithmError::InvalidParameter {
163 parameter: "value",
164 message: "Expected number or raster".to_string(),
165 }),
166 }
167}
168
169fn apply_binary_fn<F>(left: &Value, right: &Value, f: F) -> Result<Value>
171where
172 F: Fn(f64, f64) -> f64,
173{
174 match (left, right) {
175 (Value::Number(l), Value::Number(r)) => Ok(Value::Number(f(*l, *r))),
176 (Value::Raster(raster), Value::Number(scalar))
177 | (Value::Number(scalar), Value::Raster(raster)) => {
178 use oxigeo_core::types::RasterDataType;
179 let width = raster.width();
180 let height = raster.height();
181 let mut result =
182 oxigeo_core::buffer::RasterBuffer::zeros(width, height, RasterDataType::Float32);
183
184 for y in 0..height {
185 for x in 0..width {
186 let pixel = raster
187 .get_pixel(x, y)
188 .map_err(crate::error::AlgorithmError::Core)?;
189 let new_val = f(pixel, *scalar);
190 result
191 .set_pixel(x, y, new_val)
192 .map_err(crate::error::AlgorithmError::Core)?;
193 }
194 }
195
196 Ok(Value::Raster(Box::new(result)))
197 }
198 (Value::Raster(left_raster), Value::Raster(right_raster)) => {
199 use oxigeo_core::types::RasterDataType;
200 let width = left_raster.width();
201 let height = left_raster.height();
202
203 if right_raster.width() != width || right_raster.height() != height {
204 return Err(AlgorithmError::InvalidDimensions {
205 message: "Rasters must have same dimensions",
206 actual: right_raster.width() as usize,
207 expected: width as usize,
208 });
209 }
210
211 let mut result =
212 oxigeo_core::buffer::RasterBuffer::zeros(width, height, RasterDataType::Float32);
213
214 for y in 0..height {
215 for x in 0..width {
216 let left_pixel = left_raster
217 .get_pixel(x, y)
218 .map_err(crate::error::AlgorithmError::Core)?;
219 let right_pixel = right_raster
220 .get_pixel(x, y)
221 .map_err(crate::error::AlgorithmError::Core)?;
222 let new_val = f(left_pixel, right_pixel);
223 result
224 .set_pixel(x, y, new_val)
225 .map_err(crate::error::AlgorithmError::Core)?;
226 }
227 }
228
229 Ok(Value::Raster(Box::new(result)))
230 }
231 _ => Err(AlgorithmError::InvalidParameter {
232 parameter: "value",
233 message: "Expected number or raster".to_string(),
234 }),
235 }
236}
237
238fn fn_sqrt(args: &[Value]) -> Result<Value> {
239 apply_unary_fn(&args[0], |x| x.sqrt())
240}
241
242fn fn_abs(args: &[Value]) -> Result<Value> {
243 apply_unary_fn(&args[0], |x| x.abs())
244}
245
246fn fn_floor(args: &[Value]) -> Result<Value> {
247 apply_unary_fn(&args[0], |x| x.floor())
248}
249
250fn fn_ceil(args: &[Value]) -> Result<Value> {
251 apply_unary_fn(&args[0], |x| x.ceil())
252}
253
254fn fn_round(args: &[Value]) -> Result<Value> {
255 apply_unary_fn(&args[0], |x| x.round())
256}
257
258fn fn_log(args: &[Value]) -> Result<Value> {
259 apply_unary_fn(&args[0], |x| x.ln())
260}
261
262fn fn_log10(args: &[Value]) -> Result<Value> {
263 apply_unary_fn(&args[0], |x| x.log10())
264}
265
266fn fn_log2(args: &[Value]) -> Result<Value> {
267 apply_unary_fn(&args[0], |x| x.log2())
268}
269
270fn fn_exp(args: &[Value]) -> Result<Value> {
271 apply_unary_fn(&args[0], |x| x.exp())
272}
273
274fn fn_sin(args: &[Value]) -> Result<Value> {
275 apply_unary_fn(&args[0], |x| x.sin())
276}
277
278fn fn_cos(args: &[Value]) -> Result<Value> {
279 apply_unary_fn(&args[0], |x| x.cos())
280}
281
282fn fn_tan(args: &[Value]) -> Result<Value> {
283 apply_unary_fn(&args[0], |x| x.tan())
284}
285
286fn fn_asin(args: &[Value]) -> Result<Value> {
287 apply_unary_fn(&args[0], |x| x.asin())
288}
289
290fn fn_acos(args: &[Value]) -> Result<Value> {
291 apply_unary_fn(&args[0], |x| x.acos())
292}
293
294fn fn_atan(args: &[Value]) -> Result<Value> {
295 apply_unary_fn(&args[0], |x| x.atan())
296}
297
298fn fn_sinh(args: &[Value]) -> Result<Value> {
299 apply_unary_fn(&args[0], |x| x.sinh())
300}
301
302fn fn_cosh(args: &[Value]) -> Result<Value> {
303 apply_unary_fn(&args[0], |x| x.cosh())
304}
305
306fn fn_tanh(args: &[Value]) -> Result<Value> {
307 apply_unary_fn(&args[0], |x| x.tanh())
308}
309
310fn fn_atan2(args: &[Value]) -> Result<Value> {
311 apply_binary_fn(&args[0], &args[1], |y, x| y.atan2(x))
312}
313
314fn fn_pow(args: &[Value]) -> Result<Value> {
315 apply_binary_fn(&args[0], &args[1], |base, exp| base.powf(exp))
316}
317
318fn fn_hypot(args: &[Value]) -> Result<Value> {
319 apply_binary_fn(&args[0], &args[1], |x, y| x.hypot(y))
320}
321
322fn fn_min(args: &[Value]) -> Result<Value> {
323 if args.is_empty() {
324 return Err(AlgorithmError::InvalidParameter {
325 parameter: "min",
326 message: "Expected at least 1 argument".to_string(),
327 });
328 }
329
330 let mut min_val = args[0].as_number()?;
336 for arg in &args[1..] {
337 min_val = min_val.min(arg.as_number()?);
338 }
339 Ok(Value::Number(min_val))
340}
341
342fn fn_max(args: &[Value]) -> Result<Value> {
343 if args.is_empty() {
344 return Err(AlgorithmError::InvalidParameter {
345 parameter: "max",
346 message: "Expected at least 1 argument".to_string(),
347 });
348 }
349
350 let mut max_val = args[0].as_number()?;
353 for arg in &args[1..] {
354 max_val = max_val.max(arg.as_number()?);
355 }
356 Ok(Value::Number(max_val))
357}
358
359fn fn_mean(args: &[Value]) -> Result<Value> {
362 let raster = args[0].as_raster()?;
363 let mut sum = 0.0;
364 let mut count = 0u64;
365
366 for y in 0..raster.height() {
367 for x in 0..raster.width() {
368 if let Ok(val) = raster.get_pixel(x, y) {
369 if val.is_finite() {
370 sum += val;
371 count += 1;
372 }
373 }
374 }
375 }
376
377 if count == 0 {
378 return Err(AlgorithmError::EmptyInput { operation: "mean" });
379 }
380
381 Ok(Value::Number(sum / count as f64))
382}
383
384fn fn_median(args: &[Value]) -> Result<Value> {
385 let raster = args[0].as_raster()?;
386 let mut values: Vec<f64> = Vec::with_capacity((raster.width() * raster.height()) as usize);
387
388 for y in 0..raster.height() {
389 for x in 0..raster.width() {
390 if let Ok(val) = raster.get_pixel(x, y) {
391 if val.is_finite() {
392 values.push(val);
393 }
394 }
395 }
396 }
397
398 if values.is_empty() {
399 return Err(AlgorithmError::EmptyInput {
400 operation: "median",
401 });
402 }
403
404 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
406
407 let mid = values.len() / 2;
408 let median = if values.len().is_multiple_of(2) {
409 (values[mid - 1] + values[mid]) / 2.0
410 } else {
411 values[mid]
412 };
413
414 Ok(Value::Number(median))
415}
416
417fn fn_mode(args: &[Value]) -> Result<Value> {
418 let raster = args[0].as_raster()?;
419
420 use std::collections::HashMap;
423 let mut freq: HashMap<u64, (f64, u64)> = HashMap::new();
424
425 for y in 0..raster.height() {
426 for x in 0..raster.width() {
427 if let Ok(val) = raster.get_pixel(x, y) {
428 if val.is_finite() {
429 let key = val.to_bits();
430 let entry = freq.entry(key).or_insert((val, 0));
431 entry.1 += 1;
432 }
433 }
434 }
435 }
436
437 if freq.is_empty() {
438 return Err(AlgorithmError::EmptyInput { operation: "mode" });
439 }
440
441 let mode = freq
443 .values()
444 .max_by(|a, b| {
445 a.1.cmp(&b.1)
446 .then_with(|| b.0.partial_cmp(&a.0).unwrap_or(std::cmp::Ordering::Equal))
447 })
448 .map(|(val, _)| *val)
449 .ok_or(AlgorithmError::EmptyInput { operation: "mode" })?;
450
451 Ok(Value::Number(mode))
452}
453
454fn fn_stddev(args: &[Value]) -> Result<Value> {
455 let raster = args[0].as_raster()?;
456 let mut sum = 0.0;
457 let mut sum_sq = 0.0;
458 let mut count = 0u64;
459
460 for y in 0..raster.height() {
461 for x in 0..raster.width() {
462 if let Ok(val) = raster.get_pixel(x, y) {
463 if val.is_finite() {
464 sum += val;
465 sum_sq += val * val;
466 count += 1;
467 }
468 }
469 }
470 }
471
472 if count == 0 {
473 return Err(AlgorithmError::EmptyInput {
474 operation: "stddev",
475 });
476 }
477
478 let mean = sum / count as f64;
479 let variance = (sum_sq / count as f64) - (mean * mean);
480 Ok(Value::Number(variance.sqrt()))
481}
482
483fn fn_variance(args: &[Value]) -> Result<Value> {
484 let raster = args[0].as_raster()?;
485 let mut sum = 0.0;
486 let mut sum_sq = 0.0;
487 let mut count = 0u64;
488
489 for y in 0..raster.height() {
490 for x in 0..raster.width() {
491 if let Ok(val) = raster.get_pixel(x, y) {
492 if val.is_finite() {
493 sum += val;
494 sum_sq += val * val;
495 count += 1;
496 }
497 }
498 }
499 }
500
501 if count == 0 {
502 return Err(AlgorithmError::EmptyInput {
503 operation: "variance",
504 });
505 }
506
507 let mean = sum / count as f64;
508 let variance = (sum_sq / count as f64) - (mean * mean);
509 Ok(Value::Number(variance))
510}
511
512fn fn_sum(args: &[Value]) -> Result<Value> {
513 let raster = args[0].as_raster()?;
514 let mut sum = 0.0;
515
516 for y in 0..raster.height() {
517 for x in 0..raster.width() {
518 if let Ok(val) = raster.get_pixel(x, y) {
519 if val.is_finite() {
520 sum += val;
521 }
522 }
523 }
524 }
525
526 Ok(Value::Number(sum))
527}
528
529fn fn_product(args: &[Value]) -> Result<Value> {
530 let raster = args[0].as_raster()?;
531 let mut product = 1.0;
532
533 for y in 0..raster.height() {
534 for x in 0..raster.width() {
535 if let Ok(val) = raster.get_pixel(x, y) {
536 if val.is_finite() {
537 product *= val;
538 }
539 }
540 }
541 }
542
543 Ok(Value::Number(product))
544}
545
546fn fn_percentile(args: &[Value]) -> Result<Value> {
547 let raster = args[0].as_raster()?;
548 let p = args[1].as_number()?;
549
550 if !(0.0..=100.0).contains(&p) {
551 return Err(AlgorithmError::InvalidParameter {
552 parameter: "percentile",
553 message: format!("Percentile must be in [0, 100], got {p}"),
554 });
555 }
556
557 let mut values: Vec<f64> = Vec::with_capacity((raster.width() * raster.height()) as usize);
558
559 for y in 0..raster.height() {
560 for x in 0..raster.width() {
561 if let Ok(val) = raster.get_pixel(x, y) {
562 if val.is_finite() {
563 values.push(val);
564 }
565 }
566 }
567 }
568
569 if values.is_empty() {
570 return Err(AlgorithmError::EmptyInput {
571 operation: "percentile",
572 });
573 }
574
575 values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
576
577 let n = values.len();
578 if n == 1 {
579 return Ok(Value::Number(values[0]));
580 }
581
582 let rank = p / 100.0 * (n - 1) as f64;
584 let lower = rank.floor() as usize;
585 let upper = (lower + 1).min(n - 1);
586 let frac = rank - lower as f64;
587 let result = values[lower] + frac * (values[upper] - values[lower]);
588
589 Ok(Value::Number(result))
590}
591
592fn fn_gaussian(args: &[Value]) -> Result<Value> {
595 let raster = args[0].as_raster()?;
596 let sigma = args[1].as_number()?;
597
598 let result = gaussian_blur(raster, sigma, None)?;
599 Ok(Value::Raster(Box::new(result)))
600}
601
602fn fn_median_filt(args: &[Value]) -> Result<Value> {
603 let raster = args[0].as_raster()?;
604 let radius = args[1].as_number()? as usize;
605
606 let result = median_filter(raster, radius)?;
607 Ok(Value::Raster(Box::new(result)))
608}
609
610fn fn_and(args: &[Value]) -> Result<Value> {
613 let a = args[0].as_bool()?;
614 let b = args[1].as_bool()?;
615 Ok(Value::Bool(a && b))
616}
617
618fn fn_or(args: &[Value]) -> Result<Value> {
619 let a = args[0].as_bool()?;
620 let b = args[1].as_bool()?;
621 Ok(Value::Bool(a || b))
622}
623
624fn fn_not(args: &[Value]) -> Result<Value> {
625 let a = args[0].as_bool()?;
626 Ok(Value::Bool(!a))
627}
628
629fn fn_xor(args: &[Value]) -> Result<Value> {
630 let a = args[0].as_bool()?;
631 let b = args[1].as_bool()?;
632 Ok(Value::Bool(a ^ b))
633}
634
635fn fn_eq(args: &[Value]) -> Result<Value> {
638 let a = args[0].as_number()?;
639 let b = args[1].as_number()?;
640 Ok(Value::Bool((a - b).abs() < f64::EPSILON))
641}
642
643fn fn_ne(args: &[Value]) -> Result<Value> {
644 let a = args[0].as_number()?;
645 let b = args[1].as_number()?;
646 Ok(Value::Bool((a - b).abs() >= f64::EPSILON))
647}
648
649fn fn_lt(args: &[Value]) -> Result<Value> {
650 let a = args[0].as_number()?;
651 let b = args[1].as_number()?;
652 Ok(Value::Bool(a < b))
653}
654
655fn fn_le(args: &[Value]) -> Result<Value> {
656 let a = args[0].as_number()?;
657 let b = args[1].as_number()?;
658 Ok(Value::Bool(a <= b))
659}
660
661fn fn_gt(args: &[Value]) -> Result<Value> {
662 let a = args[0].as_number()?;
663 let b = args[1].as_number()?;
664 Ok(Value::Bool(a > b))
665}
666
667fn fn_ge(args: &[Value]) -> Result<Value> {
668 let a = args[0].as_number()?;
669 let b = args[1].as_number()?;
670 Ok(Value::Bool(a >= b))
671}
672
673fn fn_to_number(args: &[Value]) -> Result<Value> {
676 args[0].as_number().map(Value::Number)
677}
678
679fn fn_to_bool(args: &[Value]) -> Result<Value> {
680 args[0].as_bool().map(Value::Bool)
681}
682
683fn fn_clamp(args: &[Value]) -> Result<Value> {
686 let value = args[0].as_number()?;
687 let min = args[1].as_number()?;
688 let max = args[2].as_number()?;
689
690 let clamped = if value < min {
691 min
692 } else if value > max {
693 max
694 } else {
695 value
696 };
697
698 Ok(Value::Number(clamped))
699}
700
701fn fn_select(args: &[Value]) -> Result<Value> {
702 let cond = args[0].as_bool()?;
703 if cond {
704 Ok(args[1].clone())
705 } else {
706 Ok(args[2].clone())
707 }
708}
709
710#[cfg(test)]
711#[allow(clippy::panic)]
712mod tests {
713 use super::*;
714 use oxigeo_core::buffer::RasterBuffer;
715 use oxigeo_core::types::RasterDataType;
716
717 #[test]
718 fn test_function_registry() {
719 let registry = FunctionRegistry::new();
720 assert!(registry.exists("sqrt"));
721 assert!(registry.exists("sin"));
722 assert!(registry.exists("mean"));
723 assert!(!registry.exists("nonexistent"));
724 }
725
726 #[test]
727 fn test_math_functions() {
728 let args = vec![Value::Number(16.0)];
729 let result = fn_sqrt(&args).expect("Should work");
730 if let Value::Number(n) = result {
731 assert!((n - 4.0).abs() < 1e-10);
732 } else {
733 panic!("Expected number");
734 }
735 }
736
737 #[test]
738 fn test_min_max() {
739 let args = vec![
740 Value::Number(3.0),
741 Value::Number(1.0),
742 Value::Number(4.0),
743 Value::Number(1.0),
744 Value::Number(5.0),
745 ];
746
747 let min_result = fn_min(&args).expect("Should work");
748 if let Value::Number(n) = min_result {
749 assert!((n - 1.0).abs() < 1e-10);
750 }
751
752 let max_result = fn_max(&args).expect("Should work");
753 if let Value::Number(n) = max_result {
754 assert!((n - 5.0).abs() < 1e-10);
755 }
756 }
757
758 #[test]
759 fn test_min_max_nan_order_independent() {
760 let nan_first = vec![Value::Number(f64::NAN), Value::Number(5.0)];
763 let nan_last = vec![Value::Number(5.0), Value::Number(f64::NAN)];
764
765 let min_a = fn_min(&nan_first).expect("min should work");
766 let min_b = fn_min(&nan_last).expect("min should work");
767 if let (Value::Number(a), Value::Number(b)) = (min_a, min_b) {
768 assert!(a.is_finite() && b.is_finite(), "NaN must not poison min");
769 assert!((a - 5.0).abs() < 1e-10 && (b - 5.0).abs() < 1e-10);
770 } else {
771 panic!("expected numbers");
772 }
773
774 let max_a = fn_max(&nan_first).expect("max should work");
775 let max_b = fn_max(&nan_last).expect("max should work");
776 if let (Value::Number(a), Value::Number(b)) = (max_a, max_b) {
777 assert!(a.is_finite() && b.is_finite(), "NaN must not poison max");
778 assert!((a - 5.0).abs() < 1e-10 && (b - 5.0).abs() < 1e-10);
779 } else {
780 panic!("expected numbers");
781 }
782 }
783
784 #[test]
785 fn test_mean() {
786 let mut raster = RasterBuffer::zeros(10, 10, RasterDataType::Float32);
787 for y in 0..10 {
788 for x in 0..10 {
789 let _ = raster.set_pixel(x, y, (x + y) as f64);
790 }
791 }
792
793 let args = vec![Value::Raster(Box::new(raster))];
794 let result = fn_mean(&args);
795 assert!(result.is_ok());
796 }
797
798 #[test]
799 fn test_logical_functions() {
800 let args_true = vec![Value::Bool(true), Value::Bool(true)];
801 let result = fn_and(&args_true).expect("Should work");
802 assert!(matches!(result, Value::Bool(true)));
803
804 let args_false = vec![Value::Bool(true), Value::Bool(false)];
805 let result = fn_and(&args_false).expect("Should work");
806 assert!(matches!(result, Value::Bool(false)));
807 }
808
809 #[test]
810 fn test_clamp() {
811 let args = vec![Value::Number(15.0), Value::Number(0.0), Value::Number(10.0)];
812 let result = fn_clamp(&args).expect("Should work");
813 if let Value::Number(n) = result {
814 assert!((n - 10.0).abs() < 1e-10);
815 }
816 }
817}