Added matrix utilities

This commit is contained in:
Asher 2025-07-07 10:27:51 +01:00
parent 5603c7dc0f
commit bbed86f19d
33 changed files with 572 additions and 261 deletions

2
.clangd Normal file
View File

@ -0,0 +1,2 @@
CompileFlags:
Add: [-std=c++20]

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
.DS_Store

31
Makefile Normal file
View File

@ -0,0 +1,31 @@
# Recursive wildcard function (properly defined)
rwildcard = $(foreach d,$(wildcard $1*),$(call rwildcard,$d/,$2) $(filter $(subst *,%,$2),$d))
CXX = g++
CXXFLAGS = -Wall -std=c++20
# Use $(call ...) not ${call ...}
ALL_LIBS = $(call rwildcard,math/,*.cpp) $(call rwildcard,generators/,*.cpp) $(call rwildcard,randomness_tests/,*.cpp)
# Expand wildcard properly for current directory
SRCS = main.cpp $(ALL_LIBS)
TEST_SRCS = code_tests/test.cpp $(ALL_LIBS)
TARGET = splat
TEST_TARGET = splat_test
.PHONY: all main test clean
all: $(TARGET) ${TEST_TARGET}
$(TARGET): $(SRCS)
$(CXX) $(CXXFLAGS) -o $@ $^
test: $(TEST_TARGET)
$(TEST_TARGET): $(TEST_SRCS)
$(CXX) $(CXXFLAGS) -o $@ $^
clean:
rm -f $(TARGET) $(TEST_TARGET) *.o

BIN
a.out

Binary file not shown.

View File

@ -1,7 +1,9 @@
#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN
#include "./doctest.h"
#include "../math/incomplete_gamma.cpp"
#include "../math/incomplete_gamma.h"
#include "../math/matrix.h"
#include <iostream>
TEST_CASE("igam function"){
@ -12,6 +14,7 @@ TEST_CASE("igam function"){
// for ii in range(20):
// print(f"igamtest({i/2}, {ii/2}, {scipy.special.gammainc(i/2,ii/2)});")
std::cout << "🫟 Splat CodeTests\n";
auto igamtest = [](double a, double x, double req) {
REQUIRE(igam(a,x)== doctest::Approx(req).epsilon(0.00000000000001));
@ -198,4 +201,22 @@ TEST_CASE("igam function"){
igamtest(4.5, 9.0, 0.9648264605330151);
igamtest(4.5, 9.5, 0.974807104918331);
}
}
}
TEST_CASE("Matrix utilities"){
splat::matrix<3> testMatrix = {
std::bitset<3>("010"),
std::bitset<3>("110"),
std::bitset<3>("010")
};
splat::matrix<3> testMatrix2 = {
std::bitset<3>("010"),
std::bitset<3>("101"),
std::bitset<3>("011")
};
REQUIRE(splat::getRank(testMatrix) == 2);
REQUIRE(splat::getRank(testMatrix2) == 3);
}

View File

@ -1,3 +1,4 @@
g++ -std=c++11 code_tests/test.cpp -o testcode.o
g++ -std=c++11 math/matrix.cpp code_tests/test.cpp -o testcode.o
./testcode.o
rm testcode.o
rm testcode.o

View File

@ -7,25 +7,22 @@
#include "../../rng.h"
#include "../generator.h"
#include "./lehmer.h"
// parameters recommended by Nakazawa & Nakazawa
// https://en.wikipedia.org/wiki/Lehmer_random_number_generator
namespace splat {
class lehmer_generator : public PRNG {
public:
lehmer_generator(uint32_t genSeed) : PRNG(genSeed) {
seed = genSeed;
}
uint32_t generate() override {
seed = (a * seed) % m;
return seed;
}
std::string getName() override {
return "lehmer";
}
private:
uint64_t a = 7759097958782935LL;
uint64_t m = 18055400005099021LL;
};
}
lehmer_generator::lehmer_generator(uint32_t genSeed) : PRNG(genSeed) {
seed = genSeed;
}
uint32_t lehmer_generator::generate() {
seed = (a * seed) % m;
return seed;
}
std::string lehmer_generator::getName() {
return "lehmer";
}
}

