C++

【オブジェクト指向C++】1次元移流方程式の数値計算

Step3:流速Uの値の設定

続いて流速であるUの値の設定を行いましょう。

以下の2つのファイルを作成します。

  • Fields.h(ヘッダーファイル):変数、関数定義
  • Fields.cpp:関数処理

Uは$u(x)$と関数として定義するため、$x$座標と座標に応じた$u$の値を持つようにします。Fields.h(ヘッダーファイル)を以下のようにします。

Fields.h

vector<Fields>というvectorコンテナを使ってFieldsクラスの配列要素を作ります。
vector<Fields>と毎回書くのは煩雑になるので、以下のように名前を付けて定義しなおします。

これにより「vector<Fields>」と書かずとも「vectorField1d」と書くだけで済みます。

Fields.cppに関数処理を書きます。

Fields.cpp

肝になるところは以下の部分です。

関数処理により返す型は「Fields::vectorField1d(FieldsクラスのvectorField1d)」です。「vectorField1d」は先ほど「vector<Fields>」と定義しなおしたので、Fieldsクラスを持った配列を返すようにしています。

main.cppでFieldsクラスのインスタンス化を行います。
※余分な記述は「/* */」で挟んでコメントアウトしました。

main.cpp

「Fields FieldsOperations;」でクラスのインスタンス化を行っています。
Fields.cppで以下のように初期化されていることに注意です。

ですので、「FieldsOperations.value」はFieldsクラス内の初期値として2.0が出力されます。

は一見わかりにくいですが、要素数5つのFieldsクラスを持った配列を定義しています。
「vector<double>」は要素がdouble型の数値ですが、「vector<Fields>」は要素がFields型のクラスです。

値の出力にはクラス内のメンバ変数にアクセスする必要があるため、

のように、まず「UFieldsvec[i]」として配列の一つを取り出して「.(ドット)」でメンバ変数にアクセスします。

で、「FieldsクラスのvectorField1dのU」と「vector<double>型のmesh_.xpts」を引数にしています。

【結果】

これにて「U[i].x1d」でx座標を、「U[i].value」でUの値を格納することができました。
※「Fields::~Fields() destructor」はFieldsクラスを抜けるたびにメモリ解放のために動作するデストラクタです。勉強のためデストラクタがいつのタイミングで作動したかを出力しています。

Step4:演算子のオーバーロード

+や-の演算子は数値や文字列など特定のデータ型にしか使うことができないです。
例えば、

などの演算は可能ですが、クラスから生成されたオブジェクト同士の演算はすることができません。

もし仮に、上記のようなユーザー定義のクラスに対して演算を行うことができれば直感的なプログラムを構築することができます。その際に、演算子のオーバーロードを使います。演算子のオーバーロードはクラスでメンバ関数として定義します。ここで用いるのが operator キーワードです。

まずは、演算子のオーバーロードの理解のためにFields.hにoperator+関数を定義します。

Fields.h(主要部分のみ)

Fields.cppで関数の処理内容を書きます。

Fields.cpp(主要部分のみ)

引数としてFieldsクラスのvecAという名前の仮引数を入れています。
そしてFieldsがインスタンス化された時点で初期化するvalueとの足し算を行っています。

そしてmain.cppでクラス同士の演算を行っています。

main.cpp(主要部分のみ)

【結果】

「C_Fields = A_Fields + B_Fields;」がクラス同士の演算です。
実際は関数内でFieldsクラス内のメンバ変数valueの足し算を行っています。

これは、その下の「C_Fields = A_Fields.operator+(B_Fields);」と同じ処理を行っています。こちらの書き方の方がクラス内のメンバ関数に引数「B_Fields」を渡して計算しているのでプログラムとしては理解しやすいですが、上記の書き方の方がプログラミングの文法とは離れて直感的ですよね。

別の2つのクラス同士の演算も可能です。

Fields.h

クラス内のprivateやprotectedメンバ変数は、基本的に同じクラス内のメンバ関数からしかアクセスできませんが、フレンド機能を使うと通常の関数からもアクセスできるようになります。

Fields.cpp(主要部分のみ)

main.cpp(主要部分のみ)

【結果】

では、この機能を使ってクラス同士の演算子を自作します。

Fields.h

Fields.cpp

main.cpp

「tempResult[i].value」「tempMultiResult[i].value」「tempMultiResult1[i].value」の出力にfor文で3つも同じ記述をしているのは無駄が多いですが、後ほど関数にまとめるとして今はそのままにしておきます。

【結果】

クラス同士の演算が可能になりました。

Step5:微分の離散化

1次元移流方程式について微分を離散化して扱います。

1次元の移流方程式
\begin{align*}\frac{\partial u(x,t)}{\partial t}+c\frac{\partial u(x,t)}{\partial x}=0\tag{7}\end{align*}
※一定速度$c$
離散化すると以下のようになります。
ここでは空間微分の$\frac{\partial u}{\partial x}=\frac{u^{(n)_{i}}-u^{(n)_{i-1}}}{dx}$のプログラムを作成します。
※$dx=x_{i}-x_{i-1}$

微分の離散化を定義するため以下の2つのファイルを作成します。

  • Diff1d.h
  • Diff1d.cpp

Diff1d.h

今後のためクラス名が被らないように名前空間「fd1d」を付けてクラスを定義します。
返すデータの型はvectorField1dは「vector<Fields>」を意味することは説明済みです。これによりFieldsクラスを持ったvecotorコンテナの配列を引数に入れて「gradX1d」の関数処理を行い、Fieldsクラスを持ったvecotorコンテナの配列が返ってくるよう定義します。

Diff1d.cpp

こちらで関数の処理内容を書いています。
名前空間内のクラス内のメンバ関数の定義を行うには、先頭に「fd1d::」を付けて定義する必要があります。

以上で微分の離散化の関数処理が完成です。
以下で具体的に移流方程式を離散化した式を解くプログラムを書いていきます。

プログラムの中に「vec[i – 1].value」があるためi=0を代入するとエラーになります。
ですので、for (int i = 1; i < vec.size()-1; i++)として端はmain.cppで境界条件として設定するようにしています。

1 2 3 4 5 6

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です