42と43のリビジョン間の差分
2014-05-09 05:06:09時点のリビジョン42
サイズ: 16388
編集者: TakanoriKono
コメント:
2014-05-09 05:08:06時点のリビジョン43
サイズ: 16449
編集者: TakanoriKono
コメント:
削除された箇所はこのように表示されます。 追加された箇所はこのように表示されます。
行 186: 行 186:
 * main関数  * main関数、<<Color2(int main(int argc, char* argv[]) { ... }, blue)>>

C++演習(2014年4~5月)

目標

素粒子物理の分野で良く使われるデータ解析用のソフトウェアROOTを使うのに必要なC++プログラミングの知識を習得する。

内容と進め方

プログラミングは文法を正確に覚えることよりも、とにかくコードを書いて慣れることが重要である。したがって、文法事項を順番に説明することはせず、ソースコード例を用いる。用意されたソースコードは、コンパイルして実行すること。できれば、ソースコード中の変数の値や論理を少し変更して、コンパイル・実行してプログラムの動作を検証するということを自分でやってみること。 小さな文法的なミスのせいでコンパイルができない、ということは頻繁にあるが、それを解決するには経験を積むのが一番である。上達するにはとにかく自分でコードを書いて、コンパイル、実行するということを何度も繰り返すことが重要である。

日程

4/23(水)、4/30(水)、5/7(水)、5/21(水)、5/28(水)、6/4(水)、6/18(水)、6/25(水)

(5/14(水)、6/11(水)は休み

)

スライド: 201404-CppTutorial.pdf 201404-CppTutorial.pptx

事前準備

演習を進めていく上で、環境変数をいくつか設定する必要がある。これは、システム全体で共用され、通常ソフトウェアのインストール先、その他のディレクトリ名、外部サーバー名等を設定して様々なプログラムで参照できるようにするものである。 ログインした際に自動的に初期設定を行うファイル(~/.zshrc)を編集して、以下の行を加える。(これは共通アカウントに対しては設定済み)

export ROOTSYS=/nfs/opt/root-v5-34-07
a=$(pwd); cd $ROOTSYS; source ./bin/thisroot.sh; cd $a;
export OCHA_SVN=svn+ssh://hpx.phys.ocha.ac.jp/var/svn/repos
alias start_ssh_agent=`eval $(ssh-agent)`

環境変数が設定されているかどうかは、

echo $ROOTSYS;

のように、環境変数名の先頭に'$'を付け加えてその値を書き出させることで確認できる。または、printenvで全ての環境変数を表示することができる。

ソースコード

演習で使うプログラムのソースコードは、 Subversionレポジトリ においてある。

svn co $OCHA_SVN/Lab/Tutorials

環境変数OCHA_SVNが設定されていない場合は、以下のコマンドを実行する。コマンドで実行した場合、次回ログインした際には、もう一度行う必要があるため、初期設定ファイルで行っておくと良い。

export OCHA_SVN=svn+ssh://hpx.phys.ocha.ac.jp/var/svn/repos

Subversionの使い方

Subversionはソースコード管理に使用するためのソフトウェアで、ファイル一括して管理するため複数の人でコードを共有する場合に便利である。また、更新履歴も管理してくれるため、間違ったコードをアップロードしても容易に以前のバージョンに戻すことができる。使用するには、svnというコマンドに引数でサブコマンドや他の情報を指定する。良く使うものは、

svn co <URL>

レポジトリ(サーバー)のURLを指定して、コードをcheckoutする(=ダウンロード)

svn update

サーバー上の変更をローカルに反映させる

svn status -u

ローカルなファイルとサーバー上のファイルの違いを調べる

svn diff

ローカルとサーバー上のファイルの中身を一行ずつ比較する

svn co -m <コメント>

ローカルなファイルへの変更をサーバー上に反映させる(=アップロード)

svnコマンドを使用して、サーバーに接続する際にパスワードを聞かれます。毎回聞かれるのが面倒な場合は

start_ssh_agent
ssh-add

を一度前もって実行しておくと、以後パスワードは聞かれなくなります。

前回からソースコードを少し修正したため、Subversionサーバーから最新版をダウンロードする。そのためには、~/.../Tutorialsに移動して、

svn update

を実行する。

講習内容

第1回

  • Linuxシステムへのログイン
  • Subversionレポジトリからソースコードをダウンロード
  • 環境設定
  • emacsでソースファイルを閲覧、編集
  • プログラムのコンパイル
  • Makefile(コンパイルの自動化)
  • プログラムの実行

第2回

環境設定

前回、ソースコードをダウンロードして、環境設定、プログラムのコンパイル、実行ができるようになった。但し、改めてログインするため、各自の作業ディレクトリ等の環境設定をする必要がある。

cd ~/<自分のディレクトリ>/Tutorials; # <自分のディレクトリ>の部分は適切なディレクトリ名に置き換える
source ./TutorialSetup/setup.sh

