Паттерн бабочка появляется в случайном блуждании с помощью srand (), почему? - PullRequest
11 голосов
/ 27 мая 2020

Примерно 3 года go Я закодировал двумерное случайное блуждание вместе с коллегой на C ++, сначала казалось, что это работает правильно, поскольку мы каждый раз получали разные модели. Но всякий раз, когда мы решали увеличить количество шагов выше некоторого порога, появлялся очевидный паттерн бабочки, мы замечали, что при каждом запуске кода паттерн повторяется, но начиная с другого места бабочки. Мы пришли к выводу и сообщили, что это произошло из-за генератора псевдослучайных ситуаций, связанного с функцией srand (), но сегодня я снова нашел этот отчет, и все еще есть некоторые вещи, которые я хотел бы понять. Я хотел бы лучше понять, как работает псевдослучайный генератор, чтобы получить такую ​​симметрию и шаблон cicli c. Шаблон, о котором я говорю, таков (шаги имеют цветовую маркировку в радужной последовательности, чтобы оценить прогресс ходьбы):

enter image description here

EDIT :

Я добавляю код, использованный для получения этой цифры:

#include<iostream>
#include<cmath>
#include<stdlib.h>
#include<time.h>
#include <fstream>
#include <string.h>
#include <string>
#include <iomanip>

using namespace std;

int main ()
{
srand(time(NULL));
int num1,n=250000;



ofstream rnd_coordinates("Random2D.txt");
float x=0,y=0,sumx_f=0,sumy_f=0,sum_d=0,d_m,X,t,d;
float x_m,y_m;

x=0;
y=0;

for(int i=0;i<n;i++){

    t=i;
    num1= rand()%4;

    if(num1==0){
        x++;
    }
    if(num1==1){
        x--;
    }
    if(num1==2){
        y++;
    }
    if(num1==3){
        y--;
    }

    rnd_coordinates<<x<<','<<y<<','<<t<<endl;

}

rnd_coordinates.close();


return 0;
}

Ответы [ 4 ]

3 голосов
/ 28 мая 2020

Вы никогда не достигнете периода rand(), но имейте в виду, что вы фактически не используете весь диапазон rand(), который полностью гарантирует период 2 ^ 32.

Имея это в виду, у вас есть 2 варианта:

  1. Использовать все биты. rand() возвращает 2 байта (16 бит), и вам нужно 2 бита (для 4 возможных значений). Разделите этот 16-битный вывод на фрагменты по 2 бита и используйте их все последовательно.
  2. По крайней мере, если вы настаиваете на использовании ленивого %n способа, выберите модуль, который не является делителем вашего периода. Например, выберите 5 вместо 4, так как 5 - простое число, и если вы получите 5-е значение, перебросите.
2 голосов
/ 28 мая 2020

Приведенный ниже код представляет собой полный компилируемый пример.

screenshot of the example

Ваша проблема заключается в отбрасывании битов из генератора случайных чисел. Давайте посмотрим, как можно написать источник случайных пар битов, который не теряет биты. Требуется, чтобы RAND_MAX имел форму 2 ^ n -1, но идея может быть расширена для поддержки любых RAND_MAX >= 3.

#include <cassert>
#include <cstdint>
#include <cstdlib>

class RandomBitSource {
    int64_t bits = rand();
    int64_t bitMask = RAND_MAX;
    static_assert((int64_t(RAND_MAX + 1) & RAND_MAX) == 0, "No support for RAND_MAX != 2^(n-1)");
public:
    auto get2Bits() {
        if (!bitMask) // got 0 bits
            bits = rand(), bitMask = RAND_MAX;
        else if (bitMask == 1) // got 1 bit
            bits = (bits * (RAND_MAX+1)) | rand(), bitMask = (RAND_MAX+1) | RAND_MAX;

        assert(bitMask & 3);
        bitMask >>= 2;
        int result = bits & 3;
        bits >>= 2;
        return result;
    }
};

Затем реализация случайного блуждания могло быть следующим. Обратите внимание, что разделитель ' di git - это функция C ++ 14, что очень удобно.

#include <vector>

using num_t = int;
struct Coord { num_t x, y; };

struct Walk {
    std::vector<Coord> points;
    num_t min_x = {}, max_x = {}, min_y = {}, max_y = {};
    Walk(size_t n) : points(n) {}
};

