Program Listing for File mandelbrot_cpu.c

Return to documentation for file (software/scripts/mandelbrot_cpu.c)

/*
 * Mandelbrot Set - CPU Implementation
 * ASCII visualization with ANSI 256-color support
 */

#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>

// Default parameters
#define DEFAULT_WIDTH    80
#define DEFAULT_HEIGHT   40
#define DEFAULT_MAX_ITER 100

// Mandelbrot view bounds
#define X_MIN -2.5
#define X_MAX 1.0
#define Y_MIN -1.0
#define Y_MAX 1.0

// Character palette for density rendering (10 levels)
static const char* CHARS = " .:-=+*#%@";

// Get elapsed time in milliseconds
double get_time_ms(struct timeval* start, struct timeval* end)
{
    return (end->tv_sec - start->tv_sec) * 1000.0 +
           (end->tv_usec - start->tv_usec) / 1000.0;
}

// Map iteration count to ANSI 256-color code
int iter_to_color(int iter, int max_iter)
{
    if (iter == max_iter)
    {
        return 0;  // Black for inside the set
    }

    // Normalize iteration to 0-1 range
    double t = (double)iter / max_iter;

    // Color gradient: deep blue -> cyan -> green -> yellow -> white
    if (t < 0.1)
    {
        // Dark blue (colors 17-21)
        return 17 + (int)(t * 10 * 4);
    }
    else if (t < 0.3)
    {
        // Blue to cyan (colors 21-51)
        return 21 + (int)((t - 0.1) * 5 * 30);
    }
    else if (t < 0.5)
    {
        // Cyan to green (colors 51-46)
        return 51 - (int)((t - 0.3) * 5 * 5);
    }
    else if (t < 0.7)
    {
        // Green to yellow (colors 46-226)
        return 46 + (int)((t - 0.5) * 5 * 180);
    }
    else
    {
        // Yellow to white (colors 226-231)
        return 226 + (int)((t - 0.7) * 3.33 * 5);
    }
}

// Map iteration count to character
char iter_to_char(int iter, int max_iter)
{
    if (iter == max_iter)
    {
        return ' ';  // Inside the set
    }
    int idx = (iter * 9) / max_iter;
    if (idx > 9)
        idx = 9;
    return CHARS[idx];
}

// Compute Mandelbrot escape iterations for a point
int mandelbrot(double x0, double y0, int max_iter)
{
    double x = 0.0, y = 0.0;
    int iter = 0;

    while (x * x + y * y <= 4.0 && iter < max_iter)
    {
        double xtemp = x * x - y * y + x0;
        y = 2.0 * x * y + y0;
        x = xtemp;
        iter++;
    }

    return iter;
}

// Render a single line and return timing
void render_line(int py, int width, int height, int max_iter, int use_color,
                 double x_scale, double y_scale)
{
    double y0 = Y_MAX - py * y_scale;

    for (int px = 0; px < width; px++)
    {
        double x0 = X_MIN + px * x_scale;
        int iter = mandelbrot(x0, y0, max_iter);

        if (use_color)
        {
            int color = iter_to_color(iter, max_iter);
            char c = iter_to_char(iter, max_iter);
            printf("\033[38;5;%dm%c\033[0m", color, c);
        }
        else
        {
            printf("%c", iter_to_char(iter, max_iter));
        }
    }
}

// Compute-only mode (for timing without rendering overhead)
void compute_mandelbrot(int width, int height, int max_iter, int* results)
{
    double x_scale = (X_MAX - X_MIN) / width;
    double y_scale = (Y_MAX - Y_MIN) / height;

    for (int py = 0; py < height; py++)
    {
        double y0 = Y_MAX - py * y_scale;

        for (int px = 0; px < width; px++)
        {
            double x0 = X_MIN + px * x_scale;
            results[py * width + px] = mandelbrot(x0, y0, max_iter);
        }
    }
}

