## Coverage Report

#### Created: 2026-01-25 15:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)

    /home/noah/src/trueno/src/simulation/scheduler.rs

    Line
    Count
    Source
    1
    //! Heijunka Scheduler (Leveled Testing)
    2
    //!
    3
    //! Implements Toyota Production System's Heijunka principle:
    4
    //! level the workload to reduce waste and variability.
    5
    6
    use crate::Backend;
    7
    use std::collections::VecDeque;
    8
    use std::marker::PhantomData;
    9
    10
    /// Backend-specific tolerance configuration
    11
    ///
    12
    /// Implements Poka-Yoke (mistake-proofing) by providing compile-time
    13
    /// guarantees for correct tolerance values per backend type.
    14
    #[derive(Debug, Clone, Copy, PartialEq)]
    15
    pub struct BackendTolerance {
    16
        /// Scalar vs SIMD tolerance (should be exact: 0.0)
    17
        pub scalar_vs_simd: f32,
    18
        /// SIMD vs GPU tolerance (IEEE 754: 1e-5)
    19
        pub simd_vs_gpu: f32,
    20
        /// GPU vs GPU tolerance (same precision: 1e-6)
    21
        pub gpu_vs_gpu: f32,
    22
    }
    23
    24
    impl Default for BackendTolerance {
    25
    0
        fn default() -> Self {
    26
    0
            Self {
    27
    0
                scalar_vs_simd: 0.0,
    28
    0
                simd_vs_gpu: 1e-5,
    29
    0
                gpu_vs_gpu: 1e-6,
    30
    0
            }
    31
    0
        }
    32
    }
    33
    34
    impl BackendTolerance {
    35
        /// Strict tolerance for exact comparisons
    36
        #[must_use]
    37
    0
        pub const fn strict() -> Self {
    38
    0
            Self {
    39
    0
                scalar_vs_simd: 0.0,
    40
    0
                simd_vs_gpu: 0.0,
    41
    0
                gpu_vs_gpu: 0.0,
    42
    0
            }
    43
    0
        }
    44
    45
        /// Relaxed tolerance for approximate comparisons
    46
        #[must_use]
    47
    0
        pub const fn relaxed() -> Self {
    48
    0
            Self {
    49
    0
                scalar_vs_simd: 1e-6,
    50
    0
                simd_vs_gpu: 1e-4,
    51
    0
                gpu_vs_gpu: 1e-5,
    52
    0
            }
    53
    0
        }
    54
    55
        /// Get tolerance for comparing two backends
    56
        #[must_use]
    57
    0
        pub fn for_backends(&self, a: Backend, b: Backend) -> f32 {
    58
    0
            match (a, b) {
    59
    0
                (Backend::Scalar, Backend::Scalar) => 0.0,
    60
                (Backend::Scalar, Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512)
    61
                | (Backend::SSE2 | Backend::AVX | Backend::AVX2 | Backend::AVX512, Backend::Scalar) => {
    62
    0
                    self.scalar_vs_simd
    63
                }
    64
    0
                (Backend::GPU, Backend::GPU) => self.gpu_vs_gpu,
    65
    0
                (Backend::GPU, _) | (_, Backend::GPU) => self.simd_vs_gpu,
    66
    0
                _ => self.scalar_vs_simd, // SIMD vs SIMD
    67
            }
    68
    0
        }
    69
    }
    70
    71
    /// Poka-Yoke: Type-safe backend selection
    72
    ///
    73
    /// Provides compile-time and runtime guarantees for correct backend selection
    74
    /// based on input size and operation type.
    75
    #[derive(Debug, Clone)]
    76
    pub struct BackendSelector {
    77
        /// Minimum size for GPU offload (default: 100,000)
    78
        gpu_threshold: usize,
    79
        /// Minimum size for parallel execution (default: 1,000)
    80
        parallel_threshold: usize,
    81
    }
    82
    83
    impl Default for BackendSelector {
    84
    0
        fn default() -> Self {
    85
    0
            Self {
    86
    0
                gpu_threshold: 100_000,
    87
    0
                parallel_threshold: 1_000,
    88
    0
            }
    89
    0
        }
    90
    }
    91
    92
    impl BackendSelector {
    93
        /// Create a new backend selector with custom thresholds
    94
        #[must_use]
    95
    0
        pub const fn new(gpu_threshold: usize, parallel_threshold: usize) -> Self {
    96
    0
            Self {
    97
    0
                gpu_threshold,
    98
    0
                parallel_threshold,
    99
    0
            }
    100
    0
        }
    101
    102
        /// Get the GPU threshold
    103
        #[must_use]
    104
    0
        pub const fn gpu_threshold(&self) -> usize {
    105
    0
            self.gpu_threshold
    106
    0
        }
    107
    108
        /// Get the parallel threshold
    109
        #[must_use]
    110
    0
        pub const fn parallel_threshold(&self) -> usize {
    111
    0
            self.parallel_threshold
    112
    0
        }
    113
    114
        /// Select backend based on input size
    115
        ///
    116
        /// # Decision Logic (TRUENO-SPEC-012)
    117
        ///
    118
        /// - N < 1,000: Pure SIMD (no parallelization overhead)
    119
        /// - 1,000 <= N < 100,000: SIMD + Parallel (Rayon)
    120
        /// - N >= 100,000: GPU (if available), else SIMD + Parallel
    121
        #[must_use]
    122
    0
        pub fn select_for_size(&self, size: usize, gpu_available: bool) -> BackendCategory {
    123
    0
            if size < self.parallel_threshold {
    124
    0
                BackendCategory::SimdOnly
    125
    0
            } else if size < self.gpu_threshold {
    126
    0
                BackendCategory::SimdParallel
    127
    0
            } else if gpu_available {
    128
    0
                BackendCategory::Gpu
    129
            } else {
    130
    0
                BackendCategory::SimdParallel // Graceful fallback
    131
            }
    132
    0
        }
    133
    134
        /// Check if size is at GPU threshold boundary (for testing)
    135
        #[must_use]
    136
    0
        pub fn is_at_gpu_boundary(&self, size: usize) -> bool {
    137
    0
            size == self.gpu_threshold || size == self.gpu_threshold - 1
    138
    0
        }
    139
    140
        /// Check if size is at parallel threshold boundary (for testing)
    141
        #[must_use]
    142
    0
        pub fn is_at_parallel_boundary(&self, size: usize) -> bool {
    143
    0
            size == self.parallel_threshold || size == self.parallel_threshold - 1
    144
    0
        }
    145
    }
    146
    147
    /// Backend category for selection result
    148
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    149
    pub enum BackendCategory {
    150
        /// Pure SIMD (N < 1,000)
    151
        SimdOnly,
    152
        /// SIMD with parallel execution (1,000 <= N < 100,000)
    153
        SimdParallel,
    154
        /// GPU compute (N >= 100,000)
    155
        Gpu,
    156
    }
    157
    158
    /// Simulation test configuration
    159
    #[derive(Debug, Clone)]
    160
    pub struct SimulationTest {
    161
        /// Backend to test
    162
        pub backend: Backend,
    163
        /// Input size
    164
        pub input_size: usize,
    165
        /// Test cycle number
    166
        pub cycle: u32,
    167
        /// Seed for deterministic RNG
    168
        pub seed: u64,
    169
    }
    170
    171
    /// Heijunka: Balanced test distribution across backends and sizes
    172
    ///
    173
    /// Implements Toyota Production System's Heijunka principle:
    174
    /// level the workload to reduce waste and variability.
    175
    #[derive(Debug)]
    176
    pub struct HeijunkaScheduler {
    177
        /// Test queue balanced across backends
    178
        queue: VecDeque<SimulationTest>,
    179
        /// Backends to cycle through
    180
        backends: Vec<Backend>,
    181
    }
    182
    183
    impl HeijunkaScheduler {
    184
        /// Create a leveled test schedule
    185
        #[must_use]
    186
    0
        pub fn new(
    187
    0
            backends: Vec<Backend>,
    188
    0
            input_sizes: Vec<usize>,
    189
    0
            cycles_per_backend: u32,
    190
    0
            master_seed: u64,
    191
    0
        ) -> Self {
    192
    0
            let mut queue = VecDeque::new();
    193
    194
            // Interleave tests across backends (leveling)
    195
    0
            for size in &input_sizes {
    196
    0
                for backend in &backends {
    197
    0
                    for cycle in 0..cycles_per_backend {
    198
    0
                        let seed = compute_seed(*backend, *size, cycle, master_seed);
    199
    0
                        queue.push_back(SimulationTest {
    200
    0
                            backend: *backend,
    201
    0
                            input_size: *size,
    202
    0
                            cycle,
    203
    0
                            seed,
    204
    0
                        });
    205
    0
                    }
    206
                }
    207
            }
    208
    209
    0
            Self {
    210
    0
                queue,
    211
    0
                backends: backends.clone(),
    212
    0
            }
    213
    0
        }
    214
    215
        /// Get the next test from the queue
    216
    0
        pub fn next_test(&mut self) -> Option<SimulationTest> {
    217
    0
            self.queue.pop_front()
    218
    0
        }
    219
    220
        /// Get remaining test count
    221
        #[must_use]
    222
    0
        pub fn remaining(&self) -> usize {
    223
    0
            self.queue.len()
    224
    0
        }
    225
    226
        /// Get backends being tested
    227
        #[must_use]
    228
    0
        pub fn backends(&self) -> &[Backend] {
    229
    0
            &self.backends
    230
    0
        }
    231
    232
        /// Check if schedule is empty
    233
        #[must_use]
    234
    0
        pub fn is_empty(&self) -> bool {
    235
    0
            self.queue.is_empty()
    236
    0
        }
    237
    }
    238
    239
    /// Compute deterministic seed for a test configuration
    240
    0
    pub(crate) fn compute_seed(backend: Backend, size: usize, cycle: u32, master_seed: u64) -> u64 {
    241
    0
        let backend_bits = backend as u64;
    242
    0
        let size_bits = size as u64;
    243
    0
        let cycle_bits = u64::from(cycle);
    244
    245
    0
        master_seed
    246
    0
            .wrapping_add(backend_bits.wrapping_mul(0x9E37_79B9_7F4A_7C15))
    247
    0
            .wrapping_add(size_bits.wrapping_mul(0x6A09_E667_BB67_AE85))
    248
    0
            .wrapping_add(cycle_bits.wrapping_mul(0x3C6E_F372_FE94_F82B))
    249
    0
    }
    250
    251
    /// Simulation test configuration builder
    252
    #[derive(Debug, Clone)]
    253
    pub struct SimTestConfigBuilder<S> {
    254
        seed: u64,
    255
        tolerance: BackendTolerance,
    256
        backends: Vec<Backend>,
    257
        input_sizes: Vec<usize>,
    258
        cycles: u32,
    259
        _state: PhantomData<S>,
    260
    }
    261
    262
    /// Builder state: seed not set
    263
    pub struct NeedsSeed;
    264
    /// Builder state: ready to build
    265
    pub struct Ready;
    266
    267
    impl Default for SimTestConfigBuilder<NeedsSeed> {
    268
    0
        fn default() -> Self {
    269
    0
            Self::new()
    270
    0
        }
    271
    }
    272
    273
    impl SimTestConfigBuilder<NeedsSeed> {
    274
        /// Create a new config builder
    275
        #[must_use]
    276
    0
        pub fn new() -> Self {
    277
    0
            Self {
    278
    0
                seed: 0,
    279
    0
                tolerance: BackendTolerance::default(),
    280
    0
                backends: vec![Backend::Scalar, Backend::AVX2],
    281
    0
                input_sizes: vec![100, 1_000, 10_000, 100_000],
    282
    0
                cycles: 10,
    283
    0
                _state: PhantomData,
    284
    0
            }
    285
    0
        }
    286
    287
        /// Set the master seed (required)
    288
        #[must_use]
    289
    0
        pub fn seed(self, seed: u64) -> SimTestConfigBuilder<Ready> {
    290
    0
            SimTestConfigBuilder {
    291
    0
                seed,
    292
    0
                tolerance: self.tolerance,
    293
    0
                backends: self.backends,
    294
    0
                input_sizes: self.input_sizes,
    295
    0
                cycles: self.cycles,
    296
    0
                _state: PhantomData,
    297
    0
            }
    298
    0
        }
    299
    }
    300
    301
    impl SimTestConfigBuilder<Ready> {
    302
        /// Set tolerance configuration
    303
        #[must_use]
    304
    0
        pub fn tolerance(mut self, tolerance: BackendTolerance) -> Self {
    305
    0
            self.tolerance = tolerance;
    306
    0
            self
    307
    0
        }
    308
    309
        /// Set backends to test
    310
        #[must_use]
    311
    0
        pub fn backends(mut self, backends: Vec<Backend>) -> Self {
    312
    0
            self.backends = backends;
    313
    0
            self
    314
    0
        }
    315
    316
        /// Set input sizes to test
    317
        #[must_use]
    318
    0
        pub fn input_sizes(mut self, sizes: Vec<usize>) -> Self {
    319
    0
            self.input_sizes = sizes;
    320
    0
            self
    321
    0
        }
    322
    323
        /// Set number of test cycles
    324
        #[must_use]
    325
    0
        pub fn cycles(mut self, cycles: u32) -> Self {
    326
    0
            self.cycles = cycles;
    327
    0
            self
    328
    0
        }
    329
    330
        /// Build the configuration
    331
        #[must_use]
    332
    0
        pub fn build(self) -> SimTestConfig {
    333
    0
            SimTestConfig {
    334
    0
                seed: self.seed,
    335
    0
                tolerance: self.tolerance,
    336
    0
                backends: self.backends,
    337
    0
                input_sizes: self.input_sizes,
    338
    0
                cycles: self.cycles,
    339
    0
            }
    340
    0
        }
    341
    }
    342
    343
    /// Simulation test configuration
    344
    #[derive(Debug, Clone)]
    345
    pub struct SimTestConfig {
    346
        /// Master seed for deterministic RNG
    347
        pub seed: u64,
    348
        /// Backend tolerance configuration
    349
        pub tolerance: BackendTolerance,
    350
        /// Backends to test
    351
        pub backends: Vec<Backend>,
    352
        /// Input sizes to test
    353
        pub input_sizes: Vec<usize>,
    354
        /// Number of test cycles
    355
        pub cycles: u32,
    356
    }
    357
    358
    impl SimTestConfig {
    359
        /// Create a config builder
    360
        #[must_use]
    361
    0
        pub fn builder() -> SimTestConfigBuilder<NeedsSeed> {
    362
    0
            SimTestConfigBuilder::new()
    363
    0
        }
    364
    365
        /// Create a Heijunka scheduler from this config
    366
        #[must_use]
    367
    0
        pub fn create_scheduler(&self) -> HeijunkaScheduler {
    368
    0
            HeijunkaScheduler::new(
    369
    0
                self.backends.clone(),
    370
    0
                self.input_sizes.clone(),
    371
    0
                self.cycles,
    372
    0
                self.seed,
    373
            )
    374
    0
        }
    375
    }
    376
    377
    #[cfg(test)]
    378
    mod tests {
    379
        use super::*;
    380
    381
        #[test]
    382
        fn test_backend_selector_default_thresholds() {
    383
            // Falsifiable claim A-005, A-006
    384
            let selector = BackendSelector::default();
    385
    386
            assert_eq!(selector.gpu_threshold(), 100_000);
    387
            assert_eq!(selector.parallel_threshold(), 1_000);
    388
        }
    389
    390
        #[test]
    391
        fn test_backend_selector_simd_only() {
    392
            // N < 1,000 should use SIMD only
    393
            let selector = BackendSelector::default();
    394
    395
            assert_eq!(
    396
                selector.select_for_size(100, false),
    397
                BackendCategory::SimdOnly
    398
            );
    399
            assert_eq!(
    400
                selector.select_for_size(999, false),
    401
                BackendCategory::SimdOnly
    402
            );
    403
            assert_eq!(
    404
                selector.select_for_size(999, true),
    405
                BackendCategory::SimdOnly
    406
            );
    407
        }
    408
    409
        #[test]
    410
        fn test_backend_selector_simd_parallel() {
    411
            // 1,000 <= N < 100,000 should use SIMD + Parallel
    412
            let selector = BackendSelector::default();
    413
    414
            assert_eq!(
    415
                selector.select_for_size(1_000, false),
    416
                BackendCategory::SimdParallel
    417
            );
    418
            assert_eq!(
    419
                selector.select_for_size(50_000, false),
    420
                BackendCategory::SimdParallel
    421
            );
    422
            assert_eq!(
    423
                selector.select_for_size(99_999, false),
    424
                BackendCategory::SimdParallel
    425
            );
    426
        }
    427
    428
        #[test]
    429
        fn test_backend_selector_gpu() {
    430
            // N >= 100,000 should use GPU (if available)
    431
            let selector = BackendSelector::default();
    432
    433
            assert_eq!(
    434
                selector.select_for_size(100_000, true),
    435
                BackendCategory::Gpu
    436
            );
    437
            assert_eq!(
    438
                selector.select_for_size(1_000_000, true),
    439
                BackendCategory::Gpu
    440
            );
    441
        }
    442
    443
        #[test]
    444
        fn test_backend_selector_gpu_fallback() {
    445
            // N >= 100,000 without GPU should fallback to SIMD + Parallel
    446
            let selector = BackendSelector::default();
    447
    448
            assert_eq!(
    449
                selector.select_for_size(100_000, false),
    450
                BackendCategory::SimdParallel
    451
            );
    452
        }
    453
    454
        #[test]
    455
        fn test_backend_selector_boundary() {
    456
            // Falsifiable claim A-005
    457
            let selector = BackendSelector::default();
    458
    459
            // At GPU threshold boundary
    460
            assert!(selector.is_at_gpu_boundary(100_000));
    461
            assert!(selector.is_at_gpu_boundary(99_999));
    462
            assert!(!selector.is_at_gpu_boundary(99_998));
    463
    464
            // At parallel threshold boundary
    465
            assert!(selector.is_at_parallel_boundary(1_000));
    466
            assert!(selector.is_at_parallel_boundary(999));
    467
            assert!(!selector.is_at_parallel_boundary(998));
    468
        }
    469
    470
        #[test]
    471
        fn test_backend_tolerance_default() {
    472
            let tolerance = BackendTolerance::default();
    473
    474
            assert_eq!(tolerance.scalar_vs_simd, 0.0);
    475
            assert!((tolerance.simd_vs_gpu - 1e-5).abs() < 1e-10);
    476
            assert!((tolerance.gpu_vs_gpu - 1e-6).abs() < 1e-10);
    477
        }
    478
    479
        #[test]
    480
        fn test_backend_tolerance_strict() {
    481
            let tolerance = BackendTolerance::strict();
    482
    483
            assert_eq!(tolerance.scalar_vs_simd, 0.0);
    484
            assert_eq!(tolerance.simd_vs_gpu, 0.0);
    485
            assert_eq!(tolerance.gpu_vs_gpu, 0.0);
    486
        }
    487
    488
        #[test]
    489
        fn test_backend_tolerance_for_backends() {
    490
            // Falsifiable claim A-002, A-003, A-004
    491
            let tolerance = BackendTolerance::default();
    492
    493
            // Scalar vs Scalar
    494
            assert_eq!(
    495
                tolerance.for_backends(Backend::Scalar, Backend::Scalar),
    496
                0.0
    497
            );
    498
    499
            // Scalar vs SIMD (should be exact)
    500
            assert_eq!(tolerance.for_backends(Backend::Scalar, Backend::AVX2), 0.0);
    501
    502
            // GPU vs GPU
    503
            assert_eq!(
    504
                tolerance.for_backends(Backend::GPU, Backend::GPU),
    505
                tolerance.gpu_vs_gpu
    506
            );
    507
    508
            // SIMD vs GPU
    509
            assert_eq!(
    510
                tolerance.for_backends(Backend::AVX2, Backend::GPU),
    511
                tolerance.simd_vs_gpu
    512
            );
    513
        }
    514
    515
        #[test]
    516
        fn test_heijunka_schedule_creation() {
    517
            let backends = vec![Backend::Scalar, Backend::AVX2];
    518
            let sizes = vec![100, 1000];
    519
            let cycles = 2;
    520
    521
            let scheduler = HeijunkaScheduler::new(backends.clone(), sizes, cycles, 42);
    522
    523
            // Should have backends.len() * sizes.len() * cycles tests
    524
            // 2 backends * 2 sizes * 2 cycles = 8 tests
    525
            assert_eq!(scheduler.remaining(), 8);
    526
        }
    527
    528
        #[test]
    529
        fn test_heijunka_deterministic_seeds() {
    530
            // Falsifiable claim B-017
    531
            let backends = vec![Backend::Scalar, Backend::AVX2];
    532
            let sizes = vec![100, 1000];
    533
            let cycles = 2;
    534
    535
            let mut scheduler1 = HeijunkaScheduler::new(backends.clone(), sizes.clone(), cycles, 42);
    536
            let mut scheduler2 = HeijunkaScheduler::new(backends, sizes, cycles, 42);
    537
    538
            // Seeds should be identical for same configuration
    539
            while let (Some(t1), Some(t2)) = (scheduler1.next_test(), scheduler2.next_test()) {
    540
                assert_eq!(t1.seed, t2.seed, "Seeds must be deterministic");
    541
            }
    542
        }
    543
    544
        #[test]
    545
        fn test_heijunka_consumes_all_tests() {
    546
            let backends = vec![Backend::Scalar];
    547
            let sizes = vec![100];
    548
            let cycles = 5;
    549
    550
            let mut scheduler = HeijunkaScheduler::new(backends, sizes, cycles, 42);
    551
    552
            let mut count = 0;
    553
            while scheduler.next_test().is_some() {
    554
                count += 1;
    555
            }
    556
    557
            assert_eq!(count, 5);
    558
            assert!(scheduler.is_empty());
    559
        }
    560
    561
        #[test]
    562
        fn test_heijunka_different_master_seeds() {
    563
            // Falsifiable claim B-018
    564
            let backends = vec![Backend::Scalar];
    565
            let sizes = vec![100];
    566
            let cycles = 1;
    567
    568
            let mut scheduler1 = HeijunkaScheduler::new(backends.clone(), sizes.clone(), cycles, 42);
    569
            let mut scheduler2 = HeijunkaScheduler::new(backends, sizes, cycles, 43);
    570
    571
            let t1 = scheduler1.next_test().unwrap();
    572
            let t2 = scheduler2.next_test().unwrap();
    573
    574
            assert_ne!(
    575
                t1.seed, t2.seed,
    576
                "Different master seeds must produce different test seeds"
    577
            );
    578
        }
    579
    580
        #[test]
    581
        fn test_sim_test_config_builder() {
    582
            let config = SimTestConfig::builder()
    583
                .seed(42)
    584
                .tolerance(BackendTolerance::strict())
    585
                .backends(vec![Backend::Scalar, Backend::AVX2, Backend::GPU])
    586
                .input_sizes(vec![100, 1000, 10000])
    587
                .cycles(5)
    588
                .build();
    589
    590
            assert_eq!(config.seed, 42);
    591
            assert_eq!(config.backends.len(), 3);
    592
            assert_eq!(config.input_sizes.len(), 3);
    593
            assert_eq!(config.cycles, 5);
    594
        }
    595
    596
        #[test]
    597
        fn test_sim_test_config_creates_scheduler() {
    598
            let config = SimTestConfig::builder()
    599
                .seed(42)
    600
                .backends(vec![Backend::Scalar, Backend::AVX2])
    601
                .input_sizes(vec![100, 1000])
    602
                .cycles(3)
    603
                .build();
    604
    605
            let scheduler = config.create_scheduler();
    606
    607
            // 2 backends * 2 sizes * 3 cycles = 12 tests
    608
            assert_eq!(scheduler.remaining(), 12);
    609
        }
    610
    611
        #[test]
    612
        fn test_backend_category_debug() {
    613
            let category = BackendCategory::Gpu;
    614
            let debug = format!("{category:?}");
    615
            assert!(debug.contains("Gpu"));
    616
        }
    617
    }
    618
    619
    #[cfg(test)]
    620
    mod proptests {
    621
        use super::*;
    622
        use proptest::prelude::*;
    623
    624
        proptest! {
    625
            /// Falsifiable claim A-009: Backend selection is deterministic
    626
            #[test]
    627
            fn prop_backend_selection_deterministic(size in 0usize..1_000_000) {
    628
                let selector = BackendSelector::default();
    629
    630
                let result1 = selector.select_for_size(size, true);
    631
                let result2 = selector.select_for_size(size, true);
    632
    633
                prop_assert_eq!(result1, result2);
    634
            }
    635
    636
            /// Falsifiable claim: compute_seed is deterministic
    637
            #[test]
    638
            fn prop_compute_seed_deterministic(
    639
                backend_idx in 0u8..8,
    640
                size in 0usize..1_000_000,
    641
                cycle in 0u32..100,
    642
                master_seed in any::<u64>()
    643
            ) {
    644
                let backend = match backend_idx {
    645
                    0 => Backend::Scalar,
    646
                    1 => Backend::SSE2,
    647
                    2 => Backend::AVX,
    648
                    3 => Backend::AVX2,
    649
                    4 => Backend::AVX512,
    650
                    5 => Backend::NEON,
    651
                    6 => Backend::WasmSIMD,
    652
                    _ => Backend::GPU,
    653
                };
    654
    655
                let seed1 = compute_seed(backend, size, cycle, master_seed);
    656
                let seed2 = compute_seed(backend, size, cycle, master_seed);
    657
    658
                prop_assert_eq!(seed1, seed2);
    659
            }
    660
        }
    661
    }