pmcore/routines/expansion/
adaptative_grid.rs

1use faer::Row;
2
3use crate::structs::theta::Theta;
4
5/// Implements the adaptive grid algorithm for support point expansion.
6///
7/// This function generates up to 2 new support points in each dimension for each existing support point.
8/// New support points are symmetrically placed around the original support point, at a distance of `eps` * (range_max - range_min).
9/// If the new support point is too close to an existing support point, or it is outside the given range, it is discarded.
10///
11/// # Arguments
12///
13/// * `theta` - A mutable reference to a 2D array representing the existing support points.
14/// * `eps` - A floating-point value representing the fraction of the range to use for generating new support points.
15/// * `ranges` - A slice of tuples representing the range of values for each dimension.
16/// * `min_dist` - A floating-point value representing the minimum distance between support points.
17///
18/// # Returns
19///
20/// A 2D array containing the updated support points after the adaptive grid expansion.
21///
22pub fn adaptative_grid(theta: &mut Theta, eps: f64, ranges: &[(f64, f64)], min_dist: f64) {
23    let mut candidates = Vec::new();
24
25    // Collect all points first to avoid borrowing conflicts
26    for spp in theta.matrix().row_iter() {
27        for (j, val) in spp.iter().enumerate() {
28            let l = eps * (ranges[j].1 - ranges[j].0); //abs?
29            if val + l < ranges[j].1 {
30                let mut plus = Row::zeros(spp.ncols());
31                plus[j] = l;
32                plus += spp;
33                candidates.push(plus.iter().copied().collect::<Vec<f64>>());
34            }
35            if val - l > ranges[j].0 {
36                let mut minus = Row::zeros(spp.ncols());
37                minus[j] = -l;
38                minus += spp;
39                candidates.push(minus.iter().copied().collect::<Vec<f64>>());
40            }
41        }
42    }
43
44    // Option 1: Check all points against the original theta, then add them
45    let keep = candidates
46        .iter()
47        .filter(|point| theta.check_point(point, min_dist, ranges))
48        .cloned()
49        .collect::<Vec<_>>();
50
51    for point in keep {
52        theta.add_point(point.as_slice());
53    }
54
55    // Option 2: Check and add points one by one
56    // Now add all the points after the immutable borrow is released
57    //for point in candidates {
58    //    theta.suggest_point(point, min_dist, ranges);
59    //}
60}
61/*
62#[cfg(test)]
63mod tests {
64    use super::*;
65    use crate::structs::theta::Theta;
66    use faer::mat;
67
68    #[test]
69    fn test_expected() {
70        let original = Theta::from(mat![[1.0, 10.0]]);
71
72        let ranges = [(0.0, 1.0), (0.0, 10.0)];
73        let eps = 0.1;
74        let min_dist = 0.05;
75
76        let mut theta = original.clone();
77        adaptative_grid(&mut theta, eps, &ranges, min_dist);
78
79        let expected = mat![[1.0, 10.0], [0.9, 10.0], [1.0, 9.0]];
80
81        // Check that both matrices have the same number of rows
82        assert_eq!(
83            theta.matrix().nrows(),
84            expected.nrows(),
85            "Number of points in theta doesn't match expected"
86        );
87
88        // Check that all points in expected are in theta
89        for i in 0..expected.nrows() {
90            let expected_point = expected.row(i);
91            let mut found = false;
92
93            for j in 0..theta.matrix().nrows() {
94                let theta_point = theta.matrix().row(j);
95
96                // Check if points match (within small epsilon for floating-point comparison)
97                if (expected_point[0] - theta_point[0]).abs() < 1e-10
98                    && (expected_point[1] - theta_point[1]).abs() < 1e-10
99                {
100                    found = true;
101                    break;
102                }
103            }
104
105            assert!(
106                found,
107                "Expected point [{}, {}] not found in theta",
108                expected_point[0], expected_point[1]
109            );
110        }
111
112        // Check that all points in theta are in expected
113        for i in 0..theta.matrix().nrows() {
114            let theta_point = theta.matrix().row(i);
115            let mut found = false;
116
117            for j in 0..expected.nrows() {
118                let expected_point = expected.row(j);
119
120                // Check if points match (within small epsilon)
121                if (theta_point[0] - expected_point[0]).abs() < 1e-10
122                    && (theta_point[1] - expected_point[1]).abs() < 1e-10
123                {
124                    found = true;
125                    break;
126                }
127            }
128
129            assert!(
130                found,
131                "Point [{}, {}] in theta was not expected",
132                theta_point[0], theta_point[1]
133            );
134        }
135    }
136
137    #[test]
138    fn test_basic_expansion() {
139        // Create initial theta with a single point [0.5, 0.5]
140        let mut theta = Theta::from(mat![[0.5, 0.5]]);
141
142        // Define ranges for two dimensions
143        let ranges = [(0.0, 1.0), (0.0, 1.0)];
144
145        // Set expansion parameters
146        let eps = 0.1;
147        let min_dist = 0.05;
148
149        // Apply adaptive grid
150        adaptative_grid(&mut theta, eps, &ranges, min_dist);
151
152        // Should generate 4 new points around the original:
153        // [0.6, 0.5], [0.4, 0.5], [0.5, 0.6], [0.5, 0.4]
154        // Total 5 points including the original
155        assert_eq!(theta.matrix().nrows(), 5);
156
157        // Verify the original point is preserved
158        let matrix = theta.matrix();
159        let mut has_original = false;
160
161        for i in 0..matrix.nrows() {
162            let row = matrix.row(i);
163            if (row[0] - 0.5).abs() < 1e-10 && (row[1] - 0.5).abs() < 1e-10 {
164                has_original = true;
165                break;
166            }
167        }
168        assert!(has_original, "Original point should be preserved");
169
170        // Verify expansion points were created
171        let expected_points = vec![(0.6, 0.5), (0.4, 0.5), (0.5, 0.6), (0.5, 0.4)];
172        for (x, y) in expected_points {
173            let mut found = false;
174            for i in 0..matrix.nrows() {
175                let row = matrix.row(i);
176                if (row[0] - x).abs() < 1e-10 && (row[1] - y).abs() < 1e-10 {
177                    found = true;
178                    break;
179                }
180            }
181            assert!(found, "Expected point ({}, {}) not found", x, y);
182        }
183    }
184
185    #[test]
186    fn test_boundary_conditions() {
187        // Create initial theta with points near boundaries
188        let mut theta = Theta::from(mat![
189            [0.05, 0.5], // Near lower boundary in x
190            [0.95, 0.5], // Near upper boundary in x
191            [0.5, 0.05], // Near lower boundary in y
192            [0.5, 0.95], // Near upper boundary in y
193        ]);
194
195        let ranges = [(0.0, 1.0), (0.0, 1.0)];
196        let eps = 0.1;
197        let min_dist = 0.05;
198
199        // Store original count
200        let original_count = theta.matrix().nrows();
201
202        adaptative_grid(&mut theta, eps, &ranges, min_dist);
203
204        // Each point should generate fewer than 4 new points due to boundaries
205        assert!(theta.matrix().nrows() > original_count);
206        assert!(theta.matrix().nrows() < original_count + 4 * 4);
207
208        // Verify no points are outside the range
209        let matrix = theta.matrix();
210        for i in 0..matrix.nrows() {
211            let row = matrix.row(i);
212            assert!(row[0] >= ranges[0].0 && row[0] <= ranges[0].1);
213            assert!(row[1] >= ranges[1].0 && row[1] <= ranges[1].1);
214        }
215    }
216
217    #[test]
218    fn test_min_distance_constraint() {
219        // Create initial theta with close points
220        let mut theta = Theta::from(mat![
221            [0.5, 0.5],
222            [0.55, 0.5], // Close to first point
223        ]);
224
225        let ranges = [(0.0, 1.0), (0.0, 10.0)];
226        let eps = 0.1;
227        let min_dist = 0.15; // Large enough to prevent some points from being added
228
229        adaptative_grid(&mut theta, eps, &ranges, min_dist);
230
231        // We should have fewer points than the maximum possible expansion
232        // due to the minimum distance constraint
233        assert!(theta.matrix().nrows() < 2 + 2 * 4);
234    }
235}
236 */