Skip to content

feat(rng): add new RNGs and sparse random matrix functions (fixes ##359 - #1450

Merged
Nelson-numerical-software merged 1 commit into
masterfrom
feat-#359-better-faster-random-generator-engine
Sep 3, 2025
Merged

Nelson-numerical-software merged 1 commit into
masterfrom
feat-#359-better-faster-random-generator-engine

Conversation

@Nelson-numerical-software

@Nelson-numerical-software Nelson-numerical-software commented Aug 31, 2025 •

Copy link
Copy Markdown
Collaborator
  • Introduced new random number generators: philox, simdTwister, threefry, combRecursive
  • Added functions randi, sprand (uniform) and sprandn (normal) for sparse random matrices
  • Updated tests for compatibility with new RNGs
  • Added XML docs for sprand and sprandn

@Nelson-numerical-software
Nelson-numerical-software force-pushed the feat-#359-better-faster-random-generator-engine branch from feafd66 to 90532d6 Compare August 31, 2025 19:08

This comment was marked as outdated.

@Nelson-numerical-software
Nelson-numerical-software force-pushed the feat-#359-better-faster-random-generator-engine branch from 90532d6 to 9c2888f Compare August 31, 2025 19:18

This comment was marked as outdated.

@Nelson-numerical-software
Nelson-numerical-software force-pushed the feat-#359-better-faster-random-generator-engine branch from 9c2888f to 2f21304 Compare August 31, 2025 19:31

This comment was marked as outdated.

@Nelson-numerical-software
Nelson-numerical-software force-pushed the feat-#359-better-faster-random-generator-engine branch from 2f21304 to 06b1c43 Compare August 31, 2025 19:41

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR introduces new random number generators and sparse random matrix functionality to enhance the random number generation capabilities. The main purpose is to expand RNG options and provide compatibility with MATLAB-style sparse random matrix functions.

  • Added four new RNG engines: philox, simdTwister, threefry, and combRecursive
  • Implemented sparse random matrix functions sprand (uniform) and sprandn (normal distribution)
  • Updated existing RNG tests to work with the new generators and removed dependency on Boost random libraries

Reviewed Changes

Copilot reviewed 158 out of 158 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
modules/types/src/cpp/*.cpp Replace std::nan("NaN") with std::nan("") for consistent NaN handling
modules/string/src/cpp/*.cpp Same NaN parameter standardization across string modules
modules/statistics/src/cpp/*.cpp NaN parameter updates for statistics functions
modules/spreadsheet/src/cpp/*.cpp Consistent NaN handling in spreadsheet operations
modules/special_functions/src/cpp/*.cpp NaN standardization in interpolation functions
modules/sparse/functions/*.m New MATLAB functions for sparse random matrix generation
modules/sparse/help/en_US/xml/*.xml Documentation for new sparse matrix functions
modules/sparse/tests/*.m Test files for sparse random matrix functionality
modules/random/src/cpp/*.cpp Major refactoring to remove Boost dependencies and add new RNG engines
modules/random/tests/*.m Updated and new test files for all RNG engines

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment thread modules/random/src/cpp/RandomPhilox.hpp Resolved
Comment thread modules/sparse/functions/sprandn.m Resolved

@github-advanced-security github-advanced-security AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.

@Nelson-numerical-software
Nelson-numerical-software force-pushed the feat-#359-better-faster-random-generator-engine branch 4 times, most recently from b2f9381 to 5048242 Compare September 2, 2025 17:38
double val1 = std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * M_PI * u2);
double val2 = std::sqrt(-2.0 * std::log(u1)) * std::sin(2.0 * M_PI * u2);
ar[i] = val1;
++i;

Check notice

Code scanning / CodeQL

For loop variable changed in body

Loop counters should not be modified in the body of the [loop](1).

Copilot Autofix

AI about 1 year ago

To fix this issue, we should replace the for loop that uses both j and i as variables (and increments i within the body), with an explicit while loop over i, managing j manually. This gives clear control and eliminates the confusion around loop counters. More specifically, in lines 99-124 of modules/random/src/cpp/RandomPhilox.hpp, the inner for loop should be converted to a while loop.

  • Define j = 0 before the loop and use while (j < 4 && i < nbElements) for iteration.
  • Replace the current usage of for (int j = 0; ...) with an explicit j variable.
  • At each pass, increment j and i only as needed, preserving the current correctness.
  • All other logic inside the loop remains unchanged.
  • No new methods or imports are required.
Suggested changeset 1
modules/random/src/cpp/RandomPhilox.hpp

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/modules/random/src/cpp/RandomPhilox.hpp b/modules/random/src/cpp/RandomPhilox.hpp
--- a/modules/random/src/cpp/RandomPhilox.hpp
+++ b/modules/random/src/cpp/RandomPhilox.hpp
@@ -96,10 +96,13 @@
         while (i < nbElements) {
             generateBuffer();
             counter[2] += 2;
-            for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
+            int j = 0;
+            while (j < 4 && i < nbElements) {
                 uint32_t nextUint32 = buffer[j];
                 if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
                     ar[i] = static_cast<double>(nextUint32) / 4294967296.0;
+                    ++j;
+                    ++i;
                 } else if (_type == RNG_DISTRIBUTION_NORMAL) {
                     if (i + 1 < nbElements) {
                         uint32_t u1_int = nextUint32;
@@ -112,14 +112,19 @@
                         ++i;
                         if (i < nbElements) {
                             ar[i] = val2;
+                            ++i;
                         }
-                        ++j;
+                        j += 2;
                     } else {
                         double u1 = (static_cast<double>(nextUint32) + 0.5) / 4294967296.0;
                         ar[i] = std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * M_PI * 0.5);
+                        ++j;
+                        ++i;
                     }
                 } else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
                     ar[i] = static_cast<double>(_min + (nextUint32 % (_max - _min + 1)));
+                    ++j;
+                    ++i;
                 }
             }
         }
EOF
@@ -96,10 +96,13 @@
while (i < nbElements) {
generateBuffer();
counter[2] += 2;
for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
int j = 0;
while (j < 4 && i < nbElements) {
uint32_t nextUint32 = buffer[j];
if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
ar[i] = static_cast<double>(nextUint32) / 4294967296.0;
++j;
++i;
} else if (_type == RNG_DISTRIBUTION_NORMAL) {
if (i + 1 < nbElements) {
uint32_t u1_int = nextUint32;
@@ -112,14 +112,19 @@
++i;
if (i < nbElements) {
ar[i] = val2;
++i;
}
++j;
j += 2;
} else {
double u1 = (static_cast<double>(nextUint32) + 0.5) / 4294967296.0;
ar[i] = std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * M_PI * 0.5);
++j;
++i;
}
} else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
ar[i] = static_cast<double>(_min + (nextUint32 % (_max - _min + 1)));
++j;
++i;
}
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
if (i < nbElements) {
ar[i] = val2;
}
++j;

Check notice

Code scanning / CodeQL

For loop variable changed in body

Loop counters should not be modified in the body of the [loop](1).

Copilot Autofix

AI about 1 year ago

To fix this problem, we should replace the for loop that modifies the j loop variable in its body with a while loop, which better matches the logic when the loop counter can be changed irregularly. Specifically, in getValuesAsDouble, we must alter lines 99–124: Instead of for (int j = 0; j < 4 && i < nbElements; ++j, ++i), use a while loop with explicit control over both indices j and i. The loop body remains the same, but wherever j or i are incremented elsewhere in the block, care must be taken to not duplicate increments on exiting the loop body.

No changes are needed outside the lines indicated, and the rest of the behavior will remain. No new includes or method definitions are required.


Suggested changeset 1
modules/random/src/cpp/RandomPhilox.hpp

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/modules/random/src/cpp/RandomPhilox.hpp b/modules/random/src/cpp/RandomPhilox.hpp
--- a/modules/random/src/cpp/RandomPhilox.hpp
+++ b/modules/random/src/cpp/RandomPhilox.hpp
@@ -96,10 +96,13 @@
         while (i < nbElements) {
             generateBuffer();
             counter[2] += 2;
-            for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
+            int j = 0;
+            while (j < 4 && i < nbElements) {
                 uint32_t nextUint32 = buffer[j];
                 if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
                     ar[i] = static_cast<double>(nextUint32) / 4294967296.0;
+                    ++i;
+                    ++j;
                 } else if (_type == RNG_DISTRIBUTION_NORMAL) {
                     if (i + 1 < nbElements) {
                         uint32_t u1_int = nextUint32;
@@ -113,13 +113,18 @@
                         if (i < nbElements) {
                             ar[i] = val2;
                         }
-                        ++j;
+                        ++i;
+                        j += 2;
                     } else {
                         double u1 = (static_cast<double>(nextUint32) + 0.5) / 4294967296.0;
                         ar[i] = std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * M_PI * 0.5);
+                        ++i;
+                        ++j;
                     }
                 } else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
                     ar[i] = static_cast<double>(_min + (nextUint32 % (_max - _min + 1)));
+                    ++i;
+                    ++j;
                 }
             }
         }
EOF
@@ -96,10 +96,13 @@
while (i < nbElements) {
generateBuffer();
counter[2] += 2;
for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
int j = 0;
while (j < 4 && i < nbElements) {
uint32_t nextUint32 = buffer[j];
if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
ar[i] = static_cast<double>(nextUint32) / 4294967296.0;
++i;
++j;
} else if (_type == RNG_DISTRIBUTION_NORMAL) {
if (i + 1 < nbElements) {
uint32_t u1_int = nextUint32;
@@ -113,13 +113,18 @@
if (i < nbElements) {
ar[i] = val2;
}
++j;
++i;
j += 2;
} else {
double u1 = (static_cast<double>(nextUint32) + 0.5) / 4294967296.0;
ar[i] = std::sqrt(-2.0 * std::log(u1)) * std::cos(2.0 * M_PI * 0.5);
++i;
++j;
}
} else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
ar[i] = static_cast<double>(_min + (nextUint32 % (_max - _min + 1)));
++i;
++j;
}
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
float u2 = (static_cast<float>(u2_int) + 0.5f) / 4294967296.0f;
ar[i] = static_cast<single>(
std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * M_PI * u2));
++i;

Check notice

Code scanning / CodeQL

For loop variable changed in body

Loop counters should not be modified in the body of the [loop](1).

Copilot Autofix

AI about 1 year ago

To resolve the issue, refactor the inner for loop on lines 136–162 into a while loop so that both loop variables i and j can be controlled explicitly and incremented as needed within the loop body. This eliminates the ambiguity and maintains the correct logic regardless of how many times i is incremented in a single iteration. The outer structure and function should remain unchanged, and the inner logic should be ported to a while loop that handles increments to i and j explicitly.

Required changes:

  • Replace the for loop with a while loop, with the same exit conditions: j < 4 && i < nbElements.
  • Manually increment j and i as needed, matching the original branching logic.
  • No new imports/definitions are needed.

Suggested changeset 1
modules/random/src/cpp/RandomPhilox.hpp

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/modules/random/src/cpp/RandomPhilox.hpp b/modules/random/src/cpp/RandomPhilox.hpp
--- a/modules/random/src/cpp/RandomPhilox.hpp
+++ b/modules/random/src/cpp/RandomPhilox.hpp
@@ -133,10 +133,13 @@
         while (i < nbElements) {
             generateBuffer();
             counter[2] += 2;
-            for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
+            int j = 0;
+            while (j < 4 && i < nbElements) {
                 uint32_t nextUint32 = buffer[j];
                 if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
                     ar[i] = static_cast<float>(nextUint32) / 4294967296.0f;
+                    ++j;
+                    ++i;
                 } else if (_type == RNG_DISTRIBUTION_NORMAL) {
                     if (i + 1 < nbElements) {
                         uint32_t u1_int = nextUint32;
@@ -149,15 +149,20 @@
                         if (i < nbElements) {
                             ar[i] = static_cast<single>(
                                 std::sqrt(-2.0f * std::log(u1)) * std::sin(2.0f * M_PI * u2));
+                            ++i;
                         }
-                        ++j;
+                        j += 2; // We used j and j+1 elements from buffer
                     } else {
                         float u1 = (static_cast<float>(nextUint32) + 0.5f) / 4294967296.0f;
                         ar[i] = static_cast<single>(
                             std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * M_PI * 0.5f));
+                        ++j;
+                        ++i;
                     }
                 } else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
                     ar[i] = static_cast<single>(_min + (nextUint32 % (_max - _min + 1)));
+                    ++j;
+                    ++i;
                 }
             }
         }
EOF
@@ -133,10 +133,13 @@
while (i < nbElements) {
generateBuffer();
counter[2] += 2;
for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
int j = 0;
while (j < 4 && i < nbElements) {
uint32_t nextUint32 = buffer[j];
if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
ar[i] = static_cast<float>(nextUint32) / 4294967296.0f;
++j;
++i;
} else if (_type == RNG_DISTRIBUTION_NORMAL) {
if (i + 1 < nbElements) {
uint32_t u1_int = nextUint32;
@@ -149,15 +149,20 @@
if (i < nbElements) {
ar[i] = static_cast<single>(
std::sqrt(-2.0f * std::log(u1)) * std::sin(2.0f * M_PI * u2));
++i;
}
++j;
j += 2; // We used j and j+1 elements from buffer
} else {
float u1 = (static_cast<float>(nextUint32) + 0.5f) / 4294967296.0f;
ar[i] = static_cast<single>(
std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * M_PI * 0.5f));
++j;
++i;
}
} else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
ar[i] = static_cast<single>(_min + (nextUint32 % (_max - _min + 1)));
++j;
++i;
}
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
ar[i] = static_cast<single>(
std::sqrt(-2.0f * std::log(u1)) * std::sin(2.0f * M_PI * u2));
}
++j;

Check notice

Code scanning / CodeQL

For loop variable changed in body

Loop counters should not be modified in the body of the [loop](1).

Copilot Autofix

AI about 1 year ago

The correct fix is to replace the inner for loop (lines 136–162) with a while loop over j, since j is being manipulated not only in the loop header (with ++j), but also inside the body (specifically, inside the normal distribution branch where it may be incremented by 2 on some iterations). Replace the for loop with logic that handles j incrementing exactly as intended, and remove confusion. Implement all required logic within the while loop, including the manual increment(s), and ensure the code remains functionally unchanged. The rest of the function should remain as is, and no new imports are needed.

Suggested changeset 1
modules/random/src/cpp/RandomPhilox.hpp

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/modules/random/src/cpp/RandomPhilox.hpp b/modules/random/src/cpp/RandomPhilox.hpp
--- a/modules/random/src/cpp/RandomPhilox.hpp
+++ b/modules/random/src/cpp/RandomPhilox.hpp
@@ -133,10 +133,13 @@
         while (i < nbElements) {
             generateBuffer();
             counter[2] += 2;
-            for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
+            int j = 0;
+            while (j < 4 && i < nbElements) {
                 uint32_t nextUint32 = buffer[j];
                 if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
                     ar[i] = static_cast<float>(nextUint32) / 4294967296.0f;
+                    ++j;
+                    ++i;
                 } else if (_type == RNG_DISTRIBUTION_NORMAL) {
                     if (i + 1 < nbElements) {
                         uint32_t u1_int = nextUint32;
@@ -150,14 +150,18 @@
                             ar[i] = static_cast<single>(
                                 std::sqrt(-2.0f * std::log(u1)) * std::sin(2.0f * M_PI * u2));
                         }
-                        ++j;
+                        j += 2;
                     } else {
                         float u1 = (static_cast<float>(nextUint32) + 0.5f) / 4294967296.0f;
                         ar[i] = static_cast<single>(
                             std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * M_PI * 0.5f));
+                        ++j;
+                        ++i;
                     }
                 } else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
                     ar[i] = static_cast<single>(_min + (nextUint32 % (_max - _min + 1)));
+                    ++j;
+                    ++i;
                 }
             }
         }
EOF
@@ -133,10 +133,13 @@
while (i < nbElements) {
generateBuffer();
counter[2] += 2;
for (int j = 0; j < 4 && i < nbElements; ++j, ++i) {
int j = 0;
while (j < 4 && i < nbElements) {
uint32_t nextUint32 = buffer[j];
if (_type == RNG_DISTRIBUTION_UNIFORM_REAL) {
ar[i] = static_cast<float>(nextUint32) / 4294967296.0f;
++j;
++i;
} else if (_type == RNG_DISTRIBUTION_NORMAL) {
if (i + 1 < nbElements) {
uint32_t u1_int = nextUint32;
@@ -150,14 +150,18 @@
ar[i] = static_cast<single>(
std::sqrt(-2.0f * std::log(u1)) * std::sin(2.0f * M_PI * u2));
}
++j;
j += 2;
} else {
float u1 = (static_cast<float>(nextUint32) + 0.5f) / 4294967296.0f;
ar[i] = static_cast<single>(
std::sqrt(-2.0f * std::log(u1)) * std::cos(2.0f * M_PI * 0.5f));
++j;
++i;
}
} else if (_type == RNG_DISTRIBUTION_UNIFORM_INT) {
ar[i] = static_cast<single>(_min + (nextUint32 % (_max - _min + 1)));
++j;
++i;
}
}
}
Copilot is powered by AI and may make mistakes. Always verify output.
- Introduced new random number generators: philox, simdTwister, threefry, combRecursive
- Added functions `sprand` (uniform) and `sprandn` (normal) for sparse random matrices
- Updated tests for compatibility with new RNGs
- Added XML docs for `sprand` and `sprandn`
@Nelson-numerical-software
Nelson-numerical-software force-pushed the feat-#359-better-faster-random-generator-engine branch from 5048242 to 1cc54c0 Compare September 3, 2025 17:07
@Nelson-numerical-software
Nelson-numerical-software merged commit 1b77ffa into master Sep 3, 2025
@Nelson-numerical-software
Nelson-numerical-software deleted the feat-#359-better-faster-random-generator-engine branch September 3, 2025 18:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants