LoginSignup
2
2

More than 5 years have passed since last update.

FDPSを使ってみる その5

Last updated at Posted at 2017-03-02

はじめに

粒子系の並列シミュレーションフレームワークFDPSを使ってみる。
その4ではカットオフのあるLennard-Jones粒子系の二体衝突のシリアル計算を行った。次は全く同じ計算の並列版を実行し、軌道、エネルギーが一致するか確認する(基本)。

コードはここにおいてある。
https://github.com/kaityo256/fdps_sample

並列化にあたっての修正

まずはmakefileに、FDPSの並列化マクロなどを追加指定する。

CPPFLAGS += -DPARTICLE_SIMULATOR_MPI_PARALLEL
LDFLAGS=-lmpi -lmpi_cxx

フレームワークの初期化直後にMPIのrankとプロセス数も取得しておこう。

  PS::Initialize(argc, argv);
  const int rank = PS::Comm::getRank();
  const int procs = PS::Comm::getNumberOfProc();

粒子を追加するにあたって、とりあえずプロセス0に突っ込んで、粒子の分配はフレームワークに任せることにする。

  const int N = 2;
  if (rank == 0){
    lj_system.setNumberOfParticleLocal(N);
    lj_system[0].id = 0;
    lj_system[0].p = PS::F64vec(1.0, 0.0, 0.0);
    lj_system[0].q = PS::F64vec(2.5, 5.0, 5.0);
    lj_system[1].id = 1;
    lj_system[1].p = PS::F64vec(-1.0, 0.0, 0.0);
    lj_system[1].q = PS::F64vec(7.5, 5.0, 5.0);
  }else{
    lj_system.setNumberOfParticleLocal(0);
  }
  dinfo.decomposeDomainAll(lj_system);  // 領域分割
  lj_system.exchangeParticle(dinfo); // 粒子の分配

エネルギーの計算だが、既に二体関数であるポテンシャルエネルギーを一体関数として分配してあるため、AllReduceするだけで良い。そのラッパーであるPS::Comm::getSumを呼ぶと、エネルギーを返す関数は

template<class Tpsys>
PS::F64
energy(const Tpsys &system){
  PS::F64 e = 0.0;
  const PS::S32 n = system.getNumberOfParticleLocal();
  for(PS::S32 i = 0; i < n; i++) {
    FPLJ a = system[i];
    e += (a.p*a.p)*0.5;
    e += a.potential;
  }
  return PS::Comm::getSum(e); //←ここを修正
}

と書ける。

 結果の保存

各プロセスごとに、自分が持っている粒子を個別に吐く関数を作っておく。

template<class Tpsys>
void
dump(std::ostream &os, const PS::F64 s_time, Tpsys &system){
  const PS::S32 n = system.getNumberOfParticleLocal();
  if (n == 0)return;
  os << s_time << " ";
  for (PS::S32 i = 0; i < n; i++) {
    os << system[i].q.x << " ";
  }
  os << std::endl;
}

メインループ

計算のメインループはこんな感じになる。

  char filename[256];
  sprintf(filename,"trac%02d.dat",rank);
  std::ofstream ofs(filename);

  for(int i=0;i<LOOP;i++){
    drift(lj_system, dt*0.5);
    tree_lj.calcForceAllAndWriteBack(CalcForceLJ(), lj_system, dinfo);
    kick(lj_system, dt);
    drift(lj_system, dt*0.5);
    dump(ofs, s_time, lj_system);
    tree_lj.calcForceAllAndWriteBack(CalcForceLJ(), lj_system, dinfo);
    PS::F64 e = energy(lj_system);
    if (rank==0){
      std::cout << s_time << " " << e << std::endl;
    }
    s_time += dt;
  }

結果の出力まわり以外、シリアル版とほとんどかわらない。なお、エネルギーの計算ルーチンenergyの内部でMPI_Allreduceを呼んでいるため、

    if (rank==0){
      std::cout << s_time << " " << energy(lj_system) << std::endl;
    }

みたいなことをするとデッドロックする1。なので一度外で変数に受けてから表示するようにしている。

結果

1プロセスでシリアル実行した場合と、2プロセスで並列実行した場合の粒子の軌道は以下の通り。

image

それぞれのプロセスが1粒子ずつ担当し、かつシリアル実行した場合と軌道が一致していることがわかる。

エネルギーの時間発展はこんな感じ。

image

当然のことながら、完全に一致している。

まとめ

FDPSを使って相互作用のある粒子系を定義し、二粒子衝突の計算を並列化してみた。シリアル版がかけてしまえば、並列版に修正するのは容易だと思う。

以下のリポジトリのstep5でmake testすれば、コンパイルして、シリアル実行、2並列実行した結果の比較グラフまで作ってくれるはず。

その6に続く。


  1. ・・・というようなことを意識しなくて良いフレームワークが設計できれば、より美しいと思う。 

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2