From dbd6259ac741febd02ebf6dd379dd752300b3f96 Mon Sep 17 00:00:00 2001 From: fredrikr79 Date: Mon, 6 Oct 2025 14:30:33 +0200 Subject: [PATCH] ex4: init --- exercise4/.gitignore | 3 + exercise4/Makefile | 38 ++++ exercise4/README.md | 5 + exercise4/argument_utils.c | 127 +++++++++++++ exercise4/argument_utils.h | 21 +++ exercise4/compare.sh | 52 ++++++ exercise4/plot_image.sh | 100 +++++++++++ exercise4/wave_2d_parallel.c | 319 +++++++++++++++++++++++++++++++++ exercise4/wave_2d_sequential.c | 238 ++++++++++++++++++++++++ 9 files changed, 903 insertions(+) create mode 100644 exercise4/.gitignore create mode 100644 exercise4/Makefile create mode 100644 exercise4/README.md create mode 100644 exercise4/argument_utils.c create mode 100644 exercise4/argument_utils.h create mode 100755 exercise4/compare.sh create mode 100755 exercise4/plot_image.sh create mode 100644 exercise4/wave_2d_parallel.c create mode 100644 exercise4/wave_2d_sequential.c diff --git a/exercise4/.gitignore b/exercise4/.gitignore new file mode 100644 index 0000000..a0ae897 --- /dev/null +++ b/exercise4/.gitignore @@ -0,0 +1,3 @@ +data/ +data_sequential/ +images/ diff --git a/exercise4/Makefile b/exercise4/Makefile new file mode 100644 index 0000000..9a00b06 --- /dev/null +++ b/exercise4/Makefile @@ -0,0 +1,38 @@ +CC=gcc +PARALLEL_CC=mpicc +CFLAGS+= -std=c99 -O2 -Wall -Wextra +LDLIBS+= -lm +SEQUENTIAL_SRC_FILES=wave_2d_sequential.c argument_utils.c +PARALLEL_SRC_FILES=wave_2d_parallel.c argument_utils.c +IMAGES=$(shell find data -type f | sed s/\\.dat/.png/g | sed s/data/images/g ) +.PHONY: all clean dirs plot movie +all: dirs ${TARGETS} +dirs: + mkdir -p data images +sequential: ${SEQUENTIAL_SRC_FILES} + $(CC) $^ $(CFLAGS) -o $@ $(LDLIBS) +parallel: ${PARALLEL_SRC_FILES} + $(PARALLEL_CC) $^ $(CFLAGS) -o $@ $(LDLIBS) +plot: ${IMAGES} +images/%.png: data/%.dat + ./plot_image.sh $< +movie: ${IMAGES} + ffmpeg -y -an -i images/%5d.png -vcodec libx264 -pix_fmt yuv420p -profile:v baseline -level 3 -r 12 wave.mp4 +check: dirs sequential parallel + mkdir -p data_sequential + ./sequential + cp -rf ./data/* ./data_sequential + mpiexec -n 1 --oversubscribe ./parallel + ./compare.sh + rm ./data/* + mpiexec -n 4 --oversubscribe ./parallel + ./compare.sh + rm ./data/* + rm ./data_sequential/* + ./sequential -m 2048 -n 512 + cp -rf ./data/* ./data_sequential + mpiexec -n 16 --oversubscribe ./parallel -m 2048 -n 512 + ./compare.sh + rm -rf data_sequential +clean: + -rm -fr sequential parallel data images wave.mp4 diff --git a/exercise4/README.md b/exercise4/README.md new file mode 100644 index 0000000..2061668 --- /dev/null +++ b/exercise4/README.md @@ -0,0 +1,5 @@ +* make : builds the executable 'wave_1d' +* ./wave\_2d : fills the 'data/' directory with stored time steps +* make plot : converts saved time steps to png files under 'images/', using gnuplot. Runs faster if launched with e.g. 4 threads (make -j4 plot). +* make movie : converts collection of png files under 'images' into an mp4 movie file, using ffmpeg +* make check : builds both executeables and compares their output diff --git a/exercise4/argument_utils.c b/exercise4/argument_utils.c new file mode 100644 index 0000000..359f7a4 --- /dev/null +++ b/exercise4/argument_utils.c @@ -0,0 +1,127 @@ +#include "argument_utils.h" + +#include +#include +#include +#include +#include + + +OPTIONS *parse_args ( int argc, char **argv ) +{ + /* + * Argument parsing: don't change this! + */ + + int_t M = 1024; + int_t N = 1024; + int_t max_iteration = 10000; + int_t snapshot_frequency = 10; + + static struct option const long_options[] = { + {"help", no_argument, 0, 'h'}, + {"y_size", required_argument, 0, 'm'}, + {"x_size", required_argument, 0, 'n'}, + {"max_iteration", required_argument, 0, 'i'}, + {"snapshot_frequency", required_argument, 0, 's'}, + {0, 0, 0, 0} + }; + + static char const * short_options = "hm:n:i:s:"; + { + char *endptr; + int c; + int option_index = 0; + + while ( (c = getopt_long( argc, argv, short_options, long_options, &option_index )) != -1 ) + { + switch (c) + { + case 'h': + help( argv[0], 0, NULL ); + exit(0); + break; + case 'm': + M = strtol(optarg, &endptr, 10); + if ( endptr == optarg || M < 0 ) + { + help( argv[0], c, optarg ); + return NULL; + } + break; + case 'n': + N = strtol(optarg, &endptr, 10); + if ( endptr == optarg || N < 0 ) + { + help( argv[0], c, optarg ); + return NULL; + } + break; + case 'i': + max_iteration = strtol(optarg, &endptr, 10); + if ( endptr == optarg || max_iteration < 0 ) + { + help( argv[0], c, optarg); + return NULL; + } + break; + case 's': + snapshot_frequency = strtol(optarg, &endptr, 10); + if ( endptr == optarg || snapshot_frequency < 0 ) + { + help( argv[0], c, optarg ); + return NULL; + } + break; + default: + abort(); + } + } + } + + if ( argc < (optind) ) + { + printf("argc/optind: %d/%d\n", argc, optind); + + help( argv[0], ' ', "Not enough arugments" ); + return NULL; + } + + OPTIONS *args_parsed = malloc( sizeof(OPTIONS) ); + args_parsed->M = M; + args_parsed->N = N; + args_parsed->max_iteration = max_iteration; + args_parsed->snapshot_frequency = snapshot_frequency; + + return args_parsed; +} + + +void help ( char const *exec, char const opt, char const *optarg ) +{ + FILE *out = stdout; + + if ( opt != 0 ) + { + out = stderr; + if ( optarg ) + { + fprintf(out, "Invalid parameter - %c %s\n", opt, optarg); + } + else + { + fprintf(out, "Invalid parameter - %c\n", opt); + } + } + + fprintf(out, "%s [options]\n", exec); + fprintf(out, "\n"); + fprintf(out, "Options Description Restriction Default\n"); + fprintf(out, " -m, --y_size size of the y dimension n>0 256\n" ); + fprintf(out, " -n, --x_size size of the x dimension n>0 256\n" ); + fprintf(out, " -i, --max_iteration number of iterations i>0 100000\n" ); + fprintf(out, " -s, --snapshot_freq snapshot frequency s>0 1000\n" ); + + fprintf(out, "\n"); + fprintf(out, "Example: %s -m 256 -n 256 -i 100000 -s 1000\n", exec); +} diff --git a/exercise4/argument_utils.h b/exercise4/argument_utils.h new file mode 100644 index 0000000..ed170e3 --- /dev/null +++ b/exercise4/argument_utils.h @@ -0,0 +1,21 @@ +#ifndef _ARGUMENT_UTILS_H_ +#define _ARGUMENT_UTILS_H_ + +#include + + +typedef int64_t int_t; + +typedef struct options_struct { + int_t M; + int_t N; + int_t max_iteration; + int_t snapshot_frequency; +} OPTIONS; + + +OPTIONS *parse_args ( int argc, char **argv ); + +void help ( char const *exec, char const opt, char const *optarg ); + +#endif diff --git a/exercise4/compare.sh b/exercise4/compare.sh new file mode 100755 index 0000000..5cd69a7 --- /dev/null +++ b/exercise4/compare.sh @@ -0,0 +1,52 @@ +#! /usr/bin/env bash + + +GREEN=$(tput setaf 2) +RED=$(tput setaf 1) +RESET=$(tput sgr0) + +DIR1="data" +DIR2="data_sequential" + +if [ ! -d "$DIR1" ]; then + echo "Directory $DIR1 does not exist." + exit 1 +fi + +if [ ! -d "$DIR2" ]; then + echo "Directory $DIR2 does not exist." + exit 1 +fi + +found_difference=1 +for file in "$DIR1"/*.dat; do + # Extract the file name (basename) + filename=$(basename "$file") + + # Check if the corresponding file exists in DIR2 + if [ -f "$DIR2/$filename" ]; then + # Compare the two files using diff + diff_output=$(diff "$file" "$DIR2/$filename") + + if [ -n "$diff_output" ]; then + echo "$diff_output" + found_difference=0 + fi + else + echo "File $filename does not exist in $DIR2" + fi +done + +if [ $found_difference -eq 1 ]; then + echo + echo + echo "${GREEN}The sequential and parallel version produced mathcing output!${RESET}" + echo + echo +else + echo + echo + echo "${RED}There were mismatches between the sequential and the parallel output.${RESET}" + echo + echo +fi diff --git a/exercise4/plot_image.sh b/exercise4/plot_image.sh new file mode 100755 index 0000000..b58a58f --- /dev/null +++ b/exercise4/plot_image.sh @@ -0,0 +1,100 @@ +#! /usr/bin/env bash + +help() +{ + echo + echo "Plot 2D Wave Equation" + echo + echo "Syntax" + echo "--------------------------------------------------------" + echo "./plot_results.sh [-m|n|h] [data_folder] " + echo + echo "Option Description Arguments Default" + echo "--------------------------------------------------------" + echo "m x size Optional 1024 " + echo "n y size Optional 1024 " + echo "h Help None " + echo + echo "Example" + echo "--------------------------------------------------------" + echo "./plot_image.sh -m 1024 -n 1024" + echo +} + +#----------------------------------------------------------------- +set -e + +M=1024 +N=1024 + +# Check if the data folder is provided +if [ $# -lt 1 ]; then + echo "Error: No data folder provided." + help + exit 1 +fi + +# Parse options and arguments +while getopts ":m:n:h" opt; do + case $opt in + m) + M=$OPTARG;; + n) + N=$OPTARG;; + h) + help + exit;; + \?) + echo "Invalid option" + help + exit;; + esac +done + +# Shift parsed options so that the remaining arguments start at $1 +shift $((OPTIND - 1)) + +# Ensure that the data folder is provided and exists +DATAFOLDER=./data +if [ ! -d "$DATAFOLDER" ]; then + echo "Error: Data folder $DATAFOLDER does not exist." + exit 1 +fi + +#----------------------------------------------------------------- +# Set up the size of the grid based on the options passed +SIZE_M=`echo $M | bc` +SIZE_N=`echo $N | bc` + +# Ensure the output directory exists +mkdir -p images + +# Loop through all .dat files in the data folder +for DATAFILE in "$DATAFOLDER"/*.dat; do + # Skip if no .dat files are found + if [ ! -f "$DATAFILE" ]; then + echo "No .dat files found in the folder." + exit 1 + fi + + # Create the corresponding output image file name + IMAGEFILE=`echo $DATAFILE | sed 's/dat$/png/' | sed 's/data/images/'` + + # Run the gnuplot command to create the plot in the background + ( + cat < +#include +#include +#include +#include +#include +#include +#include +#include + +#include "argument_utils.h" + +// TASK: T1a +// Include the MPI headerfile +// BEGIN: T1a +; +// END: T1a + + +// Convert 'struct timeval' into seconds in double prec. floating point +#define WALLTIME(t) ((double)(t).tv_sec + 1e-6 * (double)(t).tv_usec) + +// Option to change numerical precision +typedef int64_t int_t; +typedef double real_t; + + +// Buffers for three time steps, indexed with 2 ghost points for the boundary +real_t + *buffers[3] = { NULL, NULL, NULL }; + +// TASK: T1b +// Declare variables each MPI process will need +// BEGIN: T1b +#define U_prv(i,j) buffers[0][((i)+1)*(N+2)+(j)+1] +#define U(i,j) buffers[1][((i)+1)*(N+2)+(j)+1] +#define U_nxt(i,j) buffers[2][((i)+1)*(N+2)+(j)+1] +// END: T1b + +// Simulation parameters: size, step count, and how often to save the state +int_t + M = 256, // rows + N = 256, // cols + max_iteration = 4000, + snapshot_freq = 20; + +// Wave equation parameters, time step is derived from the space step +const real_t + c = 1.0, + dx = 1.0, + dy = 1.0; +real_t + dt; + + + + +// Rotate the time step buffers. +void move_buffer_window ( void ) +{ + real_t *temp = buffers[0]; + buffers[0] = buffers[1]; + buffers[1] = buffers[2]; + buffers[2] = temp; +} + + +// TASK: T8 +// Save the present time step in a numbered file under 'data/' +void domain_save ( int_t step ) +{ +// BEGIN: T8 + char filename[256]; + + // Ensure output directory exists (ignore error if it already exists) + if (mkdir("data", 0755) != 0 && errno != EEXIST) { + perror("mkdir data"); + exit(EXIT_FAILURE); + } + + snprintf(filename, sizeof(filename), "data/%05" PRId64 ".dat", step); + + FILE *out = fopen(filename, "wb"); + if (out == NULL) { + perror("fopen output file"); + fprintf(stderr, "Failed to open '%s' for writing.\n", filename); + exit(EXIT_FAILURE); + } + + for ( int_t i = 0; i < M; ++i ) { + size_t written = fwrite ( &U(i,0), sizeof(real_t), (size_t)N, out ); + if ( written != (size_t)N ) { + perror("fwrite"); + fclose(out); + exit(EXIT_FAILURE); + } + } + + if ( fclose(out) != 0 ) { + perror("fclose"); + exit(EXIT_FAILURE); + } +// END: T8 +} + + +// TASK: T7 +// Neumann (reflective) boundary condition +void boundary_condition ( void ) +{ +// BEGIN: T7 + // Vertical ghost layers (bottom and top) + for (int_t i=0; i= 0 && neighbor_coords[1] >= 0) { + MPI_Cart_rank(cartesian_comm, neighbor_coords, &up_left); + } else { + up_left = MPI_PROC_NULL; + } + + neighbor_coords[0] = coordinates[0] - 1; + neighbor_coords[1] = coordinates[1] + 1; + if (neighbor_coords[0] >= 0 && neighbor_coords[1] < dims[1]) { + MPI_Cart_rank(cartesian_comm, neighbor_coords, &up_right); + } else { + up_right = MPI_PROC_NULL; + } + + neighbor_coords[0] = coordinates[0] + 1; + neighbor_coords[1] = coordinates[1] - 1; + if (neighbor_coords[0] < dims[0] && neighbor_coords[1] >= 0) { + MPI_Cart_rank(cartesian_comm, neighbor_coords, &down_left); + } else { + down_left = MPI_PROC_NULL; + } + + neighbor_coords[0] = coordinates[0] + 1; + neighbor_coords[1] = coordinates[1] + 1; + if (neighbor_coords[0] < dims[0] && neighbor_coords[1] < dims[1]) { + MPI_Cart_rank(cartesian_comm, neighbor_coords, &down_right); + } else { + down_right = MPI_PROC_NULL; + } +} + +// TASK: T6 +// Communicate the border between processes. +void border_exchange ( void ) +{ +// BEGIN: T6 + ; +// END: T6 +} + + +// Main time integration. +void simulate( void ) +{ + // Go through each time step + for ( int_t iteration=0; iteration<=max_iteration; iteration++ ) + { + if ( (iteration % snapshot_freq)==0 ) + { + domain_save ( iteration / snapshot_freq ); + } + + // Derive step t+1 from steps t and t-1 + border_exchange(); + boundary_condition(); + time_step(); + + // Rotate the time step buffers + move_buffer_window(); + } +} + + +int main ( int argc, char **argv ) +{ +// TASK: T1c +// Initialise MPI +// BEGIN: T1c + ; +// END: T1c + + +// TASK: T3 +// Distribute the user arguments to all the processes +// BEGIN: T3 + OPTIONS *options = parse_args( argc, argv ); + if ( !options ) + { + fprintf( stderr, "Argument parsing failed\n" ); + exit( EXIT_FAILURE ); + } + + M = options->M; + N = options->N; + max_iteration = options->max_iteration; + snapshot_freq = options->snapshot_frequency; +// END: T3 + + // Set up the initial state of the domain + domain_initialize(); + + + struct timeval t_start, t_end; + +// TASK: T2 +// Time your code +// BEGIN: T2 + simulate(); +// END: T2 + + // Clean up and shut down + domain_finalize(); + +// TASK: T1d +// Finalise MPI +// BEGIN: T1d + ; +// END: T1d + + exit ( EXIT_SUCCESS ); +} diff --git a/exercise4/wave_2d_sequential.c b/exercise4/wave_2d_sequential.c new file mode 100644 index 0000000..f75caa9 --- /dev/null +++ b/exercise4/wave_2d_sequential.c @@ -0,0 +1,238 @@ +#define _XOPEN_SOURCE 600 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "argument_utils.h" + + +// Convert 'struct timeval' into seconds in double prec. floating point +#define WALLTIME(t) ((double)(t).tv_sec + 1e-6 * (double)(t).tv_usec) + +// Option to change numerical precision +typedef int64_t int_t; +typedef double real_t; + +// Simulation parameters: size, step count, and how often to save the state +int_t + N = 256, + M = 256, + max_iteration = 4000, + snapshot_freq = 20; + +// Wave equation parameters, time step is derived from the space step +const real_t + c = 1.0, + dx = 1.0, + dy = 1.0; +real_t + dt; + +// Buffers for three time steps, indexed with 2 ghost points for the boundary +real_t + *buffers[3] = { NULL, NULL, NULL }; + +#define U_prv(i,j) buffers[0][((i)+1)*(N+2)+(j)+1] +#define U(i,j) buffers[1][((i)+1)*(N+2)+(j)+1] +#define U_nxt(i,j) buffers[2][((i)+1)*(N+2)+(j)+1] + + +// Rotate the time step buffers. +void move_buffer_window ( void ) +{ + real_t *temp = buffers[0]; + buffers[0] = buffers[1]; + buffers[1] = buffers[2]; + buffers[2] = temp; +} + + +// Save the present time step in a numbered file under 'data/' +void domain_save ( int_t step ) +{ + char filename[256]; + + // Ensure output directory exists (ignore error if it already exists) + if (mkdir("data", 0755) != 0 && errno != EEXIST) { + perror("mkdir data"); + exit(EXIT_FAILURE); + } + + snprintf(filename, sizeof(filename), "data/%05" PRId64 ".dat", step); + + FILE *out = fopen(filename, "wb"); + if (out == NULL) { + perror("fopen output file"); + fprintf(stderr, "Failed to open '%s' for writing.\n", filename); + exit(EXIT_FAILURE); + } + + for ( int_t i = 0; i < M; ++i ) { + size_t written = fwrite ( &U(i,0), sizeof(real_t), (size_t)N, out ); + if ( written != (size_t)N ) { + perror("fwrite"); + fclose(out); + exit(EXIT_FAILURE); + } + } + + if ( fclose(out) != 0 ) { + perror("fclose"); + exit(EXIT_FAILURE); + } +} + + + +// Neumann (reflective) boundary condition +void boundary_condition ( void ) +{ + // Vertical ghost layers (bottom and top) + for (int_t i=0; iM; + N = options->N; + max_iteration = options->max_iteration; + snapshot_freq = options->snapshot_frequency; + + if (M < 2 || N < 2) + { + fprintf(stderr, "M and N must be >= 2\n"); + exit(EXIT_FAILURE); + } + + + // Set up the initial state of the domain + domain_initialize(); + + struct timeval t_start, t_end; + + gettimeofday ( &t_start, NULL ); + simulate(); + gettimeofday ( &t_end, NULL ); + + printf ( "Total elapsed time: %lf seconds\n", + WALLTIME(t_end) - WALLTIME(t_start) + ); + + // Clean up and shut down + domain_finalize(); + exit ( EXIT_SUCCESS ); +}