|
| 1 | +/* |
| 2 | + * This file is part of Susa. |
| 3 | +
|
| 4 | + * Susa is free software: you can redistribute it and/or modify |
| 5 | + * it under the terms of the Lesser GNU General Public License as published by |
| 6 | + * the Free Software Foundation, either version 3 of the License, or |
| 7 | + * at your option) any later version. |
| 8 | +
|
| 9 | + * Susa is distributed in the hope that it will be useful, |
| 10 | + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 12 | + * Lesser GNU General Public License for more details. |
| 13 | +
|
| 14 | + * You should have received a copy of the Lesser GNU General Public License |
| 15 | + * along with Susa. If not, see <http://www.gnu.org/licenses/>. |
| 16 | + */ |
| 17 | + |
| 18 | +/** |
| 19 | + * @file mt_test.cpp |
| 20 | + * @brief Mersenne Twister Random Number Generator Unit Tests |
| 21 | + * @author Test Suite |
| 22 | + */ |
| 23 | + |
| 24 | +#include "test.h" |
| 25 | +#include <cmath> |
| 26 | + |
| 27 | +int main(void) |
| 28 | +{ |
| 29 | + // ========== Constructor Tests ========== |
| 30 | + |
| 31 | + // Test default constructor |
| 32 | + susa::mt rng_default; |
| 33 | + SUSA_TEST_EQ(true, true, "mt default constructor"); |
| 34 | + |
| 35 | + // Test constructor with seed |
| 36 | + susa::mt rng_seeded(12345); |
| 37 | + SUSA_TEST_EQ(true, true, "mt seeded constructor"); |
| 38 | + |
| 39 | + // ========== Determinism Test ========== |
| 40 | + // Same seed should produce same sequence |
| 41 | + susa::mt rng_a(42); |
| 42 | + susa::mt rng_b(42); |
| 43 | + |
| 44 | + unsigned int val_a = rng_a.randint(1000); |
| 45 | + unsigned int val_b = rng_b.randint(1000); |
| 46 | + SUSA_TEST_EQ(val_a, val_b, "mt: same seed produces same sequence"); |
| 47 | + |
| 48 | + // ========== randint Tests ========== |
| 49 | + susa::mt rng_int(54321); |
| 50 | + |
| 51 | + // Test randint range - max |
| 52 | + unsigned int rand_val_max = rng_int.randint(100); |
| 53 | + SUSA_TEST_EQ((rand_val_max < 100), true, "mt: randint produces value less than max"); |
| 54 | + |
| 55 | + // Test randint produces various values |
| 56 | + susa::mt rng_int2(12341); |
| 57 | + unsigned int val1 = rng_int2.randint(1000000); |
| 58 | + unsigned int val2 = rng_int2.randint(1000000); |
| 59 | + unsigned int val3 = rng_int2.randint(1000000); |
| 60 | + SUSA_TEST_EQ((val1 != val2 || val2 != val3), true, "mt: randint produces different values"); |
| 61 | + |
| 62 | + // ========== rand Tests ========== |
| 63 | + susa::mt rng_real(99999); |
| 64 | + |
| 65 | + // Test rand returns matrix of correct size |
| 66 | + susa::matrix<double> rand_vals = rng_real.rand(10); |
| 67 | + SUSA_TEST_EQ(rand_vals.size(), 10, "mt: rand produces correct size"); |
| 68 | + |
| 69 | + // Test rand values are in [0, 1) |
| 70 | + bool all_in_range = true; |
| 71 | + for (size_t i = 0; i < rand_vals.size(); i++) |
| 72 | + { |
| 73 | + if (rand_vals(i) < 0.0 || rand_vals(i) >= 1.0) |
| 74 | + { |
| 75 | + all_in_range = false; |
| 76 | + break; |
| 77 | + } |
| 78 | + } |
| 79 | + SUSA_TEST_EQ(all_in_range, true, "mt: rand values in [0, 1) range"); |
| 80 | + |
| 81 | + // ========== randn Tests ========== |
| 82 | + susa::mt rng_normal(555); |
| 83 | + |
| 84 | + // Test randn returns matrix of correct size |
| 85 | + susa::matrix<double> normal_vals = rng_normal.randn(50); |
| 86 | + SUSA_TEST_EQ(normal_vals.size(), 50, "mt: randn produces correct size"); |
| 87 | + |
| 88 | + // Test randn values - should have some variation |
| 89 | + double min_val = normal_vals(0); |
| 90 | + double max_val = normal_vals(0); |
| 91 | + for (size_t i = 0; i < normal_vals.size(); i++) |
| 92 | + { |
| 93 | + if (normal_vals(i) < min_val) min_val = normal_vals(i); |
| 94 | + if (normal_vals(i) > max_val) max_val = normal_vals(i); |
| 95 | + } |
| 96 | + SUSA_TEST_EQ((max_val > min_val), true, "mt: randn produces varied values"); |
| 97 | + |
| 98 | + // ========== rand_mask Tests ========== |
| 99 | + susa::mt rng_mask(777); |
| 100 | + |
| 101 | + // Test rand_mask with mask value |
| 102 | + unsigned int mask_val = rng_mask.rand_mask(0xFF); // 255 mask |
| 103 | + SUSA_TEST_EQ((mask_val <= 0xFF), true, "mt: rand_mask respects mask"); |
| 104 | + |
| 105 | + // Test rand_mask matrix output |
| 106 | + susa::matrix<unsigned int> mask_vals = rng_mask.rand_mask(0x0F, 20); // 4-bit mask, 20 values |
| 107 | + SUSA_TEST_EQ(mask_vals.size(), 20, "mt: rand_mask matrix produces correct size"); |
| 108 | + |
| 109 | + bool all_masked = true; |
| 110 | + for (size_t i = 0; i < mask_vals.size(); i++) |
| 111 | + { |
| 112 | + if (mask_vals(i) > 0x0F) |
| 113 | + { |
| 114 | + all_masked = false; |
| 115 | + break; |
| 116 | + } |
| 117 | + } |
| 118 | + SUSA_TEST_EQ(all_masked, true, "mt: rand_mask matrix values respect mask"); |
| 119 | + |
| 120 | + // ========== bernoulli Tests ========== |
| 121 | + susa::mt rng_bernoulli(888); |
| 122 | + |
| 123 | + // Test bernoulli with p=0.5 - should produce mix of 0s and 1s |
| 124 | + unsigned int heads = 0; |
| 125 | + unsigned int num_trials = 200; |
| 126 | + for (unsigned int i = 0; i < num_trials; i++) |
| 127 | + { |
| 128 | + heads += rng_bernoulli.bernoulli(0.5); |
| 129 | + } |
| 130 | + // With p=0.5 and 200 trials, expect roughly 100, but allow 30-170 range |
| 131 | + SUSA_TEST_EQ((heads > 30 && heads < 170), true, "mt: bernoulli(0.5) produces reasonable distribution"); |
| 132 | + |
| 133 | + // Test bernoulli with p=1.0 - should always return 1 |
| 134 | + unsigned int ones = 0; |
| 135 | + for (unsigned int i = 0; i < 100; i++) |
| 136 | + { |
| 137 | + ones += rng_bernoulli.bernoulli(1.0); |
| 138 | + } |
| 139 | + SUSA_TEST_EQ(ones, 100, "mt: bernoulli(1.0) always returns 1"); |
| 140 | + |
| 141 | + // Test bernoulli with p=0.0 - should always return 0 |
| 142 | + susa::mt rng_bernoulli_zero(999); |
| 143 | + unsigned int zeros = 0; |
| 144 | + for (unsigned int i = 0; i < 100; i++) |
| 145 | + { |
| 146 | + if (rng_bernoulli_zero.bernoulli(0.0) == 0) |
| 147 | + zeros++; |
| 148 | + } |
| 149 | + SUSA_TEST_EQ(zeros, 100, "mt: bernoulli(0.0) always returns 0"); |
| 150 | + |
| 151 | + // ========== Reproducibility Test ========== |
| 152 | + susa::mt rng_repro1(12321); |
| 153 | + susa::matrix<double> sequence1 = rng_repro1.rand(5); |
| 154 | + |
| 155 | + susa::mt rng_repro2(12321); |
| 156 | + susa::matrix<double> sequence2 = rng_repro2.rand(5); |
| 157 | + |
| 158 | + bool sequences_match = true; |
| 159 | + for (size_t i = 0; i < sequence1.size(); i++) |
| 160 | + { |
| 161 | + if (sequence1(i) != sequence2(i)) |
| 162 | + { |
| 163 | + sequences_match = false; |
| 164 | + break; |
| 165 | + } |
| 166 | + } |
| 167 | + SUSA_TEST_EQ(sequences_match, true, "mt: reproducible with same seed"); |
| 168 | + |
| 169 | + // ========== Different Seeds Test ========== |
| 170 | + susa::mt rng_diff1(11111); |
| 171 | + susa::mt rng_diff2(22222); |
| 172 | + |
| 173 | + susa::matrix<double> diff_seq1 = rng_diff1.rand(5); |
| 174 | + susa::matrix<double> diff_seq2 = rng_diff2.rand(5); |
| 175 | + |
| 176 | + bool sequences_differ = false; |
| 177 | + for (size_t i = 0; i < diff_seq1.size(); i++) |
| 178 | + { |
| 179 | + if (diff_seq1(i) != diff_seq2(i)) |
| 180 | + { |
| 181 | + sequences_differ = true; |
| 182 | + break; |
| 183 | + } |
| 184 | + } |
| 185 | + SUSA_TEST_EQ(sequences_differ, true, "mt: different seeds produce different sequences"); |
| 186 | + |
| 187 | + SUSA_TEST_PRINT_STATS(); |
| 188 | + |
| 189 | + return (uint_failed); |
| 190 | +} |
0 commit comments