sklears_model_selection/
noise_injection.rs

1//! Noise injection for robustness testing
2//!
3//! This module provides various noise injection strategies for testing model
4//! robustness and evaluating performance under different perturbation scenarios.
5
6use scirs2_core::ndarray::{Array1, Array2, ArrayView1, ArrayView2};
7use scirs2_core::random::essentials::{Normal as RandNormal, Uniform};
8use scirs2_core::random::prelude::*;
9use scirs2_core::random::rngs::StdRng;
10// use scirs2_core::random::Distribution;
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13use sklears_core::prelude::*;
14
15fn noise_error(msg: &str) -> SklearsError {
16    SklearsError::InvalidInput(msg.to_string())
17}
18
19#[derive(Debug, Clone, Copy, PartialEq)]
20pub enum NoiseType {
21    /// Gaussian
22    Gaussian,
23    /// Uniform
24    Uniform,
25    /// SaltAndPepper
26    SaltAndPepper,
27    /// Dropout
28    Dropout,
29    /// Multiplicative
30    Multiplicative,
31    /// Adversarial
32    Adversarial,
33    /// OutlierInjection
34    OutlierInjection,
35    /// LabelNoise
36    LabelNoise,
37    /// FeatureSwap
38    FeatureSwap,
39    /// MixedNoise
40    MixedNoise,
41}
42
43#[derive(Debug, Clone, Copy, PartialEq)]
44pub enum AdversarialMethod {
45    /// FGSM
46    FGSM,
47    /// PGD
48    PGD,
49    /// RandomNoise
50    RandomNoise,
51    /// BoundaryAttack
52    BoundaryAttack,
53}
54
55#[derive(Debug, Clone)]
56pub struct NoiseConfig {
57    pub noise_type: NoiseType,
58    pub intensity: f64,
59    pub probability: f64,
60    pub random_state: Option<u64>,
61    pub adaptive: bool,
62    pub preserve_statistics: bool,
63    pub adversarial_method: Option<AdversarialMethod>,
64    pub outlier_factor: f64,
65    pub label_flip_rate: f64,
66    pub feature_swap_rate: f64,
67}
68
69impl Default for NoiseConfig {
70    fn default() -> Self {
71        Self {
72            noise_type: NoiseType::Gaussian,
73            intensity: 0.1,
74            probability: 1.0,
75            random_state: None,
76            adaptive: false,
77            preserve_statistics: false,
78            adversarial_method: None,
79            outlier_factor: 3.0,
80            label_flip_rate: 0.1,
81            feature_swap_rate: 0.1,
82        }
83    }
84}
85
86#[derive(Debug, Clone)]
87pub struct RobustnessTestResult {
88    pub original_performance: f64,
89    pub noisy_performance: f64,
90    pub performance_degradation: f64,
91    pub noise_sensitivity: f64,
92    pub robustness_score: f64,
93    pub noise_statistics: NoiseStatistics,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize)]
97pub struct NoiseStatistics {
98    pub noise_type: String,
99    pub intensity: f64,
100    pub affected_samples: usize,
101    pub affected_features: usize,
102    pub signal_to_noise_ratio: f64,
103    pub perturbation_magnitude: f64,
104}
105
106pub struct NoiseInjector {
107    config: NoiseConfig,
108    rng: StdRng,
109}
110
111impl NoiseInjector {
112    pub fn new(config: NoiseConfig) -> Self {
113        let rng = if let Some(seed) = config.random_state {
114            StdRng::seed_from_u64(seed)
115        } else {
116            StdRng::from_rng(&mut scirs2_core::random::thread_rng())
117        };
118
119        Self { config, rng }
120    }
121
122    pub fn inject_feature_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
123        match self.config.noise_type {
124            NoiseType::Gaussian => self.inject_gaussian_noise(x),
125            NoiseType::Uniform => self.inject_uniform_noise(x),
126            NoiseType::SaltAndPepper => self.inject_salt_pepper_noise(x),
127            NoiseType::Dropout => self.inject_dropout_noise(x),
128            NoiseType::Multiplicative => self.inject_multiplicative_noise(x),
129            NoiseType::Adversarial => self.inject_adversarial_noise(x),
130            NoiseType::OutlierInjection => self.inject_outlier_noise(x),
131            NoiseType::FeatureSwap => self.inject_feature_swap_noise(x),
132            NoiseType::MixedNoise => self.inject_mixed_noise(x),
133            _ => Err(noise_error("Unsupported noise type for data type")),
134        }
135    }
136
137    pub fn inject_label_noise(&mut self, y: &ArrayView1<i32>) -> Result<Array1<i32>> {
138        if self.config.noise_type != NoiseType::LabelNoise {
139            return Err(noise_error("Unsupported noise type for data type"));
140        }
141
142        let mut noisy_y = y.to_owned();
143        let unique_labels: Vec<i32> = {
144            let mut labels: Vec<i32> = y.iter().cloned().collect();
145            labels.sort_unstable();
146            labels.dedup();
147            labels
148        };
149
150        if unique_labels.len() < 2 {
151            return Ok(noisy_y);
152        }
153
154        let flip_dist = Bernoulli::new(self.config.label_flip_rate)
155            .map_err(|_| noise_error("Invalid label flip rate"))?;
156
157        for i in 0..noisy_y.len() {
158            if self.rng.sample(flip_dist) {
159                let current_label = noisy_y[i];
160                let available_labels: Vec<i32> = unique_labels
161                    .iter()
162                    .filter(|&&label| label != current_label)
163                    .cloned()
164                    .collect();
165
166                if !available_labels.is_empty() {
167                    let new_label_idx = self.rng.gen_range(0..available_labels.len());
168                    noisy_y[i] = available_labels[new_label_idx];
169                }
170            }
171        }
172
173        Ok(noisy_y)
174    }
175
176    fn inject_gaussian_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
177        let mut noisy_x = x.to_owned();
178        let (n_samples, n_features) = x.dim();
179
180        for i in 0..n_samples {
181            for j in 0..n_features {
182                if self.rng.gen::<f64>() < self.config.probability {
183                    let noise_std = if self.config.adaptive {
184                        self.config.intensity * x[[i, j]].abs()
185                    } else {
186                        self.config.intensity
187                    };
188
189                    let normal = RandNormal::new(0.0, noise_std)
190                        .map_err(|_| noise_error("Random number generation failed"))?;
191
192                    let noise = self.rng.sample(normal);
193                    noisy_x[[i, j]] += noise;
194                }
195            }
196        }
197
198        Ok(noisy_x)
199    }
200
201    fn inject_uniform_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
202        let mut noisy_x = x.to_owned();
203        let (n_samples, n_features) = x.dim();
204
205        for i in 0..n_samples {
206            for j in 0..n_features {
207                if self.rng.gen::<f64>() < self.config.probability {
208                    let noise_range = if self.config.adaptive {
209                        self.config.intensity * x[[i, j]].abs()
210                    } else {
211                        self.config.intensity
212                    };
213
214                    let uniform = Uniform::new(-noise_range, noise_range).unwrap();
215                    let noise = self.rng.sample(uniform);
216                    noisy_x[[i, j]] += noise;
217                }
218            }
219        }
220
221        Ok(noisy_x)
222    }
223
224    fn inject_salt_pepper_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
225        let mut noisy_x = x.to_owned();
226        let (n_samples, n_features) = x.dim();
227
228        let min_val = x.iter().cloned().fold(f64::INFINITY, f64::min);
229        let max_val = x.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
230
231        for i in 0..n_samples {
232            for j in 0..n_features {
233                if self.rng.gen::<f64>() < self.config.probability {
234                    if self.rng.gen::<f64>() < 0.5 {
235                        noisy_x[[i, j]] = min_val;
236                    } else {
237                        noisy_x[[i, j]] = max_val;
238                    }
239                }
240            }
241        }
242
243        Ok(noisy_x)
244    }
245
246    fn inject_dropout_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
247        let mut noisy_x = x.to_owned();
248        let (n_samples, n_features) = x.dim();
249
250        for i in 0..n_samples {
251            for j in 0..n_features {
252                if self.rng.gen::<f64>() < self.config.intensity {
253                    noisy_x[[i, j]] = 0.0;
254                }
255            }
256        }
257
258        Ok(noisy_x)
259    }
260
261    fn inject_multiplicative_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
262        let mut noisy_x = x.to_owned();
263        let (n_samples, n_features) = x.dim();
264
265        for i in 0..n_samples {
266            for j in 0..n_features {
267                if self.rng.gen::<f64>() < self.config.probability {
268                    let noise_factor = if self.config.intensity > 0.0 {
269                        let gamma = Gamma::new(1.0 / self.config.intensity, self.config.intensity)
270                            .map_err(|_| noise_error("Random number generation failed"))?;
271                        self.rng.sample(gamma)
272                    } else {
273                        1.0
274                    };
275
276                    noisy_x[[i, j]] *= noise_factor;
277                }
278            }
279        }
280
281        Ok(noisy_x)
282    }
283
284    fn inject_adversarial_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
285        match self
286            .config
287            .adversarial_method
288            .unwrap_or(AdversarialMethod::RandomNoise)
289        {
290            AdversarialMethod::FGSM => self.inject_fgsm_noise(x),
291            AdversarialMethod::PGD => self.inject_pgd_noise(x),
292            AdversarialMethod::RandomNoise => self.inject_random_adversarial_noise(x),
293            AdversarialMethod::BoundaryAttack => self.inject_boundary_attack_noise(x),
294        }
295    }
296
297    fn inject_fgsm_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
298        let mut noisy_x = x.to_owned();
299        let (n_samples, n_features) = x.dim();
300
301        for i in 0..n_samples {
302            for j in 0..n_features {
303                let gradient_sign = if self.rng.gen::<f64>() < 0.5 {
304                    -1.0
305                } else {
306                    1.0
307                };
308                let perturbation = self.config.intensity * gradient_sign;
309                noisy_x[[i, j]] += perturbation;
310            }
311        }
312
313        Ok(noisy_x)
314    }
315
316    fn inject_pgd_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
317        let mut noisy_x = x.to_owned();
318        let (n_samples, n_features) = x.dim();
319        let step_size = self.config.intensity * 0.1;
320        let num_steps = 10;
321
322        for _ in 0..num_steps {
323            for i in 0..n_samples {
324                for j in 0..n_features {
325                    let gradient_sign = if self.rng.gen::<f64>() < 0.5 {
326                        -1.0
327                    } else {
328                        1.0
329                    };
330                    let perturbation = step_size * gradient_sign;
331                    noisy_x[[i, j]] += perturbation;
332
333                    let max_perturbation = self.config.intensity;
334                    noisy_x[[i, j]] = (noisy_x[[i, j]] - x[[i, j]])
335                        .max(-max_perturbation)
336                        .min(max_perturbation)
337                        + x[[i, j]];
338                }
339            }
340        }
341
342        Ok(noisy_x)
343    }
344
345    fn inject_random_adversarial_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
346        let mut noisy_x = x.to_owned();
347        let (n_samples, n_features) = x.dim();
348
349        for i in 0..n_samples {
350            let mut perturbation_norm: f64 = 0.0;
351            let mut perturbations: Vec<f64> = vec![0.0; n_features];
352
353            for j in 0..n_features {
354                perturbations[j] = self.rng.gen_range(-1.0..1.0);
355                perturbation_norm += perturbations[j].powi(2);
356            }
357
358            perturbation_norm = perturbation_norm.sqrt();
359            if perturbation_norm > 0.0 {
360                for j in 0..n_features {
361                    perturbations[j] =
362                        (perturbations[j] / perturbation_norm) * self.config.intensity;
363                    noisy_x[[i, j]] += perturbations[j];
364                }
365            }
366        }
367
368        Ok(noisy_x)
369    }
370
371    fn inject_boundary_attack_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
372        let mut noisy_x = x.to_owned();
373        let (n_samples, n_features) = x.dim();
374
375        for i in 0..n_samples {
376            for j in 0..n_features {
377                let direction = if self.rng.gen::<f64>() < 0.5 {
378                    -1.0
379                } else {
380                    1.0
381                };
382                let magnitude = self.rng.gen::<f64>() * self.config.intensity;
383                noisy_x[[i, j]] += direction * magnitude;
384            }
385        }
386
387        Ok(noisy_x)
388    }
389
390    fn inject_outlier_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
391        let mut noisy_x = x.to_owned();
392        let (n_samples, n_features) = x.dim();
393
394        for j in 0..n_features {
395            let feature_values: Vec<f64> = (0..n_samples).map(|i| x[[i, j]]).collect();
396            let mean = feature_values.iter().sum::<f64>() / n_samples as f64;
397            let variance = feature_values
398                .iter()
399                .map(|&val| (val - mean).powi(2))
400                .sum::<f64>()
401                / n_samples as f64;
402            let std_dev = variance.sqrt();
403
404            for i in 0..n_samples {
405                if self.rng.gen::<f64>() < self.config.probability {
406                    let outlier_direction = if self.rng.gen::<f64>() < 0.5 {
407                        -1.0
408                    } else {
409                        1.0
410                    };
411                    let outlier_magnitude = self.config.outlier_factor * std_dev;
412                    noisy_x[[i, j]] = mean + outlier_direction * outlier_magnitude;
413                }
414            }
415        }
416
417        Ok(noisy_x)
418    }
419
420    fn inject_feature_swap_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
421        let mut noisy_x = x.to_owned();
422        let (n_samples, n_features) = x.dim();
423
424        if n_features < 2 {
425            return Ok(noisy_x);
426        }
427
428        for i in 0..n_samples {
429            if self.rng.gen::<f64>() < self.config.feature_swap_rate {
430                let feature1 = self.rng.gen_range(0..n_features);
431                let feature2 = self.rng.gen_range(0..n_features);
432
433                if feature1 != feature2 {
434                    let temp = noisy_x[[i, feature1]];
435                    noisy_x[[i, feature1]] = noisy_x[[i, feature2]];
436                    noisy_x[[i, feature2]] = temp;
437                }
438            }
439        }
440
441        Ok(noisy_x)
442    }
443
444    fn inject_mixed_noise(&mut self, x: &ArrayView2<f64>) -> Result<Array2<f64>> {
445        let mut noisy_x = x.to_owned();
446        let noise_types = [
447            NoiseType::Gaussian,
448            NoiseType::Uniform,
449            NoiseType::SaltAndPepper,
450            NoiseType::Multiplicative,
451        ];
452
453        let original_noise_type = self.config.noise_type;
454        let original_intensity = self.config.intensity;
455
456        for &noise_type in &noise_types {
457            self.config.noise_type = noise_type;
458            self.config.intensity = original_intensity / noise_types.len() as f64;
459
460            noisy_x = match noise_type {
461                NoiseType::Gaussian => self.inject_gaussian_noise(&noisy_x.view())?,
462                NoiseType::Uniform => self.inject_uniform_noise(&noisy_x.view())?,
463                NoiseType::SaltAndPepper => self.inject_salt_pepper_noise(&noisy_x.view())?,
464                NoiseType::Multiplicative => self.inject_multiplicative_noise(&noisy_x.view())?,
465                _ => noisy_x,
466            };
467        }
468
469        self.config.noise_type = original_noise_type;
470        self.config.intensity = original_intensity;
471
472        Ok(noisy_x)
473    }
474
475    pub fn compute_noise_statistics(
476        &self,
477        original: &ArrayView2<f64>,
478        noisy: &ArrayView2<f64>,
479    ) -> NoiseStatistics {
480        let (n_samples, n_features) = original.dim();
481        let mut affected_samples = 0;
482        let mut affected_features = 0;
483        let mut total_perturbation = 0.0;
484
485        for i in 0..n_samples {
486            let mut sample_affected = false;
487            for j in 0..n_features {
488                let perturbation = (noisy[[i, j]] - original[[i, j]]).abs();
489                if perturbation > 1e-10 {
490                    if !sample_affected {
491                        affected_samples += 1;
492                        sample_affected = true;
493                    }
494                    total_perturbation += perturbation;
495                }
496            }
497        }
498
499        for j in 0..n_features {
500            let mut feature_affected = false;
501            for i in 0..n_samples {
502                if (noisy[[i, j]] - original[[i, j]]).abs() > 1e-10 {
503                    feature_affected = true;
504                    break;
505                }
506            }
507            if feature_affected {
508                affected_features += 1;
509            }
510        }
511
512        let signal_power: f64 = original.iter().map(|&x| x.powi(2)).sum();
513        let noise_power: f64 = original
514            .iter()
515            .zip(noisy.iter())
516            .map(|(&orig, &noise)| (noise - orig).powi(2))
517            .sum();
518
519        let snr = if noise_power > 0.0 {
520            10.0 * (signal_power / noise_power).log10()
521        } else {
522            f64::INFINITY
523        };
524
525        let avg_perturbation = total_perturbation / (n_samples * n_features) as f64;
526
527        NoiseStatistics {
528            noise_type: format!("{:?}", self.config.noise_type),
529            intensity: self.config.intensity,
530            affected_samples,
531            affected_features,
532            signal_to_noise_ratio: snr,
533            perturbation_magnitude: avg_perturbation,
534        }
535    }
536}
537
538pub fn robustness_test<M, F>(
539    model: &M,
540    x: &ArrayView2<f64>,
541    y: &ArrayView1<f64>,
542    noise_configs: Vec<NoiseConfig>,
543    eval_fn: F,
544) -> Result<Vec<RobustnessTestResult>>
545where
546    M: Clone,
547    F: Fn(&M, &ArrayView2<f64>, &ArrayView1<f64>) -> f64 + Copy,
548{
549    let original_performance = eval_fn(model, x, y);
550    let mut results = Vec::new();
551
552    for config in noise_configs {
553        let mut injector = NoiseInjector::new(config.clone());
554        let noisy_x = injector.inject_feature_noise(x)?;
555        let noisy_performance = eval_fn(model, &noisy_x.view(), y);
556
557        let performance_degradation = original_performance - noisy_performance;
558        let noise_sensitivity = performance_degradation / config.intensity.max(1e-10);
559        let robustness_score =
560            1.0 - (performance_degradation / original_performance.max(1e-10)).abs();
561
562        let noise_statistics = injector.compute_noise_statistics(x, &noisy_x.view());
563
564        results.push(RobustnessTestResult {
565            original_performance,
566            noisy_performance,
567            performance_degradation,
568            noise_sensitivity,
569            robustness_score: robustness_score.max(0.0),
570            noise_statistics,
571        });
572    }
573
574    Ok(results)
575}
576
577#[allow(non_snake_case)]
578#[cfg(test)]
579mod tests {
580    use super::*;
581    use scirs2_core::ndarray::{arr1, arr2, Array2};
582
583    fn create_test_data() -> Array2<f64> {
584        arr2(&[
585            [1.0, 2.0, 3.0],
586            [4.0, 5.0, 6.0],
587            [7.0, 8.0, 9.0],
588            [10.0, 11.0, 12.0],
589        ])
590    }
591
592    #[test]
593    fn test_gaussian_noise() {
594        let x = create_test_data();
595        let config = NoiseConfig {
596            noise_type: NoiseType::Gaussian,
597            intensity: 0.1,
598            random_state: Some(42),
599            ..Default::default()
600        };
601
602        let mut injector = NoiseInjector::new(config);
603        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
604
605        assert_eq!(noisy_x.dim(), x.dim());
606        assert!(noisy_x != x);
607    }
608
609    #[test]
610    fn test_uniform_noise() {
611        let x = create_test_data();
612        let config = NoiseConfig {
613            noise_type: NoiseType::Uniform,
614            intensity: 0.2,
615            random_state: Some(42),
616            ..Default::default()
617        };
618
619        let mut injector = NoiseInjector::new(config);
620        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
621
622        assert_eq!(noisy_x.dim(), x.dim());
623    }
624
625    #[test]
626    fn test_dropout_noise() {
627        let x = create_test_data();
628        let config = NoiseConfig {
629            noise_type: NoiseType::Dropout,
630            intensity: 0.3,
631            random_state: Some(42),
632            ..Default::default()
633        };
634
635        let mut injector = NoiseInjector::new(config);
636        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
637
638        let zero_count = noisy_x.iter().filter(|&&val| val == 0.0).count();
639        assert!(zero_count > 0);
640    }
641
642    #[test]
643    fn test_label_noise() {
644        let y = arr1(&[0, 1, 2, 0, 1, 2]);
645        let config = NoiseConfig {
646            noise_type: NoiseType::LabelNoise,
647            label_flip_rate: 0.5,
648            random_state: Some(42),
649            ..Default::default()
650        };
651
652        let mut injector = NoiseInjector::new(config);
653        let noisy_y = injector.inject_label_noise(&y.view()).unwrap();
654
655        assert_eq!(noisy_y.len(), y.len());
656        assert!(noisy_y != y);
657    }
658
659    #[test]
660    fn test_adversarial_noise() {
661        let x = create_test_data();
662        let config = NoiseConfig {
663            noise_type: NoiseType::Adversarial,
664            intensity: 0.1,
665            adversarial_method: Some(AdversarialMethod::FGSM),
666            random_state: Some(42),
667            ..Default::default()
668        };
669
670        let mut injector = NoiseInjector::new(config);
671        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
672
673        assert_eq!(noisy_x.dim(), x.dim());
674    }
675
676    #[test]
677    fn test_outlier_injection() {
678        let x = create_test_data();
679        let config = NoiseConfig {
680            noise_type: NoiseType::OutlierInjection,
681            probability: 0.1,
682            outlier_factor: 3.0,
683            random_state: Some(42),
684            ..Default::default()
685        };
686
687        let mut injector = NoiseInjector::new(config);
688        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
689
690        assert_eq!(noisy_x.dim(), x.dim());
691    }
692
693    #[test]
694    fn test_mixed_noise() {
695        let x = create_test_data();
696        let config = NoiseConfig {
697            noise_type: NoiseType::MixedNoise,
698            intensity: 0.1,
699            random_state: Some(42),
700            ..Default::default()
701        };
702
703        let mut injector = NoiseInjector::new(config);
704        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
705
706        assert_eq!(noisy_x.dim(), x.dim());
707    }
708
709    #[test]
710    fn test_noise_statistics() {
711        let x = create_test_data();
712        let config = NoiseConfig {
713            noise_type: NoiseType::Gaussian,
714            intensity: 0.1,
715            random_state: Some(42),
716            ..Default::default()
717        };
718
719        let mut injector = NoiseInjector::new(config);
720        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
721        let stats = injector.compute_noise_statistics(&x.view(), &noisy_x.view());
722
723        assert!(stats.affected_samples > 0);
724        assert!(stats.affected_features > 0);
725        assert!(stats.signal_to_noise_ratio.is_finite());
726        assert!(stats.perturbation_magnitude >= 0.0);
727    }
728
729    #[test]
730    fn test_adaptive_noise() {
731        let x = create_test_data();
732        let config = NoiseConfig {
733            noise_type: NoiseType::Gaussian,
734            intensity: 0.1,
735            adaptive: true,
736            random_state: Some(42),
737            ..Default::default()
738        };
739
740        let mut injector = NoiseInjector::new(config);
741        let noisy_x = injector.inject_feature_noise(&x.view()).unwrap();
742
743        assert_eq!(noisy_x.dim(), x.dim());
744    }
745}