discuss@lists.openscad.org

OpenSCAD general discussion Mailing-list

View all threads

Using recursion to create smoothed random variation ("red" noise)

A
amundsen
Wed, Oct 21, 2020 12:22 AM

Hello,

I am trying to use a recursive function within a list comprehension to add
some random component to both x and y coordinates of each point in a polygon
(a circle actually). However I want each random component to be based on the
previous random value in order to smooth the random component (and get some
king of brownian/red noise).

Obviously there are a few things which I don't understand because my code
doesn't provide the expected results : the noise component is not smoothed.

For instance, I am not sure if "n-1" in the function definition means using
the results of the previous iteration in the current one or substracting 1
from n.

Any help would be appreciated!
/
polygon_steps = 100;
random_range = 10;

function smoothrand(n = 0, range  = 1) = ( n == 0 ? rands(-range, range,
1)[0] : smoothrand(n-1) * 0.9 + rands(-range * 0.1, range * 0.1, 1)[0] );

start_points = [for(i=[0:polygon_steps])
let(a = smoothrand(1, random_range), b = smoothrand(1, random_range))
[cos(i/polygon_steps * 360) * (0.4 * a + 10), sin(i/polygon_steps * 360) *
(0.4 * b + 10)]
];

polygon(start_points);/

--
Sent from: http://forum.openscad.org/

Hello, I am trying to use a recursive function within a list comprehension to add some random component to both x and y coordinates of each point in a polygon (a circle actually). However I want each random component to be based on the previous random value in order to smooth the random component (and get some king of brownian/red noise). Obviously there are a few things which I don't understand because my code doesn't provide the expected results : the noise component is not smoothed. For instance, I am not sure if "n-1" in the function definition means using the results of the previous iteration in the current one or substracting 1 from n. Any help would be appreciated! / polygon_steps = 100; random_range = 10; function smoothrand(n = 0, range = 1) = ( n == 0 ? rands(-range, range, 1)[0] : smoothrand(n-1) * 0.9 + rands(-range * 0.1, range * 0.1, 1)[0] ); start_points = [for(i=[0:polygon_steps]) let(a = smoothrand(1, random_range), b = smoothrand(1, random_range)) [cos(i/polygon_steps * 360) * (0.4 * a + 10), sin(i/polygon_steps * 360) * (0.4 * b + 10)] ]; polygon(start_points);/ -- Sent from: http://forum.openscad.org/
HJ
Hugo Jackson
Wed, Oct 21, 2020 12:34 AM

Have no idea how difficult this would be, but it would be helpful for some projects to be able to import an image file like a png or jpg as a 2 dimensional object, be able to scale and rotate and set luminance. It could then be used for more rapid prototyping of an existing object, or getting things like thread pitch correct… that kind of thing. Anyone else think this worthwhile?

Have no idea how difficult this would be, but it would be helpful for some projects to be able to import an image file like a png or jpg as a 2 dimensional object, be able to scale and rotate and set luminance. It could then be used for more rapid prototyping of an existing object, or getting things like thread pitch correct… that kind of thing. Anyone else think this worthwhile?
A
adrianv
Wed, Oct 21, 2020 12:52 AM

First of all, n-1 means 1 subtracted from n.  Why would it mean something
else?

To write a function like this you need to pass the list of all previous
values and then append to it in the recursion.

But I would probably approach this problem differently.  The exact details
aren't clear to me for what you're trying to do, but I'd start by
constructing a list of random perturbations.  And then I'd write a function
that performs smoothing, which you can do non-recursively.  One major fault
(I think) of your idea is that it will have a discontinuity at the start of
the list.  To do this right the smoothing needs to wrap around the polygon.
Doing that recursively will be messy.  So something along the lines of the
code below which does a 3 point window.  An n-point window is going to
require writing a sum function and a function for extracting a sublist from
data, the former which can be done with a matrix vector product and the
latter which can be done with a carefully written loop.

function smoothdata(data) =
let(L=len(data))
[for(i=[0:1:L-1])  (data[(i-1+L)%L]+data[i]+data[(i+1)%L])/3];

rdata = [for(i=[0:1:polygon_steps-1]) rands(-0.1,0.1,2)];
circ = [for(i=[0:1:polygon_steps-1])
[cos(i360/polygon_steps),sin(i360/polygon_steps)]];
polygon(circ+smoothdata(rdata));

amundsen wrote

Hello,

I am trying to use a recursive function within a list comprehension to add
some random component to both x and y coordinates of each point in a
polygon
(a circle actually). However I want each random component to be based on
the
previous random value in order to smooth the random component (and get
some
king of brownian/red noise).

