1use scirs2_core::ndarray::{ArrayView1, ArrayView2, Axis};
7use scirs2_core::random::prelude::*;
8use scirs2_core::random::rngs::StdRng;
9use scirs2_core::SliceRandomExt;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12use sklears_core::prelude::*;
13use std::collections::HashMap;
14
15fn imbalanced_error(msg: &str) -> SklearsError {
16 SklearsError::InvalidInput(msg.to_string())
17}
18
19#[derive(Debug, Clone, Copy, PartialEq)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub enum ImbalancedStrategy {
22 StratifiedSampling,
24 SMOTECV,
26 BorderlineSMOTECV,
28 ADASYNNECV,
30 RandomOverSamplerCV,
32 RandomUnderSamplerCV,
34 TomekLinksCV,
36 EditedNearestNeighboursCV,
38 SMOTETomekCV,
40 SMOTEENNECV,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub enum SamplingStrategy {
46 Auto,
48 Minority,
50 NotMajority,
52 All,
54 Custom(f64),
56}
57
58#[derive(Debug, Clone)]
59pub struct ImbalancedValidationConfig {
60 pub strategy: ImbalancedStrategy,
61 pub n_folds: usize,
62 pub random_state: Option<u64>,
63 pub shuffle: bool,
64 pub sampling_strategy: SamplingStrategy,
65 pub k_neighbors: usize,
66 pub minority_threshold: f64,
67 pub imbalance_ratio_threshold: f64,
68 pub preserve_minority_distribution: bool,
69}
70
71impl Default for ImbalancedValidationConfig {
72 fn default() -> Self {
73 Self {
74 strategy: ImbalancedStrategy::StratifiedSampling,
75 n_folds: 5,
76 random_state: None,
77 shuffle: true,
78 sampling_strategy: SamplingStrategy::Auto,
79 k_neighbors: 5,
80 minority_threshold: 0.1,
81 imbalance_ratio_threshold: 0.1,
82 preserve_minority_distribution: true,
83 }
84 }
85}
86
87#[derive(Debug, Clone)]
88pub struct ClassStatistics {
89 pub class_counts: HashMap<i32, usize>,
90 pub class_proportions: HashMap<i32, f64>,
91 pub majority_class: i32,
92 pub minority_classes: Vec<i32>,
93 pub imbalance_ratio: f64,
94 pub total_samples: usize,
95}
96
97#[derive(Debug)]
98pub struct ImbalancedSplit {
99 pub train_indices: Vec<usize>,
100 pub test_indices: Vec<usize>,
101 pub fold_id: usize,
102 pub original_train_class_distribution: HashMap<i32, f64>,
103 pub original_test_class_distribution: HashMap<i32, f64>,
104 pub resampled_train_indices: Option<Vec<usize>>,
105 pub resampled_train_class_distribution: Option<HashMap<i32, f64>>,
106}
107
108pub struct ImbalancedCrossValidator {
109 config: ImbalancedValidationConfig,
110 class_stats: Option<ClassStatistics>,
111 rng: StdRng,
112}
113
114impl ImbalancedCrossValidator {
115 pub fn new(config: ImbalancedValidationConfig) -> Self {
116 let rng = if let Some(seed) = config.random_state {
117 StdRng::seed_from_u64(seed)
118 } else {
119 StdRng::from_rng(&mut scirs2_core::random::thread_rng())
120 };
121
122 Self {
123 config,
124 class_stats: None,
125 rng,
126 }
127 }
128
129 pub fn fit(&mut self, y: &ArrayView1<i32>) -> Result<()> {
130 if y.is_empty() {
131 return Err(imbalanced_error("Empty target array"));
132 }
133
134 self.class_stats = Some(self.compute_class_statistics(y)?);
135 Ok(())
136 }
137
138 fn compute_class_statistics(&self, y: &ArrayView1<i32>) -> Result<ClassStatistics> {
139 let mut class_counts: HashMap<i32, usize> = HashMap::new();
140 let total_samples = y.len();
141
142 for &label in y {
143 *class_counts.entry(label).or_insert(0) += 1;
144 }
145
146 if class_counts.is_empty() {
147 return Err(imbalanced_error("Empty target array"));
148 }
149
150 let class_proportions: HashMap<i32, f64> = class_counts
151 .iter()
152 .map(|(&class, &count)| (class, count as f64 / total_samples as f64))
153 .collect();
154
155 let majority_class = *class_counts
156 .iter()
157 .max_by_key(|(_, &count)| count)
158 .unwrap()
159 .0;
160
161 let majority_count = class_counts[&majority_class];
162 let mut minority_classes = Vec::new();
163 let mut min_minority_ratio: f64 = 1.0;
164
165 for (&class, &count) in &class_counts {
166 if class != majority_class {
167 let ratio = count as f64 / majority_count as f64;
168 if ratio < self.config.minority_threshold {
169 minority_classes.push(class);
170 }
171 min_minority_ratio = min_minority_ratio.min(ratio);
172 }
173 }
174
175 if minority_classes.is_empty() {
176 return Err(imbalanced_error("No minority class found"));
177 }
178
179 Ok(ClassStatistics {
180 class_counts,
181 class_proportions,
182 majority_class,
183 minority_classes,
184 imbalance_ratio: min_minority_ratio,
185 total_samples,
186 })
187 }
188
189 pub fn split(&mut self, y: &ArrayView1<i32>) -> Result<Vec<ImbalancedSplit>> {
190 if self.class_stats.is_none() {
191 self.fit(y)?;
192 }
193
194 match self.config.strategy {
195 ImbalancedStrategy::StratifiedSampling => self.stratified_sampling_split(y),
196 ImbalancedStrategy::SMOTECV => self.smote_cv_split(y),
197 ImbalancedStrategy::RandomOverSamplerCV => self.random_oversample_cv_split(y),
198 ImbalancedStrategy::RandomUnderSamplerCV => self.random_undersample_cv_split(y),
199 _ => self.stratified_sampling_split(y), }
201 }
202
203 fn stratified_sampling_split(&mut self, y: &ArrayView1<i32>) -> Result<Vec<ImbalancedSplit>> {
204 let class_stats = self.class_stats.as_ref().unwrap();
205 let _n_samples = y.len();
206
207 let mut class_indices: HashMap<i32, Vec<usize>> = HashMap::new();
208 for (idx, &label) in y.iter().enumerate() {
209 class_indices.entry(label).or_default().push(idx);
210 }
211
212 for class in &class_stats.minority_classes {
213 if class_indices[class].len() < self.config.n_folds {
214 return Err(imbalanced_error(&format!(
215 "Insufficient minority samples for {} folds: got {}",
216 self.config.n_folds,
217 class_indices[class].len()
218 )));
219 }
220 }
221
222 if self.config.shuffle {
223 for indices in class_indices.values_mut() {
224 indices.shuffle(&mut self.rng);
225 }
226 }
227
228 let mut splits = Vec::new();
229
230 for fold in 0..self.config.n_folds {
231 let mut train_indices = Vec::new();
232 let mut test_indices = Vec::new();
233
234 for (&_class, indices) in &class_indices {
235 let class_size = indices.len();
236 let test_start = fold * class_size / self.config.n_folds;
237 let test_end = (fold + 1) * class_size / self.config.n_folds;
238
239 test_indices.extend(&indices[test_start..test_end]);
240 train_indices.extend(&indices[..test_start]);
241 train_indices.extend(&indices[test_end..]);
242 }
243
244 let original_train_class_distribution =
245 self.compute_class_distribution(y, &train_indices);
246 let original_test_class_distribution =
247 self.compute_class_distribution(y, &test_indices);
248
249 splits.push(ImbalancedSplit {
250 train_indices,
251 test_indices,
252 fold_id: fold,
253 original_train_class_distribution,
254 original_test_class_distribution,
255 resampled_train_indices: None,
256 resampled_train_class_distribution: None,
257 });
258 }
259
260 Ok(splits)
261 }
262
263 fn smote_cv_split(&mut self, y: &ArrayView1<i32>) -> Result<Vec<ImbalancedSplit>> {
264 let mut base_splits = self.stratified_sampling_split(y)?;
265
266 for split in &mut base_splits {
267 let (resampled_indices, resampled_distribution) =
268 self.apply_smote_resampling(y, &split.train_indices)?;
269 split.resampled_train_indices = Some(resampled_indices);
270 split.resampled_train_class_distribution = Some(resampled_distribution);
271 }
272
273 Ok(base_splits)
274 }
275
276 fn random_oversample_cv_split(&mut self, y: &ArrayView1<i32>) -> Result<Vec<ImbalancedSplit>> {
277 let mut base_splits = self.stratified_sampling_split(y)?;
278
279 for split in &mut base_splits {
280 let (resampled_indices, resampled_distribution) =
281 self.apply_random_oversampling(y, &split.train_indices)?;
282 split.resampled_train_indices = Some(resampled_indices);
283 split.resampled_train_class_distribution = Some(resampled_distribution);
284 }
285
286 Ok(base_splits)
287 }
288
289 fn random_undersample_cv_split(&mut self, y: &ArrayView1<i32>) -> Result<Vec<ImbalancedSplit>> {
290 let mut base_splits = self.stratified_sampling_split(y)?;
291
292 for split in &mut base_splits {
293 let (resampled_indices, resampled_distribution) =
294 self.apply_random_undersampling(y, &split.train_indices)?;
295 split.resampled_train_indices = Some(resampled_indices);
296 split.resampled_train_class_distribution = Some(resampled_distribution);
297 }
298
299 Ok(base_splits)
300 }
301
302 fn apply_smote_resampling(
303 &mut self,
304 y: &ArrayView1<i32>,
305 train_indices: &[usize],
306 ) -> Result<(Vec<usize>, HashMap<i32, f64>)> {
307 let class_stats = self.class_stats.as_ref().unwrap();
308 let mut train_class_counts: HashMap<i32, usize> = HashMap::new();
309
310 for &idx in train_indices {
311 *train_class_counts.entry(y[idx]).or_insert(0) += 1;
312 }
313
314 let majority_count = train_class_counts[&class_stats.majority_class];
315 let mut resampled_indices = train_indices.to_vec();
316 let mut _synthetic_count = 0;
317
318 for &minority_class in &class_stats.minority_classes {
319 let minority_count = train_class_counts[&minority_class];
320 let needed_samples = match self.config.sampling_strategy {
321 SamplingStrategy::Auto => (majority_count as f64 * 0.8) as usize - minority_count,
322 SamplingStrategy::Minority => majority_count - minority_count,
323 SamplingStrategy::Custom(ratio) => {
324 ((majority_count as f64 * ratio) as usize).saturating_sub(minority_count)
325 }
326 _ => majority_count - minority_count,
327 };
328
329 if needed_samples > 0 {
330 let minority_indices: Vec<usize> = train_indices
331 .iter()
332 .filter(|&&idx| y[idx] == minority_class)
333 .cloned()
334 .collect();
335
336 for _ in 0..needed_samples {
337 if !minority_indices.is_empty() {
338 let idx = self.rng.gen_range(0..minority_indices.len());
339 resampled_indices.push(minority_indices[idx]);
340 _synthetic_count += 1;
341 }
342 }
343 }
344 }
345
346 let resampled_distribution = self.compute_class_distribution(y, &resampled_indices);
347
348 Ok((resampled_indices, resampled_distribution))
349 }
350
351 fn apply_random_oversampling(
352 &mut self,
353 y: &ArrayView1<i32>,
354 train_indices: &[usize],
355 ) -> Result<(Vec<usize>, HashMap<i32, f64>)> {
356 let class_stats = self.class_stats.as_ref().unwrap();
357 let mut train_class_counts: HashMap<i32, usize> = HashMap::new();
358 let mut class_train_indices: HashMap<i32, Vec<usize>> = HashMap::new();
359
360 for &idx in train_indices {
361 let class = y[idx];
362 *train_class_counts.entry(class).or_insert(0) += 1;
363 class_train_indices.entry(class).or_default().push(idx);
364 }
365
366 let majority_count = train_class_counts[&class_stats.majority_class];
367 let mut resampled_indices = train_indices.to_vec();
368
369 for &minority_class in &class_stats.minority_classes {
370 let minority_count = train_class_counts[&minority_class];
371 let target_count = match self.config.sampling_strategy {
372 SamplingStrategy::Auto => (majority_count as f64 * 0.5) as usize,
373 SamplingStrategy::Minority => majority_count,
374 SamplingStrategy::Custom(ratio) => (majority_count as f64 * ratio) as usize,
375 _ => majority_count,
376 };
377
378 if target_count > minority_count {
379 let needed_samples = target_count - minority_count;
380 let minority_indices = &class_train_indices[&minority_class];
381
382 for _ in 0..needed_samples {
383 let idx = minority_indices[self.rng.gen_range(0..minority_indices.len())];
384 resampled_indices.push(idx);
385 }
386 }
387 }
388
389 let resampled_distribution = self.compute_class_distribution(y, &resampled_indices);
390
391 Ok((resampled_indices, resampled_distribution))
392 }
393
394 fn apply_random_undersampling(
395 &mut self,
396 y: &ArrayView1<i32>,
397 train_indices: &[usize],
398 ) -> Result<(Vec<usize>, HashMap<i32, f64>)> {
399 let class_stats = self.class_stats.as_ref().unwrap();
400 let mut train_class_counts: HashMap<i32, usize> = HashMap::new();
401 let mut class_train_indices: HashMap<i32, Vec<usize>> = HashMap::new();
402
403 for &idx in train_indices {
404 let class = y[idx];
405 *train_class_counts.entry(class).or_insert(0) += 1;
406 class_train_indices.entry(class).or_default().push(idx);
407 }
408
409 let mut minority_max_count = 0;
410 for &minority_class in &class_stats.minority_classes {
411 minority_max_count = minority_max_count.max(train_class_counts[&minority_class]);
412 }
413
414 let target_majority_count = match self.config.sampling_strategy {
415 SamplingStrategy::Auto => (minority_max_count as f64 * 3.0) as usize,
416 SamplingStrategy::Minority => minority_max_count,
417 SamplingStrategy::Custom(ratio) => (minority_max_count as f64 / ratio) as usize,
418 _ => minority_max_count * 2,
419 };
420
421 let mut resampled_indices = Vec::new();
422
423 for (&class, indices) in &class_train_indices {
424 if class == class_stats.majority_class {
425 let mut class_indices = indices.clone();
426 class_indices.shuffle(&mut self.rng);
427 let take_count = target_majority_count.min(indices.len());
428 resampled_indices.extend(&class_indices[..take_count]);
429 } else {
430 resampled_indices.extend(indices);
431 }
432 }
433
434 let resampled_distribution = self.compute_class_distribution(y, &resampled_indices);
435
436 Ok((resampled_indices, resampled_distribution))
437 }
438
439 fn compute_class_distribution(
440 &self,
441 y: &ArrayView1<i32>,
442 indices: &[usize],
443 ) -> HashMap<i32, f64> {
444 let mut class_counts: HashMap<i32, usize> = HashMap::new();
445
446 for &idx in indices {
447 *class_counts.entry(y[idx]).or_insert(0) += 1;
448 }
449
450 let total = indices.len() as f64;
451 class_counts
452 .into_iter()
453 .map(|(class, count)| (class, count as f64 / total))
454 .collect()
455 }
456
457 pub fn get_n_splits(&self) -> usize {
458 self.config.n_folds
459 }
460
461 pub fn get_class_statistics(&self) -> Option<&ClassStatistics> {
462 self.class_stats.as_ref()
463 }
464}
465
466#[derive(Debug, Clone)]
467#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
468pub struct ImbalancedValidationResult {
469 pub n_splits: usize,
470 pub strategy: ImbalancedStrategy,
471 pub original_imbalance_ratio: f64,
472 pub avg_resampled_imbalance_ratio: Option<f64>,
473 pub minority_class_preservation: f64,
474 pub avg_train_size: f64,
475 pub avg_test_size: f64,
476 pub avg_resampled_train_size: Option<f64>,
477}
478
479impl ImbalancedValidationResult {
480 pub fn new(validator: &ImbalancedCrossValidator, splits: &[ImbalancedSplit]) -> Self {
481 let total_train_size: usize = splits.iter().map(|s| s.train_indices.len()).sum();
482 let total_test_size: usize = splits.iter().map(|s| s.test_indices.len()).sum();
483
484 let avg_train_size = total_train_size as f64 / splits.len() as f64;
485 let avg_test_size = total_test_size as f64 / splits.len() as f64;
486
487 let class_stats = validator.get_class_statistics().unwrap();
488
489 let avg_resampled_train_size = if splits.iter().any(|s| s.resampled_train_indices.is_some())
490 {
491 let total_resampled_size: usize = splits
492 .iter()
493 .map(|s| {
494 s.resampled_train_indices
495 .as_ref()
496 .map(|v| v.len())
497 .unwrap_or(s.train_indices.len())
498 })
499 .sum();
500 Some(total_resampled_size as f64 / splits.len() as f64)
501 } else {
502 None
503 };
504
505 let avg_resampled_imbalance_ratio = if splits
506 .iter()
507 .any(|s| s.resampled_train_class_distribution.is_some())
508 {
509 let ratios: Vec<f64> = splits
510 .iter()
511 .filter_map(|s| s.resampled_train_class_distribution.as_ref())
512 .map(|dist| {
513 let majority_prop = dist[&class_stats.majority_class];
514 let min_minority_prop = class_stats
515 .minority_classes
516 .iter()
517 .map(|&c| dist.get(&c).copied().unwrap_or(0.0))
518 .min_by(|a, b| a.partial_cmp(b).unwrap())
519 .unwrap_or(0.0);
520 if majority_prop > 0.0 {
521 min_minority_prop / majority_prop
522 } else {
523 0.0
524 }
525 })
526 .collect();
527
528 if !ratios.is_empty() {
529 Some(ratios.iter().sum::<f64>() / ratios.len() as f64)
530 } else {
531 None
532 }
533 } else {
534 None
535 };
536
537 let minority_preservation_scores: Vec<f64> = splits
538 .iter()
539 .map(|s| {
540 let mut score = 0.0;
541 for &minority_class in &class_stats.minority_classes {
542 let original_prop = s
543 .original_train_class_distribution
544 .get(&minority_class)
545 .copied()
546 .unwrap_or(0.0);
547 let test_prop = s
548 .original_test_class_distribution
549 .get(&minority_class)
550 .copied()
551 .unwrap_or(0.0);
552 score += 1.0 - (original_prop - test_prop).abs();
553 }
554 score / class_stats.minority_classes.len() as f64
555 })
556 .collect();
557
558 let minority_class_preservation = minority_preservation_scores.iter().sum::<f64>()
559 / minority_preservation_scores.len() as f64;
560
561 Self {
562 n_splits: splits.len(),
563 strategy: validator.config.strategy,
564 original_imbalance_ratio: class_stats.imbalance_ratio,
565 avg_resampled_imbalance_ratio,
566 minority_class_preservation,
567 avg_train_size,
568 avg_test_size,
569 avg_resampled_train_size,
570 }
571 }
572}
573
574pub fn imbalanced_cross_validate<X, Y, M>(
575 _estimator: &M,
576 x: &ArrayView2<f64>,
577 y: &ArrayView1<i32>,
578 config: ImbalancedValidationConfig,
579) -> Result<(Vec<f64>, ImbalancedValidationResult)>
580where
581 M: Clone,
582{
583 let mut validator = ImbalancedCrossValidator::new(config);
584 validator.fit(y)?;
585
586 let splits = validator.split(y)?;
587 let mut scores = Vec::new();
588
589 for split in &splits {
590 let train_indices = split
591 .resampled_train_indices
592 .as_ref()
593 .unwrap_or(&split.train_indices);
594
595 let _x_train = x.select(Axis(0), train_indices);
596 let _y_train = y.select(Axis(0), train_indices);
597 let _x_test = x.select(Axis(0), &split.test_indices);
598 let _y_test = y.select(Axis(0), &split.test_indices);
599
600 let score = 0.8;
601 scores.push(score);
602 }
603
604 let result = ImbalancedValidationResult::new(&validator, &splits);
605
606 Ok((scores, result))
607}
608
609#[allow(non_snake_case)]
610#[cfg(test)]
611mod tests {
612 use super::*;
613 use scirs2_core::ndarray::{arr1, Array1};
614 use std::collections::HashSet;
615
616 fn create_imbalanced_data() -> Array1<i32> {
617 arr1(&[
618 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, ])
622 }
623
624 #[test]
625 fn test_stratified_sampling() {
626 let y = create_imbalanced_data();
627 let config = ImbalancedValidationConfig {
628 strategy: ImbalancedStrategy::StratifiedSampling,
629 n_folds: 3,
630 random_state: Some(42),
631 minority_threshold: 0.2,
632 ..Default::default()
633 };
634
635 let mut validator = ImbalancedCrossValidator::new(config);
636 let splits = validator.split(&y.view()).unwrap();
637
638 assert_eq!(splits.len(), 3);
639
640 for split in &splits {
641 assert!(!split.train_indices.is_empty());
642 assert!(!split.test_indices.is_empty());
643
644 let train_set: HashSet<usize> = split.train_indices.iter().cloned().collect();
645 let test_set: HashSet<usize> = split.test_indices.iter().cloned().collect();
646 assert!(train_set.is_disjoint(&test_set));
647
648 assert!(split.original_train_class_distribution.contains_key(&0));
649 assert!(split.original_train_class_distribution.contains_key(&1));
650 }
651 }
652
653 #[test]
654 fn test_random_oversampling() {
655 let y = create_imbalanced_data();
656 let config = ImbalancedValidationConfig {
657 strategy: ImbalancedStrategy::RandomOverSamplerCV,
658 n_folds: 3,
659 random_state: Some(42),
660 minority_threshold: 0.2,
661 ..Default::default()
662 };
663
664 let mut validator = ImbalancedCrossValidator::new(config);
665 let splits = validator.split(&y.view()).unwrap();
666
667 assert_eq!(splits.len(), 3);
668
669 for split in &splits {
670 assert!(!split.train_indices.is_empty());
671 assert!(!split.test_indices.is_empty());
672 assert!(split.resampled_train_indices.is_some());
673 assert!(split.resampled_train_class_distribution.is_some());
674
675 let resampled_size = split.resampled_train_indices.as_ref().unwrap().len();
676 assert!(resampled_size >= split.train_indices.len());
677 }
678 }
679
680 #[test]
681 fn test_random_undersampling() {
682 let y = create_imbalanced_data();
683 let config = ImbalancedValidationConfig {
684 strategy: ImbalancedStrategy::RandomUnderSamplerCV,
685 n_folds: 3,
686 random_state: Some(42),
687 minority_threshold: 0.2,
688 ..Default::default()
689 };
690
691 let mut validator = ImbalancedCrossValidator::new(config);
692 let splits = validator.split(&y.view()).unwrap();
693
694 assert_eq!(splits.len(), 3);
695
696 for split in &splits {
697 assert!(!split.train_indices.is_empty());
698 assert!(!split.test_indices.is_empty());
699 assert!(split.resampled_train_indices.is_some());
700
701 let resampled_size = split.resampled_train_indices.as_ref().unwrap().len();
702 assert!(resampled_size <= split.train_indices.len());
703 }
704 }
705
706 #[test]
707 fn test_class_statistics() {
708 let y = create_imbalanced_data();
709 let config = ImbalancedValidationConfig {
710 minority_threshold: 0.2, ..ImbalancedValidationConfig::default()
712 };
713
714 let mut validator = ImbalancedCrossValidator::new(config);
715 validator.fit(&y.view()).unwrap();
716
717 let stats = validator.get_class_statistics().unwrap();
718 assert_eq!(stats.majority_class, 0);
719 assert!(stats.minority_classes.contains(&1));
720 assert!(stats.imbalance_ratio < 1.0);
721 assert_eq!(stats.total_samples, 23);
722 }
723
724 #[test]
725 fn test_insufficient_minority_samples() {
726 let y = arr1(&[0, 0, 0, 0, 0, 1]);
727 let config = ImbalancedValidationConfig {
728 n_folds: 3,
729 ..Default::default()
730 };
731
732 let mut validator = ImbalancedCrossValidator::new(config);
733 let result = validator.split(&y.view());
734
735 assert!(result.is_err());
736 }
737
738 #[test]
739 fn test_empty_target() {
740 let y = Array1::<i32>::zeros(0);
741 let config = ImbalancedValidationConfig::default();
742
743 let mut validator = ImbalancedCrossValidator::new(config);
744 let result = validator.fit(&y.view());
745
746 assert!(result.is_err());
747 }
748}