rust_fuzzylogic/
sampler.rs1use crate::{error::FuzzyError, prelude::*, Float};
2
3pub trait Sampler {
4 fn sample(&self, min: Float, max: Float) -> Result<Vec<Float>>;
7}
8
9pub struct UniformSampler {
10 pub n: usize,
11}
12
13impl Default for UniformSampler {
14 fn default() -> Self {
15 Self { n: Self::DEFAULT_N }
16 }
17}
18
19impl UniformSampler {
20 pub const DEFAULT_N: usize = 101;
21
22 pub fn new(n: usize) -> Result<Self> {
23 if n < 2 {
24 return Err(FuzzyError::OutOfBounds);
25 }
26 Ok(Self { n: n })
27 }
28}
29
30impl Sampler for UniformSampler {
31 fn sample(&self, min: Float, max: Float) -> Result<Vec<Float>> {
32 if min >= max {
33 return Err(FuzzyError::BadArity);
34 }
35
36 if !(min.is_finite() && max.is_finite()) {
37 return Err(FuzzyError::BadArity);
38 }
39
40 let n = self.n;
41 let mut sample: Vec<Float> = Vec::with_capacity(n);
42 let step = (max - min) / (n as Float - 1.0);
43
44 for i in 0..n {
45 sample.push(min + i as Float * step)
46 }
47 sample[n - 1] = max;
48
49 Ok(sample)
50 }
51}
52
53#[cfg(test)]
54mod tests {
55 use crate::error::FuzzyError;
56 use crate::sampler::{Sampler, UniformSampler};
57 use crate::Float;
58
59 #[test]
60 fn uniform_sampler_two_points_inclusive_endpoints() {
61 let s = UniformSampler::new(2).unwrap();
62 let min: Float = -3.5;
63 let max: Float = 4.5;
64 let pts = s.sample(min, max).unwrap();
65 assert_eq!(pts.len(), 2);
66 assert_eq!(pts[0], min);
67 assert_eq!(pts[1], max, "Last point must equal max for n=2");
68 }
69
70 #[test]
71 fn uniform_sampler_inclusive_endpoints_default() {
72 let s = UniformSampler::default();
73 let n = UniformSampler::DEFAULT_N;
74 let min: Float = -5.0;
75 let max: Float = 5.0;
76 let pts = s.sample(min, max).unwrap();
77 assert_eq!(pts.len(), n);
78 assert_eq!(pts.first().copied().unwrap(), min);
79 assert_eq!(
80 pts.last().copied().unwrap(),
81 max,
82 "Sampler should include max exactly"
83 );
84 }
85
86 #[test]
87 fn uniform_sampler_spacing_monotonic() {
88 let s = UniformSampler::default();
89 let min: Float = 0.0;
90 let max: Float = 10.0;
91 let pts = s.sample(min, max).unwrap();
92 assert!(pts.windows(2).all(|w| w[1] >= w[0]));
93
94 let eps = Float::EPSILON * 10.0;
96 let base_step = pts[1] - pts[0];
97 for i in 2..pts.len() {
98 let step = pts[i] - pts[i - 1];
99 assert!((step - base_step).abs() <= eps, "Non-uniform step at i={i}");
100 }
101 }
102
103 #[test]
104 fn uniform_sampler_invalid_points_rejected() {
105 assert!(matches!(
106 UniformSampler::new(0),
107 Err(FuzzyError::OutOfBounds)
108 ));
109 assert!(matches!(
110 UniformSampler::new(1),
111 Err(FuzzyError::OutOfBounds)
112 ));
113 }
114
115 #[test]
116 fn uniform_sampler_invalid_range_rejected() {
117 let s = UniformSampler::default();
118 assert!(matches!(s.sample(1.0, 0.0), Err(FuzzyError::BadArity)));
120 assert!(matches!(s.sample(1.0, 1.0), Err(_)));
122 }
123}