boo1ean / mersenne-twister

Mersenne Twister pseudorandom number generator
119 stars 23 forks source link

Create interface for full state management #5

Open GeorgesOatesLarsen opened 4 years ago

GeorgesOatesLarsen commented 4 years ago

Variables mt and mti appear to fully determine the state of the generator.

I suggest implementing functions get_state and set_state that respectively return and receive a state object containing the keys mt and mt.

For Example:

MersenneTwister.prototype.get_state = function() {
    return {
        mti:this.mti,
        mt:this.mt.slice(),
    }
}

MersenneTwister.prototype.set_state = function(newstate) {
    this.mti = newstate.mti;
    this.mt = newstate.mt.slice();
}
MersenneTwister.prototype.set_state_unsafe = function(newstate) {
    this.mti = newstate.mti;
    this.mt = newstate.mt;
}

This would be useful in situations where it is necessary to revert to an arbitrary midpoint through a random progression. Currently, it is possible just by manually controlling mti and mt, but ideally a user would not need to understand the inner-workings of the mersenne twister in order to manage its state.

GeorgesOatesLarsen commented 4 years ago

Worth note: I did not take the time to properly understand this RNG before writing the above. So, it is possible that there are other state variables that would need to be included that I missed.

That being stated, the following test code would suggest strongly against that I have succeeded:

import MersenneTwister from "mersenne-twister";

let gen = new MersenneTwister();

//Warm up the twister
for(let i = 0; i < 1000; i++) {
    gen.random_int();
}

//Store the state of the twister
let ostate = gen.mt.slice();
let omti = gen.mti;

//Generate and store a large number of sequential samples of the twister
let ntest = 10000;
let firstrun = [];
for(let i = 0; i < ntest; i++) {
    firstrun.push(gen.random_int());
}

//Revert to the stored state
gen.mt = ostate.slice();
gen.mti = omti;

//Generate another large set of similar length of numbers.
//Compare each one to the original set.
//Each should be identical if the state was fully captured.
//Count the number of failures.
let failures = 0;
let passes = 0;
for(let i = 0; i < ntest; i++) {
    let a = gen.random_int();
    let b = firstrun[i];
    console.log(a, b);
    if (a !== b) {
        failures += 1;
    } else {
        passes += 1;
    }
}
console.log("\n\n\n" + failures + " failures, " + passes + " passes.");

And, some sample output:

2995799045 2995799045
2196720773 2196720773
2243876668 2243876668
2352771636 2352771636
1379316772 1379316772
1981010483 1981010483
3153207042 3153207042
3063185403 3063185403
2111342942 2111342942
1070999471 1070999471
2842412602 2842412602
319743464 319743464
586335663 586335663
2597304042 2597304042
2894075722 2894075722
3390066269 3390066269
2753316963 2753316963

0 failures, 10000 passes.

Finally, since I used es6 import syntax, if you want to run this without thinking too hard about it, make sure the extension is .mjs, and use: node --experimental-modules mersennetest.mjs