簡単なラプラス方程式を解いてみよう。2次元ラプラス方程式は

である。拡散方程式と似ているように見えるが、時間項がない。そのため、ポアソン方程式やラプラス方程式を解くときはガウス=ザイデル法やSOR法などの反復法を使って解を求める。値が収束するまで反復する。実際、差分してみると、

となり、最終的な形としては、

となる。全て、同じ時間の変数になり、拡散方程式のように単純に計算ができないため、反復して値を求める。

計算条件

IMAX 50

JMAX 50

dx 0.1

dy 0.1

初期条件

全てu=0

境界条件

j=0, 20<x<30のとき

u=10

他の境界は全て0

 

コードの中で注意すべき点

計算の中で、参照される値と更新される値が同じ変数のため、openmpを用いた並列計算を行うときは、工夫が必要である。

openmpの説明をすると、CPUの中には幾つのコーアが入っている。並列計算をしないと、コーア1つがfor文に従って計算を行う。例えば、

for (int j = 0; j < JMAX + 1; j++) {

                  for (int i = 0; i < IMAX + 1; i++) {

                                    x[j][i] = dx * (double)i;

                                    y[j][i] = dy * (double)j;

                  }

}

であると、j=0, i=0でのx[0][0] = dx * (double)0;の計算を行って、y[0][0] = dy * (double)0;の計算をする。その後 j=0, i=1となり、x[0][1] = dx * (double)1;の計算を行って、y[0][1] = dy * (double)0;の計算を行いfor文が終わるまで繰り返す。

openmpを使ってコーア2つを使って以下のコートを並列計算を行うと、

int j,i;

#pragma omp parallel for private(i)

for (j = 0; j < JMAX + 1; j++) {

                  for (i = 0; i < IMAX + 1; i++) {

                                    x[j][i] = dx * (double)i;

                                    y[j][i] = dy * (double)j;

                  }

}

i=0のとき、コーア1ではj=0の計算、コーア2ではj=1の計算を同時に行い、終わったら、i=1のとき、コーア1ではj=0の計算、コーア2ではj=1の計算に移る。そのため、並列計算をすると計算が早くなる。

しかい、

ような式を並列計算コードで表すと、

int i, j;

#pragma omp parallel for private(i)

for (j = 1; j < JMAX; j++) {

                  for (i = 1; i < IMAX; i++) {

                                    u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy))/(2.0*(1.0/(dx*dx)+1.0/(dy*dy)));

                  }

}

となる。よく見てみると、u1を更新するのにu1の値を使っていることがわかる。このままだと、

i=1のとき、コーア1ではj=1の計算、コーア2ではj=2の計算になり、更新される値がコーア1ではu1[1][1]、コーア2ではu1[2][1]になることがわかる。ところで、参照する部分を見ると、 u1[j - 1][i]u1[j + 1][i]の変数があり、コーア1ではu1[2][1]、コーア2ではu1[1][1]になる部分が見られる。つまり、コーア1とコーア2で同じ変数を同時に使うことになり、CPUとしては混乱していまいため、計算がおかしくなる。このような問題を回避するために、以下のように修正する必要がある。

int i, j;

#pragma omp parallel for private(i)

for (j = 1; j < JMAX; j+=2) {

                  for (i = 1; i < IMAX; i ++) {

                                    u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                  }

}

#pragma omp parallel for private(i)

for (j = 2; j < JMAX; j+=2) {

                  for (i = 1; i < IMAX; i ++) {

                                    u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                  }

}

このように書いてあげると、コーア1ではj=1, i=1,2,3,4,5…を、コーア2ではj=3, i=1,2,3,4,5…の順番で計算をするので、変数が重複しないことがわかる。後、下のfor文ではコーア1ではj=2, i=1,2,3,4,5…を、コーア2ではj=4, i=1,2,3,4,5…の順番で計算し、最終的には全格子点に対して計算ができていることがわかる。また、上の計算方はガウス=ザイデル法であり、下のような計算方になると、SOR法である。

double u00 = 0.0, SGM=1.1;

int i, j;

#pragma omp parallel for private(i,u00) firstprivate(SGM)

for (j = 1; j < JMAX; j += 2) {

                  for (i = 1; i < IMAX; i ++) {

                                    u00 = u1[j][i];

                                    u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                                    u1[j][i] = u00 + SGM * (u1[j][i] - u00);

                  }

}

#pragma omp parallel for private(i,u00) firstprivate(SGM)

for (j = 2; j < JMAX; j += 2) {

                  for (i = 1; i < IMAX; i ++) {

                                    u00 = u1[j][i];

                                    u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                                    u1[j][i] = u00 + SGM * (u1[j][i] - u00);

                  }

}