Obviously there are a few things which I don't understand because my code
doesn't provide the expected results : the noise component is not
smoothed.

For instance, I am not sure if "n-1" in the function definition means
using
the results of the previous iteration in the current one or substracting 1
from n.

Any help would be appreciated!
/
polygon_steps = 100;
random_range = 10;

function smoothrand(n = 0, range  = 1) = ( n == 0 ? rands(-range, range,
1)[0] : smoothrand(n-1) * 0.9 + rands(-range * 0.1, range * 0.1, 1)[0] );

start_points = [for(i=[0:polygon_steps])
let(a = smoothrand(1, random_range), b = smoothrand(1, random_range))
[cos(i/polygon_steps * 360) * (0.4 * a + 10), sin(i/polygon_steps * 360)
*
(0.4 * b + 10)]
];

polygon(start_points);/

--
Sent from: http://forum.openscad.org/


OpenSCAD mailing list

Discuss@.openscad

First of all, n-1 means 1 subtracted from n. Why would it mean something else? To write a function like this you need to pass the list of all previous values and then append to it in the recursion. But I would probably approach this problem differently. The exact details aren't clear to me for what you're trying to do, but I'd start by constructing a list of random perturbations. And then I'd write a function that performs smoothing, which you can do non-recursively. One major fault (I think) of your idea is that it will have a discontinuity at the start of the list. To do this right the smoothing needs to wrap around the polygon. Doing that recursively will be messy. So something along the lines of the code below which does a 3 point window. An n-point window is going to require writing a sum function and a function for extracting a sublist from data, the former which can be done with a matrix vector product and the latter which can be done with a carefully written loop. function smoothdata(data) = let(L=len(data)) [for(i=[0:1:L-1]) (data[(i-1+L)%L]+data[i]+data[(i+1)%L])/3]; rdata = [for(i=[0:1:polygon_steps-1]) rands(-0.1,0.1,2)]; circ = [for(i=[0:1:polygon_steps-1]) [cos(i*360/polygon_steps),sin(i*360/polygon_steps)]]; polygon(circ+smoothdata(rdata)); amundsen wrote > Hello, > > I am trying to use a recursive function within a list comprehension to add > some random component to both x and y coordinates of each point in a > polygon > (a circle actually). However I want each random component to be based on > the > previous random value in order to smooth the random component (and get > some > king of brownian/red noise). > > Obviously there are a few things which I don't understand because my code > doesn't provide the expected results : the noise component is not > smoothed. > > For instance, I am not sure if "n-1" in the function definition means > using > the results of the previous iteration in the current one or substracting 1 > from n. > > Any help would be appreciated! > / > polygon_steps = 100; > random_range = 10; > > function smoothrand(n = 0, range = 1) = ( n == 0 ? rands(-range, range, > 1)[0] : smoothrand(n-1) * 0.9 + rands(-range * 0.1, range * 0.1, 1)[0] ); > > start_points = [for(i=[0:polygon_steps]) > let(a = smoothrand(1, random_range), b = smoothrand(1, random_range)) > [cos(i/polygon_steps * 360) * (0.4 * a + 10), sin(i/polygon_steps * 360) > * > (0.4 * b + 10)] > ]; > > polygon(start_points);/ > > > > -- > Sent from: http://forum.openscad.org/ > > _______________________________________________ > OpenSCAD mailing list > Discuss@.openscad > http://lists.openscad.org/mailman/listinfo/discuss_lists.openscad.org -- Sent from: http://forum.openscad.org/
JB
Jordan Brown
Wed, Oct 21, 2020 1:12 AM

On 10/20/2020 5:34 PM, Hugo Jackson wrote:

Have no idea how difficult this would be, but it would be helpful for some projects to be able to import an image file like a png or jpg as a 2 dimensional object, be able to scale and rotate and set luminance. It could then be used for more rapid prototyping of an existing object, or getting things like thread pitch correct… that kind of thing. Anyone else think this worthwhile?

Are you expecting it to contribute to the geometry, or just to be sort
of a backdrop that you could then line things up with?

One wild-assed thought along those lines would be to import it as the
texture of a 2D object, or even as the texture of a face of a 3D object.

