Skip to content

Commit 795e462

Browse files
authored
Merge pull request #9 from arunningcroc/master
Poisson 2D Jacobi solver benchmark with Python, Cython, C, and Fortran code
2 parents 9153a13 + b76fb4a commit 795e462

File tree

8 files changed

+459
-0
lines changed

8 files changed

+459
-0
lines changed

poisson2d/README.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
## Poisson solver in various languages
2+
3+
This solver uses a simple Jacobi iteration to solve a Poisson equation. For details, see, for example,
4+
"Computational Physics" by Mark Newman, Chap 9. Compiler commands were:
5+
6+
```gfortran sourcefile.f95 -Ofast -o program```
7+
8+
```gcc sourcefile.c -Ofast -o program```
9+
10+
For Cython module (import it to run):
11+
12+
```python setup.py build_ext --inplace```
13+
14+
The grid is a 2-dimensional array of size MxM. The timings for different values of M are, in seconds:
15+
16+
| Language | M=100 | M=200 | M=300 |
17+
|---------------------|-----------------|-----------------------|------------------------|
18+
| Python (pure) | 276 | n/a | n/a |
19+
| Cython | 1.02 | 32.8 | 229 |
20+
| Fortran (naive) | 0.34 | 13.2 | 69.7 |
21+
| Fortran (optimized) | 0.18 | 6.25 | 31.4 |
22+
| C (naive) | 0.42* | 7.25 | 33.7 |
23+
| C (optimized) | 0.37* | 6.80 | 32.8 |
24+
25+
* For all of these results, the amount of iterations performed by the respective codes was approximately
26+
the same, with the exception of the 100x100 grid C codes, which did nearly a double amount of iterations
27+
compared to the rest, for some reason. The timings are on AMD Ryzen 5 3600 @3.6GHz, using WSL2 on Windows 10.
28+
29+
Some thoughts on the code at https://runningcrocodile.fi/articles/pythonperformance.html . Also, there was
30+
discussion about this problem at Discourse: https://fortran-lang.discourse.group/t/performance-c-vs-fortran/1461
31+
with important contributions from numerous users (Mason, Beliavsky, septc, implicitall, nncarlson, han190 and pmk)

poisson2d/naive.c

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <math.h>
5+
#include <time.h>
6+
#define M 300
7+
void swap(double (*a)[M], double (*b)[M], int n)
8+
{
9+
double swp;
10+
for (int i = 0; i < n; i++) {
11+
for (int j = 0; j < n; j++) {
12+
swp = a[i][j];
13+
a[i][j] = b[i][j];
14+
b[i][j] = swp;
15+
}
16+
}
17+
}
18+
double mmax(double (*phi)[M], double (*phip)[M], int n)
19+
{
20+
double max = 0.0;
21+
double diff = 0.0;
22+
for (int i = 0; i < n; i++) {
23+
for (int j = 0; j < n; j++) {
24+
diff = fabs(phi[i][j]-phip[i][j]);
25+
if (diff > max)
26+
max = diff;
27+
}
28+
}
29+
return max;
30+
}
31+
double rho(double x, double y)
32+
{
33+
double s1 = 0.6;
34+
double e1 = 0.8;
35+
double s2 = 0.2;
36+
double e2 = 0.4;
37+
38+
if (x > s1 && x < e1 && y > s1 && y < e1) {
39+
return 1.0;
40+
} else if (x > s2 && x < e2 && y > s2 && y < e2 ) {
41+
return -1.0;
42+
} else {
43+
return 0.0;
44+
}
45+
}
46+
void run(double toler, double a)
47+
{
48+
double epsilon0 = 8.85e-12;
49+
double a2;
50+
51+
double (*phi)[M];
52+
double (*phip)[M];
53+
double (*rhoa)[M];
54+
phi = malloc(sizeof(double[M][M]));
55+
phip = malloc(sizeof(double[M][M]));
56+
rhoa = malloc(sizeof(double[M][M]));
57+
58+
int iter = 0;
59+
60+
memset(phip, 0, sizeof(phip[0][0])*M*M);
61+
memset(phi, 0, sizeof(phi[0][0])*M*M);
62+
63+
double delta = 1.0;
64+
a2 = pow(a,2.0);
65+
while (delta > toler) {
66+
iter += 1;
67+
68+
for (int i=1; i < M-1; i++) {
69+
for (int j=1; j < M-1; j++) {
70+
phip[i][j] = (phi[i+1][j] + phi[i-1][j] +
71+
phi[i][j+1] + phi[i][j-1])/4.0 +
72+
a2/(4.0 * epsilon0)*rho(i*a,j*a);
73+
}
74+
}
75+
delta = mmax(phi, phip, M);
76+
swap(phi, phip, M);
77+
78+
}
79+
printf("iters %d", iter);
80+
81+
}
82+
83+
int main(int argc, char *argv[])
84+
{
85+
clock_t start = clock();
86+
run(1e-6, 0.01);
87+
clock_t end = clock();
88+
double total = ((double)(end - start)) / CLOCKS_PER_SEC;
89+
printf("Execution time: %f\n",total);
90+
}

