10

I'm not sure if I can express this question correctly, but here it goes..

I want to code an example, where small dots have a velocity according to which they move - but also, there is a random motion superimposed to the "proper" motion. Using the Processing code below, I get the following animation:

marbles.gif

The right dot is supposed to be going towards the bottom right corner, and I'm OK with how it behaves. The problem is the left dot, which is supposed to be "static" - so it would only show the "random" motion "in place"; however, as the animated .gif shows, it tends to end up veering some distance away from its original location. The random velocity is calculated with:

this.randspeed.set(random(0,1)-0.5, random(0,1)-0.5);

I would have guessed that random(0,1)-0.5 doesn't give me a Gaussian-like "normal distribution" centered around (or converging? to) zero; but then again, even if it was a "proper" Gaussian, I still could have such "luck" so that say, positive values [0:0.5) are returned for a whole day, and then negative values [-0.5:0) are returned the next day, and in the end, it would still be a proper Gaussian.

So, I guess, I'm looking for a way to convert a (pseudo?)-random sequence (as the one generated by random(0,1)-0.5) to a pseudo-random one, but in which the average sum of N samples (say, 10) is 0. I'm not sure how to call this - a random sequence periodically converging to zero, I guess??

Note that I've been trying below with both changing position directly; and saving position with changing finalpos instead - changing the position seems more like a "natural", smoothed motion (especially with the modulo frame operation, so a new random velocity isn't assigned every frame); but then, it also allows that the random noise accumulates, and "pushes" the dot away from its central location. Also, note that it took me a few takes until I could reproduce this on the .gif, running the program "live" seems to cause the dot to diverge from the original location more quickly (I had read something about hardware events like hard-disk writes being used for changing entropy for /dev/random on Linux, but I don't really know if it's related).

Also, I thought of setting some sort of a virtual border around the dot position, and having a collision detection for the random motion going out of the border - but that seems to me like too much work (and CPU cycles for vector operations) for this kind of thing; I would have hoped that the random function can somehow be "tempered" in an easier manner, instead.

So, would there be a recommended way to approach this kind of random motion around a central location in a limited area?


marbles.pde:

import java.util.*; // added for Iterator;

ArrayList<Marble> marbles = new ArrayList<Marble>();
Iterator<Marble> imarb;
color mclr = #0000FF;
int RNDLIMIT = 2;
int framecount = 0;

void setup() {
  size(600,400,P2D);
  Marble m_moving = new Marble(width/2, height/2, 2, 2);
  marbles.add(m_moving);
  Marble m_stopped = new Marble(width/2-100, height/2, 0, 0);
  marbles.add(m_stopped);
}

void draw() {
  background(255);

  strokeWeight(1);
  stroke(mclr);
  fill(mclr);

  imarb = marbles.iterator();
  while (imarb.hasNext()) {
    Marble m = imarb.next();
    m.update();
    ellipse(m.finalpos.x, m.finalpos.y, m.radius*2, m.radius*2);
  }
  framecount++;
  //~ saveFrame("marbles-######.png");
}

class Marble {

  PVector position = new PVector(0,0);
  PVector finalpos = new PVector(0,0);
  PVector speed = new PVector(0,0);
  PVector randspeed = new PVector(0,0);
  float radius=4;

  public Marble() {
  }

  public Marble(float inx, float iny, float invx, float invy) {
    this.position.set(inx, iny);
    this.speed.set(invx, invy);
  }

