2026年2月13日

卒論と修論を製本に出した。こういうのが研究室に溜まっていくとうれしいですね。

輪講用の本を注文。

** WARNING: connection is not using a post-quantum key exchange algorithm.
** This session may be vulnerable to "store now, decrypt later" attacks.
** The server may need to be upgraded. See https://openssh.com/pq.html

マジか。もう耐量子アルゴリズムの時代なのか。

サーバを更新しないといけない。まずはWordpressのwp-contentのバックアップのためにrsyncを走らせる。rsyncはいつも最後の「/」がどっちかわからなくなるので、Makefileに残しておく。後でDockerでなんとかする。

lammpstrj-parserのテスト。

mkdir lammpstrj-parser-sample
cd lammpstrj-parser-sample
git init .
git submodule add https://github.com/kaityo256/lammpstrj-parser external/lammpstrj-parser

main.cppを作成。

#include <lammpstrj/lammpstrj.hpp>

int main() {
}

Makefileを作成。

CXX = g++
CXXFLAGS = -std=c++14 -O2 -Iexternal/lammpstrj-parser/include

all: lammpstrj-sample

lammpstrj-sample: main.cpp
  $(CXX) $(CXXFLAGS) main.cpp -o $@

.PHONY: clean

clean:
  rm -f lammpstrj-sample

makeできることを確認。

$ make 
g++ -std=c++14 -O2 -Iexternal/lammpstrj-parser/include main.cpp -o lammpstrj-sample

うまくいっていたら、以下の内容の.gitignoreを追加。

lammpstrj-sample
*.lammpstrj
log.lammps

後のためにlammpsの出力ファイルも追加しておく。

ここまでで以下のような表示になるはず。

$ git status -s 
A  .gitmodules
A  external/lammpstrj-parser
?? .gitignore
?? Makefile
?? main.cpp

これらを全て追加してコミットする。

git add .
git commit -am ":tada: initial commit"

相分離シミュレーションを実施。以下のようなインプットファイルをtest.inputとして作成。

units       lj
atom_style  atomic
boundary p p p
timestep 0.001

variable rho equal 0.5
variable L equal 10.0

lattice fcc ${rho}
region box block 0 ${L} 0 ${L} 0 ${L}
create_box 2 box
create_atoms 1 box
set type 1 type/fraction 2 0.5 98765

mass 1 1.0
mass 2 1.0

pair_style lj/cut 2.5
pair_coeff 1 1 1.0 1.0 2.5
pair_coeff 1 2 1.0 1.0 1.12246
pair_coeff 2 2 1.0 1.0 2.5

velocity all create 1.0 12345 mom yes rot yes dist gaussian

fix 1 all nvt temp 0.7 0.7 1.0

dump 1 all custom 500 test.lammpstrj id type x y z vx vy vz
thermo 500

run 5000

密度0.5、同種原子はLJ、異種原子はWCAとして温度0.7に固定する。まずは短めに5000ステップだけにしておく。 後のために速度も出力しておく。

LAMMPSを実行する。

$ lmp_serial -i test.input
LAMMPS (20 Nov 2019)
Lattice spacing in x,y,z = 2 2 2
Created orthogonal box = (0 0 0) to (20 20 20)
  1 by 1 by 1 MPI processor grid
Created 4000 atoms
(snip)
Step Temp E_pair E_mol TotEng Press
       0            1    -1.505224            0 -0.005599034  -0.81748395
     500    1.0379285   -1.9071175            0    -0.350614   0.89774455
    1000    1.0320452   -1.9831115            0   -0.4354307   0.83759061
    1500    1.0048542   -2.0875114            0  -0.58060687   0.68268169
    2000   0.95389438   -2.1579864            0  -0.72750253    0.7033103
    2500   0.92615458   -2.2477533            0  -0.85886878   0.64168551
    3000   0.90328012   -2.3386703            0  -0.98408884   0.50502435
    3500   0.86221052   -2.4125228            0   -1.1195303   0.51427453
    4000   0.83401226    -2.485898            0   -1.2351923   0.41993354
    4500   0.82124657   -2.5744925            0   -1.3429306   0.37929561
    5000   0.80680117   -2.6549983            0   -1.4450991    0.3126729
(snip)

まずはこのサイズと原子数を取得する。main.cppを以下のように書き換える。