auto makeWalk(size_t n = 250'000)
{
    Walk walk { n };
    RandomBitSource src;
    num_t x = 0, y = 0;

    for (auto& point : walk.points)
    {
        const int bits = src.get2Bits(), b0 = bits & 1, b1 = bits >> 1;
        x = x + (((~b0 & ~b1) & 1) - ((b0 & ~b1) & 1));
        y = y + (((~b0 & b1) & 1) - ((b0 & b1) & 1));

        if (x < walk.min_x)
            walk.min_x = x;
        else if (x > walk.max_x)
            walk.max_x = x;
        if (y < walk.min_y)
            walk.min_y = y;
        else if (y > walk.max_y)
            walk.max_y = y;

        point = { x, y };
    }
    return walk;
}

Приложив немного больше усилий, мы можем превратить это в интерактивное приложение Qt. Нажатие Return генерирует новое изображение.

Изображение просматривается с собственным разрешением экрана, на котором оно отображается, т. Е. Оно отображается в пикселях физического устройства. Изображение не масштабируется. Вместо этого он поворачивается при необходимости, чтобы лучше вписаться в ориентацию экрана (портретная или альбомная). Это для поклонников портретных мониторов :)

#include <QtWidgets>

QImage renderWalk(const Walk& walk, Qt::ScreenOrientation orient)
{
    using std::swap;
    auto width = walk.max_x - walk.min_x + 3;
    auto height = walk.max_y - walk.min_y + 3;
    bool const rotated = (width < height) == (orient == Qt::LandscapeOrientation);
    if (rotated) swap(width, height);
    QImage image(width, height, QPixmap(1, 1).toImage().format());
    image.fill(Qt::black);

    QPainter p(&image);
    if (rotated) {
        p.translate(width, 0);
        p.rotate(90);
    }
    p.translate(-walk.min_x, -walk.min_y);

    auto constexpr hueStep = 1.0/720.0;
    qreal hue = 0;
    int const huePeriod = walk.points.size() * hueStep;
    int i = 0;
    for (auto& point : walk.points) {
        if (!i--) {
            p.setPen(QColor::fromHsvF(hue, 1.0, 1.0, 0.5));
            hue += hueStep;
            i = huePeriod;
        }
        p.drawPoint(point.x, point.y);
    }
    return image;
}

#include <ctime>

int main(int argc, char* argv[])
{
    srand(time(NULL));
    QApplication a(argc, argv);
    QLabel view;
    view.setAlignment(Qt::AlignCenter);
    view.setStyleSheet("QLabel {background-color: black;}");
    view.show();

    auto const refresh = [&view] {
        auto *screen = view.screen();
        auto orientation = screen->orientation();
        auto pixmap = QPixmap::fromImage(renderWalk(makeWalk(), orientation));
        pixmap.setDevicePixelRatio(screen->devicePixelRatio());
        view.setPixmap(pixmap);
        view.resize(view.size().expandedTo(pixmap.size()));
    };
    refresh();
    QShortcut enter(Qt::Key_Return, &view);
    enter.setContext(Qt::ApplicationShortcut);
    QObject::connect(&enter, &QShortcut::activated, &view, refresh);
    return a.exec();
}
0 голосов
/ 28 мая 2020

Возможно, вы захотите посмотреть мой ответ на другой вопрос здесь о более старых реализациях rand(). Иногда со старыми функциями rand() and srand() биты более низкого порядка гораздо менее случайны, чем биты более высокого порядка. Некоторые из этих более старых реализаций все еще существуют, возможно, вы их использовали.

0 голосов
/ 27 мая 2020

Каждый псевдослучайный генератор представляет собой цикл некоторой последовательности чисел. Один из способов отличить guish «хороших» запросов от «плохих» - это длина этой последовательности. С генератором связано некоторое состояние, поэтому максимальный период ограничен количеством различных состояний.

Ваша реализация имеет «короткий» период, потому что он повторяется меньше, чем возраст вселенной. Вероятно, он имеет 32 бита состояния, поэтому период не более 2 ^ 32.

Поскольку вы используете C ++, вы можете попробовать еще раз, используя случайно выбранный std::mt19937 , и вы не увидите повторов.

...