SGMの値を1.0にすると、ガウス=ザイデル法になることがわかる。SGMの値を大きくすると、この計算を反復する回数がガウス=ザイデル法より少なくなるが、大きくし過ぎると、計算は発散する。そのため、適当な値を自ら探す必要がある。また、反復回数も値が正解に収束するまで計算する必要がある。この反復回数は格子点数に依存するため、この値も自ら探す必要がある。十分反復されているかを確認するために、変動が激しいと予想される格子点を注目し、その点での値を観察することで、反復回数を決める。

例えば、上の計算コードがcalculation_1にまとまっているとしるとmain関数の部分は以下のようになる。check0check1は事前に変数を救っておいた。

for (loop = 1; loop < 2001; loop++) {

                  calculation_1(u1);

}

check0 = u1[2][25];

calculation_1(u1);

check1 = u1[2][25];

cout << fabs(check1 - check0) << endl;

boundary_condition(u1);

境界条件を与えるときは必ず反復が終わったあとにする。これで、fabs(check1 - check0)の結果が10-7に収まったら、値が収束できたといえるが、10-5になっても正しく計算される場合もあるため、自分の計算条件による。

 

Cコードは以下

#include <iostream>

#include <fstream>

#include <cmath>

#include <time.h>

#include <cstdlib>

#include <cstring>

#include <stdio.h>

#include <omp.h>

using namespace std;

#define IMAX 50

#define JMAX 50

#define dx 0.1

#define dy 0.1

int loop = 0;

void initialization_values(double u0[][IMAX + 1], double u1[][IMAX + 1], double x[][IMAX + 1], double y[][IMAX + 1]) {

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     u0[j][i] = 0.0;

                                                     u1[j][i] = 0.0;

                                                     x[j][i] = 0.0;

                                                     y[j][i] = 0.0;

                                    }

                  }

}

void domain(double x[][IMAX + 1], double y[][IMAX + 1]) {

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     x[j][i] = dx * (double)i;

                                                     y[j][i] = dy * (double)j;

                                    }

                  }

}

void initial_condition(double u1[][IMAX + 1]) {

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     u1[j][i] = 0.0;

                                    }

                  }

}

void boundary_condition(double u1[][IMAX + 1]) {

                  for (int i = 0; i < IMAX + 1; i++) {

                                    u1[0][i] = 0.0;

                                    u1[JMAX][i] = 0.0;

                  }

                  for (int j = 0; j < JMAX + 1; j++) {

                                    u1[j][0] = 0.0;

                                    u1[j][IMAX] = 0.0;

                  }

                  for (int i = 21; i < 30; i++) {

                                    u1[0][i] = 10.0;

                  }

}

void calculation_1(double u1[][IMAX + 1]) {

                  double u00 = 0.0, SGM = 1.1;

                  int i, j;

#pragma omp parallel for private(i,u00) firstprivate(SGM)

                  for (j = 1; j < JMAX; j += 2) {

                                    for (i = 1; i < IMAX; i++) {

                                                     u00 = u1[j][i];

                                                     u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                                                     u1[j][i] = u00 + SGM * (u1[j][i] - u00);

                                    }

                  }

#pragma omp parallel for private(i,u00) firstprivate(SGM)

                  for (j = 2; j < JMAX; j += 2) {

                                    for (i = 1; i < IMAX; i++) {

                                                     u00 = u1[j][i];

                                                     u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                                                     u1[j][i] = u00 + SGM * (u1[j][i] - u00);

                                    }

                  }

}

void write_data_vt(double u1[][IMAX + 1], double x[][IMAX + 1], double y[][IMAX + 1]) {

                  ofstream rdi;

                  rdi.precision(18);

                  char str[20];

                  sprintf_s(str, "data%d.dat", loop);// Convert i to a char

                  string datafile = "./data/";

                  datafile.append(str);

                  rdi.open(datafile.c_str());

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     rdi << x[j][i] << ' ' << y[j][i] << ' ' << u1[j][i] << endl;

                                    }

                                    rdi << endl;

                  }

                  rdi.close();

}

int main() {

                  cout.precision(15);

                  double check0 = 0.0, check1 = 0.0;

                  typedef double IJ[IMAX + 1];

                  IJ *u0, *u1, *x, *y;

                  u0 = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  u1 = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  x = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  y = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  initialization_values(u0, u1, x, y);

                  domain(x, y);

                  initial_condition(u1);

                  boundary_condition(u1);

                  write_data_vt(u1, x, y);

                  for (loop = 1; loop < 2001; loop++) {

                                    calculation_1(u1);

                                    if (loop % 200 == 0) {

                                                     write_data_vt(u1, x, y);

                                    }

                  }

                  check0 = u1[2][25];

                  calculation_1(u1);

                  check1 = u1[2][25];

                  cout << fabs(check1 - check0) << endl;

                  boundary_condition(u1);

                  free(u0);

                  free(u1);

                  free(x);

                  free(y);

                  return 0;

}

