Skip to main content

qualia_core_db/specialized_libs/linear_algebra/
computation.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use super::core_types::*;
5use super::optimization::*;
6use super::privacy::*;
7
8/// Computation engine for matrix operations
9pub struct ComputationEngine {
10    pub operation_queue: Vec<MatrixOperation>,
11    pub execution_engine: ExecutionEngine,
12    pub parallel_executor: ParallelExecutor,
13    pub simd_optimizer: SIMDOptimizer,
14    pub privacy: PrivacyEngine,
15}
16
17/// Matrix operations
18#[derive(Debug, Clone)]
19pub enum MatrixOperation {
20    MatrixMultiply {
21        left: String,
22        right: String,
23        result: String,
24        alpha: f64,
25        beta: f64,
26    },
27    MatrixAdd {
28        left: String,
29        right: String,
30        result: String,
31        alpha: f64,
32    },
33    MatrixSubtract {
34        left: String,
35        right: String,
36        result: String,
37    },
38    MatrixTranspose {
39        input: String,
40        result: String,
41    },
42    MatrixInverse {
43        input: String,
44        result: String,
45    },
46    MatrixDecomposition {
47        input: String,
48        result: String,
49        decomposition_type: DecompositionType,
50    },
51    EigenvalueComputation {
52        input: String,
53        eigenvalues: String,
54        eigenvectors: String,
55    },
56    SolveLinearSystem {
57        matrix: String,
58        rhs: String,
59        solution: String,
60    },
61}
62
63/// Decomposition types
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub enum DecompositionType {
66    LU,
67    QR,
68    SVD,
69    Cholesky,
70    Eigen,
71    Schur,
72}
73
74/// Operation scheduler
75pub struct OperationScheduler {}
76
77/// Execution engine
78pub struct ExecutionEngine {
79    pub engine_type: ExecutionEngineType,
80    pub computation_units: Vec<ComputationUnit>,
81    pub scheduler: OperationScheduler,
82}
83
84/// Execution engine types
85#[derive(Debug, Clone, PartialEq)]
86pub enum ExecutionEngineType {
87    CPU,
88    GPU,
89    CSD,
90    Hybrid,
91}
92
93/// Computation unit
94#[derive(Debug, Clone)]
95pub struct ComputationUnit {
96    pub unit_id: String,
97    pub unit_type: ComputationUnitType,
98    pub capabilities: ComputationCapabilities,
99    pub current_load: f64,
100    pub performance_metrics: PerformanceMetrics,
101}
102
103/// Computation unit types
104#[derive(Debug, Clone, PartialEq)]
105pub enum ComputationUnitType {
106    CPU,
107    GPU,
108    CSD,
109    NPU,
110    TPU,
111}
112
113/// Computation capabilities
114#[derive(Debug, Clone)]
115pub struct ComputationCapabilities {
116    pub max_matrix_size: (usize, usize),
117    pub supported_operations: Vec<MatrixOperation>,
118    pub data_types: Vec<DataType>,
119    pub memory_bandwidth: f64,
120    pub compute_throughput: f64,
121}
122
123/// Performance metrics
124#[derive(Debug, Clone)]
125pub struct PerformanceMetrics {
126    pub operations_per_second: f64,
127    pub memory_bandwidth_utilization: f64,
128    pub compute_utilization: f64,
129    pub power_consumption: f64,
130    pub thermal_state: f64,
131}
132
133/// Parallel executor
134pub struct ParallelExecutor {
135    pub thread_pool: Vec<WorkerThread>,
136    pub task_queue: Vec<MatrixTask>,
137    pub load_balancer: LoadBalancer,
138}
139
140/// Worker thread
141#[derive(Debug, Clone)]
142pub struct WorkerThread {
143    pub thread_id: String,
144    pub current_task: Option<MatrixTask>,
145    pub performance: ThreadPerformance,
146}
147
148/// Matrix task
149#[derive(Debug, Clone)]
150pub struct MatrixTask {
151    pub task_id: String,
152    pub operation: MatrixOperation,
153    pub priority: TaskPriority,
154    pub dependencies: Vec<String>,
155    pub estimated_time: u64,
156}
157
158/// Task priorities
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub enum TaskPriority {
161    Low,
162    Normal,
163    High,
164    Critical,
165}
166
167/// A unit of parallelisable work submitted to [`ParallelExecutor::execute_parallel`].
168///
169/// This is the logical-task abstraction used by the parallel executor; it is
170/// distinct from [`MatrixTask`] (which carries a concrete [`MatrixOperation`]).
171/// A `ParallelTask` describes *what* to run, not *where* — the load balancer
172/// decides the worker assignment.
173#[derive(Debug, Clone)]
174pub struct ParallelTask {
175    /// Stable, caller-supplied task identifier.
176    pub task_id: usize,
177    /// The kind of operation this task represents.
178    pub operation: ParallelOperation,
179    /// Estimated work units (e.g. microseconds). Used for accounting only;
180    /// no real scheduling decision currently depends on it.
181    pub estimated_work: u64,
182    /// Relative priority of the task.
183    pub priority: TaskPriority,
184}
185
186/// The operation a [`ParallelTask`] performs. Mirrors the subset of
187/// [`MatrixOperation`] variants that are meaningful to dispatch in parallel,
188/// plus a `Custom` escape hatch.
189#[derive(Debug, Clone, PartialEq)]
190pub enum ParallelOperation {
191    MatrixMultiply,
192    MatrixAdd,
193    MatrixSubtract,
194    MatrixTranspose,
195    MatrixInverse,
196    Decomposition(DecompositionType),
197    EigenvalueComputation,
198    SolveLinearSystem,
199    Custom(String),
200}
201
202/// Status of a completed (or failed) parallel task.
203#[derive(Debug, Clone, PartialEq)]
204pub enum TaskStatus {
205    /// The task ran to completion.
206    Completed,
207    /// The task failed; the string carries a diagnostic.
208    Failed(String),
209    /// The task is still pending (not yet dispatched).
210    Pending,
211}
212
213/// The result of executing a [`ParallelTask`] via [`ParallelExecutor`].
214#[derive(Debug, Clone)]
215pub struct TaskResult {
216    /// Identifier of the task this result corresponds to.
217    pub task_id: usize,
218    /// Index of the logical worker the task was assigned to.
219    pub worker_index: usize,
220    /// Final status of the task.
221    pub status: TaskStatus,
222    /// Recorded execution time (currently the task's estimated work).
223    pub execution_time: u64,
224}
225
226/// Thread performance
227#[derive(Debug, Clone)]
228pub struct ThreadPerformance {
229    pub tasks_completed: u64,
230    pub average_execution_time: f64,
231    pub cache_hit_rate: f64,
232    pub efficiency: f64,
233}
234
235/// Load balancer
236///
237/// Distributes tasks across a fixed pool of logical workers according to a
238/// [`LoadBalancingStrategy`]. This is purely logical scheduling — no real
239/// threads are spawned (actual parallelism is a Phase 2 concern).
240#[derive(Debug, Clone)]
241pub struct LoadBalancer {
242    pub balancing_strategy: BalancingStrategy,
243    pub worker_metrics: HashMap<String, WorkerMetrics>,
244    /// Strategy used by [`LoadBalancer::assign_task`] to schedule tasks.
245    pub scheduling_strategy: LoadBalancingStrategy,
246    /// Number of logical workers to distribute tasks across.
247    pub num_workers: usize,
248    /// Total number of tasks in the current batch (used by `Static` partitioning).
249    pub total_tasks: usize,
250    /// Round-robin cursor: index of the next worker to assign (used by `RoundRobin`).
251    pub next_worker: usize,
252    /// Pending task count per worker (used by `WorkStealing`).
253    pub pending_tasks: Vec<usize>,
254}
255
256/// Balancing strategies
257#[derive(Debug, Clone, PartialEq)]
258pub enum BalancingStrategy {
259    RoundRobin,
260    LoadBased,
261    PerformanceBased,
262    Adaptive,
263}
264
265/// Load-balancing strategy for distributing parallel tasks across workers.
266///
267/// This is a logical scheduling policy — no real threads are spawned.
268#[derive(Debug, Clone, PartialEq)]
269pub enum LoadBalancingStrategy {
270    /// Cycle through workers in fixed order (task i → worker i mod N).
271    RoundRobin,
272    /// Assign each task to the worker with the fewest pending tasks (a simple
273    /// work-stealing approximation).
274    WorkStealing,
275    /// Pre-compute an equal contiguous partition of tasks across workers.
276    Static,
277}
278
279/// Worker metrics
280#[derive(Debug, Clone)]
281pub struct WorkerMetrics {
282    pub worker_id: String,
283    pub current_load: f64,
284    pub average_response_time: f64,
285    pub success_rate: f64,
286}
287
288/// SIMD optimizer
289pub struct SIMDOptimizer {
290    pub simd_capabilities: SIMDCapabilities,
291    pub optimization_level: OptimizationLevel,
292    pub vectorized_operations: HashMap<String, VectorizedOperation>,
293}
294
295/// SIMD capabilities
296#[derive(Debug, Clone)]
297pub struct SIMDCapabilities {
298    pub vector_width: usize,
299    pub supported_instructions: Vec<SIMDInstruction>,
300    pub alignment_requirements: usize,
301}
302
303/// SIMD instructions
304#[derive(Debug, Clone, PartialEq)]
305pub enum SIMDInstruction {
306    SSE,
307    SSE2,
308    SSE4_1,
309    SSE4_2,
310    AVX,
311    AVX2,
312    AVX512,
313    FMA,
314    NEON,
315    Custom(String),
316}
317
318/// Optimization levels
319#[derive(Debug, Clone, PartialEq)]
320pub enum OptimizationLevel {
321    None,
322    Basic,
323    Aggressive,
324    Maximum,
325}
326
327/// Vectorized operation
328#[derive(Debug, Clone)]
329pub struct VectorizedOperation {
330    pub operation_id: String,
331    pub vector_width: usize,
332    pub instruction_set: Vec<SIMDInstruction>,
333    pub performance_gain: f64,
334}
335
336impl ComputationEngine {
337    pub fn new() -> Self {
338        Self {
339            operation_queue: Vec::new(),
340            execution_engine: ExecutionEngine::new(),
341            parallel_executor: ParallelExecutor::new(4),
342            simd_optimizer: SIMDOptimizer::new(),
343            privacy: PrivacyEngine::new(),
344        }
345    }
346
347    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
348        self.execution_engine.initialize()?;
349        self.parallel_executor.initialize()?;
350        self.simd_optimizer.initialize()?;
351        self.privacy.initialize()?;
352        Ok(())
353    }
354
355    pub fn execute_multiplication(
356        &mut self,
357        operation: &OptimizedMultiplication,
358        alpha: f64,
359        beta: f64,
360    ) -> Result<Vec<f64>, LinearAlgebraError> {
361        // Composition boundary: marshal the domain matrices into a caller-owned
362        // buffer and call the engine's canonical dynamic GEMM. No inline math here.
363        let m = operation.left.rows;
364        let n = operation.right.cols;
365        let k = operation.left.cols;
366
367        // C := alpha·A·B + beta·C, row-major. `result` is freshly zeroed, so it is
368        // the accumulator C; beta·0 = 0 (this entry point always produces a fresh
369        // product — there is no prior C to accumulate into). Routing here also fixes
370        // the old inline loop, which applied beta to the just-computed product
371        // (yielding alpha·AB·(1+beta) instead of the BLAS alpha·AB + beta·C).
372        let mut result = vec![0.0; m * n];
373        crate::solvers::linear_algebra::gemm::gemm(
374            crate::solvers::linear_algebra::gemm::Transpose::No,
375            crate::solvers::linear_algebra::gemm::Transpose::No,
376            m,
377            n,
378            k,
379            alpha,
380            &operation.left.data,
381            &operation.right.data,
382            beta,
383            &mut result,
384        )
385        .map_err(|_| {
386            LinearAlgebraError::InvalidDimensions(
387                "matrix dimensions incompatible for multiplication".to_string(),
388            )
389        })?;
390
391        Ok(result)
392    }
393}
394
395impl ExecutionEngine {
396    pub fn new() -> Self {
397        Self {
398            engine_type: ExecutionEngineType::Hybrid,
399            computation_units: Vec::new(),
400            scheduler: OperationScheduler::new(),
401        }
402    }
403
404    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
405        // Initialize computation units
406        Ok(())
407    }
408}
409
410impl ParallelExecutor {
411    /// Create a parallel executor with `num_workers` logical workers.
412    ///
413    /// The default load-balancing strategy is [`LoadBalancingStrategy::RoundRobin`].
414    /// No OS threads are spawned — workers are logical slots the load balancer
415    /// assigns tasks to. `num_workers == 0` is clamped to 1 so that task
416    /// assignment always has a valid target.
417    pub fn new(num_workers: usize) -> Self {
418        let workers = num_workers.max(1);
419        let thread_pool = (0..workers)
420            .map(|i| WorkerThread {
421                thread_id: format!("worker-{i}"),
422                current_task: None,
423                performance: ThreadPerformance {
424                    tasks_completed: 0,
425                    average_execution_time: 0.0,
426                    cache_hit_rate: 0.0,
427                    efficiency: 1.0,
428                },
429            })
430            .collect();
431        Self {
432            thread_pool,
433            task_queue: Vec::new(),
434            load_balancer: LoadBalancer::new(LoadBalancingStrategy::RoundRobin)
435                .with_workers(workers),
436        }
437    }
438
439    /// Returns the number of logical workers in the pool.
440    pub fn num_workers(&self) -> usize {
441        self.thread_pool.len()
442    }
443
444    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
445        Ok(())
446    }
447
448    /// Distribute `tasks` across the worker pool using the load balancer and
449    /// collect the results.
450    ///
451    /// Tasks are dispatched in input order and results are returned in the same
452    /// order (one [`TaskResult`] per input task). Execution is logical: each
453    /// task is marked [`TaskStatus::Completed`] with its estimated work recorded
454    /// as the execution time. No real threads are spawned.
455    ///
456    /// An empty task list yields an empty result list (no error).
457    pub fn execute_parallel(
458        &self,
459        tasks: &[ParallelTask],
460    ) -> Result<Vec<TaskResult>, LinearAlgebraError> {
461        if tasks.is_empty() {
462            return Ok(Vec::new());
463        }
464
465        // Clone the balancer so a shared `&self` executor does not mutate its
466        // own scheduling state across calls. Each batch starts from a clean slate.
467        let mut balancer = self.load_balancer.clone();
468        balancer.prepare(tasks.len());
469
470        let mut results = Vec::with_capacity(tasks.len());
471        for task in tasks {
472            let worker_index = balancer.assign_task(task.task_id);
473            results.push(TaskResult {
474                task_id: task.task_id,
475                worker_index,
476                status: TaskStatus::Completed,
477                execution_time: task.estimated_work,
478            });
479        }
480
481        Ok(results)
482    }
483}
484
485impl LoadBalancer {
486    /// Create a load balancer that uses `strategy` to assign tasks.
487    ///
488    /// The balancer starts with zero workers; call [`LoadBalancer::with_workers`]
489    /// (or set [`LoadBalancer::num_workers`] directly) before invoking
490    /// [`LoadBalancer::assign_task`].
491    pub fn new(strategy: LoadBalancingStrategy) -> Self {
492        Self {
493            balancing_strategy: BalancingStrategy::LoadBased,
494            worker_metrics: HashMap::new(),
495            scheduling_strategy: strategy,
496            num_workers: 0,
497            total_tasks: 0,
498            next_worker: 0,
499            pending_tasks: Vec::new(),
500        }
501    }
502
503    /// Builder-style setter for the worker count. Resizes the per-worker pending
504    /// task vector to match and resets scheduling cursors.
505    pub fn with_workers(mut self, num_workers: usize) -> Self {
506        self.set_workers(num_workers);
507        self
508    }
509
510    /// Set the worker count, resizing the per-worker pending task vector and
511    /// resetting scheduling cursors.
512    pub fn set_workers(&mut self, num_workers: usize) {
513        self.num_workers = num_workers;
514        self.pending_tasks = vec![0; num_workers];
515        self.next_worker = 0;
516    }
517
518    /// Prepare the balancer for a fresh batch of `total_tasks` tasks.
519    ///
520    /// Resets the round-robin cursor and per-worker pending counts, and records
521    /// the batch size used by [`LoadBalancingStrategy::Static`] partitioning.
522    /// [`ParallelExecutor::execute_parallel`] calls this before assigning tasks;
523    /// callers using [`LoadBalancer::assign_task`] directly should call it first
524    /// (notably for `Static`).
525    pub fn prepare(&mut self, total_tasks: usize) {
526        self.total_tasks = total_tasks;
527        self.next_worker = 0;
528        if self.pending_tasks.len() != self.num_workers {
529            self.pending_tasks = vec![0; self.num_workers];
530        } else {
531            for count in self.pending_tasks.iter_mut() {
532                *count = 0;
533            }
534        }
535    }
536
537    /// Return the worker index to assign the next task to.
538    ///
539    /// - [`LoadBalancingStrategy::RoundRobin`]: cycles through workers
540    ///   `0,1,…,N-1,0,1,…` regardless of `task_id`.
541    /// - [`LoadBalancingStrategy::WorkStealing`]: assigns to the worker with the
542    ///   fewest pending tasks (ties broken by lowest index) and increments that
543    ///   worker's pending count.
544    /// - [`LoadBalancingStrategy::Static`]: contiguous equal partition —
545    ///   `worker = task_id * num_workers / total_tasks` (falls back to
546    ///   `task_id % num_workers` when `total_tasks` is unset).
547    ///
548    /// Returns `0` when the balancer has no workers configured.
549    pub fn assign_task(&mut self, task_id: usize) -> usize {
550        if self.num_workers == 0 {
551            return 0;
552        }
553        match self.scheduling_strategy {
554            LoadBalancingStrategy::RoundRobin => {
555                let worker = self.next_worker % self.num_workers;
556                self.next_worker = (self.next_worker + 1) % self.num_workers;
557                worker
558            }
559            LoadBalancingStrategy::WorkStealing => {
560                let worker = (0..self.num_workers)
561                    .min_by_key(|&w| (self.pending_tasks[w], w))
562                    .expect("num_workers > 0");
563                self.pending_tasks[worker] += 1;
564                worker
565            }
566            LoadBalancingStrategy::Static => {
567                if self.total_tasks == 0 {
568                    task_id % self.num_workers
569                } else {
570                    (task_id * self.num_workers) / self.total_tasks
571                }
572            }
573        }
574    }
575}
576
577impl SIMDOptimizer {
578    pub fn new() -> Self {
579        Self {
580            simd_capabilities: SIMDCapabilities::new(),
581            optimization_level: OptimizationLevel::Maximum,
582            vectorized_operations: HashMap::new(),
583        }
584    }
585
586    /// Probe the running CPU and populate the SIMD capability set.
587    ///
588    /// On `x86_64` this queries SSE2, SSE4.1, SSE4.2, AVX, AVX2, AVX-512 and
589    /// FMA via `std::arch::is_x86_feature_detected!` (CPUID at runtime). On
590    /// `aarch64` it reports NEON. On any other architecture the capability set
591    /// is left empty (scalar fallback).
592    pub fn initialize(&mut self) -> Result<(), LinearAlgebraError> {
593        self.simd_capabilities = SIMDCapabilities::detect();
594        Ok(())
595    }
596
597    /// Returns a reference to the runtime-detected SIMD capabilities.
598    pub fn capabilities(&self) -> &SIMDCapabilities {
599        &self.simd_capabilities
600    }
601
602    /// Checks whether a specific SIMD instruction set is available on the
603    /// current CPU. Returns `true` when the given instruction was detected
604    /// during `initialize()` (or `SIMDCapabilities::detect()`).
605    pub fn has_feature(&self, instruction: &SIMDInstruction) -> bool {
606        self.simd_capabilities
607            .supported_instructions
608            .iter()
609            .any(|i| i == instruction)
610    }
611}
612
613impl SIMDCapabilities {
614    pub fn new() -> Self {
615        Self::detect()
616    }
617
618    /// Probe the running CPU for available SIMD instruction sets at runtime.
619    ///
620    /// The widest detected vector width and its natural alignment are recorded.
621    /// On `x86_64` detection uses `std::arch::is_x86_feature_detected!` (CPUID);
622    /// on `aarch64` it uses `std::arch::is_aarch64_feature_detected!`. On any
623    /// other architecture no SIMD instructions are reported and a scalar
624    /// (width 1, alignment 1) configuration is returned.
625    pub fn detect() -> Self {
626        let mut instructions: Vec<SIMDInstruction> = Vec::new();
627        let mut vector_width = 0usize;
628        let mut alignment = 1usize;
629
630        #[cfg(target_arch = "x86_64")]
631        {
632            // SSE2 is mandatory in the x86_64 baseline, but probe anyway for
633            // uniformity with the rest of the feature set.
634            if is_x86_feature_detected!("sse2") {
635                instructions.push(SIMDInstruction::SSE2);
636                if vector_width < 128 {
637                    vector_width = 128;
638                    alignment = 16;
639                }
640            }
641            if is_x86_feature_detected!("sse4.1") {
642                instructions.push(SIMDInstruction::SSE4_1);
643            }
644            if is_x86_feature_detected!("sse4.2") {
645                instructions.push(SIMDInstruction::SSE4_2);
646            }
647            if is_x86_feature_detected!("avx") {
648                instructions.push(SIMDInstruction::AVX);
649                if vector_width < 256 {
650                    vector_width = 256;
651                    alignment = 32;
652                }
653            }
654            if is_x86_feature_detected!("avx2") {
655                instructions.push(SIMDInstruction::AVX2);
656            }
657            // AVX-512 Foundation; wider 512-bit registers require 64-byte alignment.
658            if is_x86_feature_detected!("avx512f") {
659                instructions.push(SIMDInstruction::AVX512);
660                if vector_width < 512 {
661                    vector_width = 512;
662                    alignment = 64;
663                }
664            }
665            if is_x86_feature_detected!("fma") {
666                instructions.push(SIMDInstruction::FMA);
667            }
668        }
669
670        #[cfg(target_arch = "aarch64")]
671        {
672            // NEON is mandatory in the ARMv8 baseline; confirm via the runtime
673            // probe for consistency with the x86_64 path.
674            if std::arch::is_aarch64_feature_detected!("neon") {
675                instructions.push(SIMDInstruction::NEON);
676                if vector_width < 128 {
677                    vector_width = 128;
678                    alignment = 16;
679                }
680            }
681        }
682
683        // Scalar fallback for architectures without a recognised SIMD baseline.
684        if vector_width == 0 {
685            vector_width = 1;
686            alignment = 1;
687        }
688
689        Self {
690            vector_width,
691            supported_instructions: instructions,
692            alignment_requirements: alignment,
693        }
694    }
695}
696
697impl OperationScheduler {
698    pub fn new() -> Self {
699        Self {}
700    }
701}
702
703// Supporting types
704
705#[derive(Debug, Clone)]
706pub struct OptimizedMultiplication {
707    pub left: Matrix,
708    pub right: Matrix,
709    pub optimization_strategy: OptimizationStrategy,
710    pub expected_performance_gain: f64,
711}
712
713#[cfg(test)]
714mod tests {
715    use super::*;
716
717    #[test]
718    fn test_simd_detection() {
719        let mut optimizer = SIMDOptimizer::new();
720        optimizer.initialize().expect("initialize should succeed");
721
722        #[cfg(target_arch = "x86_64")]
723        {
724            // SSE2 is mandatory in the x86_64 baseline, so it must always be
725            // detected at runtime.
726            assert!(
727                optimizer.has_feature(&SIMDInstruction::SSE2),
728                "SSE2 should be detected on x86_64"
729            );
730        }
731
732        #[cfg(target_arch = "aarch64")]
733        {
734            assert!(
735                optimizer.has_feature(&SIMDInstruction::NEON),
736                "NEON should be detected on aarch64"
737            );
738        }
739
740        // On architectures without a recognised SIMD baseline we simply verify
741        // that detection completed without panicking; an empty capability set
742        // is a valid "no SIMD" result.
743        #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
744        {
745            let _ = optimizer.capabilities();
746        }
747    }
748
749    #[test]
750    fn test_simd_capabilities_report() {
751        let mut optimizer = SIMDOptimizer::new();
752        optimizer.initialize().expect("initialize should succeed");
753
754        let caps = optimizer.capabilities();
755        assert!(caps.vector_width >= 1, "vector width should be at least 1");
756        assert!(
757            caps.alignment_requirements >= 1,
758            "alignment requirements should be at least 1"
759        );
760
761        #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
762        {
763            assert!(
764                !caps.supported_instructions.is_empty(),
765                "at least one SIMD instruction should be detected on x86_64/aarch64"
766            );
767        }
768    }
769
770    #[test]
771    fn test_simd_feature_check() {
772        let mut optimizer = SIMDOptimizer::new();
773        optimizer.initialize().expect("initialize should succeed");
774
775        // A known feature detected on this CPU must report as available.
776        #[cfg(target_arch = "x86_64")]
777        {
778            assert!(
779                optimizer.has_feature(&SIMDInstruction::SSE2),
780                "has_feature should report SSE2 as available on x86_64"
781            );
782        }
783
784        #[cfg(target_arch = "aarch64")]
785        {
786            assert!(
787                optimizer.has_feature(&SIMDInstruction::NEON),
788                "has_feature should report NEON as available on aarch64"
789            );
790        }
791
792        // An instruction that was never probed must report as unavailable.
793        assert!(
794            !optimizer.has_feature(&SIMDInstruction::Custom("nonexistent".to_string())),
795            "has_feature should report a non-probed custom instruction as unavailable"
796        );
797    }
798
799    #[test]
800    fn test_round_robin_assignment() {
801        let mut lb = LoadBalancer::new(LoadBalancingStrategy::RoundRobin).with_workers(2);
802        let assignments: Vec<usize> = (0..4).map(|i| lb.assign_task(i)).collect();
803        assert_eq!(
804            assignments,
805            vec![0, 1, 0, 1],
806            "round-robin over 2 workers should cycle 0,1,0,1"
807        );
808    }
809
810    #[test]
811    fn test_static_partition() {
812        let mut lb = LoadBalancer::new(LoadBalancingStrategy::Static).with_workers(3);
813        lb.prepare(6);
814        let assignments: Vec<usize> = (0..6).map(|i| lb.assign_task(i)).collect();
815
816        // Each of the 3 workers should receive exactly 2 tasks.
817        let mut counts = vec![0usize; 3];
818        for &w in &assignments {
819            counts[w] += 1;
820        }
821        assert_eq!(counts, vec![2, 2, 2], "static partition should be balanced");
822
823        // Contiguous blocks: tasks 0,1 → worker 0; 2,3 → worker 1; 4,5 → worker 2.
824        assert_eq!(
825            assignments,
826            vec![0, 0, 1, 1, 2, 2],
827            "static partition should assign contiguous blocks"
828        );
829    }
830
831    #[test]
832    fn test_work_stealing() {
833        let mut lb = LoadBalancer::new(LoadBalancingStrategy::WorkStealing).with_workers(3);
834
835        // First task: all workers have 0 pending; tie broken by lowest index → 0.
836        assert_eq!(lb.assign_task(0), 0);
837        // Worker 0 now has 1 pending; workers 1 and 2 have 0 → assign to 1.
838        assert_eq!(lb.assign_task(1), 1);
839        // Workers 0,1 have 1; worker 2 has 0 → assign to 2.
840        assert_eq!(lb.assign_task(2), 2);
841        // All have 1 pending; tie broken by lowest index → 0.
842        assert_eq!(lb.assign_task(3), 0);
843
844        // Verify the invariant directly: the next task must go to a worker with
845        // the minimum pending count.
846        let min_pending = *lb.pending_tasks.iter().min().unwrap();
847        let next = lb.assign_task(4);
848        assert_eq!(
849            lb.pending_tasks[next],
850            min_pending + 1,
851            "work-stealing should assign to the worker with the fewest pending tasks"
852        );
853    }
854
855    #[test]
856    fn test_parallel_execution_collects_results() {
857        let executor = ParallelExecutor::new(2);
858        let tasks: Vec<ParallelTask> = (0..3)
859            .map(|i| ParallelTask {
860                task_id: i,
861                operation: ParallelOperation::MatrixMultiply,
862                estimated_work: 10 + i as u64,
863                priority: TaskPriority::Normal,
864            })
865            .collect();
866
867        let results = executor
868            .execute_parallel(&tasks)
869            .expect("execution should succeed");
870
871        assert_eq!(
872            results.len(),
873            3,
874            "execute_parallel should return one result per task"
875        );
876        // Results are returned in input order with matching task ids.
877        assert_eq!(
878            results.iter().map(|r| r.task_id).collect::<Vec<_>>(),
879            vec![0, 1, 2],
880            "results should preserve input order"
881        );
882        // Every task should be marked completed.
883        assert!(
884            results.iter().all(|r| r.status == TaskStatus::Completed),
885            "all tasks should complete"
886        );
887        // Every result should be assigned to a valid worker index.
888        assert!(
889            results
890                .iter()
891                .all(|r| r.worker_index < executor.num_workers()),
892            "worker indices must be within the pool"
893        );
894    }
895
896    #[test]
897    fn test_empty_tasks() {
898        let executor = ParallelExecutor::new(4);
899        let results = executor
900            .execute_parallel(&[])
901            .expect("empty execution should succeed");
902        assert!(
903            results.is_empty(),
904            "empty task list should yield no results"
905        );
906    }
907}