#include <cstdio>
#include <lammpstrj/lammpstrj.hpp>
#include <string>

int main() {
  const auto filename = "test.lammpstrj";
  auto si = lammpstrj::read_info(filename);
  printf("(LX, LY, LZ) = (%f, %f, %f)\n", si->LX, si->LY, si->LZ);
  printf("N = %d\n", si->atoms);
}

コンパイル、実行する。

$ make
$ ./lammpstrj-sample 
(LX, LY, LZ) = (20.000000, 20.000000, 20.000000)
N = 4000

情報が正しく取れている。

温度の取得。main関数の上に以下のような関数を作成。

void calc_temperature(const std::unique_ptr<lammpstrj::SystemInfo> &si, const std::vector<lammpstrj::Atom> &atoms) {
  static int frame_ = 0;
  double e = 0.0;
  for (auto &a : atoms) {
    e += a.vx * a.vx + a.vy * a.vy + a.vz * a.vz;
  }
  e /= static_cast<double>(si->atoms);
  e /= 3.0;
  printf("%d %f\n", frame_ * 500, e);
  frame_++;
}

main関数からlammpstrj::for_each_framにファイル名とcalc_temperature関数を渡して実行。

int main() {
  const auto filename = "test.lammpstrj";
  auto si = lammpstrj::read_info(filename);
  printf("(LX, LY, LZ) = (%f, %f, %f)\n", si->LX, si->LY, si->LZ);
  printf("N = %d\n", si->atoms);
  lammpstrj::for_each_frame(filename, calc_temperature);
}

コンパイル、実行する。

$ make
$ ./lammpstrj-sample 
(LX, LY, LZ) = (20.000000, 20.000000, 20.000000)
N = 4000
0 0.999750
500 1.037669
1000 1.031787
1500 1.004603
2000 0.953656
2500 0.925923
3000 0.903054
3500 0.861995
4000 0.833804
4500 0.821041
5000 0.806599

先ほどのLAMMPSが出力した温度とほぼ同じ温度が出力されていることがわかる(1frameずれているのか、少しだけ値がずれる)。温度が取れている=原子の情報を全て取得できているので、あとは好き勝手できる。

しかし、フレームがstatic変数になっているのがちょっと気持ち悪いのと、これからいろいろ情報が増えるので、クラスを作って、calc_temperatureをメンバ関数とし、frame_filename_をメンバ変数とするLammpstrjAnalyzerクラスを作る。

class LammpstrjAnalyzer{
private:
  const std::string filename_;
  int frame_;

public:
  LammpstrjAnalyzer(const std::string filename): filename_(filename){
    frame_ = 0;
  }

  void calc_temperature(const std::unique_ptr<lammpstrj::SystemInfo> &si, const std::vector<lammpstrj::Atom> &atoms) {
    double e = 0.0;
    for (auto &a : atoms) {
      e += a.vx * a.vx + a.vy * a.vy + a.vz * a.vz;
    }
    e /= static_cast<double>(si->atoms);
    e /= 3.0;
    printf("%d %f\n", frame_ * 500, e);
    frame_++;
  }

  void analyze(void){
    auto callback_function = [this](const std::unique_ptr<lammpstrj::SystemInfo> &si, const std::vector<lammpstrj::Atom> &atoms){calc_temperature(si, atoms);};
    lammpstrj::for_each_frame(filename_, callback_function);
  }
};

ポイントはanalyze関数。lammpstrj::for_each_frameにコールバック関数としてLammpstrjAnalyzer::calc_temperatureを渡したいが、直接は渡せない。そこで、一度ラムダ式でコールバック関数を作ってやり、その中でメンバ関数を呼ぶようにする。

    auto callback_function = [this](const std::unique_ptr<lammpstrj::SystemInfo> &si, const std::vector<lammpstrj::Atom> &atoms){calc_temperature(si, atoms);};

ここで作ったコールバック関数をlammpstrj::for_each_frameに渡せば良い。

    lammpstrj::for_each_frame(filename_, callback_function);

一度callback_functionという変数に受けなくても、いきなり

lammpstrj::for_each_frame(filename_, [this](const std::unique_ptr<lammpstrj::SystemInfo> &si, const std::vector<lammpstrj::Atom> &atoms){calc_temperature(si, atoms);});

とできるが、可読性を考えると一度変数に受けた方が良い気がする。