今回は共通アカウントを使っているため、これはログインする度に行う必要がある。自分のアカウントを使っている場合は初期設定ファイル(~/.zshrc)に加えても良い。

もう一つ、svnコマンドを使うたびにパスワードを聞かれるが、これを避けたい場合、

start_ssh_agent
ssh-add

を実行する。こうすると、Subversionレポジトリのあるマシンに接続する際に使用するsshコマンドにパスワードを登録することができ、毎回パスワードを入力する必要が無くなる。

CppTutorial1のプログラムの中身の確認

プログラム名

実行例

内容

Add_1plus2.exe

 ./Add_1plus2.exe 

1+2=3に特化したプログラム

Add_Aplus2.exe

 ./Add_AplusB.exe 12 34 

実行時に2つの数値を指定できる。コマンドライン引数

Calculate_AandB.exe

 ./Calculate_AandB.exe - 100 31 

実行時に四則演算も指定できる。プログラム内で条件分岐

それぞれのプログラムのソースコードをファイルを見て確認する。

プログラム作成(1から100までの和を計算する)

プログラムを一から作成する練習として1から100までの整数を全て足して和を求めてみる。答えは5050になるはずである。以下の手順は、プログラムを書く際に、途中で何度かコンパイル・実行をしてコードに誤りが無い事を確認しながら進めている。この通りにする必要はないが、一般にもっと大きなプログラムを書く際は、全体をいくつかの部分に分けそれぞれが正しく動作していることを確認しながら書くことになるので、ここでやる方法が少しは参考になるであろう。

  1. main関数を用意する。int main(int argc, char* argv[]) { }
  2. main関数内にforループを用意する。for (int i=1; i<=100; i++i) { }

  3. forループ内にprintf("i=%d\n", i);を入れて、この時点でコンパイル・実行して、"i=1", "i=2", ...という出力が得られることを確認する
  4. forループの前にint sum=0;、ループ内にsum += i;、ループの後にprintf("sum=%d\n", sum);を追加する。
  5. 再度、コンパイルして実行する
    • {{{ g++ -o myprogram.exe myprogram.cxx
  6. Makefileにプログラムを追加する。例に倣ってPROG_SRCS = ...の最後に新しいソースファイル名を追加する

1. main関数

#include <cstdio>

using namespace std;

int main(int argc, char* argv[]) {
  std::printf("Start of the program\n");
  return 0;
}

2. forループを入れる

#include <cstdio>

using namespace std;

int main(int argc, char* argv[]) {
  std::printf("Start of the program\n");
  int i;

  for (i=1; i<100; ++i) {
    std::printf("Inside the loop, i=%d\n", i);
  }

  return 0;
}

3. ループ内で和を計算する

#include <cstdio>

using namespace std;

int main(int argc, char* argv[]) {
  std::printf("Start of the program\n");
  int i;
  int sum=0;

  for (i=1; i<100; ++i) {
    // std::printf("Inside the loop, i=%d\n", i);
    sum += i;
  }
  std::printf("Sum of numbers from 1 to 100 is %d\n", sum);

  return 0;
}

第3回

前回の1~100までの和を計算するプログラムを完成させる。その後、課題1にあるように少し修正してみる。

準備

サーバーにログインした後、以下の作業をして最新のコードのダウンロードと環境設定を行う。これは、ログインする度に行う。

cd ~/<自分のディレクトリ>/Tutorials; # <自分のディレクトリ>の部分は適切なディレクトリ名に置き換える
source ./TutorialSetup/setup.sh
start_ssh_agent
ssh-add
svn update

プログラム作成

繰り返し、条件分岐、浮動小数点数の扱いに慣れる。そのために、一からプログラムを作成する。

  • 楕円の面積の計算、(x/3)2+(y/2)2<1で囲まれる面積を計算する。y>0にある領域の面積を計算して2倍するのが簡単(?)

    • x:[-3,3]を1000分割して、dx=6.0/1000とする
    • x=-3+dx*iとxを少しずつ動かしながら、y(x)*dxを足し合わせていく
  • Poisson分布(ポアソン分布

    • 平均2のPoisson分布に対して、P(0), P(1), P(2), P(3), P(4), P(5), ...を求める
    • 平均30のPoisson分布に対して、∫0xP(x)dx<0.95となるxを求める

第4回

いくつかプログラムのソースコードを書いて実行したところで、コード、文法の内容を簡単に説明する。プログラムを構成する要素としては、

  • main関数、int main(int argc

    • プログラムに対して必ず一つ必要で、プログラムの中身はその中に記述する。main関数に記述されたコードが順番に実行される。
  • 変数の利用
    • 変数の宣言: <型> <変数名>=<初期値>; という形式を取る。初期値は省略しても良いが、その場合初期値は不定となる。

    • 値の代入: a = 3; や a = b + c;
  • 文字列の出力
    • std::printf("<書式>", <変数1>, <変数2>, ...) となる(C言語で使われていたもの)

    • または、std::cout << <文字列または変数> << std::endl; を利用(C++で導入されたもの)

  • 繰り返し
    • for (<初期処理>; <条件式>; <ループ毎の処理>) { ... }

    • { } は複数の文をまとめるために利用する。コードブロックを定義して、繰り返し行う処理の範囲を明確にする
  • 条件分岐
    • 条件式: 1<3(-> true); 1>3(-> false); a==3; など結果が真偽値となる文

    • if (<条件式>) { (条件が満たされた場合に実行)... } else { (条件が満たされなかった場合に実行)... }

    • 条件式を評価した後に実行する文は、{ }内にまとめる。
  • 関数
    • 他の場所で定義されたコードを呼び出して実行する。
    • 数学関数は典型的な例であるが、printf(...)のようにある処理をするもの一般を関数と呼ぶ
    • 関数は、<戻り値> <関数名>(<引数>); という形をしていて、呼び出す際は必要とされる引数(数および型)を与える必要がある。

    • 例えば、double sqrt(double x) という平方根を計算する関数は、double型の引数を一つ与えるとdouble型の値を返すように定義されている。

CppTutorial2にあるプログラム

素因数分解を行うプログラムを見る。繰り返し、条件分岐、関数を使えれば数10行のプログラムで書ける。

プログラム名

実行例

内容

CheckPrime1.exe

 ./CheckPrime1.exe 101 

与えられた整数が素数かどうかを判定

Factorize.exe

 ./Factorize.exe 1000 

与えられた整数を素因数分解する

ListPrime.exe

 ./ListPrime.exe 100 

1~与えられた整数までの素数を全て書き出す

第5回

もう少し複雑なコードを書く。自分で関数を作る。

  • 運動方程式を解いて粒子の軌跡をたどる
    • Runge-Kutta法(常微分方程式を解く標準的なアルゴリズム)
  • ボールの衝突を計算

CppTutorial3にあるプログラム

プログラム名

実行例

内容

classicalMotion.exe

./classicalMotion.exe 10 20 > a.dat

粒子の初速度(vx, vy)を与えて、時刻毎の座標、速度を書き出す

collisionOfBalls.exe

./collisionOfBalls.exe 10 -20 0.1 > coll.dat

2つの粒子の衝突を含めた軌跡を求める。所属度と微妙な位置調整をパラメータとして渡す

ライブラリに含まれているもの

ファイル名

関数

内容

RungeKutta.hxx (.cxx)

evolveStepRK(...)

1次元のRungeKutta法。時間dt経過後の位置、速度を更新する

CollisionTool.hxx (.cxx)

checkCollision(...)

有限の半径を持つ2つのディスクが衝突するかどうかチェック

CollisionTool.hxx (.cxx)

processCollision(...)

2つのディスクが衝突した場合、衝突後の速度を計算する

これらの関数は単独で実行するのではなく、上のmainプログラムから呼び出せれることを想定して作成されている。

分割コンパイル

プログラムのコード量が多くなってくると、全てを一つのファイルに記述すると読みづらくなる。ここでは、昨日に合わせてプログラム全体をいくつかの関数に分割しており、別々のファイルに記述し、コンパイルしてある。このような状況で実行可能形式のファイルを生成するには、以下の手順が必要である。

  1. それぞれのファイルを別々にコンパイルして、オブジェクト・ファイル(*.o)ファイルを生成する
    • .cxx --> .o

    • 各ファイルでは、後で使う個別の関数やクラスを定義するだけでmain関数を含まないかもしれない。
  2. 複数のオブジェクトファイルをまとめてライブラリ(lib*.so)を生成する
    • .o, .o, .o, ... --> lib*.so

    • ライブラリにはmain関数は含ませない。main関数を含むファイルを除く必要がある
  3. main関数の定義されている.oファイルとライブラリをリンクして、実行可能形式のファイルを生成する
    • .o(mainを含む), lib*.so --> .exe

プログラムを分割してライブラリを生成することで、同じ関数を複数のプログラムで共有することが可能である。これは、その関数を修正したい場合、それが定義されているファイルの一ヶ所だけ修正すれば良いので、プログラムの整合性を保つ上でも便利である。

課題

1. 第2回で作成したプログラムを少し変更してみる

1から100までの和を計算するのではなく、例えば以下のような計算をしてみる

  • 1~100までの数、それぞれの2乗の和を計算する
  • 12+22+32+...+n2と計算していき、和が最初に10000を超えるnを求める

  • 1+2+3+...と順にやっていき、和が最初に1000を超える数を求める

浮動小数点数の計算

CppTutorial (最終更新日時 2015-11-13 11:09:33 更新者 TakanoriKono)