  public void update() {
    this.position.add(this.speed);
    if (framecount % 4 == 0) {
      this.randspeed.set(random(0,1)-0.5, random(0,1)-0.5);
      this.randspeed.setMag(RNDLIMIT);
    }
    int algoTry = 1; // 0
    switch(algoTry) {
      case 0:
        this.finalpos.set(PVector.add(this.position, this.randspeed));
        break;
      case 1:
        this.position.set(PVector.add(this.position, this.randspeed));
        this.finalpos.set(this.position);
        break;
    }
  }
}
sdaau
  • 36,975
  • 46
  • 198
  • 278
  • 2
    Work out the distance from your central point. As it gets closer to the edge make it less likely to keep going. – Tony Hopkinson Mar 07 '14 at 22:59
  • Thanks, @TonyHopkinson - it seems like an approach worth trying; but what would be the proper "less likely"? Should the velocity vector be simply inverted? Or summed with something like 0.9*inverted_velocity? Cheers! – sdaau Mar 07 '14 at 23:04
  • 1
    It's up to you, but inverting the vector would look like a rebound while retarding the movement towards the boundary would look like a veer. Try both maybe – Tony Hopkinson Mar 07 '14 at 23:09
  • I agree, @TonyHopkinson - I am hoping to avoid a rebound, that will also make the animation jittery; will probably give the movement retarding a try. Cheers! – sdaau Mar 07 '14 at 23:14
  • 1
    At the moment, you seem to be scaling your random movement to always have a magnitude of `RNDLIMIT`. This scaling will mess up anything that is *purely* based on the random number generation itself (and not on the actual position) : Even if the sum of e.g. 4 random steps is 0, the resulting (summed up) position will depend on whether the steps have been -0.1,-0.1,-0.1,+0.3 or -0.2,+0.2,-0.2,+0.2. – Marco13 Mar 08 '14 at 01:52
  • 1
    So you'll have to use the "distance criterion" that Tony Hopkinson suggested. In this case, it may be tricky to keep a certain overall velocity, and not cause the point to "stall" (and finally become completely immobile) when it reaches the maximum distance.... – Marco13 Mar 08 '14 at 01:52
  • Thanks for that @Marco13 - I think I got it fixed in the post [below](http://stackoverflow.com/a/22264902/277826), and indeed, `RNDLIMIT` is now gone... and it seems modifying the random sequence distribution ended up working (although not extensively tested). Cheers! – sdaau Mar 08 '14 at 04:10

4 Answers4

6

A typical 'random walk' will always meander away, because statistics don't 'balance'. Moving a lot to the left will not be corrected with movement to the right. So quality of the randomness isn't the issue.

If you want the dot to stay around a specific location, you should store that location and make the "proper" motion (as you called it) always move towards that location. Some subtraction of current location from target location should get you the correct "proper" motion. With this solution, the dot will always be inclined to head back to where is started.

Damien Black
  • 5,579
  • 18
  • 24
  • Many thanks, @DamienBlack (and sorry, ran out of upvotes for the rest of the day `:)`) I guess, that is what `algoTry = 0` in the code is trying to do (storing the location) - but it was a bit too jittery when I was running it, doesn't have the same smoothness as `algoTry = 1` (which is where the the specific location ends up mixed up with the noise). If I could solve the smoothness for that case, that would be good enough for me, but for now it evades me... Cheers! – sdaau Mar 07 '14 at 23:06
  • 1
    You may want to look into the [Ornstein - Uhlenbeck process](http://en.m.wikipedia.org/wiki/Ornstein%E2%80%93Uhlenbeck_process), which behaves like a Brownian motion being pulled back to where it started. – Teepeemm Mar 07 '14 at 23:08
  • Fantastic @Teepeemm - this looks like a great read (looks like exactly what I was looking for)! Throw that as an answer, if you don't mind - if I get that to work, I'll accept it `:)` Cheers! – sdaau Mar 07 '14 at 23:17
3

Well, I think I got somewhere; thanks to the comment by @Teepeemm, I learned about Ornstein - Uhlenbeck process, and also that Brownian motion: "is described by the Wiener process ... one of the best known Lévy processes". Rereading Ornstein - Uhlenbeck process ("Over time, the process tends to drift towards its long-term mean ... is a prototype of a noisy relaxation process ...the length x(t) of the spring will fluctuate stochastically around the spring rest length x0;"), I realized it is not what I want - it would have caused my dot eventually to settle in the central position, and then I would have had to "ping" it every once in a while.

Just as I realized that it would take me forever to fist understand, and then code, those processes - I found this:

Generation of noise with given PSD - Newsreader - MATLAB Central

I want to generate noise data with especific frequency characteristics: That is, the power spectral density (PSD) has to be proportional to f^0, f, f^2 etc.

f^0 -- use randn
f^(-2) -- low-pass filter the f^0 time series, or integrate with cumsum
f^2 -- differentiate, as with diff

... so I thought, maybe I can somehow process the raw random numbers, to get a "distribution" as I want. So I came up with a Processing patch, which you'll find below as rndquest2.pde. Processing makes it easy to use alpha colors for points, and if the background is not erased, they accumulate - so it's easier to see what is the actual distribution of a random output being tweaked. I got this image:

rndquest2.pde.png

The "choice 0" seems to point out that random() generates a sequence with uniform distribution (white noise). For instance, "choice 1" would cause the dot to tend to stick on the edges; "choice 2" quite obviously shows folding ; and I'd prefer a circle, too. In the end, I got something most resembling a Gauss (most frequent in the center, and slowly diminishing to the edges) on "choice 9", by something like a radial folding, I guess. There's still a visible threshold border on "choice 9", but if it is implemented in the code above in OP, then I get something like this:

marbles2.gif

... which is, actually, as I wanted it! (not sure why the start came out as it did, though) The trick is that the random vector, once limited/processed, should be interpreted as a position (or rather, should be added to the position, to obtain a new position, used to calculate a new velocity for finalpos); it should not be directly added to the speed/velocity!

So, only these changes need to be added in the OP code:

...
float r1 =0, r2 = 0;
PVector rv = new PVector(r1, r2);
float radius = 10;
float pr1 =0; int pr3 =0;
...
int signum(float f) {
  if (f > 0) return 1;
  if (f < 0) return -1;
  return 0;
}

float getRandom() {
  float ret;
  ret = random(-radius,radius);
  return ret;
}

void getRandomVect() {
  r1 = getRandom();
  r2 = getRandom();
  rv.set(r1,r2);
  while(rv.mag() > radius) {
    r1 = getRandom();
    r2 = getRandom();
    rv.set(r1,r2);
  }
  pr1 = rv.mag()-radius/2;
  pr3 = int(radius-rv.mag());
  pr3 = (pr3 == 0) ? 1 : pr3;
  if (pr1>0) {
    r1 = rv.x - random(1)*2*signum(rv.x)*pr3;
    r2 = rv.y - random(1)*2*signum(rv.y)*pr3;
  }
  rv.set(r1,r2);
}
...
  public void update() {
    this.position.add(this.speed);
    if (framecount % 4 == 0) {
      getRandomVect();
      this.randspeed.set(PVector.div(PVector.sub(PVector.add(this.position, rv), this.finalpos), 4));
    }

    this.finalpos.set(PVector.add(this.finalpos, this.randspeed));
  }
...

... to get it working as shown on the gif in this post.

Well, hope this helps someone,
Cheers!


rndquest2.pde

PVector mainpos = new PVector(200.0, 200.0);
float radius = 50;
float x1 =0, y1 = 0;
float r1 =0, r2 = 0;
float pr1 =0, pr2 = 0;
int pr3 =0, pr4 = 0;
PVector rv = new PVector(r1, r2);
color clr = color(0,0,255,30);
int choice = 0;
int framecount = 0;

void setup() {
  size(600,400,P2D);
  background(255);
  textSize(14);
  textAlign(LEFT, TOP);
}

void draw() {
  try {
  strokeWeight(2);
  stroke(clr); // #0000FF web colors only
  fill(clr);
  point(mainpos.x, mainpos.y);
  r1 = getRandom();
  r2 = getRandom();
  switch(choice) {
    case 0:
      x1 = mainpos.x + r1;
      y1 = mainpos.y + r2;
      println("0"); // these help trigger the draw(), apparently..
      break;
    case 1:
      rv.set(r1,r2);
      if(rv.mag() > radius) {
        rv.setMag(radius);
      }
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("1");
      break;
    case 2:
      rv.set(r1,r2);
      if(rv.mag() > radius) {
        rv.sub(PVector.mult(rv,0.1*(rv.mag()-radius)));
      }
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("2");
      break;
    case 3:
      rv.set(r1,r2);
      while(rv.mag() > radius) {
        r1 = getRandom();
        r2 = getRandom();
        rv.set(r1,r2);
      }
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("3");
      break;
    case 4:
      pr1 = rv.x;
      pr2 = rv.y;
      rv.set(r1-pr1,r2-pr2);
      while(rv.mag() > radius) {
        r1 = getRandom();
        r2 = getRandom();
        rv.set(r1-pr1,r2-pr2);
      }
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("4");
      break;
    case 5:
      pr1 = rv.x;
      pr2 = rv.y;
      rv.set(r1-pr1,r2-pr2);
      if(rv.mag() > radius) {
        rv.mult(1.0/(rv.mag()-radius));
      }
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("5");
      break;
    case 6:
      pr1 = (pr1 + r1)/2.0;
      pr2 = (pr2 + r2)/2.0;
      rv.set(pr1,pr2);
      if(rv.mag() > radius) {
        rv.mult(1.0/(rv.mag()-radius));
      }
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("6");
      break;
    case 7:
      r1 = (pr1 + r1)/2.0;
      r2 = (pr2 + r2)/2.0;
      rv.set(r1,r2);
      while(rv.mag() > radius) {
        r1 = getRandom();
        r2 = getRandom();
        r1 = (pr1 + r1)/2.0;
        r2 = (pr2 + r2)/2.0;
        rv.set(r1,r2);
      }
      pr1 = rv.x;
      pr2 = rv.y;
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("7");
      break;
    case 8:
      rv.set(r1,r2);
      while(rv.mag() > radius) {
        r1 = getRandom();
        r2 = getRandom();
        rv.set(r1,r2);
      }
      //~ pr1 = abs(rv.x)-radius/2;
      //~ pr2 = abs(rv.y)-radius/2;
      pr1 = rv.mag()-radius/2;
      //~ pr3 = int(radius-abs(rv.x));
      //~ pr4 = int(radius-abs(rv.y));
      pr3 = int(radius-pr1);
      pr3 = (pr3 == 0) ? 1 : pr3;
      //~ pr4 = (pr4 == 0) ? 1 : pr4;
      if (pr1>0)
        r1 = rv.x - random(1)*2*signum(rv.x)*pr1; //framecount ; b2i(int(random(radius)) % pr3 == 0)*
      if (pr1>0) //(pr2>0)
        r2 = rv.y - random(1)*2*signum(rv.y)*pr1;//pr2;
      rv.set(r1,r2);
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("8");
      break;
    case 9:
      rv.set(r1,r2);
      while(rv.mag() > radius) {
        r1 = getRandom();
        r2 = getRandom();
        rv.set(r1,r2);
      }
      pr1 = rv.mag()-radius/2;
      pr3 = int(radius-rv.mag()); //pr1);
      pr3 = (pr3 == 0) ? 1 : pr3;
      if (pr1>0) {
        r1 = rv.x - random(1)*2*signum(rv.x)*pr3; //framecount ; b2i(int(random(radius)) % pr3 == 0)*
        r2 = rv.y - random(1)*2*signum(rv.y)*pr3;//pr2;
        //~ r1 = rv.x - 2*signum(rv.x)*pr3; //like an X for pr3 = int(radius-pr1);
        //~ r2 = rv.y - 2*signum(rv.y)*pr3;

      }
      rv.set(r1,r2);
      x1 = mainpos.x + rv.x;
      y1 = mainpos.y + rv.y;
      println("9");
      break;
  }
  // note: patch does not draw point(mainpos.x + getRandom(), ..)
  point(x1, y1);

  fill(255);
  stroke(255); //~ stroke(255,0,0);
  rect(mainpos.x-radius,100,mainpos.x-radius+100,20);
  fill(0,0,255);
  stroke(clr);
  text(String.format("choice %d (f:%d)", choice, framecount), mainpos.x-radius, 100);
  framecount++;
  if (framecount % 5000 == 0) {
    saveFrame(String.format("rndquest2-%d-%d-######.png", choice, framecount));
  }
  } catch(Exception e) {
    e.printStackTrace();
  }
}

int signum(float f) {
  if (f > 0) return 1;
  if (f < 0) return -1;
  return 0;
}

int b2i(boolean inb) {
  if (inb) return 1;
  else return 0;
}

float getRandom() {
  float ret;
  ret = random(-radius,radius);
  return ret;
}

void mousePressed() {
  choice = (choice + 1) % 10;
  background(255);
  framecount = 0;
}
Community
  • 1
  • 1
sdaau
  • 36,975
  • 46
  • 198
  • 278
1

If you want random movement within a certain distance of an "actual" point, you could try having a fixed, maximum-distance from the "actual" location, and not allowing the ball outside of that radius.

If you don't want a hard limit, you could add some kind of force that attracts the object toward its "actual" location, and make it increase with the distance from that point linearly, quadratically, or by some other function of your choosing. Then the object would be free to move around its "actual" location, but still be kept relatively nearby.

zstewart
  • 2,093
  • 12
  • 24
  • Thanks, @chirokidz (and sorry, ran out of upvotes for today `:)`) - the question is what to do exactly to not allow the ball outside? If I just invert the velocity vector, it will be a bit too jittery/unnatural; however, with a force it may be worth trying (but may complicate matters greatly if I want to add force as a property to the balls later). Cheers! – sdaau Mar 07 '14 at 23:12
  • 1
    I was noodling modelling a force as well. One way would be treating the surface being walked on as the inside of a bowl. The further away from the bottom, the less distance you can travel. Think it would have to be a flat base and steep sides to avoid it centering. – Tony Hopkinson Mar 07 '14 at 23:15
  • 1
    @sdaau That would be one reason for the force. If you have a vector for the actual location, you can use vector subtraction to get a vector pointing from the current location to the "actual" location. Then calculate the square length of that vector and it's normal. Instead of just using applying the random velocity as your change of position, apply random velocity + (vector normal * vector length squared), this way the force pulling it back gets higher with the square of the distance. – zstewart Mar 07 '14 at 23:27
0

You are simulating a random walk. Generally, a random walk after n steps will be on the order of sqrt(n) from where it started (more specifically, it will obey the Law of the Iterated Logarithm, so that its magnitude after n steps is O(sqrt(n log log n))). Which is a long way of saying that the walk will wander away as time goes on (but because it's two dimensional, it will eventually return to the origin).

To solve this, you want to have a drift back toward the origin. One random process which has this property is the Ornstein - Uhlenbeck process, which has a drift toward the origin that is proportional to its distance from the origin. (And the random part of the random walk would still cause it to wiggle around its origin.)

This could be accomplished in your original code by something along the lines of

double driftScale = .01;
double wiggleScale = 1;
Point origin = new Point(0,0);
...
this.randspeed.set(driftScale*(origin.x-this.position.x)+wiggleScale*(random(0,1)-.5),
                   driftScale*(origin.y-this.position.y)+wiggleScale*(random(0,1)-.5));

It would be better to replace random(0,1)-.5 with a standard normal Gaussian, but I don't know how noticeable that affect would be. The biggest difference is that with the current code, there is a maximum distance the point can get from its start. With a Gaussian, it could theoretically get arbitrarily far (but it would still drift back to the origin).

I'm also not quite sure how much this matches with your eventual solution. I'm having trouble following your logic (using PVector and 10 cases didn't help).

Teepeemm
  • 4,331
  • 5
  • 35
  • 58