16
generators/LCG/lehmer.h Normal file
View File

@ -0,0 +1,16 @@
#pragma once
#include "../generator.h"
#include <cstdint>
namespace splat {
class lehmer_generator : public PRNG {
public:
lehmer_generator(uint32_t genSeed);
uint32_t generate() override;
std::string getName() override;
private:
static const uint64_t a = 7759097958782935LL;
static const uint64_t m = 18055400005099021LL;
};
}

View File

@ -1,7 +1,9 @@
#include "../rng.h"
#pragma once
#include "../rng.h"
namespace splat {
class PRNG {
public:

View File

@ -89,38 +89,8 @@ namespace splat {
}
std::string mt19937_generator::getName() {
return "mt19937-32 test";
return "mt19937-32";
}
}
// namespace splat {
// class mt19937_generator : public PRNG {
// public:
// mt19937_generator(uint32_t genSeed) : PRNG(genSeed) {
// state = mt19937_init(seed);
// nextblock();
// }
// uint32_t generate() override {
// uint32_t generated = random_values[position];
// position++;
// if(position>=624){
// nextblock();
// }
// return generated;
// }
// std::string getName() override {
// return "mt19937-32";
// }
// private:
// std::array<uint32_t,624> state;
// std::array<uint32_t,624> random_values;
// int position;
// // goes to next block of 624 values
// void nextblock() {
// state = mt19937_twist(state);
// random_values = mt19937_temper(state);
// position = 0;
// }
// };
// }

View File

@ -1,25 +1,22 @@
#include "../rng.h"
#include "./generator.h"
#include <cstdint>
#include "./xorshift.h"
// parameters recommended by Nakazawa & Nakazawa
// https://en.wikipedia.org/wiki/Lehmer_random_number_generator
namespace splat {
class xorshift32 : public PRNG {
public:
xorshift32(uint32_t genSeed) : PRNG(genSeed) {
state = genSeed;
}
uint32_t generate() override {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}
std::string getName() override {
return "xorshift32";
}
private:
uint32_t state;
};
}
xorshift32_generator::xorshift32_generator(uint32_t genSeed) : PRNG(genSeed) {
state = genSeed;
}
u_int32_t xorshift32_generator::generate() {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return state;
}
std::string xorshift32_generator::getName() {
return "xorshift32";
}
}

14
generators/xorshift.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include "generator.h"
namespace splat {
class xorshift32_generator : public PRNG {
public:
xorshift32_generator(uint32_t genSeed);
uint32_t generate() override;
std::string getName() override;
private:
uint32_t state;
};
}

View File

@ -6,13 +6,13 @@
//
#include "rng.h"
#include "generators/LCG/lehmer.cpp"
#include "generators/mt19937.cpp"
#include "generators/xorshift.cpp"
#include "randomness_tests/frequency_monobit.cpp"
#include "randomness_tests/frequency_block.cpp"
#include "randomness_tests/runs.cpp"
#include "randomness_tests/runs_ones.cpp"
#include "generators/LCG/lehmer.h"
#include "generators/mt19937.h"
#include "generators/xorshift.h"
#include "randomness_tests/frequency_monobit.h"
#include "randomness_tests/frequency_block.h"
#include "randomness_tests/runs.h"
#include "randomness_tests/runs_ones.h"
@ -31,7 +31,7 @@ namespace splat {
// add generators here
addGen<mt19937_generator>(generators, seed);
addGen<lehmer_generator>(generators, seed);
addGen<xorshift32>(generators,seed);
addGen<xorshift32_generator>(generators,seed);
return generators;
}

BIN
main.o

Binary file not shown.

View File

