Интеграция Монте-Карло, возвращающая неверные значения, проблема с перезаписью памяти - PullRequest
0 голосов
/ 10 апреля 2019

У меня проблемы с вычислением интегралов для центра масс тора, который должен вернуться (2.4076, 0.16210, 0.0).

Программа работает для оценки числа пи / 4, однако, я думаю, что есть проблема, когда я пытаюсь перезаписать существующие точки с помощью функции setRandomDomain ().

Вот мой код:

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define DIM 1000000

double random_double() {
    static const int a = 16807;
    static const int c = 0;
    static const long long m = 2147483647;
    static long long seed = 1;
    seed = (a * seed + c) % m;
    return ((double) seed) / m;
}

typedef struct Domain_t {
    double *x;
    double *y;
    double *z;
} Domain;

void constructDomain(Domain (**p_domain)) {
    *p_domain = malloc(sizeof(Domain));
    if(p_domain == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }
    (*p_domain)->x = malloc(DIM * sizeof(double));
    if ((*p_domain)->x == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }
    (*p_domain)->y = malloc(DIM * sizeof(double));
    if ((*p_domain)->y == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }
    (*p_domain)->z = malloc(DIM * sizeof(double));
    if((*p_domain)->z == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }
}

void delDomain (Domain (**p_domain)) {
    if (p_domain != NULL) {
        free ((*p_domain)->z);
        free ((*p_domain)->y);
        free ((*p_domain)->x);
        free (*p_domain);
    }
}

double radiusFunc(double point_x, double point_y) {
    return sqrt(pow(point_x,2)+pow(point_y,2));
}

double G(double point_x, double point_y, double point_z, int R) {
    return pow(point_z,2)+pow(radiusFunc(point_x,point_y)-(double)R,2);
}

typedef struct Volume_t {
    int R;
    int r;
    int lower_x;
    int upper_x;
    int lower_y;
    int upper_y;
    int lower_z;
    int upper_z;
    int V;
} Volume;

void setVolume(Volume (*p_volume), int R, int r, int x1, int x2, int y1, int y2, int z1, int z2) {
    p_volume->R = R;
    p_volume->r = r;
    p_volume->lower_x = x1;
    p_volume->upper_x = x2;
    p_volume->lower_y = y1;
    p_volume->upper_y = y2;
    p_volume->lower_z = z1;
    p_volume->upper_z = z2;
    if(z1 == 0 && z2 == 0)
        p_volume->V = (x2-x1)*(y2-y1);
    else if(y1 == 0 && y2 == 0)
        p_volume->V = (x2-x1)*(z2-z1);
    else if(x1 == 0 && x2 == 0)
        p_volume->V = (y2-y1)*(z2-z1);
    else
        p_volume->V = (x2-x1)*(y2-y1)*(z2-z1);
}

void setInitialDomain(Domain (**p_domain)) {
    int i;
    for(i=0;i<DIM;i++) {
        (*p_domain)->x[i] = random_double();
        (*p_domain)->y[i] = random_double();
        (*p_domain)->z[i] = random_double();
    }
}

void setRandomDomain(Domain (*p_domain), Domain (**p_new_domain), Volume (*p_volume)) { 
    int i;
    for(i=0;i<DIM;i++) {
        (*p_new_domain)->x[i] = p_domain->x[i]*(double)(p_volume->upper_x - p_volume->lower_x) + (double)p_volume->lower_x;
        (*p_new_domain)->y[i] = p_domain->y[i]*(double)(p_volume->upper_y - p_volume->lower_y) + (double)p_volume->lower_y;
        (*p_new_domain)->z[i] = p_domain->z[i]*(double)(p_volume->upper_z - p_volume->lower_z) + (double)p_volume->lower_z;
    }
}

double setIntegrand(Domain (*p_domain), char c) {
    double *p_x = p_domain->x;
    double *p_y = p_domain->y;
    double *p_z = p_domain->z;
    if(c=='x')
        return *p_x;
    else if(c=='y')
        return *p_y;
    else if(c=='z')
        return *p_z;
    else
        return 1.;
}

double calculateIntegral(Domain (*p_domain), Volume (*p_volume), char c) {
    int i;
    double F = 0.;
    for(i=0;i<DIM;i++) {
        if(G(p_domain->x[i], p_domain->y[i], p_domain->z[i], p_volume->R)<=(double)p_volume->r) {
            F += setIntegrand(p_domain, c);
        }
    }
    return F*(double)p_volume->V/(double)DIM;
}