Gnuplotコードは以下

reset

set terminal png #font "Times New Roman,25"

set ylabel "{/Times:Italic y}" rotate by 90 offset 0,0

set xlabel "{/Times:Italic x}" offset 0,0

set yrange[0.0:5.0]

set xrange[0.0:5.0]

set ytics offset 0,0 border 0.0,1.0,5.0

set xtics offset 0,0 border 0.0,1.0,5.0

set size ratio 1.0

set palette rgbformulae 22,13,-31

set cbtics border 0.0,0.1,1.0

set cbrange[0.0:1.0]

set view map

do for [ii=0:2000:200] {

set label 1 at graph 0.45,1.1 sprintf('n=%01.0f',ii)

set output sprintf('./png/pdata%01.0f.png',ii)

splot sprintf('./data/data%01.0f.dat',ii) with pm3d notitle

}

 

結果

 

 

 

 

 

 

 

 

 

少し難易度を上げて、他の条件の計算をやってみよう。計算領域の真ん中に物体がある場合を想定する。

計算条件

IMAX 50

JMAX 50

dx 0.1

dy 0.1

初期条件

全てu=0

境界条件

22<J<27, 20<x<30のとき

u=10

他の境界は全て0

つまり、22<J<27, 20<x<30に物体があり、物体の周りの値はu=10である。物体の中は計算しない。また、計算領域に境界ができているため、それを除いて計算する必要がある。幾つか方法がある。

for文だけを利用し計算領域だけを計算する。Openmpを使っていないときを想定(コーア1つだけを利用)。

for (j = 1; j < 23; j++) {

                  for (i = 1; i < IMAX; i ++) {

                                    計算

}

}

for (j = 27; j < JMAX; j++) {

                  for (i = 1; i < IMAX; i ++) {

                                    計算

}

}

for (j = 23; j < 27; j++) {

                  for (i = 1; i < 20; i ++) {

                                    計算

}

}

for (j = 23; j < 27; j++) {

                  for (i = 31; i < IMAX; i ++) {

                                    計算

}

}

if分を使うとき

for (j = 1; j < JMAX; j++) {

                  if(j>22 && j<27){

                                    何もしない

                  }else{

                                    for (i = 1; i < IMAX; i ++) {

                                                     if(i>20 && i<30){

                                                                       何もしない

                                                     }else{

                                                                       計算

                                                     }

}

                  }

                 

}

または、

for (j = 1; j < JMAX; j++) {

                  for (i = 1; i < IMAX; i ++) {

                                    if(j>22 && j<27 && i>20 && i<30){

                                                      何もしない

                                    }else{

                                                     計算

                                    }

}

}

基本的にif分を使うと、cpuの負担が大きくなり、計算が遅くなる。

上と下のコードを比較すると上のほうが計算負担が少ない。何故かは各自考えてみること。

計算結果をすぐ境界条件に引き返す方法

for (j = 1; j < JMAX; j++) {

                  for (i = 1; i < IMAX; i ++) {

                                    普通に計算

                                    壁に対する境界条件だけをいれる(注意!!ノイマン条件を使うとloop前の値を入れないといけない)

}

}

ノイマン条件は後で勉強するので、今は注意が必要ということを覚えるだけでいい。上のようにすると計算された瞬間元通りになるため、for文とif分を利用した計算と同じ結果になる。

 

Cコード

#include <iostream>

#include <fstream>

#include <cmath>

#include <time.h>

#include <cstdlib>

#include <cstring>

#include <stdio.h>

#include <omp.h>

using namespace std;

#define IMAX 50

#define JMAX 50

#define dx 0.1

#define dy 0.1

int loop = 0;

void initialization_values(double u0[][IMAX + 1], double u1[][IMAX + 1], double x[][IMAX + 1], double y[][IMAX + 1]) {

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     u0[j][i] = 0.0;

                                                     u1[j][i] = 0.0;

                                                     x[j][i] = 0.0;

                                                     y[j][i] = 0.0;

                                    }

                  }

}

void domain(double x[][IMAX + 1], double y[][IMAX + 1]) {

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     x[j][i] = dx * (double)i;

                                                     y[j][i] = dy * (double)j;

                                    }

                  }

}

void initial_condition(double u1[][IMAX + 1]) {

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     u1[j][i] = 0.0;

                                    }

                  }

}

