├── 2024-ΑΙ.pdf ├── OpenMP-set-2019.pdf ├── OpenMP-set-2020.pdf ├── README.md ├── _config.yml ├── hello-world-parallel-id-func.c ├── hello-world-parallel-id-scope.c ├── hello-world-parallel-id.c ├── hello-world-parallel.c ├── laplace2D.c ├── pi_mc ├── pi_mc.c ├── pi_mc.ipynb ├── pi_mc.jl ├── pi_mc.py ├── pi_mc_julia.ipynb ├── pi_mc_python_multi-convergence.ipynb └── pi_mc_python_multi.ipynb ├── poisson-SOR.c ├── table-add1-combined.c ├── table-add1-manual.c ├── table-add1-wrong.c ├── table-add1.c ├── table-implicit-notpar.c ├── table-sum-wrong.c └── table-sum.c /2024-ΑΙ.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niksterg/openmp-course/c6a8e1288fbd44a0f8a0a11f356a0bc9b880c0e1/2024-ΑΙ.pdf -------------------------------------------------------------------------------- /OpenMP-set-2019.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niksterg/openmp-course/c6a8e1288fbd44a0f8a0a11f356a0bc9b880c0e1/OpenMP-set-2019.pdf -------------------------------------------------------------------------------- /OpenMP-set-2020.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niksterg/openmp-course/c6a8e1288fbd44a0f8a0a11f356a0bc9b880c0e1/OpenMP-set-2020.pdf -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## OpenMP Examples 2 | ### Prof. N. Stergioulas 3 | ### Aristotle University of Thessaloniki 4 | 5 | 6 | ### Example programs 7 | 8 | The following example programs introduce the main concepts of OpenMP step by step. 9 | 10 | 11 | 1. [**hello-world-parallel.c**](https://github.com/niksterg/openmp-course/blob/master/hello-world-parallel.c) (the most basic hello world executed in parallel) 12 | 13 | 2. [**hello-world-parallel-id.c**](https://github.com/niksterg/openmp-course/blob/master/hello-world-parallel-id.c) (similar to 1. but the id of each thread and the total number of threads are also printed) 14 | 15 | 3. [**hello-world-parallel-id-func.c**](https://github.com/niksterg/openmp-course/blob/master/hello-world-parallel-id-func.c) (same result as 2. but a function is called to print inside the parallel region) 16 | 17 | 4. [**hello-world-parallel-id-scope.c**](https://github.com/niksterg/openmp-course/blob/master/hello-world-parallel-id-scope.c) (same result as 2. but data scope is used in the parallel region) 18 | 19 | 5. [**table-add1-manual.c**](https://github.com/niksterg/openmp-course/blob/master/table-add1-manual.c) (add a number to each element of a table, using manual parallelization) 20 | 21 | 6. [**table-add1.c**](https://github.com/niksterg/openmp-course/blob/master/table-add1.c) (same result as 5. but with automatic work scheduling) 22 | 23 | 7. [**table-add1-combined.c**](https://github.com/niksterg/openmp-course/blob/master/table-add1-combined.c) (same result as 6. but with combined parallel region and for construct) 24 | 25 | 8. [**table-add1-wrong.c**](https://github.com/niksterg/openmp-course/blob/master/table-add1-wrong.c) (similar to 6. and 7. but giving wrong answer - find the error in the code!) 26 | 27 | 9. [**table-implicit-notpar.c**](https://github.com/niksterg/openmp-course/blob/master/table-implicit-notpar.c) (parallel execution gives wrong answer - find out why!) 28 | 29 | 10. [**table-sum.c**](https://github.com/niksterg/openmp-course/blob/master/table-sum.c) (computing the sum of all elements in a table) 30 | 31 | 11. [**table-sum-wrong.c**](https://github.com/niksterg/openmp-course/blob/master/table-sum-wrong.c) (similar to 10. but gives wrong answer - find the error in the code!) 32 | 33 | ### Exercises 34 | 35 | [**2020 Homework on 2D wave equation**](https://github.com/niksterg/openmp-course/blob/master/OpenMP-set-2020.pdf) 36 | 37 | [**2019 Homework on Poisson solvers**](https://github.com/niksterg/openmp-course/blob/master/OpenMP-set-2019.pdf) (see [this figure](https://www.researchgate.net/profile/Chhote-Shah/publication/336512640/figure/fig2/AS:922552943792128@1596965170203/Red-Black-ordering-technique-and-implementation-of-the-SOR-algorithm-a-updating-the.png)) 38 | 39 | [**poisson-SOR.c**](https://github.com/niksterg/openmp-course/blob/master/poisson-SOR.c) Example: ./poisson-SOR -N 400 -M 400 -a 1e-6 -o 1.9 40 | 41 | 42 | **2023/24 Homework:** 43 | 44 | **Set #1:** 45 | - Run the examples 1 - 10. 46 | - Find and correct the mistakes in examples 8, 9 and 11. 47 | 48 | **Set #2:** 49 | 50 | - Write a code in C/C++ that calculates π using a Monte-Carlo method. Use up to 10^10 points. 51 | - Parallelize the code using OpenMP. Run the code using 1, 2, 4, etc. threads (up to twice the physical number of cores, try at least up to 8). 52 | - Create a Jupyter python notebook within which you automatically run the C/C++ code using different number of cores (use a list) and plot the execution time as a function of the number of cores. Alternatively, run the C/C++ code using a script, save the results in a file and load them into a Jupyter notebook to make the plot. 53 | - In the same notebook, create a second plot of parallel speedup vs number of cores. 54 | - Use [Amdahl's law](https://en.wikipedia.org/wiki/Amdahl%27s_law) to fit the resulting curve and find the proportion p of the code that benefits from parallelization and the maximum possible speedup in the limit of 10000 cores. (You can do the fit with e.g. spipy's curvefit.) 55 | - Run the code for 10^2, 10^3, 10^4, ... , 10^11 points and, in a second notebook, calculate the convergence rate of your Monte-Carlo implementation. You can do this if you fit a line in a log-log- plot of the error in calculating π vs. the number of points. Try to find what the theoretical expectation is and compare your result to it. 56 | 57 | Use logarithmic scale wherever the numerical values change by orders of magnitude! 58 | 59 | (Bonus track: repeat with Python+Numba and/or Julia) 60 | 61 | **2024/25** 62 | 63 | Example code: [2D Laplace equation](https://github.com/niksterg/openmp-course/blob/master/laplace2D.c) with OpenMP 64 | 65 | Example codes: [Calculate pi using MCMC, with OpenMP, but also in python and Julia](https://github.com/niksterg/openmp-course/tree/master/pi_mc) 66 | 67 | Example code: [Multi-dimensional regression using ANN](https://www.kaggle.com/code/nikolaosstergioulas/house-prices-ann) 68 | 69 | **2024/25 Homework (due Feb. 28):** 70 | 71 | **Set #1:** 72 | - Run the examples 1 - 10. 73 | - Find and correct the mistakes in examples 8, 9 and 11. 74 | 75 | **Set #2:** 76 | 77 | OpenMP: Decide on your own problem to parallelize with OpenMP and construct a similar report as for Set #2 of 2023/24, following the guidelines in that problem set. 78 | 79 | **Set #3:** 80 | 81 | Select a multidimensional regression problem (real data or synthetic data that you create) and write an ANN to predict y(X). Perform many trainings varying the hyperparameters and write a report with your findings. You can use data from Kaggle or from other sources or from your own experiments or create your own synthetic data. Submit a) your codes, b) link to the data c) a detailed report of your findings. 82 | 83 | ### Tutorials 84 | 85 | 1. [Tutorial by N. Trifonidis (part 1)](http://www.astro.auth.gr/~niksterg/courses/progtools/1-OpenMP-tutorial.pdf) 86 | 2. [Tutorial by N. Trifonidis (part 2)](http://www.astro.auth.gr/~niksterg/courses/progtools/2-OpenMP-tutorial.pdf) 87 | 3. [A brief introduction by A. Kiessling](http://www.roe.ac.uk/ifa/postgrad/pedagogy/2009_kiessling.pdf) 88 | 4. [Tutorial by S.C. Huang](https://idre.ucla.edu/sites/default/files/intro-openmp-2013-02-11.pdf) 89 | 5. [Tutorial by Texas A&M](https://people.math.umass.edu/~johnston/PHI_WG_2014/OpenMPSlides_tamu_sc.pdf) 90 | 6. [Tutorial by T. Mattson and L. Meadows](http://www.openmp.org/wp-content/uploads/omp-hands-on-SC08.pdf) 91 | 7. [Tutorial by Y. W. Li (includes Vtune examples)](https://permalink.lanl.gov/object/tr?what=info:lanl-repo/lareport/LA-UR-20-23416) 92 | 8. [Online tutorial by B. Barney](https://computing.llnl.gov/tutorials/openMP/) 93 | 9. [Online tutorial by Y. Yliluoma](https://bisqwit.iki.fi/story/howto/openmp/) 94 | 10. [Online list of potential mistakes](https://www.viva64.com/en/a/0054/) 95 | 11. [Video tutorial by C. Terboven (part 1)](https://www.youtube.com/watch?v=6FMn7M5jxrM) 96 | 12. [Video tutorial by C. Terboven (part 2)](https://www.youtube.com/watch?v=Whq28OaPW08) 97 | 13. [Video channel by PPCES](https://www.youtube.com/channel/UCtdrEoe46tD2IvJJRs_JH1A) 98 | 14. [Additional resources](https://www.openmp.org/resources/tutorials-articles/) 99 | 15. [OpenMP 3.1 Quick Reference Card](https://www.openmp.org//wp-content/uploads/OpenMP3.1-CCard.pdf) 100 | 101 | 102 | 103 | ### License 104 | 105 | ##### Content provided under a Creative Commons Attribution license, [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/); code under [MIT License](https://opensource.org/licenses/MIT). (c)2018 [Nikolaos Stergioulas](http://www.astro.auth.gr/~niksterg/) 106 | 107 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /hello-world-parallel-id-func.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | void printmessage(int myid) 5 | { 6 | printf("Thread %d says: Hello World!\n",myid); 7 | } 8 | 9 | int main(void) { 10 | 11 | int nThreads; 12 | 13 | #pragma omp parallel 14 | { 15 | int myid = omp_get_thread_num(); 16 | 17 | printmessage(myid); 18 | 19 | if(myid==0) nThreads = omp_get_num_threads(); 20 | } 21 | 22 | printf("The total number of threads used was %d.\n",nThreads); 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /hello-world-parallel-id-scope.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | 6 | int myid, 7 | nThreads; 8 | 9 | #pragma omp parallel shared(nThreads) private(myid) default(none) 10 | { 11 | myid = omp_get_thread_num(); 12 | 13 | printf("Thread %d says: Hello World!\n",myid); 14 | 15 | if(myid==0) nThreads = omp_get_num_threads(); 16 | } 17 | 18 | printf("The total number of threads used was %d.\n",nThreads); 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /hello-world-parallel-id.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | 6 | int nThreads; 7 | 8 | #pragma omp parallel 9 | { 10 | int myid = omp_get_thread_num(); 11 | printf("Thread %d says: Hello World!\n",myid); 12 | 13 | if(myid == 0) nThreads = omp_get_num_threads(); 14 | } 15 | 16 | printf("The total number of threads used was %d.\n",nThreads); 17 | 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /hello-world-parallel.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(void) { 5 | 6 | #pragma omp parallel 7 | { 8 | printf("Hello World!\n"); 9 | } 10 | 11 | return 0; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /laplace2D.c: -------------------------------------------------------------------------------- 1 | /* laplace.c: Solving Laplace's equation in 2D by Jacobi iteration using OpenMP. 2 | * 3 | * ∇²u(x,y) = 0 on a rectangular domain. 4 | * 5 | * We set up a grid (nx x ny) with Dirichlet boundary conditions (here we fix 6 | * the top boundary to 1.0 and the other boundaries to 0.0). Then we iteratively 7 | * update the interior points with the average of their four neighbors. 8 | * 9 | * To compile: 10 | * gcc -O3 -fopenmp laplace.c -o laplace 11 | * 12 | * To run (example with grid size 10000x10000): 13 | * ./laplace 10000 10000 14 | * 15 | * You can control the number of threads via the environment variable OMP_NUM_THREADS. 16 | * 17 | * This code is intended as a demonstration for MSc computational physics courses. 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | int main(int argc, char *argv[]) { 26 | // Grid dimensions (default: 1000 x 1000; increase for larger memory usage / longer runtime) 27 | int nx = 1000; 28 | int ny = 1000; 29 | if (argc >= 3) { 30 | nx = atoi(argv[1]); 31 | ny = atoi(argv[2]); 32 | } 33 | 34 | // Total number of grid points. 35 | int size = nx * ny; 36 | 37 | // Allocate two 1D arrays to represent the 2D grid (row-major order). 38 | double *u = (double*) malloc(sizeof(double) * size); 39 | double *u_new = (double*) malloc(sizeof(double) * size); 40 | if (!u || !u_new) { 41 | fprintf(stderr, "Error allocating memory.\n"); 42 | return 1; 43 | } 44 | 45 | // Initialize arrays: interior points start at 0.0. 46 | for (int i = 0; i < size; i++){ 47 | u[i] = 0.0; 48 | u_new[i] = 0.0; 49 | } 50 | 51 | // Set boundary conditions. 52 | // Here, we fix the top boundary (last row) to 1.0 and all others to 0.0. 53 | for (int i = 0; i < nx; i++){ 54 | int idx = i * ny + (ny - 1); // index for top boundary in each column 55 | u[idx] = 1.0; 56 | u_new[idx] = 1.0; 57 | } 58 | 59 | // Convergence parameters. 60 | double tol = 1e-6; // convergence tolerance 61 | int max_iter = 1000000; // maximum number of iterations 62 | int iter = 0; 63 | double diff = 0.0; // will hold maximum difference between iterations 64 | 65 | // Timing the computation 66 | double start_time = omp_get_wtime(); 67 | 68 | // Main Jacobi iteration loop. 69 | while (iter < max_iter) { 70 | diff = 0.0; 71 | 72 | // Update the interior points. 73 | // Note: The update at each point depends only on values from the previous iteration, 74 | // so we can parallelize the two nested loops using OpenMP. 75 | #pragma omp parallel for reduction(max:diff) schedule(static) 76 | for (int i = 1; i < nx - 1; i++) { 77 | for (int j = 1; j < ny - 1; j++) { 78 | int idx = i * ny + j; 79 | int idx_up = (i - 1) * ny + j; 80 | int idx_down = (i + 1) * ny + j; 81 | int idx_left = i * ny + (j - 1); 82 | int idx_right = i * ny + (j + 1); 83 | 84 | // Compute the new value as the average of the four neighbors. 85 | u_new[idx] = 0.25 * (u[idx_up] + u[idx_down] + u[idx_left] + u[idx_right]); 86 | 87 | // Compute the local difference. 88 | double local_diff = fabs(u_new[idx] - u[idx]); 89 | if (local_diff > diff) 90 | diff = local_diff; 91 | } 92 | } 93 | 94 | // Copy new values back into u (this loop can also be parallelized). 95 | #pragma omp parallel for schedule(static) 96 | for (int i = 1; i < nx - 1; i++) { 97 | for (int j = 1; j < ny - 1; j++) { 98 | int idx = i * ny + j; 99 | u[idx] = u_new[idx]; 100 | } 101 | } 102 | 103 | iter++; 104 | if (iter % 100 == 0) { 105 | printf("Iteration %d: max diff = %e\n", iter, diff); 106 | } 107 | if (diff < tol) 108 | break; 109 | } 110 | 111 | double end_time = omp_get_wtime(); 112 | printf("Converged after %d iterations with max diff = %e\n", iter, diff); 113 | printf("Total runtime = %f seconds\n", end_time - start_time); 114 | 115 | // Clean up. 116 | free(u); 117 | free(u_new); 118 | 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /pi_mc/pi_mc.c: -------------------------------------------------------------------------------- 1 | // pi_mc.c 2 | // 3 | // This program calculates the value of pi using the Monte Carlo method. 4 | // It uses OpenMP for parallelization. 5 | // The number of threads is set by the environment variable OMP_NUM_THREADS. 6 | // The code is written for unsigned long int, which is 64 bits on a 64-bit machine. 7 | // The random number generator is rand_long, which is thread-safe and works with unsigned long int. 8 | // The random number generator is seeded with the time in seconds times the thread number. 9 | // 10 | // Nikolaos Stergioulas, Aristotle University of Thessaloniki 11 | // 12 | // Content provided under a Creative Commons Attribution license, CC BY-NC-SA 4.0; code under GNU GPLv3 License. 13 | // (c)2024 Nikolaos Stergioulas 14 | 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | 23 | unsigned long int rand_long(unsigned int *seed) { 24 | 25 | // This function defines a random number generator that returns an unsigned long int 26 | // between 0 and UINT_MAX*UINT_MAX, given a seed. It calls rand_r, which produces two 27 | // random numbers between 0 and RMAX and combines them into a single random number between 28 | // RAND_MAX << 32 | RAND_MAX. 29 | 30 | long int result = rand_r(seed); 31 | result <<= 32; 32 | result |= rand_r(seed); 33 | return result; 34 | } 35 | 36 | int main(int argc, char *argv[]) { 37 | 38 | int opt; // Command line option 39 | 40 | long int count = 0; 41 | 42 | long int i, // Loop counter 43 | N = 1000000000; // How many random numbers to generate (default value) 44 | 45 | double x, // Random number 46 | y, // Random number 47 | pi, // Estimate of pi 48 | scale_factor; // Scale factor for random numbers 49 | 50 | unsigned int seed; // Declare the seed variable 51 | 52 | unsigned long int unsigned_long_scale_max; // Maximum value of unsigned long int 53 | 54 | // Parse the command line arguments to set how many random numbers to generate 55 | while ((opt = getopt(argc, argv, "n:")) != -1) { 56 | switch (opt) { 57 | case 'n': 58 | N = atol(optarg); 59 | break; 60 | default: 61 | fprintf(stderr, "Usage: %s [-n N]\n", argv[0]); 62 | exit(EXIT_FAILURE); 63 | } 64 | } 65 | 66 | 67 | // This is the maximum value of unsigned long int produced by rand_long 68 | unsigned_long_scale_max = ((unsigned long int)RAND_MAX << 32) | RAND_MAX; 69 | 70 | scale_factor = 1.0/ unsigned_long_scale_max; // Scale the random numbers to the range [0, 1] 71 | 72 | #pragma omp parallel private(x, y, seed) reduction(+:count) 73 | { 74 | seed = time(NULL) * omp_get_thread_num(); // Initialize private seed for each thread 75 | 76 | #pragma omp for // Loop over the number of random numbers to generate 77 | for(i = 0; i < N; i++) { 78 | x = rand_long(&seed) *scale_factor * 2.0 - 1.0; // Generate a random number between -1 and 1 79 | y = rand_long(&seed) *scale_factor* 2.0 - 1.0; // Generate a random number between -1 and 1 80 | 81 | if(x * x + y * y <= 1.0) { // If the random number is in the unit circle 82 | count++; // increment the count 83 | } 84 | } 85 | } 86 | 87 | pi = 4.0 * count / N; // Calculate the estimate of pi 88 | 89 | printf("Estimated value of Pi = %f\n", pi); 90 | 91 | return 0; 92 | } -------------------------------------------------------------------------------- /pi_mc/pi_mc.jl: -------------------------------------------------------------------------------- 1 | # pi_mc.jl 2 | # 3 | # This program calculates the value of pi using the Monte Carlo method. 4 | # It uses Julia's thread parallelization. 5 | # The number of random trials is set by command-line argument -n. 6 | # 7 | # Nikolaos Stergioulas, Aristotle University of Thessaloniki 8 | # 9 | # Content provided under a Creative Commons Attribution license, CC BY-NC-SA 4.0; code under GNU GPLv3 License. 10 | # (c)2024 Nikolaos Stergioulas 11 | 12 | # in the Julia REPL install the ArgParse package with 13 | # import Pkg; Pkg.add("ArgParse") 14 | 15 | using ArgParse 16 | using Random 17 | 18 | function count_hits(random_trials::Int, seed::Int) 19 | rng = MersenneTwister(seed) 20 | count = 0 21 | for _ in 1:random_trials 22 | # Julia generates random numbers in the interval [0,1) 23 | # to generate random numbers in the interval [-1,1) we use 24 | # 2*(rand(rng) - 0.5), which means that we generate random 25 | # numbers in the interval [-0.5,0.5) and then we multiply 26 | # by 2 and subtract 1. 27 | x, y = 2*(rand(rng) - 0.5), 2*(rand(rng) - 0.5) 28 | if x * x + y * y <= 1.0 29 | count += 1 30 | end 31 | end 32 | return count 33 | end 34 | 35 | # function count_hits(random_trials::Int, seed::Int) 36 | # rng = MersenneTwister(seed) 37 | # xs = 2.0 * (rand(rng, random_trials) .- 0.5) 38 | # ys = 2.0 * (rand(rng, random_trials) .- 0.5) 39 | # count = 0 40 | # @inbounds for i in 1:random_trials 41 | # if xs[i]^2 + ys[i]^2 <= 1.0 42 | # count += 1 43 | # end 44 | # end 45 | # return count 46 | # end 47 | 48 | function main() 49 | s = ArgParseSettings() 50 | @add_arg_table! s begin 51 | "--random_trials", "-n" 52 | arg_type = Int 53 | required = true 54 | help = "number of random trials" 55 | "--seed", "-s" 56 | arg_type = Int 57 | default = trunc(Int, time()) 58 | help = "random seed" 59 | "--threads", "-p" 60 | arg_type = Int 61 | default = 1 62 | help = "number of threads" 63 | end 64 | 65 | parsed_args = parse_args(ARGS, s) 66 | random_trials = parsed_args["random_trials"] 67 | seed = parsed_args["seed"] 68 | threads = parsed_args["threads"] 69 | 70 | # Set the number of threads 71 | ENV["JULIA_NUM_THREADS"] = threads 72 | 73 | trials_per_thread = random_trials ÷ threads 74 | 75 | counts = zeros(Int, threads) 76 | Threads.@threads for i in 1:threads 77 | counts[i] = count_hits(trials_per_thread, seed + i) 78 | end 79 | 80 | count = sum(counts) 81 | 82 | pi_estimate = 4.0 * count / random_trials 83 | println("Pi estimate = ", pi_estimate) 84 | end 85 | 86 | main() -------------------------------------------------------------------------------- /pi_mc/pi_mc.py: -------------------------------------------------------------------------------- 1 | # pi_mc.py 2 | # 3 | # This program calculates the value of pi using the Monte Carlo method. 4 | # It uses Python multiprocessing for parallelization. 5 | # The number of threads is set by command-line argument -p. 6 | # The number of random trials is set by command-line argument -n. 7 | # 8 | # Nikolaos Stergioulas, Aristotle University of Thessaloniki 9 | # 10 | # Content provided under a Creative Commons Attribution license, CC BY-NC-SA 4.0; code under GNU GPLv3 License. 11 | # (c)2024 Nikolaos Stergioulas 12 | 13 | 14 | import argparse 15 | import random 16 | import multiprocessing 17 | import time 18 | from numba import njit 19 | from numba.typed import List 20 | from numba import prange 21 | from numba import jit 22 | import argparse 23 | import numpy as np 24 | 25 | 26 | @njit(fastmath=False) 27 | def count_hits(random_trials, seed=None): 28 | random.seed(seed) 29 | count = 0 30 | for _ in range(random_trials): 31 | x, y = random.uniform(-1.0, 1.0), random.uniform(-1.0, 1.0) 32 | if x * x + y * y <= 1.0: 33 | count += 1 34 | return count 35 | 36 | # @jit(nopython=True, parallel=True) 37 | # def count_hits(random_trials, seed=None): 38 | # np.random.seed(seed) 39 | # count = 0 40 | # for _ in prange(random_trials): 41 | # x, y = np.random.uniform(-1.0, 1.0), np.random.uniform(-1.0, 1.0) 42 | # if x * x + y * y <= 1.0: 43 | # count += 1 44 | # return count 45 | 46 | 47 | if __name__ == "__main__": 48 | 49 | parser = argparse.ArgumentParser(description='Calculate Pi using Monte Carlo method.') 50 | parser.add_argument('-p', '--processes', type=int, default=multiprocessing.cpu_count(), 51 | help='Number of processes to use') 52 | parser.add_argument('-n', '--N', type=int, default=100000000, 53 | help='Number of random trials') 54 | args = parser.parse_args() 55 | 56 | num_processes = args.processes #multiprocessing.cpu_count() 57 | 58 | N = args.N 59 | 60 | pool = multiprocessing.Pool(processes=num_processes) 61 | 62 | start_time = time.time() 63 | 64 | random_trials_per_process = N // num_processes 65 | 66 | #counts = pool.map(count_hits, [random_trials_per_process] * num_processes) 67 | 68 | #seeds = [i for i in range(num_processes)] 69 | #seeds = [random.randint(0, 1e9) for _ in range(num_processes)] 70 | seeds = [int(time.time()) + i for i in range(num_processes)] 71 | 72 | counts = pool.starmap(count_hits, [(random_trials_per_process, seed) for seed in seeds]) 73 | 74 | pi_estimate = 4 * float(sum(counts)) / float(N) 75 | 76 | end_time = time.time() 77 | print(f"Estimated value of Pi = {pi_estimate:.12f}") -------------------------------------------------------------------------------- /pi_mc/pi_mc_python_multi-convergence.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 37, 6 | "metadata": {}, 7 | "outputs": [ 8 | { 9 | "name": "stdout", 10 | "output_type": "stream", 11 | "text": [ 12 | "Estimated value of Pi = 2.960000000000\n", 13 | "\n", 14 | "Estimated value of Pi = 3.116000000000\n", 15 | "\n", 16 | "Estimated value of Pi = 3.157600000000\n", 17 | "\n", 18 | "Estimated value of Pi = 3.150760000000\n", 19 | "\n", 20 | "Estimated value of Pi = 3.140860000000\n", 21 | "\n", 22 | "Estimated value of Pi = 3.142372800000\n", 23 | "\n", 24 | "Estimated value of Pi = 3.141508280000\n", 25 | "\n", 26 | "Estimated value of Pi = 3.141616232000\n", 27 | "\n", 28 | "Estimated value of Pi = 3.141586098400\n", 29 | "\n", 30 | "Estimated value of Pi = 3.141589239680\n", 31 | "\n", 32 | "Estimated value of Pi = 3.141591983784\n", 33 | "\n" 34 | ] 35 | } 36 | ], 37 | "source": [ 38 | "import subprocess\n", 39 | "import os\n", 40 | "import time\n", 41 | "import matplotlib.pyplot as plt\n", 42 | "import numpy as np\n", 43 | "\n", 44 | "# Set the OMP_NUM_THREADS environment variable\n", 45 | "num_threads = 32\n", 46 | "os.environ[\"OMP_NUM_THREADS\"] = str(num_threads)\n", 47 | "\n", 48 | "Nmin = 100\n", 49 | "Nmax = 10**12\n", 50 | "\n", 51 | "num_list = []\n", 52 | "value = Nmin\n", 53 | "\n", 54 | "while value <= Nmax:\n", 55 | " num_list.append(value)\n", 56 | " value *= 10\n", 57 | "\n", 58 | "# List to store execution times\n", 59 | "execution_times = []\n", 60 | "output_list = []\n", 61 | "\n", 62 | "for N in num_list:\n", 63 | "\n", 64 | " # Run the Python code and capture the output\n", 65 | " start_time = time.time()\n", 66 | " output = subprocess.check_output([\"python\", \"pi_mc.py\", \"-p\", str(num_threads), \"-n\", str(N)], universal_newlines=True)\n", 67 | "\n", 68 | " # Append the output to the list\n", 69 | " output_list.append(output)\n", 70 | " print(output)\n", 71 | "\n", 72 | " end_time = time.time()\n", 73 | "\n", 74 | " # Calculate and store the execution time\n", 75 | " execution_time = end_time - start_time\n", 76 | " execution_times.append(execution_time)\n" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 38, 82 | "metadata": {}, 83 | "outputs": [ 84 | { 85 | "name": "stdout", 86 | "output_type": "stream", 87 | "text": [ 88 | "[2.96, 3.116, 3.1576, 3.15076, 3.14086, 3.1423728, 3.14150828, 3.141616232, 3.1415860984, 3.14158923968, 3.141591983784]\n" 89 | ] 90 | } 91 | ], 92 | "source": [ 93 | "numerical_values = [float(s.split('=')[1]) for s in output_list]\n", 94 | "print(numerical_values)" 95 | ] 96 | }, 97 | { 98 | "cell_type": "code", 99 | "execution_count": 46, 100 | "metadata": {}, 101 | "outputs": [ 102 | { 103 | "name": "stdout", 104 | "output_type": "stream", 105 | "text": [ 106 | " Number of threads Pi\n", 107 | " 100 2.9600000000000000\n", 108 | " 1000 3.1160000000000001\n", 109 | " 10000 3.1576000000000000\n", 110 | " 100000 3.1507600000000000\n", 111 | " 1000000 3.1408600000000000\n", 112 | " 10000000 3.1423728000000000\n", 113 | " 100000000 3.1415082800000000\n", 114 | " 1000000000 3.1416162320000001\n", 115 | " 10000000000 3.1415860983999999\n", 116 | " 100000000000 3.1415892396800000\n", 117 | " 1000000000000 3.1415919837840001\n" 118 | ] 119 | } 120 | ], 121 | "source": [ 122 | "import pandas as pd\n", 123 | "\n", 124 | "# Create a DataFrame from the list of lists\n", 125 | "table_data = []\n", 126 | "for i in range(len(num_list)):\n", 127 | " table_data.append([num_list[i], numerical_values[i]])\n", 128 | "\n", 129 | "df = pd.DataFrame(table_data, columns=['Number of threads', 'Pi'])\n", 130 | "\n", 131 | "# Set the option to display 15 digits accuracy\n", 132 | "pd.set_option('display.precision', 16)\n", 133 | "\n", 134 | "# Print the table\n", 135 | "print(df.to_string(index=False))\n" 136 | ] 137 | }, 138 | { 139 | "cell_type": "code", 140 | "execution_count": 40, 141 | "metadata": {}, 142 | "outputs": [ 143 | { 144 | "name": "stdout", 145 | "output_type": "stream", 146 | "text": [ 147 | "[-0.18159265358979315, -0.025592653589793013, 0.016007346410206846, 0.009167346410206889, -0.0007326535897931308, 0.00078014641020685, -8.437358979307419e-05, 2.3578410206948064e-05, -6.5551897932003556e-06, -3.4139097930818707e-06, -6.698057930520918e-07]\n" 148 | ] 149 | } 150 | ], 151 | "source": [ 152 | "difference = [value - np.pi for value in numerical_values]\n", 153 | "print(difference)\n" 154 | ] 155 | }, 156 | { 157 | "cell_type": "code", 158 | "execution_count": 44, 159 | "metadata": {}, 160 | "outputs": [ 161 | { 162 | "data": { 163 | "image/png": "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jNVz9+3bl/E6JiaGqKgofH19qVSpEsOHD6dXr17Uq1ePxo0b89lnnxEbG8uAAQMcWPWtR+1sH2pn+1A7Fyy1r504cMaaFCLLly+3AVl+evfunXHOxIkTbZUrV7a5ubnZ6tata1uxYoXjCr5FqZ3tQ+1sH2rngqX2tQ/tNSYiIiJOS2OERERExGkpCImIiIjTUhASERERp6UgJCIiIk5LQUhEREScloKQiIiIOC0FIREREXFaCkIiIiLitBSERERExGkpCImIiIjTUhASEafSp08fTCYT77zzTqbjP/zwAyaTyUFViYijKAiJiNPx8PBg3LhxnD171tGliIiDKQiJiNNp1aoVAQEBjB071tGliIiDKQiJiNOxWCy8/fbbfPLJJxw+fNjR5YiIAykIiYhT6tq1K6GhoYwYMcLRpYiIAykIiYjTGjduHF999RXR0dGOLkVEHERBSEScVvPmzWnbti2vvvqqo0sREQdxcXQBIiKO9M477xAaGkq1atUcXYqIOICeCImIU6tVqxaPPvoon3zyiaNLEREHUBASEac3ZswYbDabo8sQEQcw2fRvv4iIiDgpPRESERERp6UgJCIiIk5LQUhEREScloKQiIiIOC0FIREREXFaCkIiIiLitBSERERExGkpCImIiIjTUhASERERp6UgJCIiIk5LQUhERESc1v8DYJhb6zrw++4AAAAASUVORK5CYII=", 164 | "text/plain": [ 165 | "
" 166 | ] 167 | }, 168 | "metadata": {}, 169 | "output_type": "display_data" 170 | } 171 | ], 172 | "source": [ 173 | "plt.plot(num_list, np.abs(difference), marker='o')\n", 174 | "plt.xscale('log') # Set the x-axis scale to logarithmic\n", 175 | "plt.yscale('log') # Set the y-axis scale to logarithmic\n", 176 | "plt.xlabel('N')\n", 177 | "plt.ylabel('Difference')\n", 178 | "plt.title('Difference vs N')\n", 179 | "plt.grid(True)\n", 180 | "\n", 181 | "# Calculate the coefficients of the line\n", 182 | "coefficients = np.polyfit(np.log10(num_list), np.log10(np.abs(difference)), 1)\n", 183 | "slope = coefficients[0]\n", 184 | "intercept = coefficients[1]\n", 185 | "\n", 186 | "# Create the x and y values for the line\n", 187 | "x_line = np.logspace(np.log10(min(num_list)), np.log10(max(num_list)), 100)\n", 188 | "y_line = 10**(slope * np.log10(x_line) + intercept)\n", 189 | "\n", 190 | "# Plot the line\n", 191 | "plt.plot(x_line, y_line, color='red', label='fit')\n", 192 | "\n", 193 | "plt.legend()\n", 194 | "plt.show()" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 45, 200 | "metadata": {}, 201 | "outputs": [ 202 | { 203 | "name": "stdout", 204 | "output_type": "stream", 205 | "text": [ 206 | "-0.5358777509670519\n" 207 | ] 208 | } 209 | ], 210 | "source": [ 211 | "convergence_rate = slope\n", 212 | "print(convergence_rate)\n" 213 | ] 214 | }, 215 | { 216 | "cell_type": "markdown", 217 | "metadata": {}, 218 | "source": [ 219 | "The result confirms the anticipate convergence rate of Monte Carlo\n", 220 | "$$ {\\rm error} \\sim O(N^{-1/2})$$" 221 | ] 222 | }, 223 | { 224 | "cell_type": "markdown", 225 | "metadata": {}, 226 | "source": [] 227 | } 228 | ], 229 | "metadata": { 230 | "kernelspec": { 231 | "display_name": "igwn310", 232 | "language": "python", 233 | "name": "python3" 234 | }, 235 | "language_info": { 236 | "codemirror_mode": { 237 | "name": "ipython", 238 | "version": 3 239 | }, 240 | "file_extension": ".py", 241 | "mimetype": "text/x-python", 242 | "name": "python", 243 | "nbconvert_exporter": "python", 244 | "pygments_lexer": "ipython3", 245 | "version": "3.10.10" 246 | } 247 | }, 248 | "nbformat": 4, 249 | "nbformat_minor": 2 250 | } 251 | -------------------------------------------------------------------------------- /poisson-SOR.c: -------------------------------------------------------------------------------- 1 | /***************************************************************************** 2 | * 3 | * Poisson solver for red-black SOR method in OpenMP 4 | * 5 | * Author: Nikolaos Stergioulas, Aristotle University of Thessaloniki 6 | * 7 | * Content provided under a Creative Commons Attribution license, 8 | * CC BY-NC-SA 4.0; code under GNU GPLv3 License. (c)2020 Nikolaos Stergioulas 9 | *****************************************************************************/ 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | int main(int argc, 17 | char **argv) { 18 | 19 | int i, j, k=0, 20 | shift=0, 21 | *shift0, 22 | *shift1, 23 | N=200, /* default value */ 24 | M=200; /* default value */ 25 | 26 | double 27 | **uold, 28 | **unew, 29 | *x, 30 | *y, 31 | test = 0.0, 32 | oldavg, 33 | newavg, 34 | dx, 35 | dy, 36 | omega=1.9, /* default value */ 37 | accuracy = 1e-4, /* default value */ 38 | fTimeStart, 39 | fTimeEnd; 40 | 41 | // Record start time 42 | 43 | fTimeStart = omp_get_wtime(); 44 | 45 | // Read grid size, accuracy and omega from command line 46 | 47 | for(i=1;i accuracy); 180 | 181 | } 182 | 183 | // Free memory 184 | 185 | for (i = 0; i < N; i++){ 186 | free(unew[i] ); 187 | } 188 | free(unew); 189 | 190 | for (i = 0; i < N; i++){ 191 | free(uold[i] ); 192 | } 193 | free(uold); 194 | 195 | free(shift0); 196 | free(shift1); 197 | free(x); 198 | free(y); 199 | 200 | // Record end time 201 | 202 | fTimeEnd = omp_get_wtime(); 203 | 204 | // Print elapsed time 205 | 206 | printf("wall clock time = %.20f\n", fTimeEnd - fTimeStart); 207 | 208 | return 0; 209 | } 210 | -------------------------------------------------------------------------------- /table-add1-combined.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define N 12 5 | 6 | int main(void) { 7 | 8 | int i, 9 | A[N]; 10 | 11 | for(i=0;i 2 | #include 3 | 4 | #define N 12 5 | 6 | int main(void) { 7 | 8 | int myid, 9 | nThreads, 10 | i, 11 | iStart, 12 | iEnd, 13 | A[N]; 14 | 15 | for(i=0;i 2 | #include 3 | 4 | #define N 12 5 | 6 | int main(void) { 7 | 8 | int i, 9 | A[N]; 10 | 11 | for(i=0;i 2 | #include 3 | 4 | #define N 12 5 | 6 | int main(void) { 7 | 8 | int i, 9 | A[N]; 10 | 11 | for(i=0;i 2 | #include 3 | 4 | #define N 12 5 | 6 | int main(void) { 7 | 8 | int i, 9 | A[N]; 10 | 11 | for(i=0;i 2 | #include 3 | 4 | #define N 10 5 | 6 | int main(void) { 7 | 8 | int i, 9 | sum, 10 | a[N]; 11 | 12 | for(i=0;i 2 | #include 3 | 4 | #define N 10 5 | 6 | int main(void) { 7 | 8 | int i, 9 | sum, 10 | a[N]; 11 | 12 | for(i=0;i