www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Confusion about `Random`

reply jwatson-CO-edu <real.name colorado.edu> writes:
I am confused about why Program 1 produces random output but 
Program 2 does not.

---


```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
```

---



```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
}
```


```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...
Dec 22 2022
next sibling parent reply Paul Backus <snarwin gmail.com> writes:
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: ```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 } ``` ```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.
Dec 22 2022
parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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.
Dec 22 2022
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Thu, Dec 22, 2022 at 08:17:56PM +0000, 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.
Dec 22 2022
parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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?
Dec 23 2022
parent reply "H. S. Teoh" <hsteoh qfbox.info> writes:
On Fri, Dec 23, 2022 at 03:21:24PM +0000, 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 -- Дерево держится корнями, а человек - друзьями.
Dec 23 2022
parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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.
Dec 24 2022
parent reply Siarhei Siamashka <siarhei.siamashka gmail.com> writes:
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).
Dec 24 2022
parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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.
Dec 24 2022
parent reply Siarhei Siamashka <siarhei.siamashka gmail.com> writes:
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.
Dec 25 2022
parent jwatson-CO-edu <real.name colorado.edu> writes:
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.
Dec 27 2022
prev sibling next sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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.
 
 ---
 

 ```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
 ```
 
 ---
 


 ```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
 }
 ```
 

 ```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
Dec 22 2022
parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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 bool incrByOne = (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" ); }; ```
Dec 23 2022
parent Steven Schveighoffer <schveiguy gmail.com> writes:
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
Dec 23 2022
prev sibling parent reply Salih Dincer <salihdb hotmail.com> writes:
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
Dec 22 2022
next sibling parent jwatson-CO-edu <real.name colorado.edu> writes:
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) \*...*\ ```
Dec 23 2022
prev sibling parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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?
Dec 24 2022
parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Dec 24 2022
parent reply jwatson-CO-edu <real.name colorado.edu> writes:
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!
Dec 24 2022
next sibling parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Dec 24 2022
prev sibling parent Salih Dincer <salihdb hotmail.com> writes:
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
Dec 24 2022