int main() {
    Domain *p_initial_domain;
    Domain *p_random_domain;

    constructDomain(&p_initial_domain);
    printf("Point 1: successful\n");
    constructDomain(&p_random_domain);
    printf("Point 2: successful\n");

    setInitialDomain(&p_initial_domain);

    Volume circle, *p_circle;
    p_circle = &circle;
    setVolume(p_circle,0,1,0,1,0,1,0,0);
    setRandomDomain(p_initial_domain, &p_random_domain, p_circle);

    printf("PI/4 is approximately %f\n", calculateIntegral(p_random_domain, p_circle, 'p'));

    Volume torus, *p_torus;
    p_torus = &torus;
    setVolume(p_torus,3,1,1,4,-3,4,-1,1);
    setRandomDomain(p_initial_domain, &p_random_domain, p_torus);

    double M = calculateIntegral(p_random_domain, p_torus, 'p');
    double X = calculateIntegral(p_random_domain, p_torus, 'x');
    double Y = calculateIntegral(p_random_domain, p_torus, 'y');
    double Z = calculateIntegral(p_random_domain, p_torus, 'z');
    printf("rho integral is approximately %f\n", M);
    printf("x integral is approximately %f\n", X);
    printf("y integral is approximately %f\n", Y);
    printf("z integral is approximately %f\n", Z);
    printf("Centre of mass is approximately (%f, %f, %f)\n", X/M, Y/M, Z/M);

    delDomain(&p_initial_domain);
    delDomain(&p_random_domain);

// return pointers??
// array of structs??

    return 0;
}

В настоящее время выводит:

PI/4 is approximately 0.785436
rho integral is approximately 22.101282
x integral is approximately 22.101801
y integral is approximately -45.953770
z integral is approximately 11.298411
Centre of mass is approximately (1.000023, -2.079235, 0.511211)

Есть идеи, как это решить?

Также, пожалуйста, кто-нибудь может объяснить, как я буду использовать функции, возвращающие указатели, и почему может быть лучше создать массив структур вместо структуры массивов?

Ответы [ 2 ]

3 голосов
/ 10 апреля 2019

Ваша проблема в том, что вы вызываете setIntegrand в цикле по всем точкам, но вы всегда берете первую точку:

double *p_x = p_domain->x;

// ...
return *p_x;

Это возвращает первое double в вашем массиве.Помните, что *x эквивалентно x[0].Передайте индекс функции:

double setIntegrand(Domain (*p_domain), char c, int i)
{
    if (c == 'x') return p_domain->x[i];
    if (c == 'y') return p_domain->y[i];
    if (c == 'z') return p_domain->z[i];
    return 1.;
}

и затем вызовите его с этим индексом.

for (i = 0; i < DIM; i++) {
    if (G(...) <= p_volume->r) {
        F += setIntegrand(p_domain, c, i);
    }
}

Что касается ваших дополнительных вопросов: использование массива структур позволяет объединить все вместе(здесь три координаты точек) поблизости.Вы также можете легко передать точку в функцию с одним аргументом.

Если у вас есть конструктор, это функция, которая создает новую вещь, выделяя ее в куче и инициализируя новую память, возвращаяУказатель является полезной идиомой.(Я нахожу это более идиоматичным, чем передавать точку указателю, но тот, кто спроектировал функцию fopen_s, так не думал.)

Давайте соединим оба изменения:

typedef struct Point Point;
typedef struct Domain Domain;

struct Point {
    double x;
    double y;
    double z;
};

struct Domain {
    size_t length;
    Point *point;
};

Domain *constructDomain(size_t length)
{
    Domain *dom = malloc(sizeof(*dom));

    if (dom) {
        dom->length = length;
        dom->point = calloc(length, sizeof(*dom->point));

        // check success
    }

    return dom;
}
1 голос
/ 10 апреля 2019

Первое замечание: когда это возможно, обычно лучше уменьшить количество выделений кучи и оставить переменные в стеке, меньше места для ошибок.Я бы сказал, если вы хотите 1M x 3 x sizeof(double) байт, около 24M, лучше всего динамически распределить его в куче.Мы можем оставить структуру, которая хранит их в стеке.

Другое дело, когда вы создаете переменную, вам не нужно создавать другую переменную, чтобы указывать на нее, просто используйте исходную переменную.

Последнее примечание комментирует, я лично комментирую каждую строку кода, но это может быть много здесь.Я считаю полезным написать, что именно вы пытаетесь сделать с каждой строкой кода, чтобы помочь найти ошибки.

Вам не нужно возвращать указатели где-либо здесь, я не думаю, возможно, когда вы былипередавая указатель на указатель, было бы лучше просто вернуть указатель.Массив структур - это еще один способ сделать это, это означает, что только один malloc и один свободный, но выравнивание может привести к использованию дополнительной памяти (заполнение), что может быть значительным при использовании точек 1M, вероятно, здесь не будет никакого дополнения, хотяпотому что вы используете двойные с плавающей запятой.Я думаю, с вашими массивами все в порядке.

Я внес некоторые изменения в то, как вы используете указатели, это, вероятно, не решит вашу проблему, но, по крайней мере, немного прояснит ситуацию.

#include <stdio.h>
#include <math.h>
#include <stdlib.h>
#define DIM 1000000

typedef struct Domain_t {
    double *x;
    double *y;
    double *z;
} Domain;

typedef struct Volume_t {
    int R;
    int r;
    int lower_x;
    int upper_x;
    int lower_y;
    int upper_y;
    int lower_z;
    int upper_z;
    int V;
} Volume;


