在有限区域内围绕中心位置进行随机“漫步”?

10

我不确定我能正确表达这个问题,但我尝试一下...

我想编写一个例子,其中小点的速度决定了它们的移动方向,但也有一个随机运动叠加在“正常”运动上。使用下面的 Processing 代码,我得到以下动画:

marbles.gif

右边的点应该朝着右下角前进,并且我对它的行为感到满意。问题在于左边的点,它应该是“静止”的 - 所以它只会显示“原地”的“随机”运动; 然而,如动态 .gif 所示,它往往会远离其原来的位置。随机速度计算如下:

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

我本来以为random(0,1)-0.5不能给我一个以0为中心的高斯分布,但另一方面,即使它是一个“适当的”高斯分布,我仍然可能会有这样的“运气”,即比如说,整整一天都返回正值[0:0.5),然后第二天返回负值[-0.5:0),最终仍然是一个“适当的”高斯分布。

所以,我想知道如何将(伪?)随机序列(由random(0,1)-0.5生成的序列)转换为一个伪随机序列,其中N个样本(例如,10个)的平均和为0。我不确定该怎么称呼这种情况——我猜是一个周期性地收敛于零的随机序列??

请注意,我已经尝试过直接更改position以及保存position并改变finalpos - 更改位置似乎更像是一种自然的,平滑的运动(特别是用模帧操作,因此不会在每帧分配新的随机速度),但是,它也允许随机噪声积累,并将粒子从其中心位置“推”出去。此外,请注意,我尝试了几次才能在.gif上再现它,在“实时”运行程序似乎会导致粒子更快地偏离原始位置(我曾经读到过类似Linux中用于改变/dev/random熵的硬件事件如硬盘写入,但我不确定它是否相关)。

另外,我考虑设置某种虚拟边界围绕点位置,并对随机运动超出边界进行碰撞检测 - 但是对于这种情况,我认为这太费力了(并且需要大量的CPU周期进行向量操作);我希望能以更轻松的方式“调节”随机函数。

那么,有没有推荐的方法来处理在有限区域内围绕中心位置的这种随机运动呢?


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

2
计算与中心点的距离。当它靠近边缘时,使其不太可能继续前进。 - Tony Hopkinson
1
由你决定,但是反转向量看起来像是一个反弹,而减缓运动朝边界的方向看起来像是一个转向。也许可以尝试两种方法。 - Tony Hopkinson
1
目前,您似乎正在将随机移动的缩放始终设置为RNDLIMIT的大小。这种缩放会破坏任何纯粹基于随机数生成本身(而不是实际位置)的东西:即使4个随机步骤的总和为0,结果(总和)位置也取决于步骤是否为-0.1,-0.1,-0.1,+0.3或-0.2,+0.2,-0.2,+0.2。 - Marco13
1
因此,您将需要使用Tony Hopkinson建议的“距离标准”。在这种情况下,保持一定的总速度可能会很棘手,并且当点到达最大距离时不会导致其“停滞”(最终完全静止)。 - Marco13
感谢@Marco13的帮助 - 我想我已经在下面的链接中解决了它,而且确实,RNDLIMIT现在已经消失了...并且修改随机序列分布似乎也起作用了(虽然没有进行广泛测试)。干杯! - sdaau
显示剩余2条评论
4个回答

6
一种典型的“随机游走”会不断地偏离原点,因为统计学上不会自动平衡。只往左移动并不能通过向右移动得到纠正。因此,随机性的质量不是问题所在。
如果您希望点停留在特定位置,您应该存储该位置,并使“适当”的运动(如您所说)始终朝着该位置移动。通过当前位置减去目标位置,您可以得出正确的“适当”运动。采用这种解决方案,点将始终倾向于返回起点。

非常感谢,@DamienBlack(很抱歉,今天的赞已经用完了:))。我猜这就是代码中 algoTry = 0 要做的事情(存储位置)-但当我运行它时,它有点太抖动了,没有 algoTry = 1 那么平滑(其中特定位置最终与噪声混合)。如果我能解决那种情况下的平滑度,那对我来说就足够好了,但现在它还是逃脱了我的掌握...干杯! - sdaau
1
你可能需要了解一下奥恩斯坦-乌伦贝克过程,它的行为就像是一个布朗运动被拉回到起点。 - Teepeemm
太棒了@Teepeemm - 这看起来是一篇很棒的文章(看起来正是我在寻找的东西)!如果你不介意,就把它作为答案吧 - 如果我能让它工作,我会接受的 :) 干杯! - sdaau

3

我觉得我有所进展,多亏了@Teepeemm的评论,我学到了奥恩斯坦-乌伦贝克过程,以及布朗运动:““由维纳过程描述……是最著名的Lévy过程之一”。

