Confusion about `Random`
I am confused about why Program 1 produces random output but Program 2 does not. --- ### Program 1 ```d import std.stdio; import std.conv; import std.random; Mt19937 rnd; double rand01(){ // Uniform random sampling in [0,1) return uniform( 0.0, 1.0, rnd); } void main(){ rnd = Random( unpredictableSeed ); for( uint i = 0; i < 6; i++ ){ writeln( rand01() ); } } ``` Output: ``` 0.35332 0.0687847 0.563096 0.37718 0.321598 0.530525 ``` --- ### Program 2 sparrow_core.d ```d // ... Mt19937 rnd; // Randomness void init_random(){ // Seed the RNG with the clock rnd = Random( unpredictableSeed ); } // ... double rand01(){ // Uniform random sampling in [0,1) return uniform( 0.0, 1.0, rnd); } // ... // Build a dict of primitive symbols primitiveSymbols["rand"] = function Atom*(){ // Random number on [0,1) return new Atom( rand01() ); // Construct an Atom holding a random value }; // ... void init_SPARROW(){ // Populate necessary global structures init_reserved(); // - Reserved symbols init_env(); // -- Global context init_primitives(); // Special atoms and Primitive Functions init_specials(); // - Special forms init_random(); // --- RNG } ``` app.d ```d void main( string[] args ){ Atom* res = null; if( _DEBUG_VERBOSE ) writeln( "Args are: " ~ args.to!string ); // Populate necessary interpreter components init_SPARROW(); // ... Interpreter repeatedly invokes primitive symbol "rand" } ``` Output: ``` 0.961451 0.961451 0.961451 0.961451 0.961451 0.961451 ``` Note: I have enclosed `uniform` so deeply because I am implementing the random number feature of a [computer language](https://github.com/jwatson-CO-edu/SPARROW). --- What is the reason for this? Has the compiler optimized away the `uniform` call to a single double number? What is the main difference between Program 1 and Program 2? Both seem to: * Have a global RNG `rnd` * Seed RNG after `main` starts. * Generates a random number on [1,0) from a function. So I would expect both programs to behave the same...
Re: Confusion about `Random`
On Thursday, 22 December 2022 at 16:23:16 UTC, jwatson-CO-edu wrote: I am confused about why Program 1 produces random output but Program 2 does not. The code you have posted as "Program 2" is incomplete, and cannot be compiled as-is. I have made some changes in order to get it to compile and produce useful output, resulting in the following program: sparrow_core.d ```d module sparrow_core; import std.random; Mt19937 rnd; // Randomness void init_random(){ // Seed the RNG with the clock rnd = Random( unpredictableSeed ); } double rand01(){ // Uniform random sampling in [0,1) return uniform( 0.0, 1.0, rnd); } void init_SPARROW(){ // Populate necessary global structures init_random(); // --- RNG } ``` app.d ```d module app; import sparrow_core; import std.stdio; void main(){ init_SPARROW(); foreach (i; 0 .. 6) writeln(rand01()); } ``` When I compile and run the above program, I get the following output: ``` 0.289729 0.39377 0.693163 0.232496 0.388511 0.840994 ``` So, as far as I can tell, there is nothing wrong with your code, and the random number generator is working as intended. Most likely you have made a mistake somewhere in the part of the code that you did not post, and that mistake is what's causing the lack of randomness you observed in the output.
Re: Confusion about `Random`
On Thursday, 22 December 2022 at 17:33:48 UTC, Paul Backus wrote: So, as far as I can tell, there is nothing wrong with your code, and the random number generator is working as intended. Most likely you have made a mistake somewhere in the part of the code that you did not post, and that mistake is what's causing the lack of randomness you observed in the output. Right, the entire project is about 2k lines, so I didn't post it. I've isolated the problem to instances when my program is interpreting a loop. Somehow the loop context must be storing an old seed for `rnd`? I'm still searching for the issue and I have not been able to replicate it in smaller example.
Re: Confusion about `Random`
On Thu, Dec 22, 2022 at 08:17:56PM +, jwatson-CO-edu via Digitalmars-d-learn wrote: > On Thursday, 22 December 2022 at 17:33:48 UTC, Paul Backus wrote: > > So, as far as I can tell, there is nothing wrong with your code, and > > the random number generator is working as intended. > > > > Most likely you have made a mistake somewhere in the part of the > > code that you did not post, and that mistake is what's causing the > > lack of randomness you observed in the output. > > Right, the entire project is about 2k lines, so I didn't post it. > I've isolated the problem to instances when my program is interpreting > a loop. Somehow the loop context must be storing an old seed for > `rnd`? I'm still searching for the issue and I have not been able to > replicate it in smaller example. [...] You could try using DustMite to reduce it to a minimal (or at least smaller) example. My personal guess is that you forgot a `ref` somewhere when you pass the RNG to a function. Given that due to historical accident std.random uses structs for RNG implementations, and this can sometimes lead to unexpected results when you unintentionally passed an RNG state by value instead of by reference. One thing to try could be to scan all your function signatures where an RNG is passed, and make sure there's a `ref` on it. T -- Give a man a fish, and he eats once. Teach a man to fish, and he will sit forever.
Re: Confusion about `Random`
On 12/22/22 11:23 AM, jwatson-CO-edu wrote: I am confused about why Program 1 produces random output but Program 2 does not. --- ### Program 1 ```d import std.stdio; import std.conv; import std.random; Mt19937 rnd; double rand01(){ // Uniform random sampling in [0,1) return uniform( 0.0, 1.0, rnd); } void main(){ rnd = Random( unpredictableSeed ); for( uint i = 0; i < 6; i++ ){ writeln( rand01() ); } } ``` Output: ``` 0.35332 0.0687847 0.563096 0.37718 0.321598 0.530525 ``` --- ### Program 2 sparrow_core.d ```d // ... Mt19937 rnd; // Randomness void init_random(){ // Seed the RNG with the clock rnd = Random( unpredictableSeed ); } // ... double rand01(){ // Uniform random sampling in [0,1) return uniform( 0.0, 1.0, rnd); } // ... // Build a dict of primitive symbols primitiveSymbols["rand"] = function Atom*(){ // Random number on [0,1) return new Atom( rand01() ); // Construct an Atom holding a random value }; // ... void init_SPARROW(){ // Populate necessary global structures init_reserved(); // - Reserved symbols init_env(); // -- Global context init_primitives(); // Special atoms and Primitive Functions init_specials(); // - Special forms init_random(); // --- RNG } ``` app.d ```d void main( string[] args ){ Atom* res = null; if( _DEBUG_VERBOSE ) writeln( "Args are: " ~ args.to!string ); // Populate necessary interpreter components init_SPARROW(); // ... Interpreter repeatedly invokes primitive symbol "rand" } ``` Output: ``` 0.961451 0.961451 0.961451 0.961451 0.961451 0.961451 ``` Note: I have enclosed `uniform` so deeply because I am implementing the random number feature of a [computer language](https://github.com/jwatson-CO-edu/SPARROW). --- What is the reason for this? Has the compiler optimized away the `uniform` call to a single double number? What is the main difference between Program 1 and Program 2? Both seem to: * Have a global RNG `rnd` * Seed RNG after `main` starts. * Generates a random number on [1,0) from a function. So I would expect both programs to behave the same... Without the rest of the code, and how random is called, I have a hunch... Are you using threads by any chance? If, for instance, your calls to rand01 are done in a new thread, that new thread will have a *default* state of Mt19937. I tried out just a non-seeded instance, and it did not produce that exact number, so this may not be the case. But in case you are, you should be aware that globals get one instance per thread, and are default initialized. -Steve
Re: Confusion about `Random`
On Thursday, 22 December 2022 at 16:23:16 UTC, jwatson-CO-edu wrote: What is the reason for this? Has the compiler optimized away the `uniform` call to a single double number? What is the main difference between Program 1 and Program 2? Both seem to: * Have a global RNG `rnd` * Seed RNG after `main` starts. * Generates a random number on [1,0) from a function. So I would expect both programs to behave the same... I made your code runnable based on the information you provided us. There seems to be no problem if you try. You can try using static this. ```d import std.random; static this() { } // can try using Mt19937 rnd; void init_random() { rnd = Random(unpredictableSeed); } double rand01() { return uniform(0, 1.0, rnd); } void main() { init_random(); struct Atom { double num; } alias atom = Atom* function(); atom[string] primitiveSymbols = [ "rand" : () => new Atom(rand01) ]; import std.stdio; writeln(*primitiveSymbols["rand"]()); // Atom(0.630001) } ``` SDB@79
Re: Confusion about `Random`
On Friday, 23 December 2022 at 00:58:01 UTC, Steven Schveighoffer wrote: Without the rest of the code, and how random is called, I have a hunch... Are you using threads by any chance? If, for instance, your calls to rand01 are done in a new thread, that new thread will have a *default* state of Mt19937. -Steve Good question, Steve, but I do not intentionally start any threads. Below is the machinery that interprets a for-loop. Do you see anything that would enclose a previous state of the RNG? ```d specialForms["for"] = function ExprInContext( ExprInContext eINc ){ // Execute a `for` loop, Default is to increment up by one // 1. Parse loop args Atom*[] loopArgs = flatten_atom_list( second( eINc.expr ) ); // Fetch args string iVarName = loopArgs[0].str; //Get the counter var name boolincrByOne = (loopArgs.length == 3); double loBound = 0.0; double hiBound = 0.0; double incr = 1.0; double i /*---*/ = 0.0; Atom* loopProg = third( eINc.expr ); // WARNING: TYPE NOT CHECKED Atom* rtnExpr = null; Env*nuEnv = null; ExprInContext runBlock; // Case: Default loop increments by 1.0 if( incrByOne ){ loBound = loopArgs[1].num; hiBound = loopArgs[2].num; // Case: User-specified increment }else if(loopArgs.length == 4){ loBound = loopArgs[1].num; incr= loopArgs[2].num; hiBound = loopArgs[3].num; // Else: There is a syntax error }else return ExprInContext( new Atom( F_Error.SYNTAX, loopArgs.length.to!string ~ " was an incorrect number of loop args. Expected 3 or 4." ), eINc.context, "`for` got an unexpected number of args" ); // 2. Create a new nested context, bind the counter var i = loBound; nuEnv = new Env(); nuEnv.parent = eINc.context; bind_atom( nuEnv, iVarName, new Atom( loBound ) ); runBlock = ExprInContext( loopProg, nuEnv, "loop body" ); // 3. LOOP: while( i <= hiBound ){ // run block in nested context rtnExpr = block_meaning( runBlock ).expr; i += incr; // increment // Store new counter value so that loop body can access it bind_atom( nuEnv, iVarName, new Atom( i ) ); } return ExprInContext( rtnExpr, eINc.context, "loop result" ); }; ```
Re: Confusion about `Random`
On Friday, 23 December 2022 at 07:25:23 UTC, Salih Dincer wrote: You can try using static this. ```d import std.random; static this() { } // can try using Mt19937 rnd; void init_random() { rnd = Random(unpredictableSeed); } double rand01() { return uniform(0, 1.0, rnd); } void main() { init_random(); struct Atom { double num; } alias atom = Atom* function(); atom[string] primitiveSymbols = [ "rand" : () => new Atom(rand01) ]; import std.stdio; writeln(*primitiveSymbols["rand"]()); // Atom(0.630001) } ``` SDB@79 Salih, I would like to implement this tactic, but I do not understand it. What are you creating here? ```d static this() { } // can try using ``` What is this operator? ```d \*...*\ () => new Atom(rand01) \*...*\ ```
Re: Confusion about `Random`
On Friday, 23 December 2022 at 00:00:06 UTC, H. S. Teoh wrote: You could try using DustMite to reduce it to a minimal (or at least smaller) example. My personal guess is that you forgot a `ref` somewhere when you pass the RNG to a function. Given that due to historical accident std.random uses structs for RNG implementations, and this can sometimes lead to unexpected results when you unintentionally passed an RNG state by value instead of by reference. One thing to try could be to scan all your function signatures where an RNG is passed, and make sure there's a `ref` on it. T I had not passed the RNG in any case, but instead accessed the global RNG from inside any function that uses it. Is that a potential issue?
Re: Confusion about `Random`
On 12/23/22 10:07 AM, jwatson-CO-edu wrote: On Friday, 23 December 2022 at 00:58:01 UTC, Steven Schveighoffer wrote: Without the rest of the code, and how random is called, I have a hunch... Are you using threads by any chance? If, for instance, your calls to rand01 are done in a new thread, that new thread will have a *default* state of Mt19937. Good question, Steve, but I do not intentionally start any threads. Below is the machinery that interprets a for-loop. Do you see anything that would enclose a previous state of the RNG? Your code looks like it's making a function pointer, and that function pointer directly uses the global RNG. I'm not seeing how your code could be copying the RNG somehow, as I'm assuming it's not manipulating the generated code from the compiler. If it's not a threading problem, the only other possibility I can think of is that your loop code is not truly calling that function over and over. I'd start instrumenting rand01 with some printouts, and see if it's doing what you expect. If it's not, throw and catch an exception, and print the stack trace (or use a debugger) to help understand what is happening. I have been puzzled in the past with behavior that seemed to be reasonable, but given the way the implementation happened, did unexpected things (like caching values). -Steve
Re: Confusion about `Random`
On Fri, Dec 23, 2022 at 03:21:24PM +, jwatson-CO-edu via Digitalmars-d-learn wrote: > On Friday, 23 December 2022 at 00:00:06 UTC, H. S. Teoh wrote: [...] > > My personal guess is that you forgot a `ref` somewhere when you pass > > the RNG to a function. Given that due to historical accident > > std.random uses structs for RNG implementations, and this can > > sometimes lead to unexpected results when you unintentionally passed > > an RNG state by value instead of by reference. One thing to try > > could be to scan all your function signatures where an RNG is > > passed, and make sure there's a `ref` on it. [...] > I had not passed the RNG in any case, but instead accessed the global > RNG from inside any function that uses it. Is that a potential issue? Hmm, in that case it's probably not a problem with `ref`. You probably should give DustMite a shot; from the snippets you've posted so far we haven't found any clues of what might have gone wrong. To narrow down the issue we really need to start from the original code and reduce it to a minimal case. https://github.com/CyberShadow/DustMite T -- Дерево держится корнями, а человек - друзьями.
Re: Confusion about `Random`
On Friday, 23 December 2022 at 07:25:23 UTC, Salih Dincer wrote: You can try using static this. ```d import std.random; static this() { } // can try using Mt19937 rnd; void init_random() { rnd = Random(unpredictableSeed); } double rand01() { return uniform(0, 1.0, rnd); } void main() { init_random(); struct Atom { double num; } alias atom = Atom* function(); atom[string] primitiveSymbols = [ "rand" : () => new Atom(rand01) ]; import std.stdio; writeln(*primitiveSymbols["rand"]()); // Atom(0.630001) } ``` SDB@79 I would still like to learn about this idiom. Can you tell me what it means and when I should use it?
Re: Confusion about `Random`
On Friday, 23 December 2022 at 17:53:24 UTC, H. S. Teoh wrote: You probably should give DustMite a shot; from the snippets you've posted so far we haven't found any clues of what might have gone wrong. To narrow down the issue we really need to start from the original code and reduce it to a minimal case. https://github.com/CyberShadow/DustMite T No need, I have ample logging already written into the program. False alarm everyone, there was a logical error in my code. And not only this, but the error appeared in **none** of the code I posted, sorry! I was working from a wrong assumption. I counted calls to my `rand01` function and realized that the number of calls was equal to the number of times `rand` appeared in the file being interpreted, rather than the number of times it should have been evaluated. Then it became clear that the parser was replacing calls to `rand` with a number, which was displayed repeatedly when a loop was evaluated. I removed `rand` from the dictionary of substitutions the parser needs to make.
Re: Confusion about `Random`
On 12/24/22 08:18, jwatson-CO-edu wrote: > On Friday, 23 December 2022 at 07:25:23 UTC, Salih Dincer wrote: >> You can try using static this. >> >> ```d >> import std.random; >> >> static this() { } // can try using static this() blocks: executed when a thread a starts (the program has at least one thread: the main thread); so you can put initializations here ~static this() blocks: counterparts of 'static this', executed once for each thread that is terminating shared static this() blocks: executed once per program (executed by the main thread) ~shared static this() blocks executed once per program when the program is terminating >> "rand" : () => new Atom(rand01) That's the lambda (ananymous function) syntax. The "rand" key of an associative array is being associated with the function after the ':' character. In the code above, the function creates a new Atom object. So, when the following code is executed: primitiveSymbols["rand"] the same lambda would be returned but the execution of that lambda as the following primitiveSymbols["rand"]() would return a new Atom which would make a call to the rand01() function and get a new random number from the same 'rnd' object. Ali
Re: Confusion about `Random`
On Saturday, 24 December 2022 at 16:16:17 UTC, jwatson-CO-edu wrote: Then it became clear that the parser was replacing calls to `rand` with a number, which was displayed repeatedly when a loop was evaluated. Sounds like a case of https://xkcd.com/221/ BTW, you don't need to explicitly initialize unpredictableSeed, because that's how it is already configured dby efault. Setting the seed is useful if you are interested in a specific PRNG algorithm with a specific seed to produce a predictable deterministic pattern. Another thing is that the current implementation of `std.random` in Phobos is extremely slow and with some tweaks it [can be up to 3x-6x times faster](https://codeforces.com/blog/entry/99292#comment-881097). There's a more advanced [mir-random](https://code.dlang.org/packages/mir-random) third-party dub package, but I'm not sure whether they have any plans to backport all of their optimizations to Phobos (or whether such contribution would be accepted).
Re: Confusion about `Random`
On Saturday, 24 December 2022 at 16:42:36 UTC, Siarhei Siamashka wrote: Sounds like a case of https://xkcd.com/221/ BTW, you don't need to explicitly initialize unpredictableSeed, Another thing is that the current implementation of `std.random` in Phobos is extremely slow Even better, I automated the caching of dice rolls to be used later as random numbers! `;P` Thanks for the tips.
Re: Confusion about `Random`
On Saturday, 24 December 2022 at 16:34:29 UTC, Ali Çehreli wrote: static this() blocks: executed when a thread a starts (the program has at least one thread: the main thread); so you can put initializations here ~static this() blocks: counterparts of 'static this', executed once for each thread that is terminating shared static this() blocks: executed once per program (executed by the main thread) ~shared static this() blocks executed once per program when the program is terminating >> "rand" : () => new Atom(rand01) That's the lambda (ananymous function) syntax. The "rand" key of an associative array is being associated with the function after the ':' character. In the code above, the Ali, your post contained at least 3 things I did not previously know about D; thank you! And thank you all for helping troubleshoot this issue with my hobby language!
Re: Confusion about `Random`
On 12/24/22 09:58, jwatson-CO-edu wrote: >> ~static this() Should be 'static ~this()'. >> ~shared static this() Should be 'shared static ~this()'. > thank you all Happy to be helpful... Ali
Re: Confusion about `Random`
On Saturday, 24 December 2022 at 17:58:04 UTC, jwatson-CO-edu wrote: Ali, your post contained at least 3 things I did not previously know about D; thank you! And thank you all for helping troubleshoot this issue with my hobby language! Thank you for completing me: Tesekkurler hocam in Turkish. Meanwhile, the compile-time and associative array capabilities are incredible: ```d alias oT = int; enum opMap = [ "×": (oT a, oT b) => a * b, "÷": (oT a, oT b) => a / b, //... ]; auto doubleAndDivide(oT first, oT second) { const foldedDouble = opMap["×"](first, 2); return opMap["÷"](foldedDouble, second); } void main() { assert(6.doubleAndDivide(3) == 4); } ``` Don't you think it's delicious too? It's impossible not to love D. SDB@79
Re: Confusion about `Random`
On Saturday, 24 December 2022 at 17:55:11 UTC, jwatson-CO-edu wrote: On Saturday, 24 December 2022 at 16:42:36 UTC, Siarhei Siamashka wrote: Sounds like a case of https://xkcd.com/221/ BTW, you don't need to explicitly initialize unpredictableSeed, Another thing is that the current implementation of `std.random` in Phobos is extremely slow Even better, I automated the caching of dice rolls to be used later as random numbers! `;P` Just in case if you are not joking, caching a certain amount of dice rolls to reuse them later in a circular fashion would make a bad quality pseudorandom number generator (a slightly upgraded version of the xkcd joke). Don't do this. Just use `std.random` if performance doesn't really matter and you want to avoid an extra library dependency. But if performance does matter and you need hundreds of millions of random numbers for Monte Carlo simulations, fuzz testing or anything else, then consider `mir.random`. BTW, a few puzzles from https://adventofcode.com/2022 are trivialized by using randomized bruteforce instead of a more sophisticated algorithm. And having fast random numbers generation helps.
Re: Confusion about `Random`
On Sunday, 25 December 2022 at 14:47:49 UTC, Siarhei Siamashka wrote: Just in case if you are not joking, caching a certain amount of dice rolls to reuse them later in a circular fashion would make a bad quality pseudorandom number generator (a slightly upgraded version of the xkcd joke). Don't do this. Naturally. I was joking, and I was also describing the erroneous behavior of the parser of the language I was writing; which was to substitute/cache a randomly generated number into a concrete syntax tree when interpreted, instead of making actual calls to the RNG at runtime, which is the correct (now current) behavior. The joke was that I automated the manual dice-rolling part to arrive more efficiently at the incorrect solution.