Parallelise the serial matrix multiplication C code using data parallelism with MPI. You
 should implement a tile-based or submatrix approach in the parallel partitioning
 design. Please refer to Figure 2 for a sample depiction of this method. If you opt for a
 simpler row-based partitioning approach, you will not obtain the maximum marks for
 this task. Please refer to Week 11’s lecture materials for row and tile-based
 partitioning methods.
 b) Only the root process can access the input matrix binary files (e.g.,
 MA_1000x1000.bin & MB_1000x1000.bin). The root process reads the content of the
 input binary files into its memory.
 c) Implement a parallel partitioning algorithm to distribute the workload among the MPI
 processes equally.
 i)
 ii)
 iii)
 The root process will distribute the input matrices to the other processes in
 the MPI communicator. Note that you should apply an efficient approach in
 distributing the input matrices among the MPI processes, i.e., the root
 process should only send the required rows and columns of the input
 matrices to the other MPI processes. If the root process broadcasts
 unnecessary input data (matrices/sub-matrices) to any other MPI processes,
 the method will be inefficient, and marks will be deducted.
 All processes (including the root process) perform the matrix multiplication
 based on the partitioning algorithm.
 The root process gathers the computed matrices from other processes and
 writes the results into a binary file (e.g., MC.bin). Only the root process writes
 the results to a binary file.


//////////////////////////////////////////////////////////////////////////////////////
// MatrixMul_1D_bin.c
// ----------------------------------------------------------------------------------
//
// Multiplies two matrices and writes the resultant multiplication into a binary file.
//
//////////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <time.h>

int main()
{
	// Variables
	int i = 0, j = 0, k = 0;

	/* Clock information */
	struct timespec start, end, start_compute, end_compute; 
	double time_taken, time_taken_compute;

	clock_gettime(CLOCK_MONOTONIC, &start); 
	
	// 1. Read Matrix A
	int rowA = 0, colA = 0;

	printf("Matrix Multiplication using 1-Dimension Arrays - Start\n\n");

	printf("Reading Matrix A - Start\n");

	FILE *pFileA = fopen("MA_1500x1500.bin", "rb");
	fread(&rowA, sizeof(int), 1, pFileA); 
	fread(&colA, sizeof(int), 1, pFileA); 

	int *pMatrixA = (int*)malloc((rowA*colA) * sizeof(int));
	for(i = 0; i < rowA; i++){
		fread(&pMatrixA[i*colA], sizeof(int), colA, pFileA);
	}
	fclose(pFileA);

	printf("Reading Matrix A - Done\n");

	// 2. Read Matrix B
	int rowB = 0, colB = 0;

	printf("Reading Matrix B - Start\n");
	
	FILE *pFileB = fopen("MB_1500x1500.bin", "rb");
	fread(&rowB, sizeof(int), 1, pFileB); 
	fread(&colB, sizeof(int), 1, pFileB); 

	int *pMatrixB = (int*)malloc((rowB*colB) * sizeof(int));
	for(i = 0; i < rowB; i++){
		fread(&pMatrixB[i*colB], sizeof(int), colB, pFileB); 
	}
	fclose(pFileB);

	printf("Reading Matrix B - Done\n");

	// 3. Perform matrix multiplication 
	printf("Matrix Multiplication - Start\n");

	int rowC = rowA, colC = colB;
	unsigned long long *pMatrixC = (unsigned long long*)calloc((rowC*colC), sizeof(unsigned long long));

	int commonPoint = colA;

	clock_gettime(CLOCK_MONOTONIC, &start_compute); // Start the multiplication timer

	for(i = 0; i < rowC; i++){
		for(j = 0; j < colC; j++){
			for(k = 0; k < commonPoint; k++){
				pMatrixC[(i*colC)+j] += (pMatrixA[(i*colA)+k] * pMatrixB[(k*colB)+j]);
			}
		}
	}

	printf("Matrix Multiplication - Done\n");
	

	// 4. Write resuls to a new file
	clock_gettime(CLOCK_MONOTONIC, &end_compute); // Stop the multiplication timer
    time_taken_compute = (end_compute.tv_sec - start_compute.tv_sec) * 1e9;
    time_taken_compute = (time_taken_compute + (end_compute.tv_nsec - start_compute.tv_nsec)) * 1e-9;
    printf("Matrix Multiplication time: %f\n", time_taken_compute); // Print multiplication time

	printf("Matrix Multiplication - Done\n");

	FILE *pFileC = fopen("MC.bin", "wb"); 
	fwrite(&rowC, sizeof(int), 1, pFileC); 
	fwrite(&colC, sizeof(int), 1, pFileC); 
	for(i = 0; i < rowC; i++){
		fwrite(&pMatrixC[i*colC], sizeof(unsigned long long), colC, pFileC); // use fwrite to write one row of columns to the file
	}
	fclose(pFileC);
	printf("Write Resultant Matrix C to File - Done\n");

	clock_gettime(CLOCK_MONOTONIC, &end); 
	time_taken = (end.tv_sec - start.tv_sec) * 1e9; 
	time_taken = (time_taken + (end.tv_nsec - start.tv_nsec)) * 1e-9; 
	printf("Overall time (Including read, multiplication and write)(s): %lf\n", time_taken);	// ts

	// Clean up
	free(pMatrixA);
	free(pMatrixB);
	free(pMatrixC);

	printf("Matrix Multiplication using 1-Dimension Arrays - Done\n");
	return 0;
}