@ -1,6 +1,5 @@
#include "../rng.h"
#pragma once
#include "./incomplete_gamma.h"
double igam(double a, double x) {
double maxlog = 7.09782712893383996732E2;

4
math/incomplete_gamma.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
double igam(double a, double x);
double igamc(double a, double x);

87
math/matrix.cpp Normal file
View File

@ -0,0 +1,87 @@
#include <bitset>
#include <iostream>
#include <cstdint>
#include "matrix.h"
namespace splat {
template <int S>
std::bitset<S>& matrix<S>::operator[](int i){
return bits[i];
}
template <int S>
const std::bitset<S>& matrix<S>::operator[](int i) const {
return bits[i];
}
template <int S>
const bool matrix<S>::get(int row, int column) const {
return bits[row][S-column-1];
}
template <int S>
void matrix<S>::debug() {
for(int i=0; i<S; i++){
for(int ii=0; ii<S; ii++){
std::cout << get(i,ii);
}
std::cout << "\n";
}
}
template <int S>
void gaussianElimination(matrix<S>& matrix){
int top = 0;
for(int column=0; column<S; column++){
// get the position of the first one (pivot)
int firstOnePosition = -1;
for(int row=top; row<S; row++){
if(matrix.get(row, column)){
firstOnePosition = row;
break;
}
}
// if there was no one, we move on
if(firstOnePosition==-1){
top++;
continue;
}
// now swap the pivot row with the relative top row
if(firstOnePosition!=top){
std::bitset<S> temp = matrix[top];
matrix[top] = matrix[firstOnePosition];
matrix[firstOnePosition] = temp;
}
// now we XOR all the rows below our pivot with the pivot row
for(int row = firstOnePosition+1; row<S; row++){
if(matrix.get(row,column)){
matrix[row]^=matrix[top];
}
}
top++;
}
}
template <int S>
int getRank(matrix<S>& matrix){
gaussianElimination(matrix);
int rank = 0;
for(int i=0; i<S; i++){
if(matrix[i]!=std::bitset<S>(0)){
rank++;
}
}
return rank;
}
template int getRank<3>(matrix<3>&);
template int getRank<32>(matrix<32>&);
}

28
math/matrix.h Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <bitset>
#include <iostream>
#include <cstdint>
namespace splat {
template <int S>
struct matrix {
std::bitset<S> bits[S];
std::bitset<S>& operator[](int i);
const std::bitset<S>& operator[](int i) const;
const bool get(int row, int column) const;
void debug();
};
template <int S>
void gaussianElimination(matrix<S>& matrix);
template <int S>
int getRank(matrix<S>& matrix);
}

View File

@ -0,0 +1,21 @@
#include "./binary_matrix.h"
#include "./rngtest.h"
namespace splat {
binary_matrix_test::binary_matrix_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData){
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string binary_matrix_test::getName() {
return "Binary Matrix Rank Test";
}
// We will use matrices of size 32x32 as recommended
double binary_matrix_test::runTest(std::vector<std::bitset<32>> &data) {
int num_matrices = data.size() / 32;
}
}

View File

@ -0,0 +1,19 @@
#pragma once
#include "../rng.h"
#include "./rngtest.h"
#include "../math/incomplete_gamma.h"
#include "../math/matrix.h"
namespace splat {
class binary_matrix_test : public RNGTEST {
public:
binary_matrix_test(std::vector<std::bitset<32>> &testData);
std::string getName() override;
double runTest(std::vector<std::bitset<32>> &data) override;
};
}

View File

@ -6,52 +6,46 @@
//
#include "../rng.h"
#include "../math/incomplete_gamma.cpp"
#include "../math/incomplete_gamma.h"
#include "./frequency_block.h"
#include "./rngtest.h"
namespace splat {
class frequency_block_test : public RNGTEST {
public:
frequency_block_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
block_size = data.size() * 32 / 150;
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return std::format("Frequency Block [{}]", block_size);
}
double runTest(std::vector<std::bitset<32>> &data) override {
// std::cout << "DEBUG: "<<data.size() << "\n";
frequency_block_test::frequency_block_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
block_size = data.size() * 32 / 150;
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
long long bitcount = data.size() * 32;
long long chunks = bitcount / block_size;
double frequency_block_test::runTest(std::vector<std::bitset<32>> &data) {
double x2stat = 0;
for(int chunkIndex = 0; chunkIndex < chunks; chunkIndex++){
// std::cout << "NEWCHUNK: ";
double onecount = 0;
for(int i=0; i<block_size; i++){
int dataIndex = (((chunkIndex * block_size)+i) / 32);
int bitIndex = (((chunkIndex * block_size)+i) % 32);
onecount += data[dataIndex][bitIndex];
// std::cout << data[dataIndex][bitIndex];
}
double oneproportion = onecount / (double)block_size;
long long bitcount = data.size() * 32;
long long chunks = bitcount / block_size;
x2stat += 4*block_size*std::pow((oneproportion - 0.5),2);
double x2stat = 0;
for(int chunkIndex = 0; chunkIndex < chunks; chunkIndex++){
// std::cout << "NEWCHUNK: ";
double onecount = 0;
for(int i=0; i<block_size; i++){
int dataIndex = (((chunkIndex * block_size)+i) / 32);
int bitIndex = (((chunkIndex * block_size)+i) % 32);
onecount += data[dataIndex][bitIndex];
// std::cout << data[dataIndex][bitIndex];
}
double oneproportion = onecount / (double)block_size;
// std::cout << "\n";
}
x2stat += 4*block_size*std::pow((oneproportion - 0.5),2);
// std::cout << "[debug] calling igamc with "<<(chunks/2) <<','<<(x2stat/2) << "\n";
double p = igam(chunks/2, x2stat/2);
return p;
}
private:
long long block_size;
};
}
// std::cout << "\n";
}
// std::cout << "[debug] calling igamc with "<<(chunks/2) <<','<<(x2stat/2) << "\n";
double p = igam(chunks/2, x2stat/2);
return p;
}
std::string frequency_block_test::getName() {
return std::format("Frequency Block [{}]", block_size);
}
}