poisson2d/naive.f90

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
module rhofunc
2+
implicit none
3+
public
4+
integer, parameter :: dp=kind(0.d0)
5+
contains
6+
pure real(dp) function rho(x,y)
7+
real(dp), intent(in) :: x,y
8+
if (x > 0.6_dp .and. x < 0.8_dp .and. y > 0.6_dp .and. y<0.8) then
9+
rho = 1.0_dp
10+
else if (x> 0.2_dp .and. x<0.4_dp .and. y>0.2_dp .and. y<0.4_dp) then
11+
rho = -1.0_dp
12+
else
13+
rho = 0.0_dp
14+
end if
15+
end function
16+
17+
end module
18+
19+
program poisson
20+
use rhofunc, only: rho
21+
implicit none
22+
integer, parameter :: dp=kind(0.d0), M=300
23+
integer :: i,j, iter
24+
real(dp),parameter :: epsilon0=8.85E-12_dp, target=1E-6_dp, a=0.01
25+
real(dp) :: delta, b, e, phiprime(M,M), phi(M,M), a2, rhoarr(M,M), temp(M,M)
26+
27+
28+
delta = 1.0_dp
29+
iter = 0
30+
call cpu_time(b)
31+
phiprime(:,:) = 0.0_dp
32+
phi(:,:) = 0.0_dp
33+
34+
do while (delta > target )
35+
iter = iter + 1
36+
a2 = a**2.0_dp
37+
do i=2, M-1
38+
do j=2, M-1
39+
phiprime(i,j) = (phi(i+1,j) + phi(i-1,j) + phi(i,j+1) + phi(i,j-1))/4.0_dp &
40+
+ a2/4.0_dp/epsilon0*rho(i*a,j*a)
41+
end do
42+
end do
43+
delta = maxval(abs(phiprime - phi))
44+
temp = phi
45+
phi = phiprime
46+
phiprime = temp
47+
48+
end do
49+
call cpu_time(e)
50+
print *, e-b, iter
51+
end program

poisson2d/optimized.c

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#include <stdio.h>
2+
#include <stdlib.h>
3+
#include <string.h>
4+
#include <math.h>
5+
#include <time.h>
6+
#define M 300
7+
void swap(double (*a)[M], double (*b)[M], int n)
8+
{
9+
double swp;
10+
for (int i = 0; i < n; i++) {
11+
for (int j = 0; j < n; j++) {
12+
a[i][j] = b[i][j];
13+
}
14+
}
15+
}
16+
double mmax(double (*phi)[M], double (*phip)[M], int n)
17+
{
18+
double max = 0.0;
19+
double diff = 0.0;
20+
for (int i = 0; i < n; i++) {
21+
for (int j = 0; j < n; j++) {
22+
diff = fabs(phi[i][j]-phip[i][j]);
23+
if (diff > max)
24+
max = diff;
25+
}
26+
}
27+
return max;
28+
}
29+
double rho(double x, double y)
30+
{
31+
double s1 = 0.6;
32+
double e1 = 0.8;
33+
double s2 = 0.2;
34+
double e2 = 0.4;
35+
36+
if (x > s1 && x < e1 && y > s1 && y < e1) {
37+
return 1.0;
38+
} else if (x > s2 && x < e2 && y > s2 && y < e2 ) {
39+
return -1.0;
40+
} else {
41+
return 0.0;
42+
}
43+
}
44+
void run(double toler, double a)
45+
{
46+
double epsilon0 = 8.85e-12;
47+
double a2;
48+
49+
double (*phi)[M];
50+
double (*phip)[M];
51+
double (*rhoa)[M];
52+
phi = malloc(sizeof(double[M][M]));
53+
phip = malloc(sizeof(double[M][M]));
54+
rhoa = malloc(sizeof(double[M][M]));
55+
56+
int iter = 0;
57+
58+
memset(phip, 0, sizeof(phip[0][0])*M*M);
59+
memset(phi, 0, sizeof(phi[0][0])*M*M);
60+
for (int i=1; i<M-1; i++) {
61+
for (int j=1; j<M-1; j++) {
62+
rhoa[i][j] = rho(i*a,j*a);
63+
}
64+
}
65+
double delta = 1.0;
66+
a2 = pow(a,2.0);
67+
while (delta > toler) {
68+
iter += 1;
69+
70+
for (int i=1; i < M-1; i++) {
71+
for (int j=1; j < M-1; j++) {
72+
phip[i][j] = (phi[i+1][j] + phi[i-1][j] +
73+
phi[i][j+1] + phi[i][j-1])/4.0 +
74+
a2/(4.0 * epsilon0)*rhoa[i][j];
75+
}
76+
}
77+
delta = mmax(phi, phip, M);
78+
swap(phi, phip, M);
79+
80+
}
81+
printf("iters %d", iter);
82+
83+
}
84+
85+
int main(int argc, char *argv[])
86+
{
87+
clock_t start = clock();
88+
run(1e-6, 0.01);
89+
clock_t end = clock();
90+
double total = ((double)(end - start)) / CLOCKS_PER_SEC;
91+
printf("Execution time: %f\n",total);
92+
}