void print_usage(const char* prog_name)
{
    printf("Usage: %s [OPTIONS]\n", prog_name);
    printf("\nOptions:\n");
    printf("  -w, --width N      Set width (default: %d)\n", DEFAULT_WIDTH);
    printf("  -h, --height N     Set height (default: %d)\n", DEFAULT_HEIGHT);
    printf("  -i, --iterations N Set max iterations (default: %d)\n", DEFAULT_MAX_ITER);
    printf("  -n, --no-color     Disable color output\n");
    printf("  -t, --timing-only  Only output timing (for benchmarks)\n");
    printf("  -s, --stream       Stream output line by line (for race mode)\n");
    printf("  --help             Show this help message\n");
}

int main(int argc, char* argv[])
{
    int width = DEFAULT_WIDTH;
    int height = DEFAULT_HEIGHT;
    int max_iter = DEFAULT_MAX_ITER;
    int use_color = 1;
    int timing_only = 0;
    int stream_mode = 0;

    // Parse command line arguments
    for (int i = 1; i < argc; i++)
    {
        if (strcmp(argv[i], "-w") == 0 || strcmp(argv[i], "--width") == 0)
        {
            if (i + 1 < argc)
                width = atoi(argv[++i]);
        }
        else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--height") == 0)
        {
            if (i + 1 < argc)
                height = atoi(argv[++i]);
        }
        else if (strcmp(argv[i], "-i") == 0 || strcmp(argv[i], "--iterations") == 0)
        {
            if (i + 1 < argc)
                max_iter = atoi(argv[++i]);
        }
        else if (strcmp(argv[i], "-n") == 0 || strcmp(argv[i], "--no-color") == 0)
        {
            use_color = 0;
        }
        else if (strcmp(argv[i], "-t") == 0 || strcmp(argv[i], "--timing-only") == 0)
        {
            timing_only = 1;
        }
        else if (strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--stream") == 0)
        {
            stream_mode = 1;
        }
        else if (strcmp(argv[i], "--help") == 0)
        {
            print_usage(argv[0]);
            return 0;
        }
    }

    double x_scale = (X_MAX - X_MIN) / width;
    double y_scale = (Y_MAX - Y_MIN) / height;

    struct timeval start, end, line_end;
    gettimeofday(&start, NULL);

    if (stream_mode)
    {
        // Stream mode: output each line immediately with timing
        for (int py = 0; py < height; py++)
        {
            render_line(py, width, height, max_iter, use_color, x_scale, y_scale);
            gettimeofday(&line_end, NULL);
            double elapsed = get_time_ms(&start, &line_end);
            // Output line terminator with current elapsed time
            printf("|%.1f\n", elapsed);
            fflush(stdout);
        }
        gettimeofday(&end, NULL);
        printf("TIME_MS:%.2f\n", get_time_ms(&start, &end));
    }
    else if (timing_only)
    {
        // Compute only, no rendering
        int* results = (int*)malloc(width * height * sizeof(int));
        if (!results)
        {
            fprintf(stderr, "Error: Failed to allocate memory\n");
            return 1;
        }
        compute_mandelbrot(width, height, max_iter, results);
        gettimeofday(&end, NULL);
        printf("TIME_MS:%.2f\n", get_time_ms(&start, &end));
        free(results);
    }
    else
    {
        // Standard mode: compute all, then render
        int* results = (int*)malloc(width * height * sizeof(int));
        if (!results)
        {
            fprintf(stderr, "Error: Failed to allocate memory\n");
            return 1;
        }

        compute_mandelbrot(width, height, max_iter, results);
        gettimeofday(&end, NULL);

        // Render from computed results
        for (int py = 0; py < height; py++)
        {
            for (int px = 0; px < width; px++)
            {
                int iter = results[py * width + px];

                if (use_color)
                {
                    int color = iter_to_color(iter, max_iter);
                    char c = iter_to_char(iter, max_iter);
                    printf("\033[38;5;%dm%c\033[0m", color, c);
                }
                else
                {
                    printf("%c", iter_to_char(iter, max_iter));
                }
            }
            printf("\n");
        }

        printf("TIME_MS:%.2f\n", get_time_ms(&start, &end));
        free(results);
    }

    return 0;
}