View File

@ -0,0 +1,23 @@
//
#pragma once
// frequency_monobit.cpp
// rng
//
// Created by Asher Falcon on 21/06/2025.
//
#include "../rng.h"
#include "./rngtest.h"
namespace splat {
class frequency_block_test : public RNGTEST {
public:
frequency_block_test(std::vector<std::bitset<32>> &testData);
std::string getName() override;
double runTest(std::vector<std::bitset<32>> &data) override;
private:
long long block_size;
};
}

View File

@ -7,27 +7,26 @@
#include "../rng.h"
#include "./rngtest.h"
#include "./frequency_monobit.h"
namespace splat {
class frequency_monobit_test : public RNGTEST {
public:
frequency_monobit_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return "Frequency Monobit";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long s = 0;
for(auto bitset : data){
for(int i=0; i<32; i++){
s += (2*bitset[i])-1;
}
}
double sobs = ((double)std::abs(s))/std::sqrt(data.size()*32);
double pvalue = std::erfc(sobs/std::sqrt(2));
return pvalue;
}
};
}
frequency_monobit_test::frequency_monobit_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
double frequency_monobit_test::runTest(std::vector<std::bitset<32>> &data) {
long long s = 0;
for(auto bitset : data){
for(int i=0; i<32; i++){
s += (2*bitset[i])-1;
}
}
double sobs = ((double)std::abs(s))/std::sqrt(data.size()*32);
double pvalue = std::erfc(sobs/std::sqrt(2));
return pvalue;
}
std::string frequency_monobit_test::getName() {
return "Frequency Monobit";
}
}

View File

@ -0,0 +1,22 @@
#pragma once
//
// frequency_monobit.cpp
// rng
//
// Created by Asher Falcon on 21/06/2025.
//
#include "../rng.h"
#include "./rngtest.h"
namespace splat {
class frequency_monobit_test : public RNGTEST {
public:
frequency_monobit_test(std::vector<std::bitset<32>> &testData);
std::string getName() override;
double runTest(std::vector<std::bitset<32>> &data) override;
};
}