poisson2d/optimized.f90

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
module rhofunc
2+
implicit none
3+
public
4+
integer, parameter :: dp=kind(0.d0)
5+
contains
6+
pure real(dp) function rho(x,y)
7+
real(dp), intent(in) :: x,y
8+
if (x > 0.6_dp .and. x < 0.8_dp .and. y > 0.6_dp .and. y<0.8) then
9+
rho = 1.0_dp
10+
else if (x> 0.2_dp .and. x<0.4_dp .and. y>0.2_dp .and. y<0.4_dp) then
11+
rho = -1.0_dp
12+
else
13+
rho = 0.0_dp
14+
end if
15+
end function
16+
17+
end module
18+
19+
program poisson
20+
use rhofunc, only: rho
21+
implicit none
22+
integer, parameter :: dp=kind(0.d0), M=300
23+
integer :: i,j, iter
24+
real(dp),parameter :: epsilon0=8.85E-12_dp, target=1E-6_dp, a=0.01
25+
real(dp) :: delta, b, e, phiprime(M,M), phi(M,M), a2, rhoarr(M,M)
26+
27+
28+
delta = 1.0_dp
29+
iter = 0
30+
call cpu_time(b)
31+
phiprime(:,:) = 0.0_dp
32+
phi(:,:) = 0.0_dp
33+
do i=1, M
34+
do j=1, M
35+
rhoarr(i,j) = rho(i*a,j*a)
36+
end do
37+
end do
38+
39+
do while (delta > target )
40+
iter = iter + 1
41+
a2 = a**2.0_dp
42+
do i=2, M-1
43+
do j=2, M-1
44+
phiprime(i,j) = (phi(i+1,j) + phi(i-1,j) + phi(i,j+1) + phi(i,j-1))/4.0_dp &
45+
+ a2/4.0_dp/epsilon0*rhoarr(i,j)
46+
end do
47+
end do
48+
delta = maxval(abs(phiprime - phi))
49+
phi = phiprime
50+
51+
52+
end do
53+
call cpu_time(e)
54+
print *, e-b, iter
55+
end program

poisson2d/poisson.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#!/usr/bin/env python3
2+
# -*- coding: utf-8 -*-
3+
4+
import numpy as np
5+
import matplotlib.pyplot as plt
6+
import datetime
7+
8+
# Constants
9+
M = 100 # Grid squares on a side
10+
V = 1.0 # Voltage at top wall
11+
target = 1e-6 # Target accuracy
12+
a = 0.01
13+
epsilon0 = 8.85e-12
14+
15+
# Create arrays to hold potential values
16+
phi = np.zeros([M+1,M+1],float)
17+
#phi[0,:] = V
18+
phiprime = np.zeros([M+1,M+1],float)
19+
20+
def ro(x,y):
21+
if x>0.6 and x<0.8 and y>0.6 and y<0.8:
22+
return 1
23+
elif x>0.2 and x<0.4 and y>0.2 and y<0.4:
24+
return -1
25+
else:
26+
return 0
27+
28+
29+
# Main loop
30+
begin = datetime.datetime.now()
31+
delta = 1.0
32+
while delta>target:
33+
34+
# Calculate new values of the potential
35+
a2 = a**2
36+
for i in range(1,M):
37+
for j in range(1,M):
38+
39+
phiprime[i,j] = (phi[i+1,j] + phi[i-1,j] \
40+
+ phi[i,j+1] + phi[i,j-1])/4 \
41+
+ a2/4/epsilon0*ro(i*a,j*a)
42+
43+
# Calculate maximum difference from old values
44+
delta = np.max(abs(phi-phiprime))
45+
46+
# Swap the two arrays around
47+
phi,phiprime = phiprime,phi
48+
end = datetime.datetime.now()
49+
dif = end-begin
50+
print(dif.total_seconds())
51+
# Make a plot
52+
plt.imshow(phi,origin='lower')
53+
plt.savefig("purepython.png")

0 commit comments

Comments
 (0)