RW
Ray West
Tue, Oct 18, 2022 1:07 PM
Is this the way that random numbers should behave?
echo(rands(0,10,1)[0]);
echo(rands(20,30,1)[0]);
running the above a number of times, gives different random numbers -
as i would expect.
but seeding one, fixes both e.g.
echo(rands(0,10,1,0)[0]);
echo(rands(20,30,1)[0]);
running this a number of times gives the same pair each time. I would
expect the first one to be fixed, the second changing.
Is it possible to use a real time clock/timer as a seed?
Is this the way that random numbers should behave?
echo(rands(0,10,1)[0]);
echo(rands(20,30,1)[0]);
running the above a number of times, gives different random numbers -
as i would expect.
but seeding one, fixes both e.g.
echo(rands(0,10,1,0)[0]);
echo(rands(20,30,1)[0]);
running this a number of times gives the same pair each time. I would
expect the first one to be fixed, the second changing.
Is it possible to use a real time clock/timer as a seed?
JB
Jordan Brown
Tue, Oct 18, 2022 3:49 PM
I may be wrong, but it appears that once a seed is generated for
random numbers, it applies to every instant of rands.
The usual behavior of PRNG functions like this is that when you set the
seed, that seeds all further calls until you set it again.
So if you set the seed to a particular value, and you collect a thousand
values, you'll get a thousand values that look random. If you use the
same seed, you'll get the same thousand values. If you use a different
seed, you'll get a different thousand values.
Hans's scheme uses the PRNG differently, using a controlled and
predictable seed so that the "random" values are not at all random -
each one is precisely controlled and repeatable (without having to
restart the sequence).
> I may be wrong, but it appears that once a seed is generated for
> random numbers, it applies to every instant of rands.
The usual behavior of PRNG functions like this is that when you set the
seed, that seeds all further calls until you set it again.
So if you set the seed to a particular value, and you collect a thousand
values, you'll get a thousand values that look random. If you use the
same seed, you'll get the same thousand values. If you use a different
seed, you'll get a different thousand values.
Hans's scheme uses the PRNG differently, using a controlled and
predictable seed so that the "random" values are not at all random -
each one is precisely controlled and repeatable (without having to
restart the sequence).
JB
Jordan Brown
Tue, Oct 18, 2022 6:31 PM
On 10/18/2022 6:07 AM, Ray West wrote:
but seeding one, fixes both e.g.
echo(rands(0,10,1,0)[0]);
echo(rands(20,30,1)[0]);
running this a number of times gives the same pair each time. I would
expect the first one to be fixed, the second changing.
That's the way that it usually works. The seed controls all following
results. Many libraries in other languages have a separate "set seed"
function, but I think a few are like this where you can optionally
supply a seed to the random number function.
The idea is that (if you want repeatable random numbers) you set the
seed once, and then let the PNRG give you a stream of numbers. That
stream will be different for each original seed, but the same if the
seed is the same.
Is it possible to use a real time clock/timer as a seed?
OpenSCAD does not have such a function. I don't know what the default
seed is, but after you set the seed there's not much available in the
way of additional randomness.
Depending on the exact scenario, you might be able to grab a random
number, set the seed to something constant, grab however many random
numbers you need based off of that seed, then re-seed with the original
random number.
Here's a function that saves off a random number, sets a specified seed,
derives a number of random values, then re-seeds based on that saved
random number to re-establish an unpredictable stream.
function fixedrands(a, b, n, seed) =
let (old = rands(0,1,1)[0])
let (new = rands(a, b, n, seed))
let (junk = rands(0,1,1,old))
new;
echo(rands(0,10,1)[0]);
echo(fixedrands(0,10,10,1));
echo(rands(0,10,1)[0]);
Note that as you run it multiple times, the first and last numbers
change but the numbers in the middle stay the same.
I don't know whether this damages the randomness of the subsequent
values, but for most purposes it's probably OK. I wouldn't use it for
cryptography. But then again I don't think many people are doing
cryptography in OpenSCAD.
On 10/18/2022 6:07 AM, Ray West wrote:
> but seeding one, fixes both e.g.
>
> echo(rands(0,10,1,0)[0]);
> echo(rands(20,30,1)[0]);
>
> running this a number of times gives the same pair each time. I would
> expect the first one to be fixed, the second changing.
That's the way that it usually works. The seed controls all following
results. Many libraries in other languages have a separate "set seed"
function, but I think a few are like this where you can optionally
supply a seed to the random number function.
The idea is that (if you want repeatable random numbers) you set the
seed once, and then let the PNRG give you a stream of numbers. That
stream will be different for each original seed, but the same if the
seed is the same.
> Is it possible to use a real time clock/timer as a seed?
OpenSCAD does not have such a function. I don't know what the default
seed is, but after you set the seed there's not much available in the
way of additional randomness.
Depending on the exact scenario, you might be able to grab a random
number, set the seed to something constant, grab however many random
numbers you need based off of that seed, then re-seed with the original
random number.
Here's a function that saves off a random number, sets a specified seed,
derives a number of random values, then re-seeds based on that saved
random number to re-establish an unpredictable stream.
function fixedrands(a, b, n, seed) =
let (old = rands(0,1,1)[0])
let (new = rands(a, b, n, seed))
let (junk = rands(0,1,1,old))
new;
echo(rands(0,10,1)[0]);
echo(fixedrands(0,10,10,1));
echo(rands(0,10,1)[0]);
Note that as you run it multiple times, the first and last numbers
change but the numbers in the middle stay the same.
I don't know whether this damages the randomness of the subsequent
values, but for most purposes it's probably OK. I wouldn't use it for
cryptography. But then again I don't think many people are doing
cryptography in OpenSCAD.
JB
Jordan Brown
Tue, Oct 18, 2022 6:46 PM
I don't know if it's helpful here, but just for fun I tried playing with
generating random wavy lines.
function torect2(p) = [
p[0] * cos(p[1]),
p[0] * sin(p[1])
];
// Bezier functions from https://www.thingiverse.com/thing:8443
// but that yielded either single points or a raft of triangles;
// this yields a vector of points that you can then concatenate
// with other pieces to form a single polygon.
// If we were really clever, I think it would be possible to
// automatically space the output points based on how linear
// the curve is at that point. But right now I'm not that clever.
function BEZ03(u) = pow((1-u), 3);
function BEZ13(u) = 3*u*(pow((1-u),2));
function BEZ23(u) = 3*(pow(u,2))*(1-u);
function BEZ33(u) = pow(u,3);
function PointAlongBez4(p0, p1, p2, p3, u) = [
BEZ03(u)*p0[0]+BEZ13(u)*p1[0]+BEZ23(u)*p2[0]+BEZ33(u)*p3[0],
BEZ03(u)*p0[1]+BEZ13(u)*p1[1]+BEZ23(u)*p2[1]+BEZ33(u)*p3[1]];
// p0 - start point
// p1 - control point 1, line departs p0 headed this way
// p2 - control point 2, line arrives at p3 from this way
// p3 - end point
// segs - number of segments
function bez(p0, p1, p2, p3, segs) = [
for (i = [0:segs]) PointAlongBez4(p0, p1, p2, p3, i/segs)
];
function bezpolar(p0, p1, p2, p3, segs) =
bez(p0, p0+torect2(p1), p3+torect2(p2), p3, segs);
function rand1(a, b) = rands(a, b, 1)[0];
function point(maxy, maxa, minc, maxc) = [
rand1(0, maxy),
rand1(-maxa, maxa),
rand1(minc, maxc),
rand1(minc, maxc)
];
function steps(w, minstep, maxstep) = [
0,
for (x = rand1(minstep, maxstep);
x + minstep < w;
x = x + rand1(minstep, maxstep))
x,
w
];
function wavy(w, minstep, maxstep, maxy, maxa, minc, maxc) =
let (xvalues = steps(w, minstep, maxstep))
let (controls = [
for (i=[0:len(xvalues)-1])
let(p = point(maxy, maxa, minc, maxc))
[
[ xvalues[i], p[0] ], p[1], p[2], p[3]
]
])
[
for (i=[0:len(controls)-2])
let(c = controls[i], next = controls[i+1])
each bezpolar(
c[0],
[c[3], c[1]],
[next[2], 180+next[1]],
next[0],
10)
];
w = 100;
minstep = 10;
maxstep = 30;
maxy = 10;
maxa = 30;
minc = 3;
maxc = 5;
points = wavy(w, minstep, maxstep, maxy, maxa, minc, maxc);
polygon([
[0,0],
each points,
[w,0]
]);
The parameters to wavy() are:
- w - the width of the wavy line
- minstep - the minimum x distance between "reference points" where
the line curvature changes
- maxstep - the maximum x distance between reference points
- maxy - the maximum y position at a reference point (minimum is zero)
- maxa - the maximum angle that the line will have crossing the
reference point (minimum is the negative of this)
- minc - the minimum distance from the control point to the Bezier
control point
- maxc - the maximum distance from the control point to the Bezier
control point
Here's some sample outputs, with the parameters above:
What it does is to pick randomly spaced X values that total up to the
desired width[*]. For each X value, it picks a Y value and two Bezier
control points, at the same random angle but different random
distances. Then for each segment between two of those points, it
generates a Bezier curve using the first point and its right-side
control point and the second point and its left-side control point.
[*] When the next value would be less than the minimum from the end, it
stops, so the last step can be up to min+max long. There's probably a
better way to stop.
I don't know if it's helpful here, but just for fun I tried playing with
generating random wavy lines.
function torect2(p) = [
p[0] * cos(p[1]),
p[0] * sin(p[1])
];
// Bezier functions from https://www.thingiverse.com/thing:8443
// but that yielded either single points or a raft of triangles;
// this yields a vector of points that you can then concatenate
// with other pieces to form a single polygon.
// If we were really clever, I think it would be possible to
// automatically space the output points based on how linear
// the curve is at that point. But right now I'm not that clever.
function BEZ03(u) = pow((1-u), 3);
function BEZ13(u) = 3*u*(pow((1-u),2));
function BEZ23(u) = 3*(pow(u,2))*(1-u);
function BEZ33(u) = pow(u,3);
function PointAlongBez4(p0, p1, p2, p3, u) = [
BEZ03(u)*p0[0]+BEZ13(u)*p1[0]+BEZ23(u)*p2[0]+BEZ33(u)*p3[0],
BEZ03(u)*p0[1]+BEZ13(u)*p1[1]+BEZ23(u)*p2[1]+BEZ33(u)*p3[1]];
// p0 - start point
// p1 - control point 1, line departs p0 headed this way
// p2 - control point 2, line arrives at p3 from this way
// p3 - end point
// segs - number of segments
function bez(p0, p1, p2, p3, segs) = [
for (i = [0:segs]) PointAlongBez4(p0, p1, p2, p3, i/segs)
];
function bezpolar(p0, p1, p2, p3, segs) =
bez(p0, p0+torect2(p1), p3+torect2(p2), p3, segs);
function rand1(a, b) = rands(a, b, 1)[0];
function point(maxy, maxa, minc, maxc) = [
rand1(0, maxy),
rand1(-maxa, maxa),
rand1(minc, maxc),
rand1(minc, maxc)
];
function steps(w, minstep, maxstep) = [
0,
for (x = rand1(minstep, maxstep);
x + minstep < w;
x = x + rand1(minstep, maxstep))
x,
w
];
function wavy(w, minstep, maxstep, maxy, maxa, minc, maxc) =
let (xvalues = steps(w, minstep, maxstep))
let (controls = [
for (i=[0:len(xvalues)-1])
let(p = point(maxy, maxa, minc, maxc))
[
[ xvalues[i], p[0] ], p[1], p[2], p[3]
]
])
[
for (i=[0:len(controls)-2])
let(c = controls[i], next = controls[i+1])
each bezpolar(
c[0],
[c[3], c[1]],
[next[2], 180+next[1]],
next[0],
10)
];
w = 100;
minstep = 10;
maxstep = 30;
maxy = 10;
maxa = 30;
minc = 3;
maxc = 5;
points = wavy(w, minstep, maxstep, maxy, maxa, minc, maxc);
polygon([
[0,0],
each points,
[w,0]
]);
The parameters to wavy() are:
* w - the width of the wavy line
* minstep - the minimum x distance between "reference points" where
the line curvature changes
* maxstep - the maximum x distance between reference points
* maxy - the maximum y position at a reference point (minimum is zero)
* maxa - the maximum angle that the line will have crossing the
reference point (minimum is the negative of this)
* minc - the minimum distance from the control point to the Bezier
control point
* maxc - the maximum distance from the control point to the Bezier
control point
Here's some sample outputs, with the parameters above:
What it does is to pick randomly spaced X values that total up to the
desired width[*]. For each X value, it picks a Y value and two Bezier
control points, at the same random angle but different random
distances. Then for each segment between two of those points, it
generates a Bezier curve using the first point and its right-side
control point and the second point and its left-side control point.
[*] When the next value would be less than the minimum from the end, it
stops, so the last step can be up to min+max long. There's probably a
better way to stop.
RW
Rogier Wolff
Tue, Oct 18, 2022 7:22 PM
On Tue, Oct 18, 2022 at 11:31:35AM -0700, Jordan Brown wrote:
The idea is that (if you want repeatable random numbers) you set the
seed once, and then let the PNRG give you a stream of numbers. That
stream will be different for each original seed, but the same if the
seed is the same.
For SOME applications having the same stream of random numbers is
entirely unwanted. For example, when initializing a public key, the
first step is: Choose a random prime of about 1000 bits. If everybody
(or just a few!) used the same random number sequence they would end
up knowing eachothers keys.
But in other applications having a predictable "seems random" sequence
is useful. For example, if you are debugging a program and it crashes
after 50000 loops through the main program. Debugging it after the
crash shows that at some time in the past something went wrong. So you
can try to run it 40000 times around the loop and... find it crashes
after 35000 times through the loop. Now for the debugging you'd like
it to be determistic. Now you can watch it crash after N iterations
and then run it again to see the state of the program at half-N
iterations.
In OpenScad you might want to generate "grassleaves" in random
positions. But then to animate them "waving in the wind", you don't
want a new random position for every frame. You want the "random
positions" to come out the same as the first time.
I thought I'd explain the reason why random number generators have the
"set seed" function. Some people might understand better if they know
the WHY.
Roger.
--
** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 **
** Delftechpark 11 2628 XJ Delft, The Netherlands. KVK: 27239233 **
f equals m times a. When your f is steady, and your m is going down
your a is going up. -- Chris Hadfield about flying up the space shuttle.
On Tue, Oct 18, 2022 at 11:31:35AM -0700, Jordan Brown wrote:
> The idea is that (if you want repeatable random numbers) you set the
> seed once, and then let the PNRG give you a stream of numbers. That
> stream will be different for each original seed, but the same if the
> seed is the same.
For SOME applications having the same stream of random numbers is
entirely unwanted. For example, when initializing a public key, the
first step is: Choose a random prime of about 1000 bits. If everybody
(or just a few!) used the same random number sequence they would end
up knowing eachothers keys.
But in other applications having a predictable "seems random" sequence
is useful. For example, if you are debugging a program and it crashes
after 50000 loops through the main program. Debugging it after the
crash shows that at some time in the past something went wrong. So you
can try to run it 40000 times around the loop and... find it crashes
after 35000 times through the loop. Now for the debugging you'd like
it to be determistic. Now you can watch it crash after N iterations
and then run it again to see the state of the program at half-N
iterations.
In OpenScad you might want to generate "grassleaves" in random
positions. But then to animate them "waving in the wind", you don't
want a new random position for every frame. You want the "random
positions" to come out the same as the first time.
I thought I'd explain the reason why random number generators have the
"set seed" function. Some people might understand better if they know
the WHY.
Roger.
--
** R.E.Wolff@BitWizard.nl ** https://www.BitWizard.nl/ ** +31-15-2049110 **
** Delftechpark 11 2628 XJ Delft, The Netherlands. KVK: 27239233 **
f equals m times a. When your f is steady, and your m is going down
your a is going up. -- Chris Hadfield about flying up the space shuttle.
MM
Michael Marx
Wed, Oct 19, 2022 3:38 AM
rands() with no seed specified, uses Unix time + process ID as the seed.
So even if multiple processes (on one system) run at the same time they will have unique seeds.
For those interested
https://github.com/openscad/openscad/blob/master/src/core/builtin_functions.cc
It uses mt19937 (https://cplusplus.com/reference/random/mt19937/)
with a uniform_real_distribution.
I have a program to draw random 'art', I do the following so you can recreate something
as long as you note the key. You can echo() it, but I use text(key) on the output.
It is probably overkill to seed with random seeds...
// key changes to use different geometry each time
key=floor(rands(1000,999999,1)[0]); // a number easily formated
// add a previous key here to reproduce same output
//key=373710;
s=rands(-20,20,7,key); // seed
// Number of objects
n=ceil(rands(50000,99000,1,s[0])[0]); // random
// random position & size
rx=rands(-x,x,n,s[1]);
ry=rands(-y,y,n,s[2]);
rz=rands(-2,2,n,s[3]);
rs=rands(5,35,n,s[4]); // size
rt=rands(0,3.999,n,s[5]); // type
rr=rands(0,359,n,s[6]); // rotation
-----Original Message-----
Sent: Wed, 19 Oct 2022 00:07
To: OpenSCAD general discussion
Subject: [OpenSCAD] Re: how to randomness different loops?
Is this the way that random numbers should behave?
running the above a number of times, gives different random numbers -
but seeding one, fixes both e.g.
echo(rands(0,10,1,0)[0]);
running this a number of times gives the same pair each time. I would
expect the first one to be fixed, the second changing.
Is it possible to use a real time clock/timer as a seed?
--
This email has been checked for viruses by AVG antivirus software.
www.avg.com
rands() with no seed specified, uses Unix time + process ID as the seed.
So even if multiple processes (on one system) run at the same time they will have unique seeds.
For those interested
https://github.com/openscad/openscad/blob/master/src/core/builtin_functions.cc
It uses mt19937 (https://cplusplus.com/reference/random/mt19937/)
with a uniform_real_distribution.
I have a program to draw random 'art', I do the following so you can recreate something
as long as you note the key. You can echo() it, but I use text(key) on the output.
It is probably overkill to seed with random seeds...
// key changes to use different geometry each time
key=floor(rands(1000,999999,1)[0]); // a number easily formated
// add a previous key here to reproduce same output
//key=373710;
s=rands(-20,20,7,key); // seed
// Number of objects
n=ceil(rands(50000,99000,1,s[0])[0]); // random
// random position & size
rx=rands(-x,x,n,s[1]);
ry=rands(-y,y,n,s[2]);
rz=rands(-2,2,n,s[3]);
rs=rands(5,35,n,s[4]); // size
rt=rands(0,3.999,n,s[5]); // type
rr=rands(0,359,n,s[6]); // rotation
> -----Original Message-----
> From: Ray West [mailto:raywest@raywest.com]
> Sent: Wed, 19 Oct 2022 00:07
> To: OpenSCAD general discussion
> Subject: [OpenSCAD] Re: how to randomness different loops?
>
> Is this the way that random numbers should behave?
>
> echo(rands(0,10,1)[0]);
> echo(rands(20,30,1)[0]);
>
> running the above a number of times, gives different random numbers -
> as i would expect.
>
> but seeding one, fixes both e.g.
>
> echo(rands(0,10,1,0)[0]);
> echo(rands(20,30,1)[0]);
>
> running this a number of times gives the same pair each time. I would
> expect the first one to be fixed, the second changing.
>
> Is it possible to use a real time clock/timer as a seed?
>
> _______________________________________________
> OpenSCAD mailing list
> To unsubscribe send an email to discuss-leave@lists.openscad.org
--
This email has been checked for viruses by AVG antivirus software.
www.avg.com
BL
Bryan Lee
Wed, Oct 19, 2022 4:10 PM
Thus Michael Marx hast written on Wed, Oct 19, 2022 at 02:38:02PM +1100, and, according to prophecy, it shall come to pass that:
rands() with no seed specified, uses Unix time + process ID as the seed.
So even if multiple processes (on one system) run at the same time they will
have unique seeds.
Awesome to know! Thanks Michael!
And it looks like a new time-based-seed is created each time a model is compiled:
cube(rands(1,10,1)[0]);
Thus Michael Marx hast written on Wed, Oct 19, 2022 at 02:38:02PM +1100, and, according to prophecy, it shall come to pass that:
> rands() with no seed specified, uses Unix time + process ID as the seed.
> So even if multiple processes (on one system) run at the same time they will
> have unique seeds.
Awesome to know! Thanks Michael!
And it looks like a new time-based-seed is created each time a model is compiled:
cube(rands(1,10,1)[0]);
RW
Raymond West
Wed, Oct 19, 2022 5:02 PM
Hi,
Any one with windows, try this - Those with linux try it to, see if you
have the same results.
echo(rands(0,1,1,1)[0]);
echo(rands(10,20,1)[0]);
echo(rands(20,30,1)[0]);
run it a few times, and you will each time it runs, all the random
numbers do not change. i would expect the first to be the same for each
run, and the second two to change.
If you comment out the first line, then every time it is run, they are
different, since they are using the timer, I guess. But, it seems, once
you've used a seed, the timer version is switched to using the seed.
if you use a seed, then all random numbers appear to use that seed,
whether specifically requested or not. I have not noticed this behaviour
for other random numbers in other languages in windows (Not saying they
are not the same, but that I've not noticed the problem, if seed is not
specified, the random number generator carries on using the clock seed.}
from free basic documentation, for example -
For any given seed, each algorithm will produce a specific,
deterministic sequence of numbers for that seed. If you want each call
toRandomizeto produce a different sequence of numbers, a seed that is
not quite predictable should be used - for example, the value returned
fromTimer https://www.freebasic.net/wiki/wikka.php?wakka=KeyPgTimer.
Omitting the/seed/parameter will use a value based on this.
c#, c++, achieve similar results, but by a different method
This is not how it appears to work in Openscad on windows.
in effect it should be possible to have a random number seeded by the
clock,and one generated by a seed that you assign, and both work
concurrently. In Openscad it seems that once you've set a seed , then
the one that does not have your seed selected, appears to use it anyway.
On 19/10/2022 17:10, Bryan Lee wrote:
Thus Michael Marx hast written on Wed, Oct 19, 2022 at 02:38:02PM +1100, and, according to prophecy, it shall come to pass that:
rands() with no seed specified, uses Unix time + process ID as the seed.
So even if multiple processes (on one system) run at the same time they will
have unique seeds.
Awesome to know! Thanks Michael!
And it looks like a new time-based-seed is created each time a model is compiled:
cube(rands(1,10,1)[0]);
OpenSCAD mailing list
To unsubscribe send an email todiscuss-leave@lists.openscad.org
Hi,
Any one with windows, try this - Those with linux try it to, see if you
have the same results.
echo(rands(0,1,1,1)[0]);
echo(rands(10,20,1)[0]);
echo(rands(20,30,1)[0]);
run it a few times, and you will each time it runs, all the random
numbers do not change. i would expect the first to be the same for each
run, and the second two to change.
If you comment out the first line, then every time it is run, they are
different, since they are using the timer, I guess. But, it seems, once
you've used a seed, the timer version is switched to using the seed.
if you use a seed, then all random numbers appear to use that seed,
whether specifically requested or not. I have not noticed this behaviour
for other random numbers in other languages in windows (Not saying they
are not the same, but that I've not noticed the problem, if seed is not
specified, the random number generator carries on using the clock seed.}
from free basic documentation, for example -
For any given seed, each algorithm will produce a specific,
deterministic sequence of numbers for that seed. If you want each call
to*Randomize*to produce a different sequence of numbers, a seed that is
not quite predictable should be used - for example, the value returned
fromTimer <https://www.freebasic.net/wiki/wikka.php?wakka=KeyPgTimer>.
Omitting the/seed/parameter will use a value based on this.
c#, c++, achieve similar results, but by a different method
This is not how it appears to work in Openscad on windows.
in effect it should be possible to have a random number seeded by the
clock,and one generated by a seed that you assign, and both work
concurrently. In Openscad it seems that once you've set a seed , then
the one that does not have your seed selected, appears to use it anyway.
On 19/10/2022 17:10, Bryan Lee wrote:
> Thus Michael Marx hast written on Wed, Oct 19, 2022 at 02:38:02PM +1100, and, according to prophecy, it shall come to pass that:
>> rands() with no seed specified, uses Unix time + process ID as the seed.
>> So even if multiple processes (on one system) run at the same time they will
>> have unique seeds.
>
> Awesome to know! Thanks Michael!
>
> And it looks like a new time-based-seed is created each time a model is compiled:
> cube(rands(1,10,1)[0]);
> _______________________________________________
> OpenSCAD mailing list
> To unsubscribe send an email todiscuss-leave@lists.openscad.org
JB
Jordan Brown
Wed, Oct 19, 2022 7:08 PM
This is all expected behavior, and is how PRNGs usually work, and is
much simpler than you're implying. See the bottom of the message for
the five(!) lines that do all of the work.
One time, when OpenSCAD starts up, it gives the PRNG a seed that is the
sum of the current time (in seconds since the start of 1970) and the
process ID. After that, you get a stream of numbers, all based on that
original seed.
If you give it a new seed, then all further random numbers are based on
that seed.
This is pretty normal PRNG behavior. Calling rands() with a fourth
parameter is exactly equivalent to calling BASIC's RANDOMIZE with a
parameter, and then pulling some number of random numbers.
What is unusual about OpenSCAD is that you set the seed using the "get a
random number" function, rather than using a separate operation. The
expected usage pattern is that if you want repeatable random numbers you
call rands() once, at the top of the program, with a seed, and you
discard the random number that it generates. (Or you ask it for zero
numbers, and discard the empty array that results.)
There are interesting tricks that you can do by using a controlled seed
to produce repeatable sequences, but at any time there's only one
sequence, based on the most recent seed.
Bryan said:
And it looks like a new time-based-seed is created each time a model is compiled:
No, actually not. It's seeded once at startup.
Subsequent runs get new results because the random-number generator is
not reseeded for each run of the model. It continues to produce new
random numbers based on that original seed.
Here's a test case. Run this program:
echo(rands(0,1,1,0));
echo(rands(0,1,1));
Note the results. They might well be 0.592845 and 0.844266. (Not clear
to me whether the whole PRNG chain is platform-independent.)
Now comment out the second line:
echo(rands(0,1,1,0));
//echo(rands(0,1,1));
to nobody's surprise, you get the same value as the first value from the
first run, 0.592845 for me.
Restore that second line, and comment out the first line:
//echo(rands(0,1,1,0));
echo(rands(0,1,1));
Run that. You should get the second value from the original run,
0.844266 for me.
You seeded the PRNG with zero, and pulled two numbers from it. Then you
seeded it with zero again, and again pulled two numbers from it. The
fact that the second two numbers were from different runs of the model
does not matter.
Maybe it should re-seed on every run, but it doesn't.
The implementation is quite simple, and doesn't require any specialized
knowledge to understand. Knowledge of C++ isn't even really required to
understand the gist.
https://github.com/openscad/openscad/blob/484f3c670bc9a2a35351af915fbe2f3b68462166/src/core/builtin_functions.cc#L55
through line 64 establishes the original seed. This is run one time,
when OpenSCAD starts.
https://github.com/openscad/openscad/blob/484f3c670bc9a2a35351af915fbe2f3b68462166/src/core/builtin_functions.cc#L161
through 164 reseeds when you specify a new seed.
That's the entirety of the seed management, those fourteen lines. And
only five do the real work:
std::mt19937 deterministic_rng(std::time(nullptr) + process_id);
initializes the PRNG. Then in the handling for the rands() function:
if (arguments.size() > 3) {
uint32_t seed = static_cast<uint32_t>(hash_floating_point(arguments[3]->toDouble() ));
deterministic_rng.seed(seed);
}
If you supply a fourth argument, it reseeds the PRNG with that argument.
This is all expected behavior, and is how PRNGs usually work, and is
much simpler than you're implying. See the bottom of the message for
the five(!) lines that do all of the work.
One time, when OpenSCAD starts up, it gives the PRNG a seed that is the
sum of the current time (in seconds since the start of 1970) and the
process ID. After that, you get a stream of numbers, all based on that
original seed.
If you give it a new seed, then all further random numbers are based on
*that* seed.
This is pretty normal PRNG behavior. Calling rands() with a fourth
parameter is exactly equivalent to calling BASIC's RANDOMIZE with a
parameter, and then pulling some number of random numbers.
What is unusual about OpenSCAD is that you set the seed using the "get a
random number" function, rather than using a separate operation. The
expected usage pattern is that if you want repeatable random numbers you
call rands() once, at the top of the program, with a seed, and you
discard the random number that it generates. (Or you ask it for zero
numbers, and discard the empty array that results.)
There are interesting tricks that you can do by using a controlled seed
to produce repeatable sequences, but at any time there's only one
sequence, based on the most recent seed.
Bryan said:
> And it looks like a new time-based-seed is created each time a model is compiled:
No, actually not. It's seeded once at startup.
Subsequent runs get new results because the random-number generator is
*not* reseeded for each run of the model. It continues to produce new
random numbers based on that original seed.
Here's a test case. Run this program:
echo(rands(0,1,1,0));
echo(rands(0,1,1));
Note the results. They might well be 0.592845 and 0.844266. (Not clear
to me whether the whole PRNG chain is platform-independent.)
Now comment out the second line:
echo(rands(0,1,1,0));
//echo(rands(0,1,1));
to nobody's surprise, you get the same value as the first value from the
first run, 0.592845 for me.
Restore that second line, and comment out the first line:
//echo(rands(0,1,1,0));
echo(rands(0,1,1));
Run that. You should get the second value from the original run,
0.844266 for me.
You seeded the PRNG with zero, and pulled two numbers from it. Then you
seeded it with zero again, and again pulled two numbers from it. The
fact that the second two numbers were from different runs of the model
does not matter.
Maybe it *should* re-seed on every run, but it doesn't.
---
The implementation is quite simple, and doesn't require any specialized
knowledge to understand. Knowledge of C++ isn't even really required to
understand the gist.
https://github.com/openscad/openscad/blob/484f3c670bc9a2a35351af915fbe2f3b68462166/src/core/builtin_functions.cc#L55
through line 64 establishes the original seed. This is run one time,
when OpenSCAD starts.
https://github.com/openscad/openscad/blob/484f3c670bc9a2a35351af915fbe2f3b68462166/src/core/builtin_functions.cc#L161
through 164 reseeds when you specify a new seed.
That's the entirety of the seed management, those fourteen lines. And
only five do the real work:
std::mt19937 deterministic_rng(std::time(nullptr) + process_id);
initializes the PRNG. Then in the handling for the rands() function:
if (arguments.size() > 3) {
uint32_t seed = static_cast<uint32_t>(hash_floating_point(arguments[3]->toDouble() ));
deterministic_rng.seed(seed);
}
If you supply a fourth argument, it reseeds the PRNG with that argument.
JB
Jordan Brown
Wed, Oct 19, 2022 7:12 PM
And of course there's the ultimate random number generator, with all
results determined by the roll of a die: https://xkcd.com/221/
And of course there's the ultimate random number generator, with all
results determined by the roll of a die: https://xkcd.com/221/