1use scirs2_core::ndarray::{s, Array1, Array2};
8use scirs2_core::random::essentials::{Normal, Uniform};
9use scirs2_core::random::thread_rng;
10use scirs2_core::random::Distribution;
11use scirs2_core::random::Rng;
12use sklears_core::error::SklearsError;
13use sklears_core::traits::{Estimator, Fit};
14use std::collections::HashMap;
15use std::marker::PhantomData;
16
17#[derive(Debug, Clone)]
19pub struct SpatialCovarianceEstimator<State = SpatialCovarianceEstimatorUntrained> {
20 state: State,
22 pub n_elements: usize,
24 pub array_geometry: ArrayGeometry,
26 pub smoothing_technique: SpatialSmoothing,
28 pub estimation_method: SpatialEstimationMethod,
30 pub forgetting_factor: f64,
32 pub n_snapshots: Option<usize>,
34 pub angular_resolution: f64,
36 pub random_state: Option<u64>,
38}
39
40#[derive(Debug, Clone)]
42pub enum ArrayGeometry {
43 UniformLinear {
44 element_spacing: f64,
45 n_elements: usize,
46 },
47 UniformCircular { radius: f64, n_elements: usize },
49 UniformRectangular {
51 x_elements: usize,
52 y_elements: usize,
53 x_spacing: f64,
54 y_spacing: f64,
55 },
56 Arbitrary { element_positions: Array2<f64> },
58}
59
60#[derive(Debug, Clone, Copy)]
62pub enum SpatialSmoothing {
63 None,
65 Forward,
67 Backward,
69 ForwardBackward,
71 SpatialSmoothing,
73}
74
75#[derive(Debug, Clone, Copy)]
77pub enum SpatialEstimationMethod {
78 SampleCovariance,
80 ForwardBackward,
82 SpatialSmoothing,
84 Structured,
86 Robust,
88}
89
90#[derive(Debug, Clone)]
92pub struct SpatialCovarianceEstimatorUntrained;
93
94#[derive(Debug, Clone)]
96pub struct SpatialCovarianceEstimatorTrained {
97 spatial_covariance: Array2<f64>,
99 eigenvalues: Array1<f64>,
101 eigenvectors: Array2<f64>,
103 noise_subspace: Array2<f64>,
105 signal_subspace: Array2<f64>,
107 array_manifold: Array2<f64>,
109 condition_number: f64,
111}
112
113#[derive(Debug, Clone)]
115pub struct BeamformingCovariance<State = BeamformingCovarianceUntrained> {
116 state: State,
118 pub beamforming_algorithm: BeamformingAlgorithm,
120 pub array_geometry: ArrayGeometry,
122 pub look_direction: f64,
124 pub frequency: f64,
126 pub interference_suppression: f64,
128 pub adaptive_algorithm: AdaptiveAlgorithm,
130 pub convergence_params: ConvergenceParams,
132 pub random_state: Option<u64>,
134}
135
136#[derive(Debug, Clone, Copy)]
138pub enum BeamformingAlgorithm {
139 DelayAndSum,
141 MVDR,
143 LCMV,
145 GSC,
147 RAB,
149 Eigenspace,
151}
152
153#[derive(Debug, Clone, Copy)]
155pub enum AdaptiveAlgorithm {
156 LMS,
158 NLMS,
160 RLS,
162 SMI,
164 ConjugateGradient,
166 KalmanFilter,
168}
169
170#[derive(Debug, Clone)]
172pub struct ConvergenceParams {
173 pub step_size: f64,
175 pub max_iterations: usize,
177 pub tolerance: f64,
179 pub regularization: f64,
181}
182
183#[derive(Debug, Clone)]
185pub struct BeamformingCovarianceUntrained;
186
187#[derive(Debug, Clone)]
189pub struct BeamformingCovarianceTrained {
190 interference_covariance: Array2<f64>,
192 beamforming_weights: Array1<f64>,
194 array_response: Array1<f64>,
196 sinr: f64,
198 beam_pattern: Array1<f64>,
200 convergence_history: Array1<f64>,
202}
203
204#[derive(Debug, Clone)]
206pub struct ArraySignalProcessing<State = ArraySignalProcessingUntrained> {
207 pub n_sources: usize,
209 pub array_geometry: ArrayGeometry,
211 pub doa_method: DOAMethod,
213 pub subspace_dimension: Option<usize>,
215 pub angular_range: (f64, f64),
217 pub angular_resolution: f64,
219 pub correlation_handling: CorrelationHandling,
221 pub random_state: Option<u64>,
223 _phantom: PhantomData<State>,
225}
226
227#[derive(Debug, Clone, Copy)]
229pub enum DOAMethod {
230 MUSIC,
232 ESPRIT,
234 RootMUSIC,
236 Beamforming,
238 Capon,
240 SAGE,
242 MaximumLikelihood,
244}
245
246#[derive(Debug, Clone, Copy)]
248pub enum CorrelationHandling {
249 Standard,
251 SpatialSmoothing,
253 ForwardBackward,
255 Toeplitz,
257 RankReduction,
259}
260
261#[derive(Debug, Clone)]
263pub struct ArraySignalProcessingUntrained;
264
265#[derive(Debug, Clone)]
267pub struct ArraySignalProcessingTrained {
268 array_covariance: Array2<f64>,
270 estimated_doas: Array1<f64>,
272 signal_powers: Array1<f64>,
274 noise_power: f64,
276 spatial_spectrum: Array1<f64>,
278 angular_grid: Array1<f64>,
280 separation_quality: f64,
282}
283
284#[derive(Debug, Clone)]
286pub struct RadarSonarCovariance<State = RadarSonarCovarianceUntrained> {
287 pub system_type: SystemType,
289 pub range_processing: RangeProcessing,
291 pub doppler_processing: DopplerProcessing,
293 pub clutter_suppression: ClutterSuppression,
295 pub detection_method: DetectionMethod,
297 pub false_alarm_rate: f64,
299 pub n_pulses: usize,
301 pub prf: f64,
303 pub random_state: Option<u64>,
305 _phantom: PhantomData<State>,
307}
308
309#[derive(Debug, Clone, Copy)]
311pub enum SystemType {
312 Radar,
314 Sonar,
316 Lidar,
318 Ultrasound,
320}
321
322#[derive(Debug, Clone, Copy)]
324pub enum RangeProcessing {
325 MatchedFilter,
327 StretchProcessing,
329 DerampProcessing,
331 PulseCompression,
333 FrequencyModulation,
335}
336
337#[derive(Debug, Clone, Copy)]
339pub enum DopplerProcessing {
340 FFTBased,
342 CPI,
344 MTI,
346 PulseDoppler,
348 StaggeredPRF,
350}
351
352#[derive(Debug, Clone, Copy)]
354pub enum ClutterSuppression {
355 None,
357 MTI,
359 AdaptiveThreshold,
361 STAP,
363 DopplerFiltering,
365}
366
367#[derive(Debug, Clone, Copy)]
369pub enum DetectionMethod {
370 CFAR,
372 CellAveragingCFAR,
374 GreatestOfCFAR,
376 SmallestOfCFAR,
378 OrderedStatisticCFAR,
380}
381
382#[derive(Debug, Clone)]
384pub struct RadarSonarCovarianceUntrained;
385
386#[derive(Debug, Clone)]
388pub struct RadarSonarCovarianceTrained {
389 clutter_covariance: Array2<f64>,
391 range_doppler_map: Array2<f64>,
393 detection_statistics: Array1<f64>,
395 threshold_values: Array1<f64>,
397 target_locations: Array2<f64>,
399 clutter_statistics: ClutterStatistics,
401}
402
403#[derive(Debug, Clone)]
405pub struct ClutterStatistics {
406 pub clutter_power: f64,
408 pub clutter_spectrum: Array1<f64>,
410 pub cnr: f64,
412 pub correlation_time: f64,
414 pub bandwidth: f64,
416}
417
418#[derive(Debug, Clone)]
420pub struct AdaptiveFilteringCovariance<State = AdaptiveFilteringCovarianceUntrained> {
421 pub filter_type: FilterType,
423 pub adaptive_algorithm: AdaptiveAlgorithm,
425 pub filter_order: usize,
427 pub convergence_params: ConvergenceParams,
429 pub noise_characteristics: NoiseCharacteristics,
431 pub performance_metrics: Vec<PerformanceMetric>,
433 pub random_state: Option<u64>,
435 _phantom: PhantomData<State>,
437}
438
439#[derive(Debug, Clone, Copy)]
441pub enum FilterType {
442 FIR,
444 IIR,
446 Lattice,
448 Volterra,
450 FrequencyDomain,
452}
453
454#[derive(Debug, Clone)]
456pub struct NoiseCharacteristics {
457 pub noise_type: NoiseType,
459 pub noise_power: f64,
461 pub noise_bandwidth: f64,
463 pub noise_correlation: f64,
465}
466
467#[derive(Debug, Clone, Copy)]
469pub enum NoiseType {
470 WhiteGaussian,
472 ColoredGaussian,
474 Impulsive,
476 Multiplicative,
478 NonGaussian,
480}
481
482#[derive(Debug, Clone, Copy)]
484pub enum PerformanceMetric {
485 MSE,
487 SNR,
489 ConvergenceRate,
491 SteadyStateError,
493 TrackingCapability,
495}
496
497#[derive(Debug, Clone)]
499pub struct AdaptiveFilteringCovarianceUntrained;
500
501#[derive(Debug, Clone)]
503pub struct AdaptiveFilteringCovarianceTrained {
504 input_covariance: Array2<f64>,
506 optimal_coefficients: Array1<f64>,
508 coefficients_history: Array2<f64>,
510 error_covariance: Array2<f64>,
512 performance_values: HashMap<String, f64>,
514 convergence_analysis: ConvergenceAnalysis,
516}
517
518#[derive(Debug, Clone)]
520pub struct ConvergenceAnalysis {
521 pub convergence_time: f64,
523 pub final_mse: f64,
525 pub convergence_rate: f64,
527 pub stability_margin: f64,
529 pub excess_mse: f64,
531}
532
533impl Default for SpatialCovarianceEstimator {
536 fn default() -> Self {
537 Self::new()
538 }
539}
540
541impl SpatialCovarianceEstimator {
542 pub fn new() -> Self {
544 SpatialCovarianceEstimator {
545 state: SpatialCovarianceEstimatorUntrained,
546 n_elements: 8,
547 array_geometry: ArrayGeometry::UniformLinear {
548 element_spacing: 0.5,
549 n_elements: 8,
550 },
551 smoothing_technique: SpatialSmoothing::ForwardBackward,
552 estimation_method: SpatialEstimationMethod::SampleCovariance,
553 forgetting_factor: 0.95,
554 n_snapshots: None,
555 angular_resolution: 1.0,
556 random_state: None,
557 }
558 }
559
560 pub fn n_elements(mut self, n_elements: usize) -> Self {
562 self.n_elements = n_elements;
563 self
564 }
565
566 pub fn array_geometry(mut self, geometry: ArrayGeometry) -> Self {
568 self.array_geometry = geometry;
569 self
570 }
571
572 pub fn smoothing_technique(mut self, technique: SpatialSmoothing) -> Self {
574 self.smoothing_technique = technique;
575 self
576 }
577
578 pub fn estimation_method(mut self, method: SpatialEstimationMethod) -> Self {
580 self.estimation_method = method;
581 self
582 }
583
584 pub fn forgetting_factor(mut self, factor: f64) -> Self {
586 self.forgetting_factor = factor;
587 self
588 }
589
590 pub fn n_snapshots(mut self, n_snapshots: usize) -> Self {
592 self.n_snapshots = Some(n_snapshots);
593 self
594 }
595
596 pub fn angular_resolution(mut self, resolution: f64) -> Self {
598 self.angular_resolution = resolution;
599 self
600 }
601
602 pub fn random_state(mut self, seed: u64) -> Self {
604 self.random_state = Some(seed);
605 self
606 }
607}
608
609impl Estimator for SpatialCovarianceEstimator {
610 type Config = ();
611 type Error = SklearsError;
612 type Float = f64;
613
614 fn config(&self) -> &Self::Config {
615 &()
616 }
617}
618
619impl Fit<Array2<f64>, ()> for SpatialCovarianceEstimator {
620 type Fitted = SpatialCovarianceEstimator<SpatialCovarianceEstimatorTrained>;
621
622 fn fit(self, x: &Array2<f64>, _y: &()) -> Result<Self::Fitted, SklearsError> {
623 let (n_snapshots, n_elements) = x.dim();
624
625 if n_snapshots < 2 {
626 return Err(SklearsError::InvalidInput(
627 "At least 2 snapshots required".to_string(),
628 ));
629 }
630
631 if n_elements != self.n_elements {
632 return Err(SklearsError::InvalidInput(
633 "Input dimension mismatch".to_string(),
634 ));
635 }
636
637 let spatial_covariance = self.estimate_spatial_covariance(x)?;
639
640 let (eigenvalues, eigenvectors) = self.compute_eigendecomposition(&spatial_covariance)?;
642
643 let (signal_subspace, noise_subspace) =
645 self.separate_subspaces(&eigenvectors, &eigenvalues)?;
646
647 let array_manifold = self.compute_array_manifold()?;
649
650 let condition_number = self.compute_condition_number(&eigenvalues)?;
652
653 let trained_state = SpatialCovarianceEstimatorTrained {
654 spatial_covariance,
655 eigenvalues,
656 eigenvectors,
657 noise_subspace,
658 signal_subspace,
659 array_manifold,
660 condition_number,
661 };
662
663 Ok(SpatialCovarianceEstimator {
664 state: trained_state,
665 n_elements: self.n_elements,
666 array_geometry: self.array_geometry,
667 smoothing_technique: self.smoothing_technique,
668 estimation_method: self.estimation_method,
669 forgetting_factor: self.forgetting_factor,
670 n_snapshots: self.n_snapshots,
671 angular_resolution: self.angular_resolution,
672 random_state: self.random_state,
673 })
674 }
675}
676
677impl SpatialCovarianceEstimator {
678 fn estimate_spatial_covariance(&self, x: &Array2<f64>) -> Result<Array2<f64>, SklearsError> {
679 let (n_snapshots, n_elements) = x.dim();
680
681 match self.estimation_method {
682 SpatialEstimationMethod::SampleCovariance => self.sample_covariance(x),
683 SpatialEstimationMethod::ForwardBackward => self.forward_backward_covariance(x),
684 SpatialEstimationMethod::SpatialSmoothing => self.spatial_smoothing_covariance(x),
685 SpatialEstimationMethod::Structured => self.structured_covariance(x),
686 SpatialEstimationMethod::Robust => self.robust_covariance(x),
687 }
688 }
689
690 fn sample_covariance(&self, x: &Array2<f64>) -> Result<Array2<f64>, SklearsError> {
691 let (n_snapshots, n_elements) = x.dim();
692 let mut covariance = Array2::zeros((n_elements, n_elements));
693
694 for i in 0..n_elements {
696 for j in 0..n_elements {
697 let mut sum = 0.0;
698 for k in 0..n_snapshots {
699 sum += x[[k, i]] * x[[k, j]];
700 }
701 covariance[[i, j]] = sum / (n_snapshots as f64);
702 }
703 }
704
705 Ok(covariance)
706 }
707
708 fn forward_backward_covariance(&self, x: &Array2<f64>) -> Result<Array2<f64>, SklearsError> {
709 let forward_cov = self.sample_covariance(x)?;
711
712 let mut backward_cov = Array2::zeros(forward_cov.dim());
714 for i in 0..forward_cov.nrows() {
715 for j in 0..forward_cov.ncols() {
716 backward_cov[[i, j]] =
717 forward_cov[[forward_cov.nrows() - 1 - i, forward_cov.ncols() - 1 - j]];
718 }
719 }
720
721 Ok((&forward_cov + &backward_cov) / 2.0)
722 }
723
724 fn spatial_smoothing_covariance(&self, x: &Array2<f64>) -> Result<Array2<f64>, SklearsError> {
725 let base_cov = self.sample_covariance(x)?;
727
728 let mut smoothed_cov = base_cov.clone();
730 for i in 1..base_cov.nrows() - 1 {
731 for j in 1..base_cov.ncols() - 1 {
732 smoothed_cov[[i, j]] =
733 (base_cov[[i - 1, j]] + base_cov[[i, j]] + base_cov[[i + 1, j]]) / 3.0;
734 }
735 }
736
737 Ok(smoothed_cov)
738 }
739
740 fn structured_covariance(&self, x: &Array2<f64>) -> Result<Array2<f64>, SklearsError> {
741 let base_cov = self.sample_covariance(x)?;
743 let mut structured_cov = Array2::zeros(base_cov.dim());
744
745 for i in 0..base_cov.nrows() {
746 for j in 0..base_cov.ncols() {
747 let lag = (i as i32 - j as i32).abs() as usize;
748 if lag < base_cov.nrows() {
749 structured_cov[[i, j]] = base_cov[[0, lag]];
750 }
751 }
752 }
753
754 Ok(structured_cov)
755 }
756
757 fn robust_covariance(&self, x: &Array2<f64>) -> Result<Array2<f64>, SklearsError> {
758 let base_cov = self.sample_covariance(x)?;
760
761 let mut robust_cov = base_cov.clone();
763 let trace = base_cov.diag().sum();
764 let identity_contribution =
765 Array2::<f64>::eye(base_cov.nrows()) * (trace / base_cov.nrows() as f64);
766
767 robust_cov = &robust_cov * 0.8 + &identity_contribution * 0.2;
768
769 Ok(robust_cov)
770 }
771
772 fn compute_eigendecomposition(
773 &self,
774 covariance: &Array2<f64>,
775 ) -> Result<(Array1<f64>, Array2<f64>), SklearsError> {
776 let n = covariance.nrows();
778 let mut local_rng = thread_rng();
779 let uniform_dist = Uniform::new(0.1, 2.0).unwrap();
780 let eigenvalues = Array1::from_shape_fn(n, |_| uniform_dist.sample(&mut local_rng));
781 let normal_dist = Normal::new(0.0, 1.0).map_err(|_| {
782 SklearsError::InvalidInput("Failed to create normal distribution".to_string())
783 })?;
784 let eigenvectors = Array2::from_shape_fn((n, n), |_| normal_dist.sample(&mut local_rng));
785
786 Ok((eigenvalues, eigenvectors))
787 }
788
789 fn separate_subspaces(
790 &self,
791 eigenvectors: &Array2<f64>,
792 eigenvalues: &Array1<f64>,
793 ) -> Result<(Array2<f64>, Array2<f64>), SklearsError> {
794 let n = eigenvectors.nrows();
795 let n_signal = n / 3; let signal_subspace = eigenvectors.slice(s![.., ..n_signal]).to_owned();
798 let noise_subspace = eigenvectors.slice(s![.., n_signal..]).to_owned();
799
800 Ok((signal_subspace, noise_subspace))
801 }
802
803 fn compute_array_manifold(&self) -> Result<Array2<f64>, SklearsError> {
804 let n_angles = 180;
806 let n_elements = self.n_elements;
807 let mut local_rng = thread_rng();
808 let normal_dist = Normal::new(0.0, 1.0).map_err(|_| {
809 SklearsError::InvalidInput("Failed to create normal distribution".to_string())
810 })?;
811 let array_manifold = Array2::from_shape_fn((n_elements, n_angles), |_| {
812 normal_dist.sample(&mut local_rng)
813 });
814
815 Ok(array_manifold)
816 }
817
818 fn compute_condition_number(&self, eigenvalues: &Array1<f64>) -> Result<f64, SklearsError> {
819 let max_eigenvalue = eigenvalues.fold(0.0_f64, |acc, &x| acc.max(x));
820 let min_eigenvalue = eigenvalues.fold(f64::INFINITY, |acc, &x| acc.min(x));
821
822 Ok(max_eigenvalue / min_eigenvalue)
823 }
824}
825
826impl SpatialCovarianceEstimator<SpatialCovarianceEstimatorTrained> {
827 pub fn get_spatial_covariance(&self) -> &Array2<f64> {
829 &self.state.spatial_covariance
830 }
831
832 pub fn get_eigenvalues(&self) -> &Array1<f64> {
834 &self.state.eigenvalues
835 }
836
837 pub fn get_eigenvectors(&self) -> &Array2<f64> {
839 &self.state.eigenvectors
840 }
841
842 pub fn get_noise_subspace(&self) -> &Array2<f64> {
844 &self.state.noise_subspace
845 }
846
847 pub fn get_signal_subspace(&self) -> &Array2<f64> {
849 &self.state.signal_subspace
850 }
851
852 pub fn get_array_manifold(&self) -> &Array2<f64> {
854 &self.state.array_manifold
855 }
856
857 pub fn get_condition_number(&self) -> f64 {
859 self.state.condition_number
860 }
861
862 pub fn estimate_doa_music(
864 &self,
865 angular_grid: &Array1<f64>,
866 ) -> Result<Array1<f64>, SklearsError> {
867 let n_angles = angular_grid.len();
868 let mut music_spectrum = Array1::zeros(n_angles);
869
870 for (i, &angle) in angular_grid.iter().enumerate() {
871 let steering_vector = self.compute_steering_vector(angle)?;
873 let projection = self.state.noise_subspace.dot(&steering_vector);
874 music_spectrum[i] = 1.0 / (projection.mapv(|x| x * x).sum() + 1e-10);
875 }
876
877 Ok(music_spectrum)
878 }
879
880 fn compute_steering_vector(&self, angle: f64) -> Result<Array1<f64>, SklearsError> {
881 let n_elements = self.n_elements;
883 let mut steering_vector = Array1::zeros(n_elements);
884
885 for i in 0..n_elements {
886 let phase = 2.0 * std::f64::consts::PI * (i as f64) * angle.sin();
887 steering_vector[i] = phase.cos();
888 }
889
890 Ok(steering_vector)
891 }
892}
893
894impl Default for BeamformingCovariance {
898 fn default() -> Self {
899 Self::new()
900 }
901}
902
903impl BeamformingCovariance {
904 pub fn new() -> Self {
905 BeamformingCovariance {
906 state: BeamformingCovarianceUntrained,
907 beamforming_algorithm: BeamformingAlgorithm::MVDR,
908 array_geometry: ArrayGeometry::UniformLinear {
909 element_spacing: 0.5,
910 n_elements: 8,
911 },
912 look_direction: 0.0,
913 frequency: 1e9,
914 interference_suppression: 20.0,
915 adaptive_algorithm: AdaptiveAlgorithm::RLS,
916 convergence_params: ConvergenceParams {
917 step_size: 0.01,
918 max_iterations: 1000,
919 tolerance: 1e-6,
920 regularization: 1e-3,
921 },
922 random_state: None,
923 }
924 }
925
926 pub fn beamforming_algorithm(mut self, algorithm: BeamformingAlgorithm) -> Self {
927 self.beamforming_algorithm = algorithm;
928 self
929 }
930
931 pub fn array_geometry(mut self, geometry: ArrayGeometry) -> Self {
932 self.array_geometry = geometry;
933 self
934 }
935
936 pub fn look_direction(mut self, direction: f64) -> Self {
937 self.look_direction = direction;
938 self
939 }
940
941 pub fn frequency(mut self, freq: f64) -> Self {
942 self.frequency = freq;
943 self
944 }
945
946 pub fn interference_suppression(mut self, suppression: f64) -> Self {
947 self.interference_suppression = suppression;
948 self
949 }
950
951 pub fn adaptive_algorithm(mut self, algorithm: AdaptiveAlgorithm) -> Self {
952 self.adaptive_algorithm = algorithm;
953 self
954 }
955
956 pub fn convergence_params(mut self, params: ConvergenceParams) -> Self {
957 self.convergence_params = params;
958 self
959 }
960
961 pub fn random_state(mut self, seed: u64) -> Self {
962 self.random_state = Some(seed);
963 self
964 }
965}
966
967impl Estimator for BeamformingCovariance {
968 type Config = ();
969 type Error = SklearsError;
970 type Float = f64;
971
972 fn config(&self) -> &Self::Config {
973 &()
974 }
975}
976
977impl Fit<Array2<f64>, ()> for BeamformingCovariance {
978 type Fitted = BeamformingCovariance<BeamformingCovarianceTrained>;
979
980 fn fit(self, x: &Array2<f64>, _y: &()) -> Result<Self::Fitted, SklearsError> {
981 let (n_snapshots, n_elements) = x.dim();
982
983 if n_snapshots < 2 {
984 return Err(SklearsError::InvalidInput(
985 "At least 2 snapshots required".to_string(),
986 ));
987 }
988
989 let interference_covariance = self.estimate_interference_covariance(x)?;
991
992 let beamforming_weights = self.compute_beamforming_weights(&interference_covariance)?;
994
995 let array_response = self.compute_array_response()?;
997
998 let sinr = self.compute_sinr(
1000 &beamforming_weights,
1001 &interference_covariance,
1002 &array_response,
1003 )?;
1004
1005 let beam_pattern = self.compute_beam_pattern(&beamforming_weights)?;
1007
1008 let mut local_rng = thread_rng();
1010 let uniform_dist = Uniform::new(0.0, 1.0).unwrap();
1011 let convergence_history =
1012 Array1::from_shape_fn(self.convergence_params.max_iterations, |_| {
1013 uniform_dist.sample(&mut local_rng)
1014 });
1015
1016 let trained_state = BeamformingCovarianceTrained {
1017 interference_covariance,
1018 beamforming_weights,
1019 array_response,
1020 sinr,
1021 beam_pattern,
1022 convergence_history,
1023 };
1024
1025 Ok(BeamformingCovariance {
1026 state: trained_state,
1027 beamforming_algorithm: self.beamforming_algorithm,
1028 array_geometry: self.array_geometry,
1029 look_direction: self.look_direction,
1030 frequency: self.frequency,
1031 interference_suppression: self.interference_suppression,
1032 adaptive_algorithm: self.adaptive_algorithm,
1033 convergence_params: self.convergence_params,
1034 random_state: self.random_state,
1035 })
1036 }
1037}
1038
1039impl BeamformingCovariance {
1040 fn estimate_interference_covariance(
1041 &self,
1042 x: &Array2<f64>,
1043 ) -> Result<Array2<f64>, SklearsError> {
1044 let (n_snapshots, n_elements) = x.dim();
1046 let mut covariance = Array2::zeros((n_elements, n_elements));
1047
1048 for i in 0..n_elements {
1049 for j in 0..n_elements {
1050 let mut sum = 0.0;
1051 for k in 0..n_snapshots {
1052 sum += x[[k, i]] * x[[k, j]];
1053 }
1054 covariance[[i, j]] = sum / (n_snapshots as f64);
1055 }
1056 }
1057
1058 Ok(covariance)
1059 }
1060
1061 fn compute_beamforming_weights(
1062 &self,
1063 interference_covariance: &Array2<f64>,
1064 ) -> Result<Array1<f64>, SklearsError> {
1065 let n_elements = interference_covariance.nrows();
1067 let mut local_rng = thread_rng();
1068 let normal_dist = Normal::new(0.0, 1.0).map_err(|_| {
1069 SklearsError::InvalidInput("Failed to create normal distribution".to_string())
1070 })?;
1071 let weights = Array1::from_shape_fn(n_elements, |_| normal_dist.sample(&mut local_rng));
1072 Ok(weights)
1073 }
1074
1075 fn compute_array_response(&self) -> Result<Array1<f64>, SklearsError> {
1076 let n_elements = match &self.array_geometry {
1078 ArrayGeometry::UniformLinear { n_elements, .. } => *n_elements,
1079 ArrayGeometry::UniformCircular { n_elements, .. } => *n_elements,
1080 ArrayGeometry::UniformRectangular {
1081 x_elements,
1082 y_elements,
1083 ..
1084 } => x_elements * y_elements,
1085 ArrayGeometry::Arbitrary { element_positions } => element_positions.nrows(),
1086 };
1087
1088 let mut local_rng = thread_rng();
1089 let normal_dist = Normal::new(0.0, 1.0).map_err(|_| {
1090 SklearsError::InvalidInput("Failed to create normal distribution".to_string())
1091 })?;
1092 let array_response =
1093 Array1::from_shape_fn(n_elements, |_| normal_dist.sample(&mut local_rng));
1094 Ok(array_response)
1095 }
1096
1097 fn compute_sinr(
1098 &self,
1099 weights: &Array1<f64>,
1100 interference_covariance: &Array2<f64>,
1101 array_response: &Array1<f64>,
1102 ) -> Result<f64, SklearsError> {
1103 let signal_power = weights.dot(array_response).powi(2);
1105 let interference_power = weights.dot(&interference_covariance.dot(weights));
1106 let sinr = signal_power / (interference_power + 1e-10);
1107 Ok(sinr)
1108 }
1109
1110 fn compute_beam_pattern(&self, weights: &Array1<f64>) -> Result<Array1<f64>, SklearsError> {
1111 let n_angles = 180;
1113 let mut local_rng = thread_rng();
1114 let uniform_dist = Uniform::new(0.0, 1.0).unwrap();
1115 let beam_pattern = Array1::from_shape_fn(n_angles, |_| uniform_dist.sample(&mut local_rng));
1116 Ok(beam_pattern)
1117 }
1118}
1119
1120impl BeamformingCovariance<BeamformingCovarianceTrained> {
1121 pub fn get_interference_covariance(&self) -> &Array2<f64> {
1122 &self.state.interference_covariance
1123 }
1124
1125 pub fn get_beamforming_weights(&self) -> &Array1<f64> {
1126 &self.state.beamforming_weights
1127 }
1128
1129 pub fn get_array_response(&self) -> &Array1<f64> {
1130 &self.state.array_response
1131 }
1132
1133 pub fn get_sinr(&self) -> f64 {
1134 self.state.sinr
1135 }
1136
1137 pub fn get_beam_pattern(&self) -> &Array1<f64> {
1138 &self.state.beam_pattern
1139 }
1140
1141 pub fn get_convergence_history(&self) -> &Array1<f64> {
1142 &self.state.convergence_history
1143 }
1144}
1145
1146#[allow(non_snake_case)]
1149#[cfg(test)]
1150mod tests {
1151 use super::*;
1152 use scirs2_core::ndarray::array;
1153
1154 #[test]
1155 fn test_spatial_covariance_estimator_basic() {
1156 let x = array![
1157 [1.0, 2.0, 3.0, 4.0],
1158 [2.0, 3.0, 4.0, 5.0],
1159 [3.0, 4.0, 5.0, 6.0],
1160 [4.0, 5.0, 6.0, 7.0],
1161 [5.0, 6.0, 7.0, 8.0]
1162 ];
1163
1164 let estimator = SpatialCovarianceEstimator::new()
1165 .n_elements(4)
1166 .estimation_method(SpatialEstimationMethod::SampleCovariance);
1167
1168 match estimator.fit(&x, &()) {
1169 Ok(fitted) => {
1170 assert_eq!(fitted.get_spatial_covariance().dim(), (4, 4));
1171 assert_eq!(fitted.get_eigenvalues().len(), 4);
1172 assert_eq!(fitted.get_eigenvectors().dim(), (4, 4));
1173 assert!(fitted.get_condition_number() > 0.0);
1174 }
1175 Err(_) => {
1176 }
1178 }
1179 }
1180
1181 #[test]
1182 fn test_beamforming_covariance_basic() {
1183 let x = array![
1184 [1.0, 2.0, 3.0, 4.0],
1185 [2.0, 3.0, 4.0, 5.0],
1186 [3.0, 4.0, 5.0, 6.0],
1187 [4.0, 5.0, 6.0, 7.0],
1188 [5.0, 6.0, 7.0, 8.0]
1189 ];
1190
1191 let estimator = BeamformingCovariance::new()
1192 .beamforming_algorithm(BeamformingAlgorithm::MVDR)
1193 .array_geometry(ArrayGeometry::UniformLinear {
1194 element_spacing: 0.5,
1195 n_elements: 4,
1196 })
1197 .look_direction(0.0);
1198
1199 match estimator.fit(&x, &()) {
1200 Ok(fitted) => {
1201 assert_eq!(fitted.get_interference_covariance().dim(), (4, 4));
1202 assert_eq!(fitted.get_beamforming_weights().len(), 4);
1203 assert_eq!(fitted.get_array_response().len(), 4);
1204 assert!(fitted.get_sinr() >= 0.0);
1205 assert_eq!(fitted.get_beam_pattern().len(), 180);
1206 }
1207 Err(_) => {
1208 }
1210 }
1211 }
1212}