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 */