On 10/20/2020 5:34 PM, Hugo Jackson wrote: > Have no idea how difficult this would be, but it would be helpful for some projects to be able to import an image file like a png or jpg as a 2 dimensional object, be able to scale and rotate and set luminance. It could then be used for more rapid prototyping of an existing object, or getting things like thread pitch correct… that kind of thing. Anyone else think this worthwhile? Are you expecting it to contribute to the geometry, or just to be sort of a backdrop that you could then line things up with? One wild-assed thought along those lines would be to import it as the texture of a 2D object, or even as the texture of a face of a 3D object.
JB
Jordan Brown
Wed, Oct 21, 2020 1:12 AM

Adrian gave a good idea on how to accomplish your goal.

Here are some thoughts on why what you tried didn't work, in the hopes
of helping you to better understand recursive functions.

On 10/20/2020 5:22 PM, amundsen wrote:

Obviously there are a few things which I don't understand because my
code doesn't provide the expected results : the noise component is not
smoothed.

For instance, I am not sure if "n-1" in the function definition means
using the results of the previous iteration in the current one or
substracting 1 from n.

/
polygon_steps = 100;
random_range = 10;

function smoothrand(n = 0, range  = 1) = ( n == 0 ? rands(-range, range,
1)[0] : smoothrand(n-1) * 0.9 + rands(-range * 0.1, range * 0.1, 1)[0] );

start_points = [for(i=[0:polygon_steps])
let(a = smoothrand(1, random_range), b = smoothrand(1, random_range))
[cos(i/polygon_steps * 360) * (0.4 * a + 10), sin(i/polygon_steps * 360) *
(0.4 * b + 10)]
];

polygon(start_points);/

"n-1" means "subtract 1 from n".  Always.

"smoothrand(n-1)" means "call smoothrand with one less than n".  The
fact that it's being called from inside smoothrand() is irrelevant,
except that you have to ensure that the recursion eventually
terminates.  (Which you did.)

First, you're calling smoothrand with n=1, which means that you'll
always get exactly one recursion:  once with n=1 and once with n=0.  You
might have been thinking of calling it with n=i, so that each call would
recursively calculate the previous value and base its result on that value.

However, second, you're talking about random numbers.  If you called
smoothrand(0) and it gave you some number, and then called
smoothrand(1), the two numbers would be unrelated to one another because
when the smoothrand(1) internally called smoothrand(0), it would be
using different random numbers than the first call.