View File

@ -1,7 +1,10 @@
#include "../rng.h"
#pragma once
#include "../rng.h"
namespace splat {
class RNGTEST {
public:

View File

@ -1,38 +1,38 @@
#include "../rng.h"
#include "./rngtest.h"
#include "./runs.h"
namespace splat {
class runs_test : public RNGTEST {
public:
runs_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return "Runs";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long totalSize = data.size() * 32;
double vnobs = 1;
runs_test::runs_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string runs_test::getName(){
return "Runs";
double onesproportion = 0;
}
double runs_test::runTest(std::vector<std::bitset<32>> &data) {
long long totalSize = data.size() * 32;
for(int i=0; i<totalSize-1; i++){
double vnobs = 1;
if(data[i/32][i%32]!=data[(i+1)/32][(i+1)%32]){
vnobs++;
}
onesproportion+=data[i/32][i%32];
}
double onesproportion = 0;
onesproportion/=totalSize;
for(int i=0; i<totalSize-1; i++){
return std::erfc(
std::abs(vnobs-(2*totalSize*onesproportion*(1-onesproportion)))
/
(2*std::sqrt(2*totalSize)*onesproportion*(1-onesproportion))
);
}
};
}
if(data[i/32][i%32]!=data[(i+1)/32][(i+1)%32]){
vnobs++;
}
onesproportion+=data[i/32][i%32];
}
onesproportion/=totalSize;
return std::erfc(
std::abs(vnobs-(2*totalSize*onesproportion*(1-onesproportion)))
/
(2*std::sqrt(2*totalSize)*onesproportion*(1-onesproportion))
);
}
}

13
randomness_tests/runs.h Normal file
View File

@ -0,0 +1,13 @@
#pragma once
#include "../rng.h"
#include "./rngtest.h"
namespace splat {
class runs_test : public RNGTEST {
public:
runs_test(std::vector<std::bitset<32>> &testData);
std::string getName() override;
double runTest(std::vector<std::bitset<32>> &data) override;
};
}

View File

