scirs2_sparse/linalg/
enhanced_operators.rs

1//! Enhanced linear operators with SIMD and parallel acceleration
2//!
3//! This module provides performance-optimized linear operators that leverage
4//! the parallel vector operations and SIMD acceleration from scirs2-core.
5
6use crate::error::{SparseError, SparseResult};
7use crate::linalg::interface::{AdjointOperator, LinearOperator};
8use crate::parallel_vector_ops::*;
9use scirs2_core::numeric::{Float, NumAssign};
10use scirs2_core::SparseElement;
11use std::fmt::Debug;
12use std::iter::Sum;
13
14// Import SIMD operations from scirs2-core
15use scirs2_core::simd_ops::SimdUnifiedOps;
16
17/// Configuration for enhanced operators
18#[derive(Debug, Clone)]
19pub struct EnhancedOperatorOptions {
20    /// Use parallel processing for large vectors
21    pub use_parallel: bool,
22    /// Threshold for switching to parallel processing
23    pub parallel_threshold: usize,
24    /// Use SIMD acceleration when available
25    pub use_simd: bool,
26    /// Threshold for switching to SIMD processing
27    pub simd_threshold: usize,
28    /// Chunk size for parallel processing
29    pub chunk_size: usize,
30}
31
32impl Default for EnhancedOperatorOptions {
33    fn default() -> Self {
34        Self {
35            use_parallel: true,
36            parallel_threshold: 10000,
37            use_simd: true,
38            simd_threshold: 32,
39            chunk_size: 1024,
40        }
41    }
42}
43
44/// Enhanced diagonal operator with SIMD and parallel acceleration
45#[derive(Clone)]
46pub struct EnhancedDiagonalOperator<F> {
47    diagonal: Vec<F>,
48    #[allow(dead_code)]
49    options: EnhancedOperatorOptions,
50}
51
52impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps> EnhancedDiagonalOperator<F> {
53    /// Create a new enhanced diagonal operator
54    #[allow(dead_code)]
55    pub fn new(diagonal: Vec<F>) -> Self {
56        Self {
57            diagonal,
58            options: EnhancedOperatorOptions::default(),
59        }
60    }
61
62    /// Create with custom options
63    #[allow(dead_code)]
64    pub fn with_options(diagonal: Vec<F>, options: EnhancedOperatorOptions) -> Self {
65        Self { diagonal, options }
66    }
67
68    /// Get the diagonal values
69    pub fn diagonal(&self) -> &[F] {
70        &self.diagonal
71    }
72}
73
74impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
75    LinearOperator<F> for EnhancedDiagonalOperator<F>
76{
77    fn shape(&self) -> (usize, usize) {
78        let n = self.diagonal.len();
79        (n, n)
80    }
81
82    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
83        if x.len() != self.diagonal.len() {
84            return Err(SparseError::DimensionMismatch {
85                expected: self.diagonal.len(),
86                found: x.len(),
87            });
88        }
89
90        let mut result = vec![F::sparse_zero(); x.len()];
91
92        // Use optimized element-wise multiplication via parallel vector operations
93        // This is equivalent to diagonal multiplication: result[i] = diagonal[i] * x[i]
94
95        if self.options.use_parallel && x.len() >= self.options.parallel_threshold {
96            // Use parallel processing for large diagonal operations
97            use scirs2_core::parallel_ops::*;
98            let indices: Vec<usize> = (0..x.len()).collect();
99            let values = parallel_map(&indices, |&i| self.diagonal[i] * x[i]);
100            result = values;
101        } else {
102            // Sequential computation for small vectors
103            #[allow(clippy::needless_range_loop)]
104            for i in 0..x.len() {
105                result[i] = self.diagonal[i] * x[i];
106            }
107        }
108
109        Ok(result)
110    }
111
112    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
113        // For real diagonal matrices, adjoint equals original
114        self.matvec(x)
115    }
116
117    fn has_adjoint(&self) -> bool {
118        true
119    }
120}
121
122/// Enhanced sum operator with parallel acceleration
123pub struct EnhancedSumOperator<F> {
124    a: Box<dyn LinearOperator<F>>,
125    b: Box<dyn LinearOperator<F>>,
126    #[allow(dead_code)]
127    options: EnhancedOperatorOptions,
128}
129
130impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps> EnhancedSumOperator<F> {
131    /// Create a new enhanced sum operator
132    #[allow(dead_code)]
133    pub fn new(a: Box<dyn LinearOperator<F>>, b: Box<dyn LinearOperator<F>>) -> SparseResult<Self> {
134        if a.shape() != b.shape() {
135            return Err(SparseError::ShapeMismatch {
136                expected: a.shape(),
137                found: b.shape(),
138            });
139        }
140        Ok(Self {
141            a,
142            b,
143            options: EnhancedOperatorOptions::default(),
144        })
145    }
146
147    /// Create with custom options
148    #[allow(dead_code)]
149    pub fn with_options(
150        a: Box<dyn LinearOperator<F>>,
151        b: Box<dyn LinearOperator<F>>,
152        #[allow(dead_code)] options: EnhancedOperatorOptions,
153    ) -> SparseResult<Self> {
154        if a.shape() != b.shape() {
155            return Err(SparseError::ShapeMismatch {
156                expected: a.shape(),
157                found: b.shape(),
158            });
159        }
160        Ok(Self { a, b, options })
161    }
162}
163
164impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
165    LinearOperator<F> for EnhancedSumOperator<F>
166{
167    fn shape(&self) -> (usize, usize) {
168        self.a.shape()
169    }
170
171    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
172        let a_result = self.a.matvec(x)?;
173        let b_result = self.b.matvec(x)?;
174
175        let mut result = vec![F::sparse_zero(); a_result.len()];
176        let parallel_opts = Some(ParallelVectorOptions {
177            use_parallel: self.options.use_parallel,
178            parallel_threshold: self.options.parallel_threshold,
179            chunk_size: self.options.chunk_size,
180            use_simd: self.options.use_simd,
181            simd_threshold: self.options.simd_threshold,
182        });
183
184        // Use optimized parallel vector addition
185        parallel_vector_add(&a_result, &b_result, &mut result, parallel_opts);
186        Ok(result)
187    }
188
189    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
190        if !self.a.has_adjoint() || !self.b.has_adjoint() {
191            return Err(SparseError::OperationNotSupported(
192                "adjoint not supported for one or both operators".to_string(),
193            ));
194        }
195        let a_result = self.a.rmatvec(x)?;
196        let b_result = self.b.rmatvec(x)?;
197
198        let mut result = vec![F::sparse_zero(); a_result.len()];
199        let parallel_opts = Some(ParallelVectorOptions {
200            use_parallel: self.options.use_parallel,
201            parallel_threshold: self.options.parallel_threshold,
202            chunk_size: self.options.chunk_size,
203            use_simd: self.options.use_simd,
204            simd_threshold: self.options.simd_threshold,
205        });
206
207        parallel_vector_add(&a_result, &b_result, &mut result, parallel_opts);
208        Ok(result)
209    }
210
211    fn has_adjoint(&self) -> bool {
212        self.a.has_adjoint() && self.b.has_adjoint()
213    }
214}
215
216/// Enhanced difference operator with parallel acceleration
217pub struct EnhancedDifferenceOperator<F> {
218    a: Box<dyn LinearOperator<F>>,
219    b: Box<dyn LinearOperator<F>>,
220    #[allow(dead_code)]
221    options: EnhancedOperatorOptions,
222}
223
224impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
225    EnhancedDifferenceOperator<F>
226{
227    /// Create a new enhanced difference operator
228    #[allow(dead_code)]
229    pub fn new(a: Box<dyn LinearOperator<F>>, b: Box<dyn LinearOperator<F>>) -> SparseResult<Self> {
230        if a.shape() != b.shape() {
231            return Err(SparseError::ShapeMismatch {
232                expected: a.shape(),
233                found: b.shape(),
234            });
235        }
236        Ok(Self {
237            a,
238            b,
239            options: EnhancedOperatorOptions::default(),
240        })
241    }
242
243    /// Create with custom options
244    #[allow(dead_code)]
245    pub fn with_options(
246        a: Box<dyn LinearOperator<F>>,
247        b: Box<dyn LinearOperator<F>>,
248        #[allow(dead_code)] options: EnhancedOperatorOptions,
249    ) -> SparseResult<Self> {
250        if a.shape() != b.shape() {
251            return Err(SparseError::ShapeMismatch {
252                expected: a.shape(),
253                found: b.shape(),
254            });
255        }
256        Ok(Self { a, b, options })
257    }
258}
259
260impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
261    LinearOperator<F> for EnhancedDifferenceOperator<F>
262{
263    fn shape(&self) -> (usize, usize) {
264        self.a.shape()
265    }
266
267    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
268        let a_result = self.a.matvec(x)?;
269        let b_result = self.b.matvec(x)?;
270
271        let mut result = vec![F::sparse_zero(); a_result.len()];
272        let parallel_opts = Some(ParallelVectorOptions {
273            use_parallel: self.options.use_parallel,
274            parallel_threshold: self.options.parallel_threshold,
275            chunk_size: self.options.chunk_size,
276            use_simd: self.options.use_simd,
277            simd_threshold: self.options.simd_threshold,
278        });
279
280        // Use optimized parallel vector subtraction
281        parallel_vector_sub(&a_result, &b_result, &mut result, parallel_opts);
282        Ok(result)
283    }
284
285    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
286        if !self.a.has_adjoint() || !self.b.has_adjoint() {
287            return Err(SparseError::OperationNotSupported(
288                "adjoint not supported for one or both operators".to_string(),
289            ));
290        }
291        let a_result = self.a.rmatvec(x)?;
292        let b_result = self.b.rmatvec(x)?;
293
294        let mut result = vec![F::sparse_zero(); a_result.len()];
295        let parallel_opts = Some(ParallelVectorOptions {
296            use_parallel: self.options.use_parallel,
297            parallel_threshold: self.options.parallel_threshold,
298            chunk_size: self.options.chunk_size,
299            use_simd: self.options.use_simd,
300            simd_threshold: self.options.simd_threshold,
301        });
302
303        parallel_vector_sub(&a_result, &b_result, &mut result, parallel_opts);
304        Ok(result)
305    }
306
307    fn has_adjoint(&self) -> bool {
308        self.a.has_adjoint() && self.b.has_adjoint()
309    }
310}
311
312/// Enhanced scaled operator with parallel acceleration
313pub struct EnhancedScaledOperator<F> {
314    alpha: F,
315    operator: Box<dyn LinearOperator<F>>,
316    #[allow(dead_code)]
317    options: EnhancedOperatorOptions,
318}
319
320impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps> EnhancedScaledOperator<F> {
321    /// Create a new enhanced scaled operator
322    #[allow(dead_code)]
323    pub fn new(alpha: F, operator: Box<dyn LinearOperator<F>>) -> Self {
324        Self {
325            alpha,
326            operator,
327            options: EnhancedOperatorOptions::default(),
328        }
329    }
330
331    /// Create with custom options
332    #[allow(dead_code)]
333    pub fn with_options(
334        alpha: F,
335        operator: Box<dyn LinearOperator<F>>,
336        #[allow(dead_code)] options: EnhancedOperatorOptions,
337    ) -> Self {
338        Self {
339            alpha,
340            operator,
341            options,
342        }
343    }
344}
345
346impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
347    LinearOperator<F> for EnhancedScaledOperator<F>
348{
349    fn shape(&self) -> (usize, usize) {
350        self.operator.shape()
351    }
352
353    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
354        let result = self.operator.matvec(x)?;
355        let mut scaled_result = vec![F::sparse_zero(); result.len()];
356
357        let parallel_opts = Some(ParallelVectorOptions {
358            use_parallel: self.options.use_parallel,
359            parallel_threshold: self.options.parallel_threshold,
360            chunk_size: self.options.chunk_size,
361            use_simd: self.options.use_simd,
362            simd_threshold: self.options.simd_threshold,
363        });
364
365        // Use optimized parallel vector scaling
366        parallel_vector_scale(self.alpha, &result, &mut scaled_result, parallel_opts);
367        Ok(scaled_result)
368    }
369
370    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
371        if !self.operator.has_adjoint() {
372            return Err(SparseError::OperationNotSupported(
373                "adjoint not supported for underlying operator".to_string(),
374            ));
375        }
376        let result = self.operator.rmatvec(x)?;
377        let mut scaled_result = vec![F::sparse_zero(); result.len()];
378
379        let parallel_opts = Some(ParallelVectorOptions {
380            use_parallel: self.options.use_parallel,
381            parallel_threshold: self.options.parallel_threshold,
382            chunk_size: self.options.chunk_size,
383            use_simd: self.options.use_simd,
384            simd_threshold: self.options.simd_threshold,
385        });
386
387        parallel_vector_scale(self.alpha, &result, &mut scaled_result, parallel_opts);
388        Ok(scaled_result)
389    }
390
391    fn has_adjoint(&self) -> bool {
392        self.operator.has_adjoint()
393    }
394}
395
396/// Convolution operator for matrix-free convolution operations
397pub struct ConvolutionOperator<F> {
398    kernel: Vec<F>,
399    input_size: usize,
400    output_size: usize,
401    mode: ConvolutionMode,
402    #[allow(dead_code)]
403    options: EnhancedOperatorOptions,
404}
405
406#[derive(Debug, Clone, Copy)]
407pub enum ConvolutionMode {
408    /// Full convolution (output_size = input_size + kernel_size - 1)
409    Full,
410    /// Same convolution (output_size = input_size)
411    Same,
412    /// Valid convolution (output_size = input_size - kernel_size + 1)
413    Valid,
414}
415
416impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
417    ConvolutionOperator<F>
418{
419    /// Create a new convolution operator
420    #[allow(dead_code)]
421    pub fn new(kernel: Vec<F>, input_size: usize, mode: ConvolutionMode) -> Self {
422        let output_size = match mode {
423            ConvolutionMode::Full => input_size + kernel.len() - 1,
424            ConvolutionMode::Same => input_size,
425            ConvolutionMode::Valid => {
426                if input_size >= kernel.len() {
427                    input_size - kernel.len() + 1
428                } else {
429                    0
430                }
431            }
432        };
433
434        Self {
435            kernel,
436            input_size,
437            output_size,
438            mode,
439            options: EnhancedOperatorOptions::default(),
440        }
441    }
442
443    /// Parallel optimization for large convolutions
444    fn optimize_convolution_parallel(&self, result: &mut [F], x: &[F]) -> SparseResult<()> {
445        use scirs2_core::parallel_ops::*;
446
447        // Split the output computation across parallel chunks
448        let chunk_size = self.options.chunk_size.min(self.output_size);
449        let indices: Vec<usize> = (0..self.output_size).collect();
450        let chunks: Vec<&[usize]> = indices.chunks(chunk_size).collect();
451
452        let parallel_results: Vec<Vec<F>> = parallel_map(&chunks, |chunk| {
453            let mut chunk_result = vec![F::sparse_zero(); chunk.len()];
454
455            for (local_i, &global_i) in chunk.iter().enumerate() {
456                chunk_result[local_i] = self.compute_convolution_at_index(global_i, x);
457            }
458
459            chunk_result
460        });
461
462        // Collect results back into the main result vector
463        let mut result_idx = 0;
464        for chunk_result in parallel_results {
465            for val in chunk_result {
466                if result_idx < result.len() {
467                    result[result_idx] = val;
468                    result_idx += 1;
469                }
470            }
471        }
472
473        Ok(())
474    }
475
476    /// SIMD optimization for medium-sized convolutions
477    fn optimize_convolution_simd(&self, result: &mut [F], x: &[F]) -> SparseResult<()> {
478        // For SIMD optimization, we can vectorize the inner product computation
479        // when the kernel is sufficiently large
480
481        #[allow(clippy::needless_range_loop)]
482        for i in 0..self.output_size {
483            // Use SIMD-optimized dot product for kernel computation
484            let convolution_result = self.compute_convolution_at_index_simd(i, x);
485            result[i] = convolution_result;
486        }
487
488        Ok(())
489    }
490
491    /// Compute convolution at a specific output index (scalar version)
492    fn compute_convolution_at_index(&self, i: usize, x: &[F]) -> F {
493        let mut sum = F::sparse_zero();
494
495        match self.mode {
496            ConvolutionMode::Full => {
497                for (j, &kernel_val) in self.kernel.iter().enumerate() {
498                    if i >= j && (i - j) < x.len() {
499                        sum += kernel_val * x[i - j];
500                    }
501                }
502            }
503            ConvolutionMode::Same => {
504                let pad = self.kernel.len() / 2;
505                for (j, &kernel_val) in self.kernel.iter().enumerate() {
506                    let idx = i + j;
507                    if idx >= pad && (idx - pad) < x.len() {
508                        sum += kernel_val * x[idx - pad];
509                    }
510                }
511            }
512            ConvolutionMode::Valid => {
513                for (j, &kernel_val) in self.kernel.iter().enumerate() {
514                    let idx = i + j;
515                    if idx < x.len() {
516                        sum += kernel_val * x[idx];
517                    }
518                }
519            }
520        }
521
522        sum
523    }
524
525    /// Compute convolution at a specific output index using SIMD optimization
526    fn compute_convolution_at_index_simd(&self, i: usize, x: &[F]) -> F {
527        // Extract relevant portions of kernel and input for this output position
528        let mut x_segment = Vec::new();
529        let mut kernel_segment = Vec::new();
530
531        match self.mode {
532            ConvolutionMode::Full => {
533                for (j, &kernel_val) in self.kernel.iter().enumerate() {
534                    if i >= j && (i - j) < x.len() {
535                        kernel_segment.push(kernel_val);
536                        x_segment.push(x[i - j]);
537                    }
538                }
539            }
540            ConvolutionMode::Same => {
541                let pad = self.kernel.len() / 2;
542                for (j, &kernel_val) in self.kernel.iter().enumerate() {
543                    let idx = i + j;
544                    if idx >= pad && (idx - pad) < x.len() {
545                        kernel_segment.push(kernel_val);
546                        x_segment.push(x[idx - pad]);
547                    }
548                }
549            }
550            ConvolutionMode::Valid => {
551                for (j, &kernel_val) in self.kernel.iter().enumerate() {
552                    let idx = i + j;
553                    if idx < x.len() {
554                        kernel_segment.push(kernel_val);
555                        x_segment.push(x[idx]);
556                    }
557                }
558            }
559        }
560
561        // Use SIMD-optimized dot product if segments are large enough
562        if kernel_segment.len() >= self.options.simd_threshold {
563            use scirs2_core::ndarray::ArrayView1;
564            let kernel_view = ArrayView1::from(&kernel_segment);
565            let x_view = ArrayView1::from(&x_segment);
566            F::simd_dot(&kernel_view, &x_view)
567        } else {
568            // Fall back to scalar computation for small segments
569            kernel_segment
570                .iter()
571                .zip(x_segment.iter())
572                .map(|(&k, &x)| k * x)
573                .sum()
574        }
575    }
576
577    /// Create with custom options
578    #[allow(dead_code)]
579    pub fn with_options(
580        kernel: Vec<F>,
581        input_size: usize,
582        mode: ConvolutionMode,
583        #[allow(dead_code)] options: EnhancedOperatorOptions,
584    ) -> Self {
585        let output_size = match mode {
586            ConvolutionMode::Full => input_size + kernel.len() - 1,
587            ConvolutionMode::Same => input_size,
588            ConvolutionMode::Valid => {
589                if input_size >= kernel.len() {
590                    input_size - kernel.len() + 1
591                } else {
592                    0
593                }
594            }
595        };
596
597        Self {
598            kernel,
599            input_size,
600            output_size,
601            mode,
602            options,
603        }
604    }
605}
606
607impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
608    LinearOperator<F> for ConvolutionOperator<F>
609{
610    fn shape(&self) -> (usize, usize) {
611        (self.output_size, self.input_size)
612    }
613
614    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
615        if x.len() != self.input_size {
616            return Err(SparseError::DimensionMismatch {
617                expected: self.input_size,
618                found: x.len(),
619            });
620        }
621
622        let mut result = vec![F::sparse_zero(); self.output_size];
623
624        // Implement convolution based on mode
625        match self.mode {
626            ConvolutionMode::Full => {
627                for i in 0..self.output_size {
628                    let mut sum = F::sparse_zero();
629                    for (j, &kernel_val) in self.kernel.iter().enumerate() {
630                        if i >= j && (i - j) < x.len() {
631                            sum += kernel_val * x[i - j];
632                        }
633                    }
634                    result[i] = sum;
635                }
636            }
637            ConvolutionMode::Same => {
638                let pad = self.kernel.len() / 2;
639                #[allow(clippy::needless_range_loop)]
640                for i in 0..self.output_size {
641                    let mut sum = F::sparse_zero();
642                    for (j, &kernel_val) in self.kernel.iter().enumerate() {
643                        let idx = i + j;
644                        if idx >= pad && (idx - pad) < x.len() {
645                            sum += kernel_val * x[idx - pad];
646                        }
647                    }
648                    result[i] = sum;
649                }
650            }
651            ConvolutionMode::Valid =>
652            {
653                #[allow(clippy::needless_range_loop)]
654                for i in 0..self.output_size {
655                    let mut sum = F::sparse_zero();
656                    for (j, &kernel_val) in self.kernel.iter().enumerate() {
657                        let idx = i + j;
658                        if idx < x.len() {
659                            sum += kernel_val * x[idx];
660                        }
661                    }
662                    result[i] = sum;
663                }
664            }
665        }
666
667        // Apply parallel/SIMD optimization for large convolutions
668        if self.options.use_parallel && self.output_size >= self.options.parallel_threshold {
669            self.optimize_convolution_parallel(&mut result, x)?;
670        } else if self.options.use_simd && self.kernel.len() >= self.options.simd_threshold {
671            self.optimize_convolution_simd(&mut result, x)?;
672        }
673
674        Ok(result)
675    }
676
677    fn has_adjoint(&self) -> bool {
678        true
679    }
680
681    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
682        if x.len() != self.output_size {
683            return Err(SparseError::DimensionMismatch {
684                expected: self.output_size,
685                found: x.len(),
686            });
687        }
688
689        // Adjoint of convolution is correlation with flipped kernel
690        let mut result = vec![F::sparse_zero(); self.input_size];
691        let flipped_kernel: Vec<F> = self.kernel.iter().rev().copied().collect();
692
693        // Implement correlation (adjoint of convolution)
694        match self.mode {
695            ConvolutionMode::Full =>
696            {
697                #[allow(clippy::needless_range_loop)]
698                for i in 0..self.input_size {
699                    let mut sum = F::sparse_zero();
700                    for (j, &kernel_val) in flipped_kernel.iter().enumerate() {
701                        let idx = i + j;
702                        if idx < x.len() {
703                            sum += kernel_val * x[idx];
704                        }
705                    }
706                    result[i] = sum;
707                }
708            }
709            ConvolutionMode::Same => {
710                let pad = flipped_kernel.len() / 2;
711                for i in 0..self.input_size {
712                    let mut sum = F::sparse_zero();
713                    for (j, &kernel_val) in flipped_kernel.iter().enumerate() {
714                        if i + pad >= j && (i + pad - j) < x.len() {
715                            sum += kernel_val * x[i + pad - j];
716                        }
717                    }
718                    result[i] = sum;
719                }
720            }
721            ConvolutionMode::Valid => {
722                for i in 0..self.input_size {
723                    let mut sum = F::sparse_zero();
724                    for (j, &kernel_val) in flipped_kernel.iter().enumerate() {
725                        if i >= j && (i - j) < x.len() {
726                            sum += kernel_val * x[i - j];
727                        }
728                    }
729                    result[i] = sum;
730                }
731            }
732        }
733
734        Ok(result)
735    }
736}
737
738/// Finite difference operator for computing derivatives
739pub struct FiniteDifferenceOperator<F> {
740    size: usize,
741    order: usize,
742    spacing: F,
743    boundary: BoundaryCondition,
744    #[allow(dead_code)]
745    options: EnhancedOperatorOptions,
746}
747
748#[derive(Debug, Clone, Copy)]
749pub enum BoundaryCondition {
750    /// Neumann boundary conditions (zero derivative)
751    Neumann,
752    /// Dirichlet boundary conditions (zero value)
753    Dirichlet,
754    /// Periodic boundary conditions
755    Periodic,
756}
757
758impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
759    FiniteDifferenceOperator<F>
760{
761    /// Create a new finite difference operator
762    #[allow(dead_code)]
763    pub fn new(size: usize, order: usize, spacing: F, boundary: BoundaryCondition) -> Self {
764        Self {
765            size,
766            order,
767            spacing,
768            boundary,
769            options: EnhancedOperatorOptions::default(),
770        }
771    }
772
773    /// Create with custom options
774    #[allow(dead_code)]
775    pub fn with_options(
776        size: usize,
777        order: usize,
778        spacing: F,
779        boundary: BoundaryCondition,
780        #[allow(dead_code)] options: EnhancedOperatorOptions,
781    ) -> Self {
782        Self {
783            size,
784            order,
785            spacing,
786            boundary,
787            options,
788        }
789    }
790
791    /// Get finite difference coefficients for given order
792    fn get_coefficients(&self) -> Vec<F> {
793        match self.order {
794            1 => {
795                // First derivative: central difference
796                vec![
797                    -F::sparse_one()
798                        / (F::from(2.0).expect("Failed to convert constant to float")
799                            * self.spacing),
800                    F::sparse_zero(),
801                    F::sparse_one()
802                        / (F::from(2.0).expect("Failed to convert constant to float")
803                            * self.spacing),
804                ]
805            }
806            2 => {
807                // Second derivative: central difference
808                let h_sq = self.spacing * self.spacing;
809                vec![
810                    F::sparse_one() / h_sq,
811                    -F::from(2.0).expect("Failed to convert constant to float") / h_sq,
812                    F::sparse_one() / h_sq,
813                ]
814            }
815            _ => {
816                // Higher order derivatives - simplified implementation
817                // In practice, would use more sophisticated stencils
818                vec![F::sparse_zero(); 2 * self.order + 1]
819            }
820        }
821    }
822}
823
824impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
825    LinearOperator<F> for FiniteDifferenceOperator<F>
826{
827    fn shape(&self) -> (usize, usize) {
828        (self.size, self.size)
829    }
830
831    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
832        if x.len() != self.size {
833            return Err(SparseError::DimensionMismatch {
834                expected: self.size,
835                found: x.len(),
836            });
837        }
838
839        let mut result = vec![F::sparse_zero(); self.size];
840        let coeffs = self.get_coefficients();
841        let stencil_radius = coeffs.len() / 2;
842
843        #[allow(clippy::needless_range_loop)]
844        for i in 0..self.size {
845            let mut sum = F::sparse_zero();
846            for (j, &coeff) in coeffs.iter().enumerate() {
847                let idx = i as isize + j as isize - stencil_radius as isize;
848
849                let value = match self.boundary {
850                    BoundaryCondition::Neumann => {
851                        if idx < 0 {
852                            x[0]
853                        } else if idx >= self.size as isize {
854                            x[self.size - 1]
855                        } else {
856                            x[idx as usize]
857                        }
858                    }
859                    BoundaryCondition::Dirichlet => {
860                        if idx < 0 || idx >= self.size as isize {
861                            F::sparse_zero()
862                        } else {
863                            x[idx as usize]
864                        }
865                    }
866                    BoundaryCondition::Periodic => {
867                        let periodic_idx = ((idx % self.size as isize + self.size as isize)
868                            % self.size as isize)
869                            as usize;
870                        x[periodic_idx]
871                    }
872                };
873
874                sum += coeff * value;
875            }
876            result[i] = sum;
877        }
878
879        Ok(result)
880    }
881
882    fn has_adjoint(&self) -> bool {
883        true
884    }
885
886    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
887        // For symmetric finite difference operators, adjoint equals transpose
888        // For asymmetric operators, would need to implement proper adjoint
889        self.matvec(x)
890    }
891}
892
893/// Create utility functions for enhanced operators
894/// Create an enhanced diagonal operator
895#[allow(dead_code)]
896pub fn enhanced_diagonal<
897    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
898>(
899    diagonal: Vec<F>,
900) -> Box<dyn LinearOperator<F>> {
901    Box::new(EnhancedDiagonalOperator::new(diagonal))
902}
903
904/// Create an enhanced sum operator
905#[allow(dead_code)]
906pub fn enhanced_add<
907    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
908>(
909    left: Box<dyn LinearOperator<F>>,
910    right: Box<dyn LinearOperator<F>>,
911) -> SparseResult<Box<dyn LinearOperator<F>>> {
912    Ok(Box::new(EnhancedSumOperator::new(left, right)?))
913}
914
915/// Create an enhanced difference operator
916#[allow(dead_code)]
917pub fn enhanced_subtract<
918    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
919>(
920    left: Box<dyn LinearOperator<F>>,
921    right: Box<dyn LinearOperator<F>>,
922) -> SparseResult<Box<dyn LinearOperator<F>>> {
923    Ok(Box::new(EnhancedDifferenceOperator::new(left, right)?))
924}
925
926/// Create an enhanced scaled operator
927#[allow(dead_code)]
928pub fn enhanced_scale<
929    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
930>(
931    alpha: F,
932    operator: Box<dyn LinearOperator<F>>,
933) -> Box<dyn LinearOperator<F>> {
934    Box::new(EnhancedScaledOperator::new(alpha, operator))
935}
936
937/// Create a convolution operator
938#[allow(dead_code)]
939pub fn convolution_operator<
940    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
941>(
942    kernel: Vec<F>,
943    input_size: usize,
944    mode: ConvolutionMode,
945) -> Box<dyn LinearOperator<F>> {
946    Box::new(ConvolutionOperator::new(kernel, input_size, mode))
947}
948
949/// Create a finite difference operator
950#[allow(dead_code)]
951pub fn finite_difference_operator<
952    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
953>(
954    size: usize,
955    order: usize,
956    spacing: F,
957    boundary: BoundaryCondition,
958) -> Box<dyn LinearOperator<F>> {
959    Box::new(FiniteDifferenceOperator::new(
960        size, order, spacing, boundary,
961    ))
962}
963
964/// Enhanced composition operator for operator multiplication (A * B)
965pub struct EnhancedCompositionOperator<F> {
966    left: Box<dyn LinearOperator<F>>,  // Applied second
967    right: Box<dyn LinearOperator<F>>, // Applied first
968    #[allow(dead_code)]
969    options: EnhancedOperatorOptions,
970}
971
972impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
973    EnhancedCompositionOperator<F>
974{
975    /// Create a new composition operator (left * right)
976    /// This represents the operation: left(right(x))
977    #[allow(dead_code)]
978    pub fn new(
979        left: Box<dyn LinearOperator<F>>,
980        right: Box<dyn LinearOperator<F>>,
981    ) -> SparseResult<Self> {
982        let (left_rows, left_cols) = left.shape();
983        let (right_rows, right_cols) = right.shape();
984
985        if left_cols != right_rows {
986            return Err(SparseError::ShapeMismatch {
987                expected: (left_rows, right_rows),
988                found: (left_rows, left_cols),
989            });
990        }
991
992        Ok(Self {
993            left,
994            right,
995            options: EnhancedOperatorOptions::default(),
996        })
997    }
998
999    /// Create with custom options
1000    #[allow(dead_code)]
1001    pub fn with_options(
1002        left: Box<dyn LinearOperator<F>>,
1003        right: Box<dyn LinearOperator<F>>,
1004        options: EnhancedOperatorOptions,
1005    ) -> SparseResult<Self> {
1006        let (left_rows, left_cols) = left.shape();
1007        let (right_rows, right_cols) = right.shape();
1008
1009        if left_cols != right_rows {
1010            return Err(SparseError::ShapeMismatch {
1011                expected: (left_rows, right_rows),
1012                found: (left_rows, left_cols),
1013            });
1014        }
1015
1016        Ok(Self {
1017            left,
1018            right,
1019            options,
1020        })
1021    }
1022}
1023
1024impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
1025    LinearOperator<F> for EnhancedCompositionOperator<F>
1026{
1027    fn shape(&self) -> (usize, usize) {
1028        let (left_rows, _) = self.left.shape();
1029        let (_, right_cols) = self.right.shape();
1030        (left_rows, right_cols)
1031    }
1032
1033    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
1034        // Apply right operator first, then left operator
1035        let intermediate = self.right.matvec(x)?;
1036        self.left.matvec(&intermediate)
1037    }
1038
1039    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
1040        if !self.left.has_adjoint() || !self.right.has_adjoint() {
1041            return Err(SparseError::OperationNotSupported(
1042                "adjoint not supported for one or both operators".to_string(),
1043            ));
1044        }
1045
1046        // Adjoint of composition: (A*B)^H = B^H * A^H
1047        let intermediate = self.left.rmatvec(x)?;
1048        self.right.rmatvec(&intermediate)
1049    }
1050
1051    fn has_adjoint(&self) -> bool {
1052        self.left.has_adjoint() && self.right.has_adjoint()
1053    }
1054}
1055
1056/// Matrix-free operator for applying a function element-wise
1057pub struct ElementwiseFunctionOperator<F> {
1058    function: Box<dyn Fn(F) -> F + Send + Sync>,
1059    size: usize,
1060    #[allow(dead_code)]
1061    options: EnhancedOperatorOptions,
1062}
1063
1064impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
1065    ElementwiseFunctionOperator<F>
1066{
1067    /// Create a new element-wise function operator
1068    pub fn new<Func>(function: Func, size: usize) -> Self
1069    where
1070        Func: Fn(F) -> F + Send + Sync + 'static,
1071    {
1072        Self {
1073            function: Box::new(function),
1074            size,
1075            options: EnhancedOperatorOptions::default(),
1076        }
1077    }
1078
1079    /// Create with custom options
1080    #[allow(dead_code)]
1081    pub fn with_options<Func>(function: Func, size: usize, options: EnhancedOperatorOptions) -> Self
1082    where
1083        Func: Fn(F) -> F + Send + Sync + 'static,
1084    {
1085        Self {
1086            function: Box::new(function),
1087            size,
1088            options,
1089        }
1090    }
1091}
1092
1093impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
1094    LinearOperator<F> for ElementwiseFunctionOperator<F>
1095{
1096    fn shape(&self) -> (usize, usize) {
1097        (self.size, self.size)
1098    }
1099
1100    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
1101        if x.len() != self.size {
1102            return Err(SparseError::DimensionMismatch {
1103                expected: self.size,
1104                found: x.len(),
1105            });
1106        }
1107
1108        if self.options.use_parallel && x.len() >= self.options.parallel_threshold {
1109            // Use parallel processing for large vectors
1110            use scirs2_core::parallel_ops::*;
1111            let result = parallel_map(x, |&val| (self.function)(val));
1112            Ok(result)
1113        } else {
1114            // Sequential processing for small vectors
1115            let result: Vec<F> = x.iter().map(|&val| (self.function)(val)).collect();
1116            Ok(result)
1117        }
1118    }
1119
1120    fn has_adjoint(&self) -> bool {
1121        false // General functions don't have well-defined adjoints
1122    }
1123
1124    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
1125        Err(SparseError::OperationNotSupported(
1126            "adjoint not supported for general element-wise functions".to_string(),
1127        ))
1128    }
1129}
1130
1131/// Kronecker product operator for tensor operations
1132pub struct KroneckerProductOperator<F> {
1133    left: Box<dyn LinearOperator<F>>,
1134    right: Box<dyn LinearOperator<F>>,
1135    #[allow(dead_code)]
1136    options: EnhancedOperatorOptions,
1137}
1138
1139impl<F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps> KroneckerProductOperator<F> {
1140    /// Create a new Kronecker product operator (left ⊗ right)
1141    #[allow(dead_code)]
1142    pub fn new(left: Box<dyn LinearOperator<F>>, right: Box<dyn LinearOperator<F>>) -> Self {
1143        Self {
1144            left,
1145            right,
1146            options: EnhancedOperatorOptions::default(),
1147        }
1148    }
1149
1150    /// Create with custom options
1151    #[allow(dead_code)]
1152    pub fn with_options(
1153        left: Box<dyn LinearOperator<F>>,
1154        right: Box<dyn LinearOperator<F>>,
1155        options: EnhancedOperatorOptions,
1156    ) -> Self {
1157        Self {
1158            left,
1159            right,
1160            options,
1161        }
1162    }
1163}
1164
1165impl<F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps>
1166    LinearOperator<F> for KroneckerProductOperator<F>
1167{
1168    fn shape(&self) -> (usize, usize) {
1169        let (left_rows, left_cols) = self.left.shape();
1170        let (right_rows, right_cols) = self.right.shape();
1171        (left_rows * right_rows, left_cols * right_cols)
1172    }
1173
1174    fn matvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
1175        let (left_rows, left_cols) = self.left.shape();
1176        let (right_rows, right_cols) = self.right.shape();
1177
1178        if x.len() != left_cols * right_cols {
1179            return Err(SparseError::DimensionMismatch {
1180                expected: left_cols * right_cols,
1181                found: x.len(),
1182            });
1183        }
1184
1185        // Reshape x as a matrix (right_cols × left_cols)
1186        // Apply right operator to each column, then left operator to each row
1187        let mut result = vec![F::sparse_zero(); left_rows * right_rows];
1188
1189        for j in 0..left_cols {
1190            // Extract column j from the reshaped x
1191            let mut col: Vec<F> = Vec::with_capacity(right_cols);
1192            for i in 0..right_cols {
1193                col.push(x[i * left_cols + j]);
1194            }
1195
1196            // Apply right operator to this column
1197            let right_result = self.right.matvec(&col)?;
1198
1199            // Store the result in the appropriate position
1200            for i in 0..right_rows {
1201                result[i * left_cols + j] = right_result[i];
1202            }
1203        }
1204
1205        // Now apply the left operator to each row
1206        let mut final_result = vec![F::sparse_zero(); left_rows * right_rows];
1207        for i in 0..right_rows {
1208            // Extract row i
1209            let mut row: Vec<F> = Vec::with_capacity(left_cols);
1210            for j in 0..left_cols {
1211                row.push(result[i * left_cols + j]);
1212            }
1213
1214            // Apply left operator to this row
1215            let left_result = self.left.matvec(&row)?;
1216
1217            // Store the result
1218            for k in 0..left_rows {
1219                final_result[k * right_rows + i] = left_result[k];
1220            }
1221        }
1222
1223        Ok(final_result)
1224    }
1225
1226    fn has_adjoint(&self) -> bool {
1227        self.left.has_adjoint() && self.right.has_adjoint()
1228    }
1229
1230    fn rmatvec(&self, x: &[F]) -> SparseResult<Vec<F>> {
1231        if !self.has_adjoint() {
1232            return Err(SparseError::OperationNotSupported(
1233                "adjoint not supported for one or both operators".to_string(),
1234            ));
1235        }
1236
1237        // For now, implement Kronecker product adjoint directly without cloning
1238        // This is a simplified implementation - in practice, you'd need a more sophisticated approach
1239        // to handle adjoint of Kronecker products without cloning operators
1240        Err(SparseError::OperationNotSupported(
1241            "Kronecker product adjoint requires operator cloning which is not currently supported"
1242                .to_string(),
1243        ))
1244    }
1245}
1246
1247/// Create an enhanced composition operator (left * right)
1248#[allow(dead_code)]
1249pub fn enhanced_compose<
1250    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
1251>(
1252    left: Box<dyn LinearOperator<F>>,
1253    right: Box<dyn LinearOperator<F>>,
1254) -> SparseResult<Box<dyn LinearOperator<F>>> {
1255    Ok(Box::new(EnhancedCompositionOperator::new(left, right)?))
1256}
1257
1258/// Create an element-wise function operator
1259#[allow(dead_code)]
1260pub fn elementwisefunction<
1261    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
1262    Func: Fn(F) -> F + Send + Sync + 'static,
1263>(
1264    function: Func,
1265    size: usize,
1266) -> Box<dyn LinearOperator<F>> {
1267    Box::new(ElementwiseFunctionOperator::new(function, size))
1268}
1269
1270/// Create a Kronecker product operator
1271#[allow(dead_code)]
1272pub fn kronecker_product<
1273    F: Float + SparseElement + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
1274>(
1275    left: Box<dyn LinearOperator<F>>,
1276    right: Box<dyn LinearOperator<F>>,
1277) -> Box<dyn LinearOperator<F>> {
1278    Box::new(KroneckerProductOperator::new(left, right))
1279}
1280
1281/// Create an adjoint operator
1282#[allow(dead_code)]
1283pub fn adjoint_operator<
1284    F: Float + NumAssign + Sum + Copy + Send + Sync + SimdUnifiedOps + 'static,
1285>(
1286    operator: Box<dyn LinearOperator<F>>,
1287) -> SparseResult<Box<dyn LinearOperator<F>>> {
1288    Ok(Box::new(AdjointOperator::new(operator)?))
1289}
1290
1291#[cfg(test)]
1292mod tests {
1293    use super::*;
1294    use approx::assert_relative_eq;
1295
1296    #[test]
1297    fn test_enhanced_diagonal_operator() {
1298        let diag = vec![2.0, 3.0, 4.0];
1299        let op = EnhancedDiagonalOperator::new(diag);
1300        let x = vec![1.0, 2.0, 3.0];
1301        let y = op.matvec(&x).expect("Operation failed");
1302        assert_eq!(y, vec![2.0, 6.0, 12.0]);
1303    }
1304
1305    #[test]
1306    fn test_enhanced_sum_operator() {
1307        let diag1 = enhanced_diagonal(vec![1.0, 2.0, 3.0]);
1308        let diag2 = enhanced_diagonal(vec![2.0, 1.0, 1.0]);
1309        let sum_op = EnhancedSumOperator::new(diag1, diag2).expect("Operation failed");
1310
1311        let x = vec![1.0, 1.0, 1.0];
1312        let y = sum_op.matvec(&x).expect("Operation failed");
1313        assert_eq!(y, vec![3.0, 3.0, 4.0]); // (1+2)*1, (2+1)*1, (3+1)*1
1314    }
1315
1316    #[test]
1317    fn test_enhanced_difference_operator() {
1318        let diag1 = enhanced_diagonal(vec![3.0, 4.0, 5.0]);
1319        let diag2 = enhanced_diagonal(vec![1.0, 2.0, 1.0]);
1320        let diff_op = EnhancedDifferenceOperator::new(diag1, diag2).expect("Operation failed");
1321
1322        let x = vec![1.0, 1.0, 1.0];
1323        let y = diff_op.matvec(&x).expect("Operation failed");
1324        assert_eq!(y, vec![2.0, 2.0, 4.0]); // (3-1)*1, (4-2)*1, (5-1)*1
1325    }
1326
1327    #[test]
1328    fn test_enhanced_scaled_operator() {
1329        let diag = enhanced_diagonal(vec![1.0, 2.0, 3.0]);
1330        let scaled_op = EnhancedScaledOperator::new(2.0, diag);
1331
1332        let x = vec![1.0, 1.0, 1.0];
1333        let y = scaled_op.matvec(&x).expect("Operation failed");
1334        assert_eq!(y, vec![2.0, 4.0, 6.0]); // 2 * [1, 2, 3]
1335    }
1336
1337    #[test]
1338    fn test_convolution_operator_full() {
1339        let kernel = vec![1.0, 2.0, 3.0];
1340        let conv_op = ConvolutionOperator::new(kernel, 3, ConvolutionMode::Full);
1341
1342        let x = vec![1.0, 0.0, 0.0];
1343        let y = conv_op.matvec(&x).expect("Operation failed");
1344        assert_eq!(y, vec![1.0, 2.0, 3.0, 0.0, 0.0]); // Full convolution output
1345    }
1346
1347    #[test]
1348    fn test_convolution_operator_same() {
1349        let kernel = vec![0.0, 1.0, 0.0]; // Identity kernel
1350        let conv_op = ConvolutionOperator::new(kernel, 3, ConvolutionMode::Same);
1351
1352        let x = vec![1.0, 2.0, 3.0];
1353        let y = conv_op.matvec(&x).expect("Operation failed");
1354        assert_relative_eq!(y[0], 1.0, epsilon = 1e-10);
1355        assert_relative_eq!(y[1], 2.0, epsilon = 1e-10);
1356        assert_relative_eq!(y[2], 3.0, epsilon = 1e-10);
1357    }
1358
1359    #[test]
1360    fn test_finite_difference_first_derivative() {
1361        let fd_op = FiniteDifferenceOperator::new(5, 1, 1.0, BoundaryCondition::Dirichlet);
1362
1363        // Test on a linear function: f(x) = x
1364        let x = vec![0.0, 1.0, 2.0, 3.0, 4.0];
1365        let y = fd_op.matvec(&x).expect("Operation failed");
1366
1367        // First derivative of linear function should be approximately 1
1368        #[allow(clippy::needless_range_loop)]
1369        for i in 1..y.len() - 1 {
1370            assert_relative_eq!(y[i], 1.0, epsilon = 0.1);
1371        }
1372    }
1373
1374    #[test]
1375    fn test_finite_difference_second_derivative() {
1376        let fd_op = FiniteDifferenceOperator::new(5, 2, 1.0, BoundaryCondition::Dirichlet);
1377
1378        // Test on a quadratic function: f(x) = x^2
1379        let x = vec![0.0, 1.0, 4.0, 9.0, 16.0];
1380        let y = fd_op.matvec(&x).expect("Operation failed");
1381
1382        // Second derivative of x^2 should be approximately 2
1383        #[allow(clippy::needless_range_loop)]
1384        for i in 1..y.len() - 1 {
1385            assert_relative_eq!(y[i], 2.0, epsilon = 0.5);
1386        }
1387    }
1388
1389    #[test]
1390    fn test_enhanced_operators_with_large_vectors() {
1391        // Test that large vectors trigger parallel processing
1392        let large_size = 15000; // Above default parallel threshold
1393        let diag1: Vec<f64> = (0..large_size).map(|i| (i + 1) as f64).collect();
1394        let diag2: Vec<f64> = vec![2.0; large_size];
1395
1396        let op1 = enhanced_diagonal(diag1);
1397        let op2 = enhanced_diagonal(diag2);
1398        let sum_op = EnhancedSumOperator::new(op1, op2).expect("Operation failed");
1399
1400        let x = vec![1.0; large_size];
1401        let y = sum_op.matvec(&x).expect("Operation failed");
1402
1403        // Check some values
1404        assert_relative_eq!(y[0], 3.0, epsilon = 1e-10); // (1 + 2) * 1
1405        assert_relative_eq!(y[1], 4.0, epsilon = 1e-10); // (2 + 2) * 1
1406        assert_relative_eq!(y[999], 1002.0, epsilon = 1e-10); // (1000 + 2) * 1
1407    }
1408}