Skip to main content

scirs2_interpolate/
api_standards.rs

1//! API standardization guidelines and examples
2//!
3//! This module demonstrates the standardized API patterns that should be used
4//! throughout the interpolation library for consistency.
5
6use crate::traits::*;
7use crate::{InterpolateError, InterpolateResult};
8use scirs2_core::ndarray::{ArrayView1, ArrayView2};
9
10/// Standard factory function pattern
11///
12/// All factory functions should follow this pattern:
13/// 1. Name: `make_<interpolator_name>`
14/// 2. Parameters: points, values, config (optional)
15/// 3. Return: `InterpolateResult<Interpolator>`
16///
17/// # Example Implementation
18/// ```ignore
19/// pub fn make_example_interpolator<T: InterpolationFloat>(
20///     points: &ArrayView2<T>,
21///     values: &ArrayView1<T>,
22///     config: Option<ExampleConfig>,
23/// ) -> InterpolateResult<ExampleInterpolator<T>> {
24///     // Validate input data
25///     validation::validate_data_consistency(points, values)?;
26///     
27///     // Use default config if not provided
28///     let config = config.unwrap_or_default();
29///     config.validate()?;
30///     
31///     // Build and return interpolator
32///     ExampleInterpolator::new(points, values, config)
33/// }
34/// ```
35pub mod factory_pattern {
36    use super::*;
37
38    /// Example configuration structure
39    #[derive(Debug, Clone)]
40    pub struct StandardConfig<T: InterpolationFloat> {
41        /// Smoothing parameter
42        pub smoothing: Option<T>,
43
44        /// Regularization parameter
45        pub regularization: Option<T>,
46
47        /// Maximum iterations for iterative methods
48        pub max_iterations: usize,
49
50        /// Convergence tolerance
51        pub tolerance: T,
52    }
53
54    impl<T: InterpolationFloat> Default for StandardConfig<T> {
55        fn default() -> Self {
56            Self {
57                smoothing: None,
58                regularization: None,
59                max_iterations: 100,
60                tolerance: T::default_tolerance(),
61            }
62        }
63    }
64
65    impl<T: InterpolationFloat> InterpolationConfig for StandardConfig<T> {
66        fn validate(&self) -> InterpolateResult<()> {
67            if self.max_iterations == 0 {
68                return Err(InterpolateError::invalid_input(
69                    "max_iterations must be greater than 0",
70                ));
71            }
72
73            if let Some(s) = self.smoothing {
74                if s <= T::zero() {
75                    return Err(InterpolateError::invalid_input(
76                        "smoothing parameter must be positive",
77                    ));
78                }
79            }
80
81            Ok(())
82        }
83
84        fn default() -> Self {
85            <Self as std::default::Default>::default()
86        }
87    }
88}
89
90/// Standard builder pattern
91///
92/// For more complex interpolators, use a builder pattern that follows
93/// these conventions:
94///
95/// # Example
96/// ```ignore
97/// let interpolator = ExampleInterpolatorBuilder::new()
98///     .with_smoothing(0.1)
99///     .with_regularization(0.01)
100///     .with_max_iterations(200)
101///     .build(points, values)?;
102/// ```
103pub mod builder_pattern {
104    use super::*;
105    use crate::api_standards::factory_pattern::StandardConfig;
106
107    /// Example builder structure
108    #[derive(Debug, Clone)]
109    pub struct StandardInterpolatorBuilder<T: InterpolationFloat> {
110        config_factory_pattern: StandardConfig<T>,
111    }
112
113    impl<T: InterpolationFloat> StandardInterpolatorBuilder<T> {
114        /// Create a new builder with default configuration
115        pub fn new() -> Self {
116            Self {
117                config_factory_pattern: Default::default(),
118            }
119        }
120
121        /// Set smoothing parameter
122        pub fn with_smoothing(mut self, smoothing: T) -> Self {
123            self.config_factory_pattern.smoothing = Some(smoothing);
124            self
125        }
126
127        /// Set regularization parameter  
128        pub fn with_regularization(mut self, regularization: T) -> Self {
129            self.config_factory_pattern.regularization = Some(regularization);
130            self
131        }
132
133        /// Set maximum iterations
134        pub fn with_max_iterations(mut self, maxiterations: usize) -> Self {
135            self.config_factory_pattern.max_iterations = maxiterations;
136            self
137        }
138
139        /// Set convergence tolerance
140        pub fn with_tolerance(mut self, tolerance: T) -> Self {
141            self.config_factory_pattern.tolerance = tolerance;
142            self
143        }
144
145        /// Build the interpolator
146        pub fn build<I>(
147            self,
148            points: &ArrayView2<T>,
149            values: &ArrayView1<T>,
150        ) -> InterpolateResult<I>
151        where
152            for<'a> I: From<(
153                ArrayView2<'a, T>,
154                ArrayView1<'a, T>,
155                factory_pattern::StandardConfig<T>,
156            )>,
157        {
158            validation::validate_data_consistency(points, values)?;
159            self.config_factory_pattern.validate()?;
160
161            Ok(I::from((
162                points.view(),
163                values.view(),
164                self.config_factory_pattern,
165            )))
166        }
167    }
168
169    impl<T: InterpolationFloat> Default for StandardInterpolatorBuilder<T> {
170        fn default() -> Self {
171            Self::new()
172        }
173    }
174}
175
176/// Standard evaluation interface
177///
178/// All interpolators should implement consistent evaluation methods
179pub mod evaluation_pattern {
180    use super::*;
181
182    /// Standard batch evaluation with options
183    pub fn evaluate_batch<T, I>(
184        interpolator: &I,
185        query_points: &ArrayView2<T>,
186        options: Option<EvaluationOptions>,
187    ) -> InterpolateResult<BatchEvaluationResult<T>>
188    where
189        T: InterpolationFloat,
190        I: Interpolator<T>,
191    {
192        let _options = options.unwrap_or_default();
193
194        // Validate query dimension
195        // validation::validate_query_dimension(interpolator.data_dim(), query_points)?;
196
197        // Perform evaluation
198        let values = interpolator.evaluate(query_points)?;
199
200        Ok(BatchEvaluationResult {
201            values,
202            uncertainties: None,
203            out_of_bounds: Vec::new(),
204        })
205    }
206}
207
208/// Standard error handling
209///
210/// Consistent error creation and messages across the library
211pub mod error_handling {
212    use crate::InterpolateError;
213
214    /// Create standard dimension mismatch error using structured error type
215    pub fn dimension_mismatch(expected: usize, actual: usize, context: &str) -> InterpolateError {
216        InterpolateError::dimension_mismatch(expected, actual, context)
217    }
218
219    /// Create standard empty data error using structured error type
220    pub fn empty_data(context: &str) -> InterpolateError {
221        InterpolateError::empty_data(context)
222    }
223
224    /// Create standard invalid parameter error using structured error type
225    pub fn invalid_parameter<T: std::fmt::Display>(
226        param: &str,
227        expected: &str,
228        actual: T,
229        context: &str,
230    ) -> InterpolateError {
231        InterpolateError::invalid_parameter(param, expected, actual, context)
232    }
233
234    /// Create standard convergence failure error using structured error type
235    pub fn convergence_failure(method: &str, iterations: usize) -> InterpolateError {
236        InterpolateError::convergence_failure(method, iterations)
237    }
238
239    /// Create standard numerical instability error
240    pub fn numerical_instability(context: &str, details: &str) -> InterpolateError {
241        InterpolateError::numerical_instability(context, details)
242    }
243
244    /// Create standard insufficient points error
245    pub fn insufficient_points(
246        _required: usize,
247        provided: usize,
248        method: &str,
249    ) -> InterpolateError {
250        InterpolateError::insufficient_points(_required, provided, method)
251    }
252}
253
254/// Standard input validation
255///
256/// Comprehensive validation utilities for consistent input checking
257pub mod input_validation {
258    use crate::{traits::InterpolationFloat, InterpolateError, InterpolateResult};
259    use scirs2_core::ndarray::{ArrayView1, ArrayView2};
260
261    /// Validate that data points are finite and well-formed
262    pub fn validate_finite_data<T: InterpolationFloat>(
263        points: &ArrayView2<T>,
264        values: &ArrayView1<T>,
265        context: &str,
266    ) -> InterpolateResult<()> {
267        // Check for NaN or infinite values in points
268        for (i, point_slice) in points.outer_iter().enumerate() {
269            for (j, &val) in point_slice.iter().enumerate() {
270                if !val.is_finite() {
271                    return Err(InterpolateError::InvalidInput {
272                        message: format!(
273                            "Non-finite value found in {context} points at position ({i}, {j}): {val}"
274                        ),
275                    });
276                }
277            }
278        }
279
280        // Check for NaN or infinite values in function values
281        for (i, &val) in values.iter().enumerate() {
282            if !val.is_finite() {
283                return Err(InterpolateError::InvalidInput {
284                    message: format!(
285                        "Non-finite value found in {context} values at position {i}: {val}"
286                    ),
287                });
288            }
289        }
290
291        Ok(())
292    }
293
294    /// Validate that data has sufficient points for the method
295    pub fn validate_sufficient_points<T: InterpolationFloat>(
296        points: &ArrayView2<T>,
297        _values: &ArrayView1<T>,
298        minimum_required: usize,
299        method_name: &str,
300    ) -> InterpolateResult<()> {
301        let n_points = points.nrows();
302        if n_points < minimum_required {
303            return Err(InterpolateError::insufficient_points(
304                minimum_required,
305                n_points,
306                method_name,
307            ));
308        }
309        Ok(())
310    }
311
312    /// Validate query points have correct dimensions and are finite
313    pub fn validate_query_points<T: InterpolationFloat>(
314        query_points: &ArrayView2<T>,
315        expected_dim: usize,
316        context: &str,
317    ) -> InterpolateResult<()> {
318        if query_points.ncols() != expected_dim {
319            return Err(InterpolateError::dimension_mismatch(
320                expected_dim,
321                query_points.ncols(),
322                &format!("{context} query _points"),
323            ));
324        }
325
326        // Check for finite values
327        for (i, point_slice) in query_points.outer_iter().enumerate() {
328            for (j, &val) in point_slice.iter().enumerate() {
329                if !val.is_finite() {
330                    return Err(InterpolateError::InvalidInput {
331                        message: format!(
332                            "Non-finite value found in {context} query _points at position ({i}, {j}): {val}"
333                        ),
334                    });
335                }
336            }
337        }
338
339        Ok(())
340    }
341
342    /// Validate parameter is positive
343    pub fn validate_positive<T: InterpolationFloat>(
344        value: T,
345        param_name: &str,
346        context: &str,
347    ) -> InterpolateResult<()> {
348        if value <= T::zero() {
349            return Err(InterpolateError::invalid_parameter(
350                param_name,
351                "positive value",
352                value,
353                context,
354            ));
355        }
356        Ok(())
357    }
358
359    /// Validate parameter is non-negative
360    pub fn validate_non_negative<T: InterpolationFloat>(
361        value: T,
362        param_name: &str,
363        context: &str,
364    ) -> InterpolateResult<()> {
365        if value < T::zero() {
366            return Err(InterpolateError::invalid_parameter(
367                param_name,
368                "non-negative value",
369                value,
370                context,
371            ));
372        }
373        Ok(())
374    }
375
376    /// Validate parameter is within a specific range
377    pub fn validate_range<T: InterpolationFloat>(
378        value: T,
379        min: T,
380        max: T,
381        param_name: &str,
382        context: &str,
383    ) -> InterpolateResult<()> {
384        if value < min || value > max {
385            return Err(InterpolateError::invalid_parameter(
386                param_name,
387                format!("value between {min} and {max}"),
388                value,
389                context,
390            ));
391        }
392        Ok(())
393    }
394}
395
396/// Migration examples
397///
398/// Examples of how to migrate existing APIs to the new standard
399pub mod migration_examples {
400    use super::*;
401
402    // Old API:
403    // pub fn make_rbf_interpolator<F>(
404    //     x: &ArrayView2<F>,
405    //     y: &ArrayView1<F>,
406    //     kernel: RBFKernel,
407    //     epsilon: F,
408    // ) -> InterpolateResult<RBFInterpolator<F>>
409
410    // New standardized API:
411    #[derive(Debug, Clone)]
412    pub struct RBFConfig<T: InterpolationFloat> {
413        pub kernel: RBFKernel,
414        pub epsilon: T,
415    }
416
417    #[derive(Debug, Clone)]
418    pub enum RBFKernel {
419        Gaussian,
420        Multiquadric,
421        InverseMultiquadric,
422        ThinPlate,
423    }
424
425    impl<T: InterpolationFloat> Default for RBFConfig<T> {
426        fn default() -> Self {
427            Self {
428                kernel: RBFKernel::Gaussian,
429                epsilon: T::from_f64(1.0).expect("Operation failed"),
430            }
431        }
432    }
433
434    impl<T: InterpolationFloat> InterpolationConfig for RBFConfig<T> {
435        fn validate(&self) -> InterpolateResult<()> {
436            if self.epsilon <= T::zero() {
437                return Err(InterpolateError::invalid_input("epsilon must be positive"));
438            }
439            Ok(())
440        }
441
442        fn default() -> Self {
443            <Self as std::default::Default>::default()
444        }
445    }
446
447    /// Standardized RBF factory function
448    pub fn make_rbf_interpolator<T: InterpolationFloat, I>(
449        points: &ArrayView2<T>,
450        values: &ArrayView1<T>,
451        config: Option<RBFConfig<T>>,
452    ) -> InterpolateResult<I>
453    where
454        for<'a> I: From<(ArrayView2<'a, T>, ArrayView1<'a, T>, RBFConfig<T>)>,
455    {
456        validation::validate_data_consistency(points, values)?;
457
458        let config = config.unwrap_or_default();
459        config.validate()?;
460
461        Ok(I::from((points.view(), values.view(), config)))
462    }
463}