1use 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#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum GridTransformMethod {
15 Nearest,
17 Linear,
19 Cubic,
21}
22
23#[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#[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 let grid_coords = create_regular_grid(grid_bounds, gridshape)?;
161
162 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#[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 let gridshape: Vec<usize> = grid_coords.iter().map(|coord| coord.len()).collect();
197
198 let mut indices = vec![0; n_dims];
200
201 loop {
202 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 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 grid_values[&indices[..]] = nearest_value;
227
228 if !increment_indices(&mut indices, &gridshape) {
230 break;
231 }
232 }
233
234 Ok(())
235}
236
237#[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 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 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 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 if !increment_indices(&mut indices, &gridshape) {
292 break;
293 }
294 }
295
296 Ok(())
297}
298
299#[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 resample_nearest_neighbor(points, values, grid_coords, grid_values, fill_value)
314}
315
316#[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#[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(); 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 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 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#[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#[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 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 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 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 if valid {
473 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 if !increment_indices(&mut indices, &dstshape) {
485 break;
486 }
487 }
488
489 Ok(())
490}
491
492#[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 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 let interpolated_value =
518 multilinear_interpolate(src_coords, src_values, &dst_point, fill_value)?;
519
520 dst_values[&indices[..]] = interpolated_value;
521
522 if !increment_indices(&mut indices, &dstshape) {
524 break;
525 }
526 }
527
528 Ok(())
529}
530
531#[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 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 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 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 return Ok(fill_value);
576 }
577 }
578
579 let n_corners = 1 << n_dims; 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 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#[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 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 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#[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 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#[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#[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 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 for (dim, &idx) in indices.iter().enumerate() {
760 result[[row, dim]] = coords[dim][idx];
761 }
762
763 increment_indices(&mut indices, &shapes);
765 }
766
767 Ok(result)
768}
769
770#[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 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 #[test]
803 fn test_create_regular_grid() {
804 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 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}