Also, it would be really expensive.  I believe you'd have 10,100 calls
to smoothrand.  The first point, i=0, would be one call: smoothrand(0). 
The second point, i=1, would be two calls:  smoothrand(1) and
smoothrand(0).  The third point, i=2, three.  (That's six so far.)  The
100th point would require 100 calls to smoothrand.  The total, the sum
of all of the numbers from 1 to 100, is (n²+n)/2, or 5050.  Doubled,
because you call it twice.  (That's (n^2+n)/2 if you can't see the
superscript properly.)

Adrian gave a good idea on how to accomplish your goal. Here are some thoughts on why what you tried didn't work, in the hopes of helping you to better understand recursive functions. On 10/20/2020 5:22 PM, amundsen wrote: > > Obviously there are a few things which I don't understand because my > code doesn't provide the expected results : the noise component is not > smoothed. > > For instance, I am not sure if "n-1" in the function definition means > using the results of the previous iteration in the current one or > substracting 1 from n. > > / > polygon_steps = 100; > random_range = 10; > > function smoothrand(n = 0, range = 1) = ( n == 0 ? rands(-range, range, > 1)[0] : smoothrand(n-1) * 0.9 + rands(-range * 0.1, range * 0.1, 1)[0] ); > > start_points = [for(i=[0:polygon_steps]) > let(a = smoothrand(1, random_range), b = smoothrand(1, random_range)) > [cos(i/polygon_steps * 360) * (0.4 * a + 10), sin(i/polygon_steps * 360) * > (0.4 * b + 10)] > ]; > > polygon(start_points);/ "n-1" means "subtract 1 from n".  Always. "smoothrand(n-1)" means "call smoothrand with one less than n".  The fact that it's being called from inside smoothrand() is irrelevant, except that you have to ensure that the recursion eventually terminates.  (Which you did.) First, you're calling smoothrand with n=1, which means that you'll always get exactly one recursion:  once with n=1 and once with n=0.  You might have been thinking of calling it with n=i, so that each call would recursively calculate the previous value and base its result on that value. However, second, you're talking about random numbers.  If you called smoothrand(0) and it gave you some number, and then called smoothrand(1), the two numbers would be unrelated to one another because when the smoothrand(1) internally called smoothrand(0), it would be using different random numbers than the first call. Also, it would be really expensive.  I believe you'd have 10,100 calls to smoothrand.  The first point, i=0, would be one call: smoothrand(0).  The second point, i=1, would be two calls:  smoothrand(1) and smoothrand(0).  The third point, i=2, three.  (That's six so far.)  The 100th point would require 100 calls to smoothrand.  The total, the sum of all of the numbers from 1 to 100, is (n²+n)/2, or 5050.  Doubled, because you call it twice.  (That's (n^2+n)/2 if you can't see the superscript properly.)
A
amundsen
Wed, Oct 21, 2020 6:17 AM

Thank you @adrianv for this clear and clever solution!

On my way to try some window with 5 points or more...

I was aware of the discontinuity at the start but had no idea on how to
solve this.

Also, it's great to learn that one can combine two sets of values by virtue
of a simple addition in order to set the edges of a polygon, as you do with
the deterministic and the random parts.

adrianv wrote

First of all, n-1 means 1 subtracted from n.  Why would it mean something
else?

To write a function like this you need to pass the list of all previous
values and then append to it in the recursion.

But I would probably approach this problem differently.  The exact details
aren't clear to me for what you're trying to do, but I'd start by
constructing a list of random perturbations.  And then I'd write a
function
that performs smoothing, which you can do non-recursively.  One major
fault
(I think) of your idea is that it will have a discontinuity at the start
of
the list.  To do this right the smoothing needs to wrap around the
polygon.
Doing that recursively will be messy.  So something along the lines of the
code below which does a 3 point window.  An n-point window is going to
require writing a sum function and a function for extracting a sublist
from
data, the former which can be done with a matrix vector product and the
latter which can be done with a carefully written loop.

function smoothdata(data) =
let(L=len(data))
[for(i=[0:1:L-1])  (data[(i-1+L)%L]+data[i]+data[(i+1)%L])/3];

rdata = [for(i=[0:1:polygon_steps-1]) rands(-0.1,0.1,2)];
circ = [for(i=[0:1:polygon_steps-1])
[cos(i360/polygon_steps),sin(i360/polygon_steps)]];
polygon(circ+smoothdata(rdata));

Thank you @adrianv for this clear and clever solution! On my way to try some window with 5 points or more... I was aware of the discontinuity at the start but had no idea on how to solve this. Also, it's great to learn that one can combine two sets of values by virtue of a simple addition in order to set the edges of a polygon, as you do with the deterministic and the random parts. adrianv wrote > First of all, n-1 means 1 subtracted from n. Why would it mean something > else? > > To write a function like this you need to pass the list of all previous > values and then append to it in the recursion. > > But I would probably approach this problem differently. The exact details > aren't clear to me for what you're trying to do, but I'd start by > constructing a list of random perturbations. And then I'd write a > function > that performs smoothing, which you can do non-recursively. One major > fault > (I think) of your idea is that it will have a discontinuity at the start > of > the list. To do this right the smoothing needs to wrap around the > polygon. > Doing that recursively will be messy. So something along the lines of the > code below which does a 3 point window. An n-point window is going to > require writing a sum function and a function for extracting a sublist > from > data, the former which can be done with a matrix vector product and the > latter which can be done with a carefully written loop. > > function smoothdata(data) = > let(L=len(data)) > [for(i=[0:1:L-1]) (data[(i-1+L)%L]+data[i]+data[(i+1)%L])/3]; > > rdata = [for(i=[0:1:polygon_steps-1]) rands(-0.1,0.1,2)]; > circ = [for(i=[0:1:polygon_steps-1]) > [cos(i*360/polygon_steps),sin(i*360/polygon_steps)]]; > polygon(circ+smoothdata(rdata)); -- Sent from: http://forum.openscad.org/
TP
Torsten Paul
Wed, Oct 21, 2020 1:25 PM

On 21.10.20 02:34, Hugo Jackson wrote:

Have no idea how difficult this would be, but it would be
helpful for some projects to be able to import an image
file like a png or jpg as a 2 dimensional object, be able
to scale and rotate and set luminance.

Sounds like https://github.com/openscad/openscad/issues/194

I suppose it could be quite useful. As mentioned in one of
the notes the Blender feature might be a good reference.

ciao,
Torsten.

On 21.10.20 02:34, Hugo Jackson wrote: > Have no idea how difficult this would be, but it would be > helpful for some projects to be able to import an image > file like a png or jpg as a 2 dimensional object, be able > to scale and rotate and set luminance. Sounds like https://github.com/openscad/openscad/issues/194 I suppose it could be quite useful. As mentioned in one of the notes the Blender feature might be a good reference. ciao, Torsten.