void boundary_condition(double u1[][IMAX + 1]) {

                  for (int i = 0; i < IMAX + 1; i++) {

                                    u1[0][i] = 0.0;

                                    u1[JMAX][i] = 0.0;

                  }

                  for (int j = 0; j < JMAX + 1; j++) {

                                    u1[j][0] = 0.0;

                                    u1[j][IMAX] = 0.0;

                  }

                  for (int j = 23; j < 27; j++) {

                                    for (int i = 21; i < 30; i++) {

                                                     u1[j][i] = 10.0;

                                    }

                  }

}

void calculation_1(double u1[][IMAX + 1]) {

                  double u00 = 0.0, SGM = 1.1;

                  int i, j;

#pragma omp parallel for private(i,u00) firstprivate(SGM)

                  for (j = 1; j < JMAX; j += 2) {

                                    for (i = 1; i < IMAX; i++) {

                                                     u00 = u1[j][i];

                                                     u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                                                     u1[j][i] = u00 + SGM * (u1[j][i] - u00);

                                                     for (int j = 23; j < 27; j++) {

                                                                       for (int i = 21; i < 30; i++) {

                                                                                         u1[j][i] = 10.0;

                                                                       }

                                                     }

                                    }

                  }

#pragma omp parallel for private(i,u00) firstprivate(SGM)

                  for (j = 2; j < JMAX; j += 2) {

                                    for (i = 1; i < IMAX; i++) {

                                                     u00 = u1[j][i];

                                                     u1[j][i] = ((u1[j][i - 1] + u1[j][i + 1]) / (dx*dx) + (u1[j - 1][i] + u1[j + 1][i]) / (dy*dy)) / (2.0*(1.0 / (dx*dx) + 1.0 / (dy*dy)));

                                                     u1[j][i] = u00 + SGM * (u1[j][i] - u00);

                                                     for (int j = 23; j < 27; j++) {

                                                                       for (int i = 21; i < 30; i++) {

                                                                                         u1[j][i] = 10.0;

                                                                       }

                                                     }

                                    }

                  }

}

void write_data_vt(double u1[][IMAX + 1], double x[][IMAX + 1], double y[][IMAX + 1]) {

                  ofstream rdi;

                  rdi.precision(18);

                  char str[20];

                  sprintf_s(str, "data%d.dat", loop);// Convert i to a char

                  string datafile = "./data/";

                  datafile.append(str);

                  rdi.open(datafile.c_str());

                  for (int j = 0; j < JMAX + 1; j++) {

                                    for (int i = 0; i < IMAX + 1; i++) {

                                                     rdi << x[j][i] << ' ' << y[j][i] << ' ' << u1[j][i] << endl;

                                    }

                                    rdi << endl;

                  }

                  rdi.close();

}

int main() {

                  cout.precision(15);

                  double check0 = 0.0, check1 = 0.0;

                  typedef double IJ[IMAX + 1];

                  IJ *u0, *u1, *x, *y;

                  u0 = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  u1 = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  x = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  y = (IJ *)malloc((IMAX + 1) * (JMAX + 1) * sizeof(double));

                  initialization_values(u0, u1, x, y);

                  domain(x, y);

                  initial_condition(u1);

                  boundary_condition(u1);

                  write_data_vt(u1, x, y);

                  for (loop = 1; loop < 1001; loop++) {

                                    calculation_1(u1);

                                    if (loop % 50 == 0) {

                                                     write_data_vt(u1, x, y);

                                    }

                  }

                  check0 = u1[28][25];

                  calculation_1(u1);

                  check1 = u1[28][25];

                  cout << fabs(check1 - check0) << endl;

                  boundary_condition(u1);

                  free(u0);

                  free(u1);

                  free(x);

                  free(y);

                  return 0;

}

Gnuplotコード

reset

set terminal png #font "Times New Roman,25"

set ylabel "{/Times:Italic y}" rotate by 90 offset 0,0

set xlabel "{/Times:Italic x}" offset 0,0

set yrange[0.0:5.0]

set xrange[0.0:5.0]

set ytics offset 0,0 border 0.0,1.0,5.0

set xtics offset 0,0 border 0.0,1.0,5.0

set size ratio 1.0

set palette rgbformulae 22,13,-31

set cbtics border 0.0,0.1,1.0

set cbrange[0.0:1.0]

set view map

do for [ii=0:1000:50] {

set label 1 at graph 0.45,1.1 sprintf('n=%01.0f',ii)

set output sprintf('./png/pdata%01.0f.png',ii)

splot sprintf('./data/data%01.0f.dat',ii) with pm3d notitle

}

 

結果