@ -1,78 +1,76 @@
#include "../rng.h"
#include "./rngtest.h"
#include "../math/incomplete_gamma.cpp"
#define vecdebug(list) for(auto vecdebugitem : list){ std::cout << vecdebugitem <<","; }std::cout << "\n";
#include "../math/incomplete_gamma.h"
#include "./runs_ones.h"
namespace splat {
class runs_ones_test : public RNGTEST {
public:
runs_ones_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string getName() override {
return "Runs of 1s";
}
double runTest(std::vector<std::bitset<32>> &data) override {
long long totalSize = data.size() * 32;
if(totalSize < 750000){
std::cout << "Error, runs of ones test requires more than 750K bits of input\n";
return 0;
runs_ones_test::runs_ones_test(std::vector<std::bitset<32>> &testData) : RNGTEST(testData) {
testPValue = runTest(data);
testPassed = testPValue > 0.01;
}
std::string runs_ones_test::getName(){
return "Runs of 1s";
}
double runs_ones_test::runTest(std::vector<std::bitset<32>> &data) {
long long totalSize = data.size() * 32;
if(totalSize < 750000){
std::cout << "Error, runs of ones test requires more than 750K bits of input\n";
return 0;
}
// we do it with M=10^4 here
std::vector<double> counts(7,0);
double chunks = totalSize / 10000;
for(int chunkIndex=0; chunkIndex<chunks; chunkIndex++){
int longestRun = 0;
int ones = 0;
for(int i=0; i<10000; i++){
long long index = chunkIndex*10000 + i;
int dataIndex = (index / 32);
int bitIndex = (index % 32);
if(data[dataIndex][bitIndex]){
ones++;
}else{
if(ones>longestRun) longestRun = ones;
ones = 0;
}
// we do it with M=10^4 here
std::vector<double> counts(7,0);
double chunks = totalSize / 10000;
for(int chunkIndex=0; chunkIndex<chunks; chunkIndex++){
int longestRun = 0;
int ones = 0;
for(int i=0; i<10000; i++){
long long index = chunkIndex*10000 + i;
int dataIndex = (index / 32);
int bitIndex = (index % 32);
if(data[dataIndex][bitIndex]){
ones++;
}else{
if(ones>longestRun) longestRun = ones;
ones = 0;
}
}
if(longestRun <=10){
counts[0]++;
}else if(longestRun>=16){
counts[6]++;
}else{
counts[longestRun-10]++;
}
}
double x2 = 0;
std::vector<double> probabilities = {
0.0882,
0.2092,
0.2483,
0.1933,
0.1208,
0.0675,
0.0727
};
for(int i=0; i<7; i++){
x2 += std::pow((counts[i] - (chunks * probabilities[i])),2.0) /(chunks * probabilities[i]);
}
return igamc(3, x2/2.0);
}
};
}
if(longestRun <=10){
counts[0]++;
}else if(longestRun>=16){
counts[6]++;
}else{
counts[longestRun-10]++;
}
}
double x2 = 0;
std::vector<double> probabilities = {
0.0882,
0.2092,
0.2483,
0.1933,
0.1208,
0.0675,
0.0727
};
for(int i=0; i<7; i++){
x2 += std::pow((counts[i] - (chunks * probabilities[i])),2.0) /(chunks * probabilities[i]);
}
return igamc(3, x2/2.0);
}
}

View File

@ -0,0 +1,15 @@
#pragma once
#include "../rng.h"
#include "./rngtest.h"
namespace splat {
class runs_ones_test : public RNGTEST {
public:
runs_ones_test(std::vector<std::bitset<32>> &testData);
std::string getName() override;
double runTest(std::vector<std::bitset<32>> &data) override;
};
}

BIN
splat Executable file

Binary file not shown.

BIN
splat_test Executable file

Binary file not shown.

View File

@ -1,12 +1 @@
# React + Vite
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
Currently, two official plugins are available:
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
## Expanding the ESLint configuration
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
This is a web showcase of the 🫟 Splat library, using emscripten to compile the cpp to webassembly in the 'build_wasm.sh' script

View File

@ -7,6 +7,27 @@ function App() {
const [randomNum, setRandomNum] = useState(null);
const [rng, setRng] = useState(null)
const [nums, setNums] = useState(null);
const [matrixRows, setMatrixRows] = useState(null)
const max = (2**32)-1;
const rows = 500;
const cols = 500;
const newNums = () => {
const newMatrixRows = []
for(let i=0; i<rows; i++){
const rowItems = [];
for(let ii=0; ii<cols; ii++){
rowItems.push(rng.generate()/max);
}
newMatrixRows.push(rowItems)
}
setMatrixRows(newMatrixRows)
}
useEffect(() => {
createModule().then((module) => {
setRng(new module.mt19937(5489))
@ -31,15 +52,39 @@ function App() {
<span className='text-md'>Loading...</span>
</div>
) : (
<div>
<div>
<div className='flex flex-row justify-center'>
<span>{randomNum ? randomNum : "Generate a number below"}</span>
</div>
<div className='flex flex-row justify-center'>
<button onClick={() => {generateRandom()}}>Generate</button>
</div>
<button onClick={() => {newNums()}}>Generate</button>
{matrixRows ?(
<div>
{matrixRows.map((row) => {
return (
<div key={row[0]+row[1]} className="flex flex-row">
{
row.map((item) => {
return (
<div key={item} className="w-[2px] h-[2px]"
style={{
backgroundColor: `rgba(0,0,0,${item.toFixed(2)}`
}}
></div>
)
})
}
</div>
)
})}
</div>
) : (
<span>Loading...</span>
)
}
</div>
</div>
)}
</div>
</>
)