Skip to main content

scirs2_interpolate/
grid.rs

1//! Grid transformation and resampling utilities
2//!
3//! This module provides functions for transforming irregular data onto regular grids
4//! and vice versa, as well as various grid manipulation utilities.
5
6use crate::advanced::rbf::{RBFInterpolator, RBFKernel};
7use crate::error::{InterpolateError, InterpolateResult};
8use scirs2_core::ndarray::{Array1, Array2, ArrayD, ArrayView1, ArrayView2, Axis};
9use scirs2_core::numeric::{Float, FromPrimitive, Zero};
10use std::fmt::Debug;
11
12/// Grid transformation methods
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum GridTransformMethod {
15    /// Nearest neighbor assignment
16    Nearest,
17    /// Linear interpolation
18    Linear,
19    /// Cubic spline interpolation
20    Cubic,
21}
22
23/// Create a grid of evenly spaced coordinates
24///
25/// # Arguments
26///
27/// * `bounds` - Min and max bounds for each dimension
28/// * `shape` - Number of points in each dimension
29///
30/// # Returns
31///
32/// Vector of coordinate arrays, one for each dimension
33///
34/// # Examples
35///
36/// ```
37/// use scirs2_interpolate::grid::create_regular_grid;
38///
39/// // Create a 10×20 grid from (0,0) to (1,2)
40/// let grid_coords = create_regular_grid(
41///     &[(0.0, 1.0), (0.0, 2.0)],
42///     &[10, 20]
43/// ).expect("Operation failed");
44///
45/// assert_eq!(grid_coords.len(), 2);
46/// assert_eq!(grid_coords[0].len(), 10);
47/// assert_eq!(grid_coords[1].len(), 20);
48/// ```
49#[allow(dead_code)]
50pub fn create_regular_grid<F>(
51    bounds: &[(F, F)],
52    shape: &[usize],
53) -> InterpolateResult<Vec<Array1<F>>>
54where
55    F: Float + FromPrimitive + Debug,
56{
57    if bounds.len() != shape.len() {
58        return Err(InterpolateError::invalid_input(
59            "bounds and shape must have the same length".to_string(),
60        ));
61    }
62
63    let n_dims = bounds.len();
64    let mut grid_coords = Vec::with_capacity(n_dims);
65
66    for i in 0..n_dims {
67        let (min, max) = bounds[i];
68        let n_points = shape[i];
69
70        if min >= max {
71            return Err(InterpolateError::invalid_input(
72                "min bound must be less than max bound".to_string(),
73            ));
74        }
75
76        if n_points < 2 {
77            return Err(InterpolateError::invalid_input(
78                "grid shape must have at least 2 points in each dimension".to_string(),
79            ));
80        }
81
82        let mut coords = Array1::zeros(n_points);
83
84        if n_points == 1 {
85            coords[0] = min;
86        } else {
87            let step = (max - min) / F::from_usize(n_points - 1).expect("Operation failed");
88            for j in 0..n_points {
89                coords[j] = min + F::from_usize(j).expect("Operation failed") * step;
90            }
91        }
92
93        grid_coords.push(coords);
94    }
95
96    Ok(grid_coords)
97}
98
99/// Resample irregular (scattered) data onto a regular grid
100///
101/// # Arguments
102///
103/// * `points` - Coordinates of scattered data points (n_points × n_dimensions)
104/// * `values` - Values at the scattered data points (n_points)
105/// * `gridshape` - Shape of the output grid (number of points in each dimension)
106/// * `grid_bounds` - Min and max bounds for each dimension of the grid
107/// * `method` - Interpolation method to use
108/// * `fill_value` - Value to use for grid points outside the convex hull of input points
109///
110/// # Returns
111///
112/// A tuple containing:
113/// * The grid coordinates for each dimension (vector of arrays)
114/// * The resampled values on the regular grid
115#[allow(dead_code)]
116pub fn resample_to_grid<F>(
117    points: &ArrayView2<F>,
118    values: &ArrayView1<F>,
119    gridshape: &[usize],
120    grid_bounds: &[(F, F)],
121    method: GridTransformMethod,
122    fill_value: F,
123) -> InterpolateResult<(Vec<Array1<F>>, ArrayD<F>)>
124where
125    F: Float
126        + FromPrimitive
127        + Debug
128        + Clone
129        + PartialOrd
130        + Zero
131        + 'static
132        + std::fmt::Display
133        + std::ops::AddAssign
134        + std::ops::SubAssign
135        + std::ops::MulAssign
136        + std::ops::DivAssign
137        + std::fmt::LowerExp
138        + Send
139        + Sync,
140{
141    if points.nrows() != values.len() {
142        return Err(InterpolateError::invalid_input(
143            "Number of points and values must match".to_string(),
144        ));
145    }
146
147    if points.ncols() != grid_bounds.len() {
148        return Err(InterpolateError::invalid_input(
149            "Point dimensions must match grid _bounds dimensions".to_string(),
150        ));
151    }
152
153    if grid_bounds.len() != gridshape.len() {
154        return Err(InterpolateError::invalid_input(
155            "Grid _bounds and shape dimensions must match".to_string(),
156        ));
157    }
158
159    // Create the regular grid
160    let grid_coords = create_regular_grid(grid_bounds, gridshape)?;
161
162    // Create a multidimensional array with the specified shape
163    let shape: Vec<usize> = gridshape.to_vec();
164    let mut grid_values = ArrayD::from_elem(shape.clone(), fill_value);
165
166    match method {
167        GridTransformMethod::Nearest => {
168            resample_nearest_neighbor(points, values, &grid_coords, &mut grid_values, fill_value)?;
169        }
170        GridTransformMethod::Linear => {
171            resample_linear(points, values, &grid_coords, &mut grid_values, fill_value)?;
172        }
173        GridTransformMethod::Cubic => {
174            resample_rbf(points, values, &grid_coords, &mut grid_values, fill_value)?;
175        }
176    }
177
178    Ok((grid_coords, grid_values))
179}
180
181/// Resample using nearest neighbor interpolation
182#[allow(dead_code)]
183fn resample_nearest_neighbor<F>(
184    points: &ArrayView2<F>,
185    values: &ArrayView1<F>,
186    grid_coords: &[Array1<F>],
187    grid_values: &mut ArrayD<F>,
188    fill_value: F,
189) -> InterpolateResult<()>
190where
191    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero,
192{
193    let n_dims = grid_coords.len();
194
195    // For each grid point, find the nearest data point
196    let gridshape: Vec<usize> = grid_coords.iter().map(|coord| coord.len()).collect();
197
198    // Generate all grid point coordinates
199    let mut indices = vec![0; n_dims];
200
201    loop {
202        // Convert indices to actual coordinates
203        let mut grid_point = vec![F::zero(); n_dims];
204        for (dim, &idx) in indices.iter().enumerate() {
205            grid_point[dim] = grid_coords[dim][idx];
206        }
207
208        // Find nearest data point
209        let mut min_dist_sq = F::infinity();
210        let mut nearest_value = fill_value;
211
212        for i in 0..points.nrows() {
213            let mut dist_sq = F::zero();
214            for j in 0..n_dims {
215                let diff = points[[i, j]] - grid_point[j];
216                dist_sq = dist_sq + diff * diff;
217            }
218
219            if dist_sq < min_dist_sq {
220                min_dist_sq = dist_sq;
221                nearest_value = values[i];
222            }
223        }
224
225        // Set the grid _value
226        grid_values[&indices[..]] = nearest_value;
227
228        // Increment indices
229        if !increment_indices(&mut indices, &gridshape) {
230            break;
231        }
232    }
233
234    Ok(())
235}
236
237/// Resample using RBF interpolation for smooth results
238#[allow(dead_code)]
239fn resample_rbf<F>(
240    points: &ArrayView2<F>,
241    values: &ArrayView1<F>,
242    grid_coords: &[Array1<F>],
243    grid_values: &mut ArrayD<F>,
244    fill_value: F,
245) -> InterpolateResult<()>
246where
247    F: Float
248        + FromPrimitive
249        + Debug
250        + Clone
251        + PartialOrd
252        + Zero
253        + 'static
254        + std::fmt::Display
255        + std::ops::AddAssign
256        + std::ops::SubAssign
257        + std::ops::MulAssign
258        + std::ops::DivAssign
259        + std::fmt::LowerExp
260        + Send
261        + Sync,
262{
263    // Create RBF interpolator
264    let rbf = RBFInterpolator::new(
265        points,
266        values,
267        RBFKernel::Gaussian,
268        F::from_f64(1.0).unwrap_or_else(|| F::one()),
269    )?;
270
271    let n_dims = grid_coords.len();
272    let gridshape: Vec<usize> = grid_coords.iter().map(|coord| coord.len()).collect();
273    let mut indices = vec![0; n_dims];
274
275    loop {
276        // Convert indices to actual coordinates
277        let mut grid_point = Array1::zeros(n_dims);
278        for (dim, &idx) in indices.iter().enumerate() {
279            grid_point[dim] = grid_coords[dim][idx];
280        }
281
282        // Evaluate RBF at this grid point
283        let interp_value = match rbf.interpolate(&grid_point.view().insert_axis(Axis(0))) {
284            Ok(val) => val[0],
285            Err(_) => fill_value,
286        };
287
288        grid_values[&indices[..]] = interp_value;
289
290        // Increment indices
291        if !increment_indices(&mut indices, &gridshape) {
292            break;
293        }
294    }
295
296    Ok(())
297}
298
299/// Linear interpolation for grid resampling (simplified implementation)
300#[allow(dead_code)]
301fn resample_linear<F>(
302    points: &ArrayView2<F>,
303    values: &ArrayView1<F>,
304    grid_coords: &[Array1<F>],
305    grid_values: &mut ArrayD<F>,
306    fill_value: F,
307) -> InterpolateResult<()>
308where
309    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero,
310{
311    // For simplicity, fall back to nearest neighbor for multidimensional case
312    // A full implementation would use multilinear interpolation
313    resample_nearest_neighbor(points, values, grid_coords, grid_values, fill_value)
314}
315
316/// Helper function to increment multi-dimensional indices
317#[allow(dead_code)]
318fn increment_indices(indices: &mut [usize], shape: &[usize]) -> bool {
319    for i in (0..indices.len()).rev() {
320        indices[i] += 1;
321        if indices[i] < shape[i] {
322            return true;
323        }
324        indices[i] = 0;
325    }
326    false
327}
328
329/// Resample a regular grid to another regular grid with different resolution or bounds
330///
331/// # Arguments
332///
333/// * `src_coords` - Source grid coordinates (vector of arrays, one per dimension)
334/// * `src_values` - Source grid values
335/// * `dst_coords` - Destination grid coordinates (vector of arrays, one per dimension)
336/// * `method` - Interpolation method to use
337/// * `fill_value` - Value to use for grid points outside the source grid
338///
339/// # Returns
340///
341/// Resampled values on the destination grid
342#[allow(dead_code)]
343pub fn resample_grid_to_grid<F, D>(
344    src_coords: &[Array1<F>],
345    src_values: &scirs2_core::ndarray::ArrayView<F, D>,
346    dst_coords: &[Array1<F>],
347    method: GridTransformMethod,
348    fill_value: F,
349) -> InterpolateResult<ArrayD<F>>
350where
351    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero + 'static,
352    D: scirs2_core::ndarray::Dimension,
353{
354    if src_coords.len() != dst_coords.len() {
355        return Err(InterpolateError::invalid_input(
356            "Source and destination must have same number of dimensions".to_string(),
357        ));
358    }
359
360    let _n_dims = src_coords.len(); // Reserved for future use
361
362    // Verify source coordinates match source _values shape
363    for (i, coord) in src_coords.iter().enumerate() {
364        if coord.len() != src_values.shape()[i] {
365            return Err(InterpolateError::invalid_input(format!(
366                "Source coordinate dimension {} length doesn't match _values shape",
367                i
368            )));
369        }
370    }
371
372    // Create destination grid shape
373    let dstshape: Vec<usize> = dst_coords.iter().map(|coord| coord.len()).collect();
374    let mut dst_values = ArrayD::from_elem(dstshape.clone(), fill_value);
375
376    match method {
377        GridTransformMethod::Nearest => {
378            grid_to_grid_nearest(
379                src_coords,
380                src_values,
381                dst_coords,
382                &mut dst_values,
383                fill_value,
384            )?;
385        }
386        GridTransformMethod::Linear => {
387            grid_to_grid_linear(
388                src_coords,
389                src_values,
390                dst_coords,
391                &mut dst_values,
392                fill_value,
393            )?;
394        }
395        GridTransformMethod::Cubic => {
396            // For cubic, we'll use linear interpolation as it's more stable for grids
397            grid_to_grid_linear(
398                src_coords,
399                src_values,
400                dst_coords,
401                &mut dst_values,
402                fill_value,
403            )?;
404        }
405    }
406
407    Ok(dst_values)
408}
409
410/// Convert multi-dimensional indices to linear index
411#[allow(dead_code)]
412fn ravel_multi_index(indices: &[usize], shape: &[usize]) -> usize {
413    let mut linear_idx = 0;
414    let mut stride = 1;
415
416    for i in (0..indices.len()).rev() {
417        linear_idx += indices[i] * stride;
418        stride *= shape[i];
419    }
420
421    linear_idx
422}
423
424/// Grid-to-grid resampling using nearest neighbor
425#[allow(dead_code)]
426fn grid_to_grid_nearest<F, D>(
427    src_coords: &[Array1<F>],
428    src_values: &scirs2_core::ndarray::ArrayView<F, D>,
429    dst_coords: &[Array1<F>],
430    dst_values: &mut ArrayD<F>,
431    fill_value: F,
432) -> InterpolateResult<()>
433where
434    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero,
435    D: scirs2_core::ndarray::Dimension,
436{
437    let n_dims = src_coords.len();
438    let dstshape: Vec<usize> = dst_coords.iter().map(|coord| coord.len()).collect();
439    let mut indices = vec![0; n_dims];
440
441    loop {
442        // Get destination grid point coordinates
443        let mut dst_point = vec![F::zero(); n_dims];
444        for (dim, &idx) in indices.iter().enumerate() {
445            dst_point[dim] = dst_coords[dim][idx];
446        }
447
448        // Find nearest source grid indices
449        let mut src_indices = vec![0; n_dims];
450        let valid = true;
451
452        for dim in 0..n_dims {
453            let coord = dst_point[dim];
454            let src_coord = &src_coords[dim];
455
456            // Find nearest index in source coordinates
457            let mut best_idx = 0;
458            let mut min_dist = (src_coord[0] - coord).abs();
459
460            for (i, &src_val) in src_coord.iter().enumerate() {
461                let dist = (src_val - coord).abs();
462                if dist < min_dist {
463                    min_dist = dist;
464                    best_idx = i;
465                }
466            }
467
468            src_indices[dim] = best_idx;
469        }
470
471        // Get the source _value and assign to destination
472        if valid {
473            // Use linear indexing for all cases to avoid generic dimension issues
474            let linear_idx = ravel_multi_index(&src_indices, src_values.shape());
475            let src_value = src_values.as_slice().expect("Operation failed")[linear_idx];
476            let dst_linear_idx = ravel_multi_index(&indices, &dstshape);
477            dst_values.as_slice_mut().expect("Operation failed")[dst_linear_idx] = src_value;
478        } else {
479            let dst_linear_idx = ravel_multi_index(&indices, &dstshape);
480            dst_values.as_slice_mut().expect("Operation failed")[dst_linear_idx] = fill_value;
481        }
482
483        // Increment indices
484        if !increment_indices(&mut indices, &dstshape) {
485            break;
486        }
487    }
488
489    Ok(())
490}
491
492/// Grid-to-grid resampling using linear interpolation
493#[allow(dead_code)]
494fn grid_to_grid_linear<F, D>(
495    src_coords: &[Array1<F>],
496    src_values: &scirs2_core::ndarray::ArrayView<F, D>,
497    dst_coords: &[Array1<F>],
498    dst_values: &mut ArrayD<F>,
499    fill_value: F,
500) -> InterpolateResult<()>
501where
502    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero,
503    D: scirs2_core::ndarray::Dimension,
504{
505    let n_dims = src_coords.len();
506    let dstshape: Vec<usize> = dst_coords.iter().map(|coord| coord.len()).collect();
507    let mut indices = vec![0; n_dims];
508
509    loop {
510        // Get destination grid point coordinates
511        let mut dst_point = vec![F::zero(); n_dims];
512        for (dim, &idx) in indices.iter().enumerate() {
513            dst_point[dim] = dst_coords[dim][idx];
514        }
515
516        // Perform multilinear interpolation
517        let interpolated_value =
518            multilinear_interpolate(src_coords, src_values, &dst_point, fill_value)?;
519
520        dst_values[&indices[..]] = interpolated_value;
521
522        // Increment indices
523        if !increment_indices(&mut indices, &dstshape) {
524            break;
525        }
526    }
527
528    Ok(())
529}
530
531/// Perform multilinear interpolation at a single point
532#[allow(dead_code)]
533fn multilinear_interpolate<F, D>(
534    coords: &[Array1<F>],
535    values: &scirs2_core::ndarray::ArrayView<F, D>,
536    point: &[F],
537    fill_value: F,
538) -> InterpolateResult<F>
539where
540    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero,
541    D: scirs2_core::ndarray::Dimension,
542{
543    let n_dims = coords.len();
544
545    // Find bounding grid cells for each dimension
546    let mut lower_indices = vec![0; n_dims];
547    let mut upper_indices = vec![0; n_dims];
548    let mut weights = vec![F::zero(); n_dims];
549
550    for dim in 0..n_dims {
551        let coord_array = &coords[dim];
552        let target = point[dim];
553
554        // Find the interval containing the target
555        let mut found = false;
556        for i in 0..coord_array.len() - 1 {
557            if target >= coord_array[i] && target <= coord_array[i + 1] {
558                lower_indices[dim] = i;
559                upper_indices[dim] = i + 1;
560
561                // Calculate interpolation weight
562                let dx = coord_array[i + 1] - coord_array[i];
563                if dx.abs() > F::zero() {
564                    weights[dim] = (target - coord_array[i]) / dx;
565                } else {
566                    weights[dim] = F::zero();
567                }
568                found = true;
569                break;
570            }
571        }
572
573        if !found {
574            // Point is outside grid bounds
575            return Ok(fill_value);
576        }
577    }
578
579    // Perform multilinear interpolation
580    // For N dimensions, we need 2^N corner values
581    let n_corners = 1 << n_dims; // 2^n_dims
582    let mut result = F::zero();
583
584    for corner in 0..n_corners {
585        let mut corner_indices = vec![0; n_dims];
586        let mut corner_weight = F::one();
587
588        for dim in 0..n_dims {
589            if (corner >> dim) & 1 == 0 {
590                corner_indices[dim] = lower_indices[dim];
591                corner_weight = corner_weight * (F::one() - weights[dim]);
592            } else {
593                corner_indices[dim] = upper_indices[dim];
594                corner_weight = corner_weight * weights[dim];
595            }
596        }
597
598        // Get _value at this corner using linear indexing
599        let linear_idx = ravel_multi_index(&corner_indices, values.shape());
600        let corner_value = values.as_slice().expect("Operation failed")[linear_idx];
601        result = result + corner_weight * corner_value;
602    }
603
604    Ok(result)
605}
606
607/// Maps values from a regular grid to arbitrary points using interpolation
608///
609/// # Arguments
610///
611/// * `grid_coords` - Grid coordinates (vector of arrays, one per dimension)
612/// * `grid_values` - Values on the regular grid
613/// * `query_points` - Points at which to evaluate (n_points × n_dimensions)
614/// * `method` - Interpolation method to use
615/// * `fill_value` - Value to use for query points outside the grid
616///
617/// # Returns
618///
619/// Values at the query points
620#[allow(dead_code)]
621pub fn map_grid_to_points<F, D>(
622    grid_coords: &[Array1<F>],
623    grid_values: &scirs2_core::ndarray::ArrayView<F, D>,
624    query_points: &ArrayView2<F>,
625    method: GridTransformMethod,
626    fill_value: F,
627) -> InterpolateResult<Array1<F>>
628where
629    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero + 'static,
630    D: scirs2_core::ndarray::Dimension,
631{
632    let n_points = query_points.nrows();
633    let n_dims = query_points.ncols();
634
635    if grid_coords.len() != n_dims {
636        return Err(InterpolateError::invalid_input(
637            "Grid coordinates and query point dimensions must match".to_string(),
638        ));
639    }
640
641    // Verify grid coordinates match grid _values shape
642    for (i, coord) in grid_coords.iter().enumerate() {
643        if coord.len() != grid_values.shape()[i] {
644            return Err(InterpolateError::invalid_input(format!(
645                "Grid coordinate dimension {} length doesn't match _values shape",
646                i
647            )));
648        }
649    }
650
651    let mut result = Array1::zeros(n_points);
652
653    for i in 0..n_points {
654        let query_point: Vec<F> = query_points.row(i).to_vec();
655
656        let interpolated_value = match method {
657            GridTransformMethod::Nearest => {
658                grid_nearest_neighbor(grid_coords, grid_values, &query_point, fill_value)?
659            }
660            GridTransformMethod::Linear => {
661                multilinear_interpolate(grid_coords, grid_values, &query_point, fill_value)?
662            }
663            GridTransformMethod::Cubic => {
664                // For cubic, we fall back to linear for stability
665                multilinear_interpolate(grid_coords, grid_values, &query_point, fill_value)?
666            }
667        };
668
669        result[i] = interpolated_value;
670    }
671
672    Ok(result)
673}
674
675/// Find the nearest grid point value
676#[allow(dead_code)]
677fn grid_nearest_neighbor<F, D>(
678    grid_coords: &[Array1<F>],
679    grid_values: &scirs2_core::ndarray::ArrayView<F, D>,
680    query_point: &[F],
681    _fill_value: F,
682) -> InterpolateResult<F>
683where
684    F: Float + FromPrimitive + Debug + Clone + PartialOrd + Zero,
685    D: scirs2_core::ndarray::Dimension,
686{
687    let n_dims = grid_coords.len();
688    let mut nearest_indices = vec![0; n_dims];
689
690    for dim in 0..n_dims {
691        let coord_array = &grid_coords[dim];
692        let target = query_point[dim];
693
694        // Find nearest index
695        let mut best_idx = 0;
696        let mut min_dist = (coord_array[0] - target).abs();
697
698        for (i, &coord_val) in coord_array.iter().enumerate() {
699            let dist = (coord_val - target).abs();
700            if dist < min_dist {
701                min_dist = dist;
702                best_idx = i;
703            }
704        }
705
706        nearest_indices[dim] = best_idx;
707    }
708
709    let linear_idx = ravel_multi_index(&nearest_indices, grid_values.shape());
710    Ok(grid_values.as_slice().expect("Operation failed")[linear_idx])
711}
712
713/// Efficient grid coordinate range checking
714#[allow(dead_code)]
715fn point_in_grid_bounds<F>(_gridcoords: &[Array1<F>], point: &[F]) -> bool
716where
717    F: Float + PartialOrd,
718{
719    for (dim, coord_array) in _gridcoords.iter().enumerate() {
720        let target = point[dim];
721        let min_coord = coord_array[0];
722        let max_coord = coord_array[coord_array.len() - 1];
723
724        if target < min_coord || target > max_coord {
725            return false;
726        }
727    }
728    true
729}
730
731/// Create a tensor product grid from coordinate arrays
732///
733/// This function creates all combinations of coordinates from the input arrays,
734/// useful for creating meshgrids for evaluation.
735#[allow(dead_code)]
736pub fn create_meshgrid<F>(coords: &[Array1<F>]) -> InterpolateResult<Array2<F>>
737where
738    F: Float + FromPrimitive + Debug + Clone + Zero,
739{
740    let n_dims = coords.len();
741    if n_dims == 0 {
742        return Err(InterpolateError::invalid_input(
743            "At least one coordinate array required".to_string(),
744        ));
745    }
746
747    // Calculate total number of grid points
748    let mut total_points = 1;
749    for coord in coords {
750        total_points *= coord.len();
751    }
752
753    let mut result = Array2::zeros((total_points, n_dims));
754    let shapes: Vec<usize> = coords.iter().map(|c| c.len()).collect();
755    let mut indices = vec![0; n_dims];
756
757    for row in 0..total_points {
758        // Set coordinates for this grid point
759        for (dim, &idx) in indices.iter().enumerate() {
760            result[[row, dim]] = coords[dim][idx];
761        }
762
763        // Increment multi-dimensional indices
764        increment_indices(&mut indices, &shapes);
765    }
766
767    Ok(result)
768}
769
770/// Calculate grid spacing for each dimension
771#[allow(dead_code)]
772pub fn calculate_grid_spacing<F>(coords: &[Array1<F>]) -> InterpolateResult<Vec<F>>
773where
774    F: Float + FromPrimitive + Debug + Clone,
775{
776    let mut spacings = Vec::with_capacity(coords.len());
777
778    for coord in coords {
779        if coord.len() < 2 {
780            return Err(InterpolateError::invalid_input(
781                "Grid coordinates must have at least 2 points".to_string(),
782            ));
783        }
784
785        // Calculate average spacing (assumes roughly uniform grid)
786        let total_range = coord[coord.len() - 1] - coord[0];
787        let n_intervals = F::from_usize(coord.len() - 1).expect("Operation failed");
788        let avg_spacing = total_range / n_intervals;
789
790        spacings.push(avg_spacing);
791    }
792
793    Ok(spacings)
794}
795
796#[cfg(test)]
797mod tests {
798    use super::*;
799    use approx::assert_abs_diff_eq;
800    // テスト関数で使用するモジュール
801
802    #[test]
803    fn test_create_regular_grid() {
804        // 1D grid
805        let grid_1d = create_regular_grid(&[(0.0, 1.0)], &[5]).expect("Operation failed");
806
807        assert_eq!(grid_1d.len(), 1);
808        assert_eq!(grid_1d[0].len(), 5);
809        assert_abs_diff_eq!(grid_1d[0][0], 0.0, epsilon = 1e-10);
810        assert_abs_diff_eq!(grid_1d[0][4], 1.0, epsilon = 1e-10);
811
812        // 2D grid
813        let grid_2d =
814            create_regular_grid(&[(0.0, 1.0), (-1.0, 1.0)], &[3, 5]).expect("Operation failed");
815
816        assert_eq!(grid_2d.len(), 2);
817        assert_eq!(grid_2d[0].len(), 3);
818        assert_eq!(grid_2d[1].len(), 5);
819        assert_abs_diff_eq!(grid_2d[0][0], 0.0, epsilon = 1e-10);
820        assert_abs_diff_eq!(grid_2d[0][2], 1.0, epsilon = 1e-10);
821        assert_abs_diff_eq!(grid_2d[1][0], -1.0, epsilon = 1e-10);
822        assert_abs_diff_eq!(grid_2d[1][4], 1.0, epsilon = 1e-10);
823    }
824}