1use crate::beamforming::array::{inner_product_conj, invert_hermitian_matrix, mat_vec_mul};
19use crate::beamforming::stap::{space_time_steering_vector, STAPConfig, STAPResult};
20use crate::error::{SignalError, SignalResult};
21use scirs2_core::numeric::Complex64;
22use std::f64::consts::PI;
23
24#[derive(Debug, Clone)]
30pub struct SubarrayConfig {
31 pub spatial_subarrays: usize,
33 pub temporal_taps: usize,
35 pub overlap: usize,
37}
38
39impl Default for SubarrayConfig {
40 fn default() -> Self {
41 Self {
42 spatial_subarrays: 4,
43 temporal_taps: 4,
44 overlap: 1,
45 }
46 }
47}
48
49pub fn jdl_stap(
76 data: &[Vec<Complex64>],
77 training_data: &[Vec<Vec<Complex64>>],
78 target_angle: f64,
79 target_doppler: f64,
80 stap_config: &STAPConfig,
81 subarray_config: &SubarrayConfig,
82) -> SignalResult<STAPResult> {
83 let n = stap_config.n_elements;
84 let m = stap_config.n_pulses;
85 let n_sub = subarray_config.spatial_subarrays;
86 let n_taps = subarray_config.temporal_taps;
87
88 validate_subarray_config(n, m, subarray_config)?;
89
90 let reduced_dim = n_sub * n_taps;
92
93 let x_reduced =
95 transform_to_beam_doppler(data, n, m, target_angle, target_doppler, subarray_config)?;
96
97 let mut training_reduced = Vec::with_capacity(training_data.len());
99 for cell in training_data {
100 let t_reduced =
101 transform_to_beam_doppler(cell, n, m, target_angle, target_doppler, subarray_config)?;
102 training_reduced.push(t_reduced);
103 }
104
105 let mut r_reduced = vec![vec![Complex64::new(0.0, 0.0); reduced_dim]; reduced_dim];
107 let n_training = training_reduced.len();
108 if n_training == 0 {
109 return Err(SignalError::ValueError(
110 "Training data must not be empty".to_string(),
111 ));
112 }
113
114 for t_vec in &training_reduced {
115 for i in 0..reduced_dim {
116 for j in 0..reduced_dim {
117 r_reduced[i][j] += t_vec[i] * t_vec[j].conj();
118 }
119 }
120 }
121
122 let scale = 1.0 / n_training as f64;
123 for row in &mut r_reduced {
124 for val in row.iter_mut() {
125 *val *= scale;
126 }
127 }
128
129 let trace_val: f64 = (0..reduced_dim).map(|i| r_reduced[i][i].re).sum();
131 let loading = 1e-4 * trace_val / reduced_dim as f64;
132 for i in 0..reduced_dim {
133 r_reduced[i][i] += Complex64::new(loading, 0.0);
134 }
135
136 let s_reduced =
138 reduced_steering_vector(target_angle, target_doppler, subarray_config, stap_config)?;
139
140 let r_inv = invert_hermitian_matrix(&r_reduced)?;
142 let r_inv_s = mat_vec_mul(&r_inv, &s_reduced);
143 let denom = inner_product_conj(&s_reduced, &r_inv_s);
144
145 if denom.norm() < 1e-20 {
146 return Err(SignalError::ComputationError(
147 "JDL STAP: denominator near zero".to_string(),
148 ));
149 }
150
151 let w_reduced: Vec<Complex64> = r_inv_s.iter().map(|&v| v / denom).collect();
152
153 let rw = mat_vec_mul(&r_reduced, &w_reduced);
155 let output_power = inner_product_conj(&w_reduced, &rw).re;
156
157 let full_weights = expand_weights_to_full(
159 &w_reduced,
160 n,
161 m,
162 target_angle,
163 target_doppler,
164 subarray_config,
165 )?;
166
167 let sinr_improvement = if denom.re > 1.0 {
169 10.0 * denom.re.log10()
170 } else {
171 0.0
172 };
173
174 Ok(STAPResult {
175 weights: full_weights,
176 output_power: output_power.max(0.0),
177 sinr_improvement,
178 clutter_rank: reduced_dim.min(n * m),
179 })
180}
181
182pub fn factored_stap(
206 data: &[Vec<Complex64>],
207 training_data: &[Vec<Vec<Complex64>>],
208 target_angle: f64,
209 target_doppler: f64,
210 config: &STAPConfig,
211) -> SignalResult<STAPResult> {
212 let n = config.n_elements;
213 let m = config.n_pulses;
214
215 if data.len() != n {
216 return Err(SignalError::DimensionMismatch(format!(
217 "Data has {} elements, expected {}",
218 data.len(),
219 n
220 )));
221 }
222 if training_data.is_empty() {
223 return Err(SignalError::ValueError(
224 "Training data must not be empty".to_string(),
225 ));
226 }
227
228 let mut r_spatial = vec![vec![Complex64::new(0.0, 0.0); n]; n];
231 let mut spatial_count = 0usize;
232
233 for cell in training_data {
234 if cell.len() != n {
235 continue;
236 }
237 for p in 0..m {
238 let mut x_spatial = Vec::with_capacity(n);
240 let mut valid = true;
241 for k in 0..n {
242 if p < cell[k].len() {
243 x_spatial.push(cell[k][p]);
244 } else {
245 valid = false;
246 break;
247 }
248 }
249 if !valid {
250 continue;
251 }
252
253 for i in 0..n {
254 for j in 0..n {
255 r_spatial[i][j] += x_spatial[i] * x_spatial[j].conj();
256 }
257 }
258 spatial_count += 1;
259 }
260 }
261
262 if spatial_count == 0 {
263 return Err(SignalError::ComputationError(
264 "No valid spatial snapshots in training data".to_string(),
265 ));
266 }
267
268 let spatial_scale = 1.0 / spatial_count as f64;
269 for row in &mut r_spatial {
270 for val in row.iter_mut() {
271 *val *= spatial_scale;
272 }
273 }
274
275 let trace_s: f64 = (0..n).map(|i| r_spatial[i][i].re).sum();
277 let load_s = 1e-4 * trace_s / n as f64;
278 for i in 0..n {
279 r_spatial[i][i] += Complex64::new(load_s, 0.0);
280 }
281
282 let spatial_phase = 2.0 * PI * config.element_spacing * target_angle.sin();
284 let s_spatial: Vec<Complex64> = (0..n)
285 .map(|k| {
286 let phase = spatial_phase * k as f64;
287 Complex64::new(phase.cos(), phase.sin())
288 })
289 .collect();
290
291 let r_s_inv = invert_hermitian_matrix(&r_spatial)?;
293 let r_s_inv_a = mat_vec_mul(&r_s_inv, &s_spatial);
294 let denom_s = inner_product_conj(&s_spatial, &r_s_inv_a);
295
296 if denom_s.norm() < 1e-20 {
297 return Err(SignalError::ComputationError(
298 "Factored STAP: spatial denominator near zero".to_string(),
299 ));
300 }
301
302 let w_spatial: Vec<Complex64> = r_s_inv_a.iter().map(|&v| v / denom_s).collect();
303
304 let mut r_temporal = vec![vec![Complex64::new(0.0, 0.0); m]; m];
309 let mut temporal_count = 0usize;
310
311 for cell in training_data {
312 if cell.len() != n {
313 continue;
314 }
315 let mut y = Vec::with_capacity(m);
317 let mut valid = true;
318 for p in 0..m {
319 let mut beam_out = Complex64::new(0.0, 0.0);
320 for k in 0..n {
321 if p < cell[k].len() {
322 beam_out += w_spatial[k].conj() * cell[k][p];
323 } else {
324 valid = false;
325 break;
326 }
327 }
328 if !valid {
329 break;
330 }
331 y.push(beam_out);
332 }
333 if !valid || y.len() != m {
334 continue;
335 }
336
337 for i in 0..m {
338 for j in 0..m {
339 r_temporal[i][j] += y[i] * y[j].conj();
340 }
341 }
342 temporal_count += 1;
343 }
344
345 if temporal_count == 0 {
346 return Err(SignalError::ComputationError(
347 "No valid temporal snapshots in training data".to_string(),
348 ));
349 }
350
351 let temporal_scale = 1.0 / temporal_count as f64;
352 for row in &mut r_temporal {
353 for val in row.iter_mut() {
354 *val *= temporal_scale;
355 }
356 }
357
358 let trace_t: f64 = (0..m).map(|i| r_temporal[i][i].re).sum();
360 let load_t = 1e-4 * trace_t / m as f64;
361 for i in 0..m {
362 r_temporal[i][i] += Complex64::new(load_t, 0.0);
363 }
364
365 let temporal_phase = 2.0 * PI * target_doppler;
367 let s_temporal: Vec<Complex64> = (0..m)
368 .map(|p| {
369 let phase = temporal_phase * p as f64;
370 Complex64::new(phase.cos(), phase.sin())
371 })
372 .collect();
373
374 let r_t_inv = invert_hermitian_matrix(&r_temporal)?;
376 let r_t_inv_b = mat_vec_mul(&r_t_inv, &s_temporal);
377 let denom_t = inner_product_conj(&s_temporal, &r_t_inv_b);
378
379 if denom_t.norm() < 1e-20 {
380 return Err(SignalError::ComputationError(
381 "Factored STAP: temporal denominator near zero".to_string(),
382 ));
383 }
384
385 let w_temporal: Vec<Complex64> = r_t_inv_b.iter().map(|&v| v / denom_t).collect();
386
387 let mut weights = Vec::with_capacity(n * m);
389 for p in 0..m {
390 for k in 0..n {
391 weights.push(w_temporal[p] * w_spatial[k]);
392 }
393 }
394
395 let mut x_st = Vec::with_capacity(n * m);
398 for p in 0..m {
399 for k in 0..n {
400 if p < data[k].len() {
401 x_st.push(data[k][p]);
402 } else {
403 x_st.push(Complex64::new(0.0, 0.0));
404 }
405 }
406 }
407
408 let beam_output = inner_product_conj(&weights, &x_st);
409 let output_power = beam_output.norm_sqr();
410
411 let sinr_s = denom_s.re;
413 let sinr_t = denom_t.re;
414 let sinr_improvement = if sinr_s > 0.0 && sinr_t > 0.0 {
415 10.0 * (sinr_s * sinr_t).log10()
416 } else {
417 0.0
418 };
419
420 Ok(STAPResult {
421 weights,
422 output_power,
423 sinr_improvement,
424 clutter_rank: n + m, })
426}
427
428pub fn dimension_reduction_ratio(stap: &STAPConfig, subarray: &SubarrayConfig) -> f64 {
439 let full_dim = stap.n_elements * stap.n_pulses;
440 let reduced_dim = subarray.spatial_subarrays * subarray.temporal_taps;
441
442 if reduced_dim == 0 {
443 return f64::INFINITY;
444 }
445
446 full_dim as f64 / reduced_dim as f64
447}
448
449fn validate_subarray_config(
455 n_elements: usize,
456 n_pulses: usize,
457 config: &SubarrayConfig,
458) -> SignalResult<()> {
459 if config.spatial_subarrays == 0 {
460 return Err(SignalError::ValueError(
461 "Number of spatial subarrays must be positive".to_string(),
462 ));
463 }
464 if config.temporal_taps == 0 {
465 return Err(SignalError::ValueError(
466 "Number of temporal taps must be positive".to_string(),
467 ));
468 }
469 if config.spatial_subarrays > n_elements {
470 return Err(SignalError::ValueError(format!(
471 "Number of spatial subarrays ({}) exceeds number of elements ({})",
472 config.spatial_subarrays, n_elements
473 )));
474 }
475 if config.temporal_taps > n_pulses {
476 return Err(SignalError::ValueError(format!(
477 "Number of temporal taps ({}) exceeds number of pulses ({})",
478 config.temporal_taps, n_pulses
479 )));
480 }
481 Ok(())
482}
483
484fn transform_to_beam_doppler(
489 data: &[Vec<Complex64>],
490 n_elements: usize,
491 n_pulses: usize,
492 target_angle: f64,
493 target_doppler: f64,
494 config: &SubarrayConfig,
495) -> SignalResult<Vec<Complex64>> {
496 let n_sub = config.spatial_subarrays;
497 let n_taps = config.temporal_taps;
498 let reduced_dim = n_sub * n_taps;
499
500 if data.len() != n_elements {
501 return Err(SignalError::DimensionMismatch(format!(
502 "Data has {} elements, expected {}",
503 data.len(),
504 n_elements
505 )));
506 }
507
508 let target_spatial_freq = target_angle.sin() * 0.5;
513 let centre_beam = (target_spatial_freq * n_elements as f64)
514 .round()
515 .rem_euclid(n_elements as f64) as i64;
516
517 let mut beams = Vec::with_capacity(n_sub * n_pulses);
518 for sub_idx in 0..n_sub {
519 let beam_idx_raw = centre_beam - (n_sub as i64 / 2) + sub_idx as i64;
520 let beam_idx = beam_idx_raw.rem_euclid(n_elements as i64) as usize;
521
522 for p in 0..n_pulses {
523 let mut beam_val = Complex64::new(0.0, 0.0);
524 for k in 0..n_elements {
525 if p < data[k].len() {
526 let phase = -2.0 * PI * (beam_idx as f64) * (k as f64) / (n_elements as f64);
527 let twiddle = Complex64::new(phase.cos(), phase.sin());
528 beam_val += data[k][p] * twiddle;
529 }
530 }
531 beams.push(beam_val);
532 }
533 }
534
535 let centre_doppler = (target_doppler * n_pulses as f64)
539 .round()
540 .rem_euclid(n_pulses as f64) as i64;
541
542 let mut result = Vec::with_capacity(reduced_dim);
543 for sub_idx in 0..n_sub {
544 for tap_idx in 0..n_taps {
545 let dop_idx_raw = centre_doppler - (n_taps as i64 / 2) + tap_idx as i64;
546 let dop_idx = dop_idx_raw.rem_euclid(n_pulses as i64) as usize;
547
548 let mut bin_val = Complex64::new(0.0, 0.0);
549 for p in 0..n_pulses {
550 let beam_sample = beams[sub_idx * n_pulses + p];
551 let phase = -2.0 * PI * (dop_idx as f64) * (p as f64) / (n_pulses as f64);
552 let twiddle = Complex64::new(phase.cos(), phase.sin());
553 bin_val += beam_sample * twiddle;
554 }
555 result.push(bin_val);
556 }
557 }
558
559 Ok(result)
560}
561
562fn reduced_steering_vector(
564 target_angle: f64,
565 target_doppler: f64,
566 subarray_config: &SubarrayConfig,
567 stap_config: &STAPConfig,
568) -> SignalResult<Vec<Complex64>> {
569 let n_sub = subarray_config.spatial_subarrays;
570 let n_taps = subarray_config.temporal_taps;
571 let n = stap_config.n_elements;
572 let m = stap_config.n_pulses;
573 let reduced_dim = n_sub * n_taps;
574
575 let target_spatial_freq = target_angle.sin() * 0.5;
576 let centre_beam = (target_spatial_freq * n as f64)
577 .round()
578 .rem_euclid(n as f64) as i64;
579 let centre_doppler = (target_doppler * m as f64).round().rem_euclid(m as f64) as i64;
580
581 let spatial_phase = 2.0 * PI * stap_config.element_spacing * target_angle.sin();
585 let temporal_phase = 2.0 * PI * target_doppler;
586
587 let mut s_reduced = Vec::with_capacity(reduced_dim);
588 for sub_idx in 0..n_sub {
589 let beam_idx_raw = centre_beam - (n_sub as i64 / 2) + sub_idx as i64;
590 let beam_idx = beam_idx_raw.rem_euclid(n as i64) as usize;
591
592 let mut s_beam = Complex64::new(0.0, 0.0);
594 for k in 0..n {
595 let sv_phase = spatial_phase * k as f64;
596 let sv_k = Complex64::new(sv_phase.cos(), sv_phase.sin());
597 let dft_phase = -2.0 * PI * (beam_idx as f64) * (k as f64) / (n as f64);
598 let twiddle = Complex64::new(dft_phase.cos(), dft_phase.sin());
599 s_beam += sv_k * twiddle;
600 }
601
602 for tap_idx in 0..n_taps {
603 let dop_idx_raw = centre_doppler - (n_taps as i64 / 2) + tap_idx as i64;
604 let dop_idx = dop_idx_raw.rem_euclid(m as i64) as usize;
605
606 let mut s_dop = Complex64::new(0.0, 0.0);
608 for p in 0..m {
609 let sv_phase_t = temporal_phase * p as f64;
610 let sv_p = Complex64::new(sv_phase_t.cos(), sv_phase_t.sin());
611 let dft_phase_t = -2.0 * PI * (dop_idx as f64) * (p as f64) / (m as f64);
612 let twiddle_t = Complex64::new(dft_phase_t.cos(), dft_phase_t.sin());
613 s_dop += sv_p * twiddle_t;
614 }
615
616 s_reduced.push(s_beam * s_dop);
617 }
618 }
619
620 let norm_sq: f64 = s_reduced.iter().map(|v| v.norm_sqr()).sum();
623 if norm_sq > 1e-30 {
624 let scale = (reduced_dim as f64 / norm_sq).sqrt();
625 for v in &mut s_reduced {
626 *v *= scale;
627 }
628 } else {
629 let val = Complex64::new(1.0, 0.0);
631 for v in &mut s_reduced {
632 *v = val;
633 }
634 }
635
636 Ok(s_reduced)
637}
638
639fn expand_weights_to_full(
641 w_reduced: &[Complex64],
642 n_elements: usize,
643 n_pulses: usize,
644 target_angle: f64,
645 target_doppler: f64,
646 config: &SubarrayConfig,
647) -> SignalResult<Vec<Complex64>> {
648 let n_sub = config.spatial_subarrays;
649 let n_taps = config.temporal_taps;
650 let full_dim = n_elements * n_pulses;
651
652 let target_spatial_freq = target_angle.sin() * 0.5;
653 let centre_beam = (target_spatial_freq * n_elements as f64)
654 .round()
655 .rem_euclid(n_elements as f64) as i64;
656 let centre_doppler = (target_doppler * n_pulses as f64)
657 .round()
658 .rem_euclid(n_pulses as f64) as i64;
659
660 let mut w_full = vec![Complex64::new(0.0, 0.0); full_dim];
662
663 for sub_idx in 0..n_sub {
664 let beam_idx_raw = centre_beam - (n_sub as i64 / 2) + sub_idx as i64;
665 let beam_idx = beam_idx_raw.rem_euclid(n_elements as i64) as usize;
666
667 for tap_idx in 0..n_taps {
668 let dop_idx_raw = centre_doppler - (n_taps as i64 / 2) + tap_idx as i64;
669 let dop_idx = dop_idx_raw.rem_euclid(n_pulses as i64) as usize;
670
671 let w_val = w_reduced[sub_idx * n_taps + tap_idx];
672
673 for k in 0..n_elements {
675 let phase_s = 2.0 * PI * (beam_idx as f64) * (k as f64) / (n_elements as f64);
676 let twiddle_s = Complex64::new(phase_s.cos(), phase_s.sin());
677
678 for p in 0..n_pulses {
680 let phase_t = 2.0 * PI * (dop_idx as f64) * (p as f64) / (n_pulses as f64);
681 let twiddle_t = Complex64::new(phase_t.cos(), phase_t.sin());
682
683 let idx = p * n_elements + k;
684 w_full[idx] += w_val * twiddle_s * twiddle_t;
685 }
686 }
687 }
688 }
689
690 let norm_factor = 1.0 / ((n_elements * n_pulses) as f64);
692 for w in &mut w_full {
693 *w *= norm_factor;
694 }
695
696 Ok(w_full)
697}
698
699#[cfg(test)]
704mod tests {
705 use super::*;
706
707 fn make_training_data(
708 n_elements: usize,
709 n_pulses: usize,
710 n_cells: usize,
711 ) -> Vec<Vec<Vec<Complex64>>> {
712 let mut training = Vec::with_capacity(n_cells);
713 for cell_idx in 0..n_cells {
714 let mut snapshot = vec![vec![Complex64::new(0.0, 0.0); n_pulses]; n_elements];
715 for k in 0..n_elements {
716 for p in 0..n_pulses {
717 let seed = (cell_idx * 1000 + k * 37 + p * 13) as f64;
718 let re = (seed * 0.618033988749).fract() - 0.5;
719 let im = (seed * 0.414213562373).fract() - 0.5;
720 snapshot[k][p] = Complex64::new(re, im);
721 }
722 }
723 training.push(snapshot);
724 }
725 training
726 }
727
728 #[test]
729 fn test_jdl_dimension_less_than_full() {
730 let stap_config = STAPConfig {
731 n_elements: 8,
732 n_pulses: 8,
733 ..STAPConfig::default()
734 };
735 let sub_config = SubarrayConfig {
736 spatial_subarrays: 3,
737 temporal_taps: 3,
738 overlap: 1,
739 };
740
741 let full_dim = stap_config.n_elements * stap_config.n_pulses; let reduced_dim = sub_config.spatial_subarrays * sub_config.temporal_taps; assert!(
745 reduced_dim < full_dim,
746 "Reduced dim {} should be < full dim {}",
747 reduced_dim,
748 full_dim
749 );
750
751 let ratio = dimension_reduction_ratio(&stap_config, &sub_config);
752 assert!(ratio > 1.0, "Ratio should be > 1, got {}", ratio);
753 assert!((ratio - (64.0 / 9.0)).abs() < 1e-10);
754 }
755
756 #[test]
757 fn test_jdl_stap_basic() {
758 let stap_config = STAPConfig {
759 n_elements: 4,
760 n_pulses: 4,
761 ..STAPConfig::default()
762 };
763 let sub_config = SubarrayConfig {
764 spatial_subarrays: 2,
765 temporal_taps: 2,
766 overlap: 0,
767 };
768
769 let n = stap_config.n_elements;
770 let m = stap_config.n_pulses;
771 let training = make_training_data(n, m, 30);
772 let data = vec![vec![Complex64::new(1.0, 0.0); m]; n];
773
774 let result = jdl_stap(&data, &training, 0.0, 0.0, &stap_config, &sub_config)
775 .expect("JDL STAP should succeed");
776
777 assert_eq!(result.weights.len(), n * m);
778 assert!(
779 result.output_power >= 0.0,
780 "Output power should be non-negative"
781 );
782 assert!(result.output_power.is_finite());
783 }
784
785 #[test]
786 fn test_factored_stap_output_power_positive() {
787 let config = STAPConfig {
788 n_elements: 4,
789 n_pulses: 4,
790 element_spacing: 0.5,
791 ..STAPConfig::default()
792 };
793
794 let n = config.n_elements;
795 let m = config.n_pulses;
796 let training = make_training_data(n, m, 30);
797 let data = vec![vec![Complex64::new(1.0, 0.0); m]; n];
798
799 let result = factored_stap(&data, &training, 0.1, 0.05, &config)
800 .expect("Factored STAP should succeed");
801
802 assert_eq!(result.weights.len(), n * m);
803 assert!(
804 result.output_power > 0.0,
805 "Output power should be positive, got {}",
806 result.output_power
807 );
808 assert!(result.output_power.is_finite());
809 }
810
811 #[test]
812 fn test_dimension_reduction_ratio_correct() {
813 let stap = STAPConfig {
814 n_elements: 8,
815 n_pulses: 16,
816 ..STAPConfig::default()
817 };
818 let sub = SubarrayConfig {
819 spatial_subarrays: 4,
820 temporal_taps: 4,
821 overlap: 1,
822 };
823
824 let ratio = dimension_reduction_ratio(&stap, &sub);
825 assert!((ratio - 8.0).abs() < 1e-10, "Expected 8.0, got {}", ratio);
827 }
828
829 #[test]
830 fn test_subarray_validation() {
831 assert!(validate_subarray_config(
832 4,
833 4,
834 &SubarrayConfig {
835 spatial_subarrays: 0,
836 temporal_taps: 2,
837 overlap: 0,
838 }
839 )
840 .is_err());
841
842 assert!(validate_subarray_config(
843 4,
844 4,
845 &SubarrayConfig {
846 spatial_subarrays: 2,
847 temporal_taps: 0,
848 overlap: 0,
849 }
850 )
851 .is_err());
852
853 assert!(validate_subarray_config(
854 4,
855 4,
856 &SubarrayConfig {
857 spatial_subarrays: 5,
858 temporal_taps: 2,
859 overlap: 0,
860 }
861 )
862 .is_err());
863 }
864
865 #[test]
866 fn test_factored_stap_validation() {
867 let config = STAPConfig {
868 n_elements: 4,
869 n_pulses: 4,
870 ..STAPConfig::default()
871 };
872
873 let data = vec![vec![Complex64::new(1.0, 0.0); 4]; 4];
875 let empty_training: Vec<Vec<Vec<Complex64>>> = vec![];
876 assert!(factored_stap(&data, &empty_training, 0.0, 0.0, &config).is_err());
877
878 let wrong_data = vec![vec![Complex64::new(1.0, 0.0); 4]; 2]; let training = make_training_data(4, 4, 10);
881 assert!(factored_stap(&wrong_data, &training, 0.0, 0.0, &config).is_err());
882 }
883}