1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154
use ndarray::{Array, Array2};
use crate::routines::condensation::prune::prune;
/// Implements the adaptive grid algorithm for support point expansion.
///
/// This function generates up to 2 new support points in each dimension for each existing support point.
/// New support points are symmetrically placed around the original support point, at a distance of `eps` * (range_max - range_min).
/// If the new support point is too close to an existing support point, or it is outside the given range, it is discarded.
///
/// # Arguments
///
/// * `theta` - A mutable reference to a 2D array representing the existing support points.
/// * `eps` - A floating-point value representing the fraction of the range to use for generating new support points.
/// * `ranges` - A slice of tuples representing the range of values for each dimension.
/// * `min_dist` - A floating-point value representing the minimum distance between support points.
///
/// # Returns
///
/// A 2D array containing the updated support points after the adaptive grid expansion.
///
pub fn adaptative_grid(
theta: &mut Array2<f64>,
eps: f64,
ranges: &[(f64, f64)],
min_dist: f64,
) -> Array2<f64> {
let old_theta = theta.clone();
for spp in old_theta.rows() {
for (j, val) in spp.into_iter().enumerate() {
let l = eps * (ranges[j].1 - ranges[j].0); //abs?
// dbg!(val + l);
// dbg!(val - l);
if val + l < ranges[j].1 {
let mut plus = Array::zeros(spp.len());
plus[j] = l;
plus = plus + spp;
prune(theta, plus, ranges, min_dist);
// (n_spp, _) = theta.dim();
} else {
// tracing::debug!(
// "AG: Rejected point. Out of bounds. p:{}, p+eps:{}",
// val,
// val + l,
// );
}
if val - l > ranges[j].0 {
let mut minus = Array::zeros(spp.len());
minus[j] = -l;
minus = minus + spp;
prune(theta, minus, ranges, min_dist);
// (n_spp, _) = theta.dim();
} else {
// tracing::debug!(
// "AG: Rejected point. Out of bounds. p:{}, p-eps:{}",
// val,
// val - l
// );
}
}
}
if theta.nrows() != (old_theta.nrows() + 2 * old_theta.ncols()) {
// tracing::debug!(
// "3) The adaptive grid tried to add {} support points, from those {} were rejected.",
// 2 * old_theta.ncols(),
// 2 * old_theta.ncols() + old_theta.nrows() - theta.nrows()
// );
}
theta.to_owned()
}
#[cfg(test)]
mod tests {
use super::*;
use ndarray::array;
#[test]
fn test_adaptive_grid_1d() {
// Initial theta: [[0.5]]
let mut theta = array![[0.5]];
let eps = 0.1;
let ranges = [(0.0, 1.0)];
let min_dist = 0.05;
// Call adaptative_grid
let new_theta = adaptative_grid(&mut theta, eps, &ranges, min_dist);
// Expected theta: [[0.5], [0.6], [0.4]]
let expected_theta = array![[0.5], [0.6], [0.4]];
// Assert that new_theta matches expected_theta
assert_eq!(new_theta, expected_theta);
}
#[test]
fn test_adaptive_grid_2d() {
// Initial theta: [[0.5, 0.5]]
let mut theta = array![[0.5, 0.5]];
let eps = 0.1;
let ranges = [(0.0, 1.0), (0.0, 1.0)];
let min_dist = 0.05;
// Call adaptative_grid
let new_theta = adaptative_grid(&mut theta, eps, &ranges, min_dist);
// Expected new points are:
// For dimension 0: [0.6, 0.5], [0.4, 0.5]
// For dimension 1: [0.5, 0.6], [0.5, 0.4]
// So expected theta is [[0.5, 0.5], [0.6, 0.5], [0.4, 0.5], [0.5, 0.6], [0.5, 0.4]]
let expected_theta = array![[0.5, 0.5], [0.6, 0.5], [0.4, 0.5], [0.5, 0.6], [0.5, 0.4]];
// Assert that new_theta matches expected_theta
assert_eq!(new_theta, expected_theta);
}
#[test]
fn test_adaptive_grid_min_dist() {
// Initial theta: [[0.5]]
let mut theta = array![[0.5]];
let eps = 0.1;
let ranges = [(0.0, 1.0)];
let min_dist = 0.2;
// Call adaptative_grid
let new_theta = adaptative_grid(&mut theta, eps, &ranges, min_dist);
// Since min_dist is 0.2, the new points at 0.6 and 0.4 are too close to 0.5 (distance 0.1)
// So no new points should be added
let expected_theta = array![[0.5]];
// Assert that new_theta matches expected_theta
assert_eq!(new_theta, expected_theta);
}
#[test]
fn test_adaptive_grid_out_of_bounds() {
// Initial theta: [[0.95]]
let mut theta = array![[0.95]];
let eps = 0.1;
let ranges = [(0.0, 1.0)];
let min_dist = 0.05;
// Call adaptative_grid
let new_theta = adaptative_grid(&mut theta, eps, &ranges, min_dist);
// val + l = 0.95 + 0.1 = 1.05 > 1.0, so point at 1.05 is out of bounds and should not be added
// val - l = 0.95 - 0.1 = 0.85, which is within range
// So only [0.85] should be added
let expected_theta = array![[0.95], [0.85]];
// Assert that new_theta matches expected_theta
assert_eq!(new_theta, expected_theta);
}
}