double random_double() {
    static const int a = 16807;
    static const int c = 0;
    static const long long m = 2147483647;
    static long long seed = 1;
    seed = (a * seed + c) % m;
    return ((double) seed) / m;
}


void constructDomain(Domain *p_domain) {

    p_domain->x = malloc(DIM * sizeof(double));
    if (p_domain->x == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }

    p_domain->y = malloc(DIM * sizeof(double));
    if (p_domain->y == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }

    p_domain->z = malloc(DIM * sizeof(double));
    if(p_domain->z == NULL) {
        printf("ERROR: Memory allocation failed\n");
    }
}

void delDomain (Domain *p_domain) {
    if (p_domain != NULL) {
        free (p_domain->z);
        free (p_domain->y);
        free (p_domain->x);
    }
}

double radiusFunc(double point_x, double point_y) {
    return sqrt(pow(point_x,2)+pow(point_y,2));
}

double G(double point_x, double point_y, double point_z, int R) {
    return pow(point_z,2)+pow(radiusFunc(point_x,point_y)-(double)R,2);
}


void setVolume(Volume *p_volume, int R, int r, int x1, int x2, int y1, int y2, int z1, int z2) {
    p_volume->R = R;
    p_volume->r = r;
    p_volume->lower_x = x1;
    p_volume->upper_x = x2;
    p_volume->lower_y = y1;
    p_volume->upper_y = y2;
    p_volume->lower_z = z1;
    p_volume->upper_z = z2;
    if(z1 == 0 && z2 == 0)
        p_volume->V = (x2-x1)*(y2-y1);
    else if(y1 == 0 && y2 == 0)
        p_volume->V = (x2-x1)*(z2-z1);
    else if(x1 == 0 && x2 == 0)
        p_volume->V = (y2-y1)*(z2-z1);
    else
        p_volume->V = (x2-x1)*(y2-y1)*(z2-z1);
}

void setInitialDomain(Domain *p_domain) {
    int i;
    for(i=0;i<DIM;i++) {
        p_domain->x[i] = random_double();
        p_domain->y[i] = random_double();
        p_domain->z[i] = random_double();
    }
}

void setRandomDomain(Domain *p_domain, Domain *p_new_domain, Volume *p_volume) {
    int i;
    for(i=0;i<DIM;i++) {
        p_new_domain->x[i] = p_domain->x[i] * (double) (p_volume->upper_x - p_volume->lower_x) + (double) p_volume->lower_x;
        p_new_domain->y[i] = p_domain->y[i] * (double) (p_volume->upper_y - p_volume->lower_y) + (double) p_volume->lower_y;
        p_new_domain->z[i] = p_domain->z[i] * (double) (p_volume->upper_z - p_volume->lower_z) + (double) p_volume->lower_z;
    }
}

double setIntegrand(Domain (*p_domain), char c) {
    double *p_x = p_domain->x;
    double *p_y = p_domain->y;
    double *p_z = p_domain->z;
    if(c=='x')
        return *p_x;
    else if(c=='y')
        return *p_y;
    else if(c=='z')
        return *p_z;
    else
        return 1.0;
}

double calculateIntegral(Domain *p_domain, Volume *p_volume, char c) {
    int i;
    double F = 0.0;
    for(i=0;i<DIM;i++) {
        if(G(p_domain->x[i], p_domain->y[i], p_domain->z[i], p_volume->R)<=(double)p_volume->r) {
            F += setIntegrand(p_domain, c);
        }
    }
    return F * (double) p_volume->V / (double)DIM;
}

int main() {
    Domain initial_domain;
    Domain random_domain;
    Volume circle;
    Volume torus;

    /* memory allocation */
    constructDomain(&initial_domain);
    constructDomain(&random_domain);

    /* initialization */
    setInitialDomain(&initial_domain);

    /* volume */
    setVolume(&circle,0,1,0,1,0,1,0,0);
    setRandomDomain(&initial_domain, &random_domain, &circle);

    /* integral */
    printf("PI/4 is approximately %f\n", calculateIntegral(&random_domain, &circle, 'p'));


    setVolume(&torus,3,1,1,4,-3,4,-1,1);
    setRandomDomain(&initial_domain, &random_domain, &torus);

    double M = calculateIntegral(&random_domain, &torus, 'p');
    double X = calculateIntegral(&random_domain, &torus, 'x');
    double Y = calculateIntegral(&random_domain, &torus, 'y');
    double Z = calculateIntegral(&random_domain, &torus, 'z');

    printf("rho integral is approximately %f\n", M);
    printf("x integral is approximately %f\n", X);
    printf("y integral is approximately %f\n", Y);
    printf("z integral is approximately %f\n", Z);
    printf("Centre of mass is approximately (%f, %f, %f)\n", X/M, Y/M, Z/M);

    delDomain(&initial_domain);
    delDomain(&random_domain);

    return 0;
}
...