Initial commit.
10
.gitignore
vendored
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
sandpile
|
||||||
|
sandpile-video
|
||||||
|
sandpile_1d
|
||||||
|
sandpile_add
|
||||||
|
sandpile_fifo
|
||||||
|
sandpile_pointers
|
||||||
|
sandpile_quadran
|
||||||
|
sandpile_quarter
|
||||||
|
sandpile_quarter_fifo
|
||||||
|
sandpile_struct
|
80
README.md
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
# Searching for a fast Abelian Sandpile implementation
|
||||||
|
|
||||||
|
For context about the Abelian Sandpile Model see: https://en.wikipedia.org/wiki/Abelian_sandpile_model
|
||||||
|
|
||||||
|
I'm focusing only on the "flattening" step which I like to call "apply_gravity" in my code.
|
||||||
|
|
||||||
|
I'm focusing only in flattening a single huge pile of sand placed in the middle.
|
||||||
|
|
||||||
|
Here the performance I'm getting on an intel `i9-9980HK`:
|
||||||
|
|
||||||
|
## sandpile_quarter.c
|
||||||
|
|
||||||
|
This is my fastest implemtation, it relies on the 8-fold symetry of
|
||||||
|
flattening a centered pile. I in fact only rely on a 4-fold symmetry
|
||||||
|
for simplicity, so it could be enchanced again.
|
||||||
|
|
||||||
|
> Mean +- std dev: 208 ms +- 5 ms
|
||||||
|
|
||||||
|
## sandpile_1d.c
|
||||||
|
|
||||||
|
This one does not rely on pointers of pointers to represent a 2d
|
||||||
|
array. So no `[x][y]` access. Instead it represents the surface as an
|
||||||
|
1d line, in which, if the current cell is `i`:
|
||||||
|
|
||||||
|
- the previous cell is at `surface[i - 1]`
|
||||||
|
- the cell in the next row is at `surface[i + width]`
|
||||||
|
- the cell in the previous row is at `surface[i - width]`
|
||||||
|
- the next cell is at `surface[i + 1]`
|
||||||
|
|
||||||
|
> Mean +- std dev: 731 ms +- 9 ms
|
||||||
|
|
||||||
|
|
||||||
|
## sandpile.c
|
||||||
|
|
||||||
|
This is the "dumb" implementation.
|
||||||
|
|
||||||
|
> Mean +- std dev: 750 ms +- 10 ms
|
||||||
|
|
||||||
|
|
||||||
|
## sandpile_struct.c
|
||||||
|
|
||||||
|
This one is just to measure the cost of using a struct to store together the width and the values.
|
||||||
|
|
||||||
|
> Mean +- std dev: 936 ms +- 16 ms
|
||||||
|
|
||||||
|
|
||||||
|
## sandpile_quarter_fifo.c
|
||||||
|
|
||||||
|
This is a merge of the `fifo` implementation and the `quarter` one.
|
||||||
|
|
||||||
|
> Mean +- std dev: 831 ms +- 14 ms
|
||||||
|
|
||||||
|
|
||||||
|
## sandpile_pointers.c
|
||||||
|
|
||||||
|
This one stores, for each cells, 4 pointers to the west, south, north,
|
||||||
|
and east cells so incrementing them is easy.
|
||||||
|
|
||||||
|
> Mean +- std dev: 933 ms +- 27 ms
|
||||||
|
|
||||||
|
|
||||||
|
## sandpile_add.c
|
||||||
|
|
||||||
|
This one starts with a small (1024 typically) power of two stack of
|
||||||
|
sand grains, applies gravity on it, and multiply the resulting grid by
|
||||||
|
two. This is done iteratively until the needed number of grains is
|
||||||
|
reached.
|
||||||
|
|
||||||
|
The resulting grid is the same, but the performance are not better.
|
||||||
|
|
||||||
|
> Mean +- std dev: 1.18 sec +- 0.03 sec
|
||||||
|
|
||||||
|
|
||||||
|
## sandpile_fifo.c
|
||||||
|
|
||||||
|
This one is the "smart" one, instead of doing a full scan of the grid,
|
||||||
|
a FIFO of points to fire is kept up to date each time a cell is
|
||||||
|
firered.
|
||||||
|
|
||||||
|
> Mean +- std dev: 2.81 sec +- 0.05 sec
|
12
check.sh
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
for file in sandpile.c sandpile_*.c
|
||||||
|
do
|
||||||
|
echo "## $file"
|
||||||
|
dest=${file%.c}
|
||||||
|
cc -DNDEBUG -Ofast -W -Wall -pedantic --std=c99 "$file" -lm -lpng -o "$dest"
|
||||||
|
rm -f "$dest-100000.png"
|
||||||
|
python -m pyperf command --fast --quiet "./$dest" 100000
|
||||||
|
./"$dest" 16 --stdout
|
||||||
|
./"$dest" 100000 "$dest-100000.png"
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
sha1sum ./*-100000.png
|
106
common.c
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include <png.h>
|
||||||
|
|
||||||
|
typedef struct args {
|
||||||
|
int height;
|
||||||
|
char *png_filename;
|
||||||
|
bool print_to_stdout;
|
||||||
|
} args;
|
||||||
|
|
||||||
|
|
||||||
|
int parse_args(int argc, char **argv, args *args)
|
||||||
|
{
|
||||||
|
if (argc < 2) {
|
||||||
|
fprintf(stderr, "Usage:\n");
|
||||||
|
fprintf(stderr, " %s --stdout # To print as an ASCII table.\n", argv[0]);
|
||||||
|
fprintf(stderr, " %s file.png # To write a PNG file.\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sscanf(argv[1], "%i", &(args->height)) == EOF) {
|
||||||
|
fprintf(stderr, "Can't read height as an integer.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (argc > 2) {
|
||||||
|
if (strcmp(argv[2], "--stdout") == 0)
|
||||||
|
args->print_to_stdout = true;
|
||||||
|
else
|
||||||
|
args->png_filename = argv[2];
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_line(int width)
|
||||||
|
{
|
||||||
|
printf("+");
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
printf("---+");
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void terrain_to_stdout(int width, int **terrain)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
print_line(width);
|
||||||
|
printf("|");
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
if (terrain[x][y] == 0)
|
||||||
|
printf(" |");
|
||||||
|
else if (terrain[x][y] < 10)
|
||||||
|
printf(" %i |", terrain[x][y]);
|
||||||
|
else
|
||||||
|
printf("%3i|", terrain[x][y]);
|
||||||
|
}
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
print_line(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define ONE_GRAIN "\x3E\x92\xCC"
|
||||||
|
#define TWO_GRAINS "\x2A\x62\x8F"
|
||||||
|
#define THREE_GRAINS "\x13\x29\x3D"
|
||||||
|
#define CMAP "\xFF\xFF\xFF" ONE_GRAIN TWO_GRAINS THREE_GRAINS
|
||||||
|
|
||||||
|
int terrain_to_png(int width, int **terrain, char *filename)
|
||||||
|
{
|
||||||
|
png_image image;
|
||||||
|
png_byte *bytes;
|
||||||
|
|
||||||
|
bytes = malloc(sizeof(*bytes) * width * width);
|
||||||
|
memset(&image, 0, (sizeof image));
|
||||||
|
image.version = PNG_IMAGE_VERSION;
|
||||||
|
image.format = PNG_FORMAT_FLAG_COLORMAP|PNG_FORMAT_FLAG_COLOR;
|
||||||
|
image.width = width;
|
||||||
|
image.height = width;
|
||||||
|
image.colormap_entries = 4;
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
for (int y = 0; y < width; y++)
|
||||||
|
bytes[x * width + y] = terrain[x][y];
|
||||||
|
if (png_image_write_to_file(&image, filename, 0, bytes, width, CMAP))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fprintf(stderr, "pngtopng: error: %s\n", image.message);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void save_terrain(int width, int **terrain, args *args)
|
||||||
|
{
|
||||||
|
if (args->png_filename)
|
||||||
|
terrain_to_png(width, terrain, args->png_filename);
|
||||||
|
if (args->print_to_stdout)
|
||||||
|
terrain_to_stdout(width, terrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sandpile_width(int ngrains) {
|
||||||
|
int width;
|
||||||
|
|
||||||
|
width = pow(ngrains / 2 / 3.1415926535, 0.5) * 2 + 1;
|
||||||
|
width += 1 - width % 2; // Ensure width is odd (terrain has a center)
|
||||||
|
return width;
|
||||||
|
}
|
BIN
sandpile-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
78
sandpile-video.c
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
// Compile using:
|
||||||
|
// cc -W -Wall --pedantic --std=c99 -Ofast sandpile-video.c -lpng -lm -o sandpile-video
|
||||||
|
// The run it to produce png files:
|
||||||
|
// sandpile-video 2000
|
||||||
|
// Then use ffmpeg to convert to a video
|
||||||
|
// ffmpeg -pattern_type glob -i '*.png' out.mp4
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
|
||||||
|
void apply_gravity(int width, int **terrain)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
int div;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
if (terrain[x][y] >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
div = terrain[x][y] / 4;
|
||||||
|
terrain[x][y] = terrain[x][y] % 4;
|
||||||
|
terrain[x - 1][y] += div;
|
||||||
|
terrain[x + 1][y] += div;
|
||||||
|
terrain[x][y + 1] += div;
|
||||||
|
terrain[x][y - 1] += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int **sandpile_new(int width)
|
||||||
|
{
|
||||||
|
int **terrain = calloc(width, sizeof(int*));
|
||||||
|
int *data = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define STEP 100
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
int **terrain = sandpile_new(width);
|
||||||
|
|
||||||
|
char imgname[255] = {0};
|
||||||
|
for (int i = 0; i < args.height; i += STEP) {
|
||||||
|
terrain[width / 2][width / 2] += STEP;
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
sprintf(imgname, "sandpile-%i-%010i.png", args.height, i);
|
||||||
|
if (save_terrain(width, terrain, imgname) == 0)
|
||||||
|
printf("%i/%i saved\n", i, args.height);
|
||||||
|
else
|
||||||
|
printf("Can't save terrain\n");
|
||||||
|
}
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
61
sandpile.c
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
|
||||||
|
void apply_gravity(int width, int **terrain)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
int div;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
if (terrain[x][y] >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
div = terrain[x][y] / 4;
|
||||||
|
terrain[x][y] %= 4;
|
||||||
|
terrain[x - 1][y] += div;
|
||||||
|
terrain[x + 1][y] += div;
|
||||||
|
terrain[x][y + 1] += div;
|
||||||
|
terrain[x][y - 1] += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int **sandpile_new(int width)
|
||||||
|
{
|
||||||
|
int **terrain = calloc(width, sizeof(int*));
|
||||||
|
int *data = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
int **terrain = sandpile_new(width);
|
||||||
|
terrain[width / 2][width / 2] = args.height;
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
save_terrain(width, terrain, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_100000.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
sandpile_1d-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
64
sandpile_1d.c
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
|
||||||
|
void apply_gravity(int width, int *terrain)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
int div;
|
||||||
|
int grid_size = width * width;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
for (int x = 0; x < grid_size; x++) {
|
||||||
|
if (terrain[x] >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
div = terrain[x] / 4;
|
||||||
|
terrain[x] %= 4;
|
||||||
|
terrain[x - 1] += div;
|
||||||
|
terrain[x + 1] += div;
|
||||||
|
terrain[x - width] += div;
|
||||||
|
terrain[x + width] += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something) {
|
||||||
|
#ifdef DEBUG
|
||||||
|
fprintf(stderr, "looped %i times:\n", loops);
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
fprintf(stderr, "- %"PRIu64" times to break a pile of %i grains\n", by_height[i], i);
|
||||||
|
}
|
||||||
|
fprintf(stderr, "- %"PRIu64" times to break a pile of more than 13 grains\n", by_height[9]);
|
||||||
|
fprintf(stderr, "Total %i collapses\n", collapses);
|
||||||
|
#endif
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
int *terrain = calloc(width * width, sizeof(int*));
|
||||||
|
terrain[width * (width / 2) + (width / 2)] = args.height;
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
|
||||||
|
// "convert" to 2D for printing:
|
||||||
|
int **terrain_2d = calloc(width, sizeof(int*));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain_2d[i] = terrain + i * width;
|
||||||
|
}
|
||||||
|
|
||||||
|
save_terrain(width, terrain_2d, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_1d_100000.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
sandpile_add-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
83
sandpile_add.c
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
|
||||||
|
void sandpile_multiply(int width, int **terrain, int mul)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
for (int y = 0; y < width; y++)
|
||||||
|
terrain[x][y] *= mul;
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_gravity(int width, int **terrain)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
int div;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
if (terrain[x][y] >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
div = terrain[x][y] / 4;
|
||||||
|
terrain[x][y] %= 4;
|
||||||
|
terrain[x - 1][y] += div;
|
||||||
|
terrain[x + 1][y] += div;
|
||||||
|
terrain[x][y + 1] += div;
|
||||||
|
terrain[x][y - 1] += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int **sandpile_new(int width)
|
||||||
|
{
|
||||||
|
int **terrain = calloc(width, sizeof(int*));
|
||||||
|
int *data = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
int **terrain = sandpile_new(width);
|
||||||
|
terrain[width / 2][width / 2] = args.height;
|
||||||
|
int target_power_of_two = (int)log2(args.height);
|
||||||
|
int current_height = args.height > 1024 ? 1024 : args.height;
|
||||||
|
terrain[width / 2][width / 2] = current_height;
|
||||||
|
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
for (int power_of_two = 10; power_of_two < target_power_of_two; power_of_two++) {
|
||||||
|
current_height *= 2;
|
||||||
|
sandpile_multiply(width, terrain, 2);
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_height != args.height) {
|
||||||
|
terrain[width / 2][width / 2] += args.height - current_height;
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
save_terrain(width, terrain, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_fifo-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
126
sandpile_fifo.c
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct point {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} point_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct todo {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int size;
|
||||||
|
int idx;
|
||||||
|
int qty;
|
||||||
|
point_t *points;
|
||||||
|
bool *dedup; // Used to avoid adding twice the same point, stored as x*width+y
|
||||||
|
} todo_t;
|
||||||
|
|
||||||
|
|
||||||
|
// The todo is deduplicated list of points.
|
||||||
|
todo_t *todo_new(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
todo_t *todo = malloc(sizeof(*todo));
|
||||||
|
|
||||||
|
todo->qty = 0;
|
||||||
|
todo->width = width;
|
||||||
|
todo->height = height;
|
||||||
|
todo->size = width * 4;
|
||||||
|
todo->dedup = calloc(todo->width * todo->height, sizeof(bool));
|
||||||
|
todo->points = malloc(todo->size * sizeof(point_t));
|
||||||
|
return todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void todo_write(todo_t *todo, point_t point)
|
||||||
|
{
|
||||||
|
if (todo->dedup[point.x * todo->width + point.y])
|
||||||
|
return;
|
||||||
|
if (todo->qty == todo->size - 1) {
|
||||||
|
todo->points = realloc(todo->points, sizeof(point_t) * todo->size * 2);
|
||||||
|
todo->size *= 4;
|
||||||
|
}
|
||||||
|
todo->dedup[point.x * todo->width + point.y] = true;
|
||||||
|
todo->points[todo->qty] = point;
|
||||||
|
todo->qty += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static point_t todo_read(todo_t *todo)
|
||||||
|
{
|
||||||
|
assert(todo->qty > 0);
|
||||||
|
todo->qty -= 1;
|
||||||
|
todo->dedup[todo->points[todo->qty].x * todo->width + todo->points[todo->qty].y] = false;
|
||||||
|
return todo->points[todo->qty];
|
||||||
|
}
|
||||||
|
|
||||||
|
void unstable_to_todo(int width, int **terrain, todo_t *todo)
|
||||||
|
{
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
for (int y = 0; y < width; y++)
|
||||||
|
if (terrain[x][y] >= 4)
|
||||||
|
todo_write(todo, (point_t){x, y});
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_gravity(int width, int **terrain)
|
||||||
|
{
|
||||||
|
int div;
|
||||||
|
todo_t *todo;
|
||||||
|
point_t point;
|
||||||
|
unsigned int collapses = 0;
|
||||||
|
|
||||||
|
todo = todo_new(width, width);
|
||||||
|
unstable_to_todo(width, terrain, todo);
|
||||||
|
while (todo->qty) {
|
||||||
|
point = todo_read(todo);
|
||||||
|
collapses += 1;
|
||||||
|
div = terrain[point.x][point.y] / 4;
|
||||||
|
terrain[point.x][point.y] %= 4;
|
||||||
|
terrain[point.x - 1][point.y] += div;
|
||||||
|
terrain[point.x + 1][point.y] += div;
|
||||||
|
terrain[point.x][point.y + 1] += div;
|
||||||
|
terrain[point.x][point.y - 1] += div;
|
||||||
|
if (terrain[point.x - 1][point.y] >= 4)
|
||||||
|
todo_write(todo, (point_t){point.x-1, point.y});
|
||||||
|
if (terrain[point.x + 1][point.y] >= 4)
|
||||||
|
todo_write(todo, (point_t){point.x+1, point.y});
|
||||||
|
if (terrain[point.x][point.y + 1] >= 4)
|
||||||
|
todo_write(todo, (point_t){point.x, point.y+1});
|
||||||
|
if (terrain[point.x][point.y - 1] >= 4)
|
||||||
|
todo_write(todo, (point_t){point.x, point.y-1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int **sandpile_new(int width)
|
||||||
|
{
|
||||||
|
int **terrain = calloc(width, sizeof(int*));
|
||||||
|
int *data = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
int **terrain = sandpile_new(width);
|
||||||
|
terrain[width / 2][width / 2] = args.height;
|
||||||
|
apply_gravity(width, terrain);
|
||||||
|
save_terrain(width, terrain, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_pointers-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
109
sandpile_pointers.c
Normal file
|
@ -0,0 +1,109 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
typedef struct cell cell;
|
||||||
|
|
||||||
|
struct cell {
|
||||||
|
cell *west;
|
||||||
|
cell *north;
|
||||||
|
cell *south;
|
||||||
|
cell *east;
|
||||||
|
int height;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static inline void fire(cell *cell) {
|
||||||
|
int div, rest;
|
||||||
|
|
||||||
|
div = cell->height / 4;
|
||||||
|
rest = cell->height % 4;
|
||||||
|
cell->height = rest;
|
||||||
|
cell->west->height += div;
|
||||||
|
cell->south->height += div;
|
||||||
|
cell->north->height += div;
|
||||||
|
cell->east->height += div;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void apply_gravity(int width, cell *cells)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
for (int x = 0; x < width*width; x++) {
|
||||||
|
if (cells[x].height >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
fire(cells+x);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cell *sandpile_new(int width, int height)
|
||||||
|
{
|
||||||
|
cell **terrain = calloc(width, sizeof(cell*));
|
||||||
|
cell *data = calloc(width * width, sizeof(cell));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
terrain[width / 2][width / 2].height = height;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
terrain[x][y].west = &terrain[x-1][y];
|
||||||
|
terrain[x][y].south = &terrain[x][y-1];
|
||||||
|
terrain[x][y].north = &terrain[x][y+1];
|
||||||
|
terrain[x][y].east = &terrain[x+1][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
int **cells_to_array(int width, cell *cells) {
|
||||||
|
// Create terrain:
|
||||||
|
int **terrain = calloc(width, sizeof(int*));
|
||||||
|
int *data = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Organize cells in a 2d structure:
|
||||||
|
cell **cell_array = calloc(width, sizeof(cell*));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
cell_array[i] = cells + i * width;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy cells to terrain:
|
||||||
|
for (int x = 0; x < width; x++)
|
||||||
|
for (int y = 0; y < width; y++)
|
||||||
|
terrain[x][y] = cell_array[x][y].height;
|
||||||
|
return terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
cell *cells = sandpile_new(width, args.height);
|
||||||
|
apply_gravity(width, cells);
|
||||||
|
|
||||||
|
if (args.png_filename != NULL || args.print_to_stdout) {
|
||||||
|
// Convert to int** for easy display:
|
||||||
|
int **terrain = cells_to_array(width, cells);
|
||||||
|
save_terrain(width, terrain, &args);
|
||||||
|
}
|
||||||
|
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_quadran-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
sandpile_quadran_fifo-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
BIN
sandpile_quarter-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
106
sandpile_quarter.c
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
Some performance measures:
|
||||||
|
|
||||||
|
+--------+--------+
|
||||||
|
| height | time |
|
||||||
|
+--------+--------+
|
||||||
|
| 2**16 | 0.1s |
|
||||||
|
| 2**17 | 0.4s |
|
||||||
|
| 2**18 | 1.5s |
|
||||||
|
| 2**19 | 6.5s |
|
||||||
|
| 2**20 | 23s |
|
||||||
|
| 2**21 | 1m36s |
|
||||||
|
| 2**22 | 6m18s |
|
||||||
|
| 2**23 | 25m4s |
|
||||||
|
| 2**24 | 96m54s |
|
||||||
|
+--------+--------+
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
typedef struct terrain {
|
||||||
|
int width;
|
||||||
|
int **cells;
|
||||||
|
} terrain;
|
||||||
|
|
||||||
|
|
||||||
|
void propagate_to_quartiles(terrain *quartile, terrain *full)
|
||||||
|
{
|
||||||
|
for (int x = 0; x <= full->width / 2; x++)
|
||||||
|
for (int y = 0; y <= full->width / 2; y++) {
|
||||||
|
full->cells[x][y] = quartile->cells[x][y];
|
||||||
|
full->cells[full->width-x-1][y] = quartile->cells[x][y];
|
||||||
|
full->cells[x][full->width-y-1] = quartile->cells[x][y];
|
||||||
|
full->cells[full->width-x-1][full->width-y-1] = quartile->cells[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void apply_gravity(terrain *land)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
int div;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
for (int x = 0; x <= land->width - 2; x++) {
|
||||||
|
for (int y = 0; y <= land->width - 2; y++) {
|
||||||
|
if (land->cells[x][y] >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
div = land->cells[x][y] / 4;
|
||||||
|
land->cells[x][y] = land->cells[x][y] % 4;
|
||||||
|
land->cells[x - 1][y] += div;
|
||||||
|
land->cells[x + 1][y] += div;
|
||||||
|
land->cells[x][y + 1] += div;
|
||||||
|
land->cells[x][y - 1] += div;
|
||||||
|
if (x == land->width - 3)
|
||||||
|
land->cells[x + 1][y] += div;
|
||||||
|
if (y == land->width - 3)
|
||||||
|
land->cells[x][y + 1] += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terrain *terrain_new(int width)
|
||||||
|
{
|
||||||
|
terrain *land = malloc(sizeof(terrain));
|
||||||
|
int **lines = calloc(width, sizeof(int*));
|
||||||
|
int *cells = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
lines[i] = cells + i * width;
|
||||||
|
}
|
||||||
|
land->width = width;
|
||||||
|
land->cells = lines;
|
||||||
|
return land;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
|
||||||
|
terrain *land = terrain_new(width);
|
||||||
|
terrain *small_land = terrain_new(width / 2 + 2);
|
||||||
|
small_land->cells[width / 2][width / 2] = args.height;
|
||||||
|
apply_gravity(small_land);
|
||||||
|
propagate_to_quartiles(small_land, land);
|
||||||
|
save_terrain(land->width, land->cells, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_quarter_fifo-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
143
sandpile_quarter_fifo.c
Normal file
|
@ -0,0 +1,143 @@
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
typedef struct terrain {
|
||||||
|
int width;
|
||||||
|
int **cells;
|
||||||
|
} terrain;
|
||||||
|
|
||||||
|
|
||||||
|
void propagate_to_quartiles(terrain *quartile, terrain *full)
|
||||||
|
{
|
||||||
|
for (int x = 0; x <= full->width / 2; x++)
|
||||||
|
for (int y = 0; y <= full->width / 2; y++) {
|
||||||
|
full->cells[x][y] = quartile->cells[x][y];
|
||||||
|
full->cells[full->width-x-1][y] = quartile->cells[x][y];
|
||||||
|
full->cells[x][full->width-y-1] = quartile->cells[x][y];
|
||||||
|
full->cells[full->width-x-1][full->width-y-1] = quartile->cells[x][y];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct point {
|
||||||
|
int x;
|
||||||
|
int y;
|
||||||
|
} point_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct todo {
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
int size;
|
||||||
|
int idx;
|
||||||
|
int qty;
|
||||||
|
point_t *points;
|
||||||
|
bool *dedup; // Used to avoid adding twice the same point, stored as x*width+y
|
||||||
|
} todo_t;
|
||||||
|
|
||||||
|
|
||||||
|
// The todo is deduplicated list of points.
|
||||||
|
todo_t *todo_new(size_t width, size_t height)
|
||||||
|
{
|
||||||
|
todo_t *todo = malloc(sizeof(*todo));
|
||||||
|
|
||||||
|
todo->qty = 0;
|
||||||
|
todo->width = width;
|
||||||
|
todo->height = height;
|
||||||
|
todo->size = width * 4;
|
||||||
|
todo->dedup = calloc(todo->width * todo->height, sizeof(bool));
|
||||||
|
todo->points = malloc(todo->size * sizeof(point_t));
|
||||||
|
return todo;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void todo_write(todo_t *todo, point_t point)
|
||||||
|
{
|
||||||
|
if (todo->dedup[point.x * todo->width + point.y])
|
||||||
|
return;
|
||||||
|
if (todo->qty == todo->size - 1) {
|
||||||
|
todo->points = realloc(todo->points, sizeof(point_t) * todo->size * 2);
|
||||||
|
todo->size *= 4;
|
||||||
|
}
|
||||||
|
todo->dedup[point.x * todo->width + point.y] = true;
|
||||||
|
todo->points[todo->qty] = point;
|
||||||
|
todo->qty += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static point_t todo_read(todo_t *todo)
|
||||||
|
{
|
||||||
|
assert(todo->qty > 0);
|
||||||
|
todo->qty -= 1;
|
||||||
|
todo->dedup[todo->points[todo->qty].x * todo->width + todo->points[todo->qty].y] = false;
|
||||||
|
return todo->points[todo->qty];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void apply_gravity(terrain *land, int middle_column, todo_t *todo)
|
||||||
|
{
|
||||||
|
int div;
|
||||||
|
point_t point;
|
||||||
|
|
||||||
|
while (todo->qty) {
|
||||||
|
point = todo_read(todo);
|
||||||
|
div = land->cells[point.x][point.y] / 4;
|
||||||
|
land->cells[point.x][point.y] = land->cells[point.x][point.y] % 4;
|
||||||
|
land->cells[point.x - 1][point.y] += div;
|
||||||
|
if (point.x < middle_column)
|
||||||
|
land->cells[point.x + 1][point.y] += div;
|
||||||
|
if (point.y < middle_column)
|
||||||
|
land->cells[point.x][point.y + 1] += div;
|
||||||
|
land->cells[point.x][point.y - 1] += div;
|
||||||
|
if (point.x == middle_column - 1)
|
||||||
|
land->cells[point.x + 1][point.y] += div;
|
||||||
|
if (point.y == middle_column - 1)
|
||||||
|
land->cells[point.x][point.y + 1] += div;
|
||||||
|
if (land->cells[point.x - 1][point.y] > 3)
|
||||||
|
todo_write(todo, (point_t){point.x-1, point.y});
|
||||||
|
if (point.x < middle_column && land->cells[point.x + 1][point.y] > 3)
|
||||||
|
todo_write(todo, (point_t){point.x+1, point.y});
|
||||||
|
if (point.y < middle_column && land->cells[point.x][point.y + 1] > 3)
|
||||||
|
todo_write(todo, (point_t){point.x, point.y+1});
|
||||||
|
if (land->cells[point.x][point.y - 1] > 3)
|
||||||
|
todo_write(todo, (point_t){point.x-1, point.y-1});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terrain *terrain_new(int width)
|
||||||
|
{
|
||||||
|
terrain *land = malloc(sizeof(terrain));
|
||||||
|
int **lines = calloc(width, sizeof(int*));
|
||||||
|
int *cells = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
lines[i] = cells + i * width;
|
||||||
|
}
|
||||||
|
land->width = width;
|
||||||
|
land->cells = lines;
|
||||||
|
return land;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
|
||||||
|
terrain *land = terrain_new(width);
|
||||||
|
terrain *small_land = terrain_new(width / 2 + 1);
|
||||||
|
small_land->cells[width / 2][width / 2] = args.height;
|
||||||
|
todo_t *todo = todo_new(width, args.height);
|
||||||
|
todo_write(todo, (point_t){width/2, width/2});
|
||||||
|
apply_gravity(small_land, width/2, todo);
|
||||||
|
propagate_to_quartiles(small_land, land);
|
||||||
|
save_terrain(land->width, land->cells, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|
BIN
sandpile_struct-100000.png
Normal file
After Width: | Height: | Size: 5.1 KiB |
70
sandpile_struct.c
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "common.c"
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct terrain {
|
||||||
|
int width;
|
||||||
|
int **cells;
|
||||||
|
} terrain;
|
||||||
|
|
||||||
|
|
||||||
|
void apply_gravity(terrain *land)
|
||||||
|
{
|
||||||
|
bool did_something;
|
||||||
|
int div;
|
||||||
|
int width = land->width;
|
||||||
|
int **terrain = land->cells;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
did_something = false;
|
||||||
|
for (int x = 0; x < width; x++) {
|
||||||
|
for (int y = 0; y < width; y++) {
|
||||||
|
if (terrain[x][y] >= 4) {
|
||||||
|
did_something = true;
|
||||||
|
div = terrain[x][y] / 4;
|
||||||
|
terrain[x][y] %= 4;
|
||||||
|
terrain[x - 1][y] += div;
|
||||||
|
terrain[x + 1][y] += div;
|
||||||
|
terrain[x][y + 1] += div;
|
||||||
|
terrain[x][y - 1] += div;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!did_something)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
terrain *sandpile_new(int width)
|
||||||
|
{
|
||||||
|
terrain *land = malloc(sizeof(terrain));
|
||||||
|
int **terrain = calloc(width, sizeof(int*));
|
||||||
|
int *data = calloc(width * width, sizeof(int));
|
||||||
|
for (int i = 0; i < width; i++) {
|
||||||
|
terrain[i] = data + i * width;
|
||||||
|
}
|
||||||
|
land->width = width;
|
||||||
|
land->cells = terrain;
|
||||||
|
return land;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
args args = {0};
|
||||||
|
|
||||||
|
if (parse_args(argc, argv, &args))
|
||||||
|
return EXIT_FAILURE;
|
||||||
|
|
||||||
|
int width = sandpile_width(args.height);
|
||||||
|
terrain *land = sandpile_new(width);
|
||||||
|
land->cells[width / 2][width / 2] = args.height;
|
||||||
|
apply_gravity(land);
|
||||||
|
save_terrain(land->width, land->cells, &args);
|
||||||
|
return EXIT_SUCCESS;
|
||||||
|
}
|