重新阅读奥恩斯坦-乌伦贝克过程(“随着时间的推移,该过程趋向于漂移到其长期平均值……是嘈杂松弛过程的原型……弹簧的长度x(t)将随机地在弹簧静止长度x0周围波动”),我意识到这不是我想要的——它最终会导致我的点定居在中心位置,然后我不得不每隔一段时间就“ping”它。

正如我意识到要理解并编写这些过程需要很长时间一样,我发现了这个:

Generation of noise with given PSD - Newsreader - MATLAB Central

我想生成具有特定频率特性的噪声数据:即功率谱密度(PSD)必须与f ^ 0、f、f ^ 2等成比例。

f ^ 0--使用randn
f ^(-2)--低通滤波f ^ 0时间序列,或与cumsum集成
f ^ 2--差分,如diff

……所以我想,也许我可以以某种方式处理原始随机数字,以获得我想要的“分布”。于是我想出了一个Processing补丁程序,您将在下面找到它作为rndquest2.pde。 Processing使使用alpha颜色对点易如反掌,如果不擦除背景,它们会累积-因此更容易看到随机输出被调整的实际分布。 我得到了这张图片:

rndquest2.pde.png

“选择0”似乎表明random()生成具有均匀分布(白噪声)的序列。例如,“选择1”会导致点倾向于停留在边缘;“选择2”明显显示折叠;我也更喜欢圆形。最后,通过类似于径向折叠的方式,在“选择9”上得到了最接近高斯分布(在中心最频繁,逐渐减少到边缘)的东西。在“选择9”上仍然有一个可见的阈值边界,但如果在OP上面的代码中实现它,那么我会得到像这样的东西:

marbles2.gif

...这正是我想要的!(虽然不确定为什么起始状态会出现这样的情况)。关键在于,一旦限制/处理随机向量,它应该被解释为一个位置(或者说,应该添加到位置上,以获得一个新位置,用于计算finalpos的新速度);它不应该直接添加到速度/方向上!
因此,只需要在OP代码中添加这些更改:
...
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));
  }
...

...以便像这篇帖子中的gif所示那样使其正常工作。

希望这可以帮助到某些人,
干杯!


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

1
如果你想让物体在“实际”点附近随机移动一定距离,你可以尝试将其限制在距离“实际”位置的固定最大距离内,不允许物体超出该半径。
如果你不想有硬性限制,你可以添加某种力量来将物体向其“实际”位置吸引,并使它线性、二次或其他函数方式增加与该点之间的距离。然后物体就可以自由地在其“实际”位置周围移动,但仍保持相对靠近。

谢谢,@chirokidz(很抱歉,今天的赞已经用完了 :))- 问题是要怎么做才能不让球出界?如果我只是反转速度向量,它会有点抖动/不自然;但是,使用力可能值得一试(但如果我以后想将力作为球的属性添加,可能会使事情变得非常复杂)。干杯! - sdaau
1
我也在思考建模一个力。一种方法是将被行走的表面视为碗的内部。离底部越远,可以行进的距离就越短。我认为它必须有一个平坦的底部和陡峭的侧面,以避免它居中。 - Tony Hopkinson
1
@sdaau 这就是力量的一个原因。如果你有一个实际位置的向量,可以使用向量减法得到一个指向当前位置到“实际”位置的向量。然后计算该向量的平方长度和法线。不要仅仅将随机速度应用为位置变化,而是应用随机速度+(向量法线*向量长度的平方),这样拉回它的力量将随距离的平方增加。 - zstewart

0
你正在模拟一个随机游走。一般而言,经过 n 步的随机游走大约会距离初始位置 sqrt(n) (更具体地说,它会遵循迭代对数定理,因此在 n 步后其大小为 O(sqrt(n log log n)))。这意味着随着时间推移,游走将逐渐偏离初始位置(但由于是二维的,最终会回到原点)。
为了解决这个问题,你希望让游走趋向于原点。一个具有此属性的随机过程是奥恩斯坦-乌伦贝克过程,它具有与原点距离成比例的向原点漂移的特性。(然后随机游走的随机部分仍会导致其在原点周围波动)。
可以通过以下方式在原始代码中实现这一目标:
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));

最好用标准正态高斯替换random(0,1)-.5,但我不知道这个影响会有多明显。最大的区别在于,使用当前代码,点与其起始点之间存在最大距离。而使用高斯分布,理论上可以无限远(但仍会漂回原点)。

我也不太确定这与您最终解决方案的匹配程度如何。我很难理解您的逻辑(使用PVector和10个案例并没有帮助)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接