(前頁)<<      >>(次頁)          (Up)

2章 準備勉強
2.2 FORTRANのモデルをC#で制作する (その3)



 前節の「人」と「夫」等の例に習い、「DynamicWaveモデル」と 「KinematicWaveモデル」の上位概念としての「河川モデル」を制作してみます。 既に制作した2つの具体的モデルの処理構造から、上位の「河川モデル」クラスの処理構造に対しても下図の通りメソッドが必要である考えられます。

          

 これまでの節でモデルを作成してきた内容を見返すと 計算結果の出力:OUTPUT」処理分は 表示する情報を作成する部分とその内容をファイルに出力する部分に分けることができます。 ファイル出力部分はFileStreamクラス等を扱い、モデルの開発者にとって計算論理を考える際にはあまり関係しない部分です。
 そこで、この部分は親クラスに持たせてファイル操作の煩わしさをモデル開発者から隠蔽します。 ここで、ファイル操作を 共通メソッドとして、親クラスに持たせます(FileOutメソッド)。  追加したFileOutメソッドは 外部からはアクセスする必要が無いので、public 宣言はしません。
 また、Calculateメソッド、OUTPUTメソッドは 計算制御メソッド(CalControlメソッド)が制御し、外部から制御する必要がないので 両メソッドも public にはしません。 ここでは、protected 宣言を行ないます。 protected 宣言は「外部からは使用できないが、派生クラスからは使用できる」事を示します。(派生クラスにも使用させない場合には private宣言します)

    public    :  外部クラスも含めて 誰でも使用(コール)できる
    protected  :  自クラス及び派生クラスのみ使用できる
    private     :  自クラス以外使用できない

         

 それでは、実際にプロジェクトを作成してみます。

<親クラスとメインフレームの作成>

 前に作成したソリューション「CSSMDL3.lzh」に 親クラスを追加します。
 先ず、親クラス用のDLLを追加します。

      


 追加したDLLを ModelMainで使用するため メインプログラムの参照に追加します。

      

       

 次に、デフォルトで追加された Class1.cs の名称を RiverModel.cs に変更します。

      

 ファイル名を変更すると クラス名も自動的に変更されます。

      

 ついでにプロパティ設定用のデータクラス( PrptyInf )も追加しておきます。

namespace RiverModelDLL
{
    public class PrptyInf
    {
    }
} 

 これで、親クラス「RiverModel」を利用する準備が整いました。


 RiverModel に 先に考えたメソッドを追加します。(最初は中身を実装しません。 スタブと呼びます) 派生クラスにオーバライドさせるメソッドには「virtual」宣言を忘れないようにして下さい。
 

namespace RiverModelDLL
{
    public class RiverModel
    {
        // プロパティ設定
        public virtual void INPUT(PrptyInf csPrpty)
        {
        }
        // 初期化
        public virtual void INITIAL()
        {
        }
        // 計算制御
        public void CalControl()
        {
        }
        // 計算
        protected virtual void Calculate()
        {
        }
        // 計算結果出力
        protected virtual void OUTPUT()
        {
        }
        // ファイル出力
        protected virtual void FileOut(string csString)
        {
        }
    }
}


 以前作成した「DynamicWaveモデル」と 「KinematicWaveモデル」のコードを見ると 計算制御部はそれぞれ 『public void DYNAMIC()』メソッドと『public void KINEMATIC()』メソッドに相当します。 この計算制御部分はほぼ同じコードなので親クラスに持たせることにします。 但し、親クラスに実装する時には ループの制御を 特定回数ループするのではなく、シミュレーション時刻が目標時刻に達したら計算を終了する処理に変更します。 このために、メンバー変数として シミュレーション時刻(m_T)、計算完了目標時刻を(m_GoalT)、及び時刻増加分(m_DT)を追加します。 さらに、FileOutメソッド内で使用する出力ファイル名称を保存する変数(m_sOutFileName)もメンバー変数とします。

メンバー変数には m_ という接頭文字を付けます。 この方法はハンガリアン表記法の中の一つの規則で、CommonMPも大枠はハンガリアン表記法に準じています。 ハンガリアン表記法には色々批判もありますが、ローカル変数とメンバー変数の区別が区別できるなど プログラムをわかり易くする効果があります。


 メンバー変数も含んだクラスの構造を下に示します。

     
 スタブの生成が終わったらコンパイルして、文法上の問題が無いことを確認しておきます。 
 スタブのコンパイルが終了した所で、スタブの中身の実装を行います。
 先ずはメンバー変数を追加します。

 // -- メンバー変数 --
        /// <summary>シミュレーション時刻</summary>
        protected double m_T;
        /// <summary>計算目標時刻</summary>
        protected double m_GoalT;
        /// <summary>δT</summary>
        protected double m_DT;
        /// <summary>出力ファイル名称</summary>
        protected string m_sOutFileName = "CalOut.csv";

コメント ///<summary>。。。。。</summary>はMicrosoft Visual C# を使用し、変数定義文の前行で’ /// ’ と入力すると自動的に追加されます。 メンバー変数の宣言及びメソッドの宣言を Microsoft Visual C# が追記してくれたコメント形式で作成しておくと、Doxgen等のツールを利用してのソースからドキュメント生成等に役立ちます。

 RiverModelクラスのINPUTメソッドで RiverModelのメンバー変数に値を設定したいので、INPUTメソッドの引数であるPrptyInf クラスに、計算目標時刻と時刻増加分のメンバー変数を追加します。

// -- メンバー変数 --
        /// <summary>計算目標時刻(秒)</summary>
        public double m_GoalT = 48.0 * 3600.0;
        /// <summary>δT(秒)</summary>
        public double m_DT = 48.0 * 3600.0 / 2400.0; 


 次に RiverModel クラスの  INPUT( )、Calculate( ) 、及び FileOut( )  メソッドの実装を行います。 他のメソッドはスタブのまま残しておきます。(派生クラス側でオーバーライドします)

        /// <summary>プロパティ設定</summary>
        /// <param name="csPrpty"></param>
        public virtual void INPUT (PrptyInf csPrpty)
        {
            m_DT = csPrpty.m_DT;
            m_GoalT = csPrpty.m_GoalT;
        }
        /// <summary>計算制御</summary>
        public virtual void CalControl ()
        {
            for (;;)
            { // 無限ループ

                if( m_T > m_GoalT )
                {   // 目標時刻まで経過したら ループを止める
                    break;
                }

                Calculate(); // 計算

                OUTPUT(); // 計算結果出力

                m_T = m_T + m_DT;
            }// end of for(J)

        }
        /// <summary>ファイル出力</summary>
        /// <param name="csString">出力ファイル名称</param>
        protected virtual void FileOut (string csString)
        {
            FileStream csFile = new FileStream(m_sOutFileName, FileMode.Append, FileAccess.Write, FileShare.Read);
            StreamWriter csStreamWriter = new StreamWriter(csFile, Encoding.Default);
            csStreamWriter.WriteLine(csString);
            csStreamWriter.Flush();
            csStreamWriter.Close();
            csFile.Close();
        }


  次にメイン側の処理を実装します。

using RiverModelDLL;

namespace ModelMain
{
    class Program
    {
        static void Main(string[] args)
        {
            // モデルインスタンス生成
            RiverModel csRvrMdl = new RiverModel();
            // プロパティ情報インスタンス生成
            PrptyInf csMdlPrp = new PrptyInf();

            // プロパティ情報を設定
            csRvrMdl. INPUT(csMdlPrp);

            // 初期化
            csRvrMdl. INITIAL();

            // 計算開始
            csRvrMdl. CalControl();
        }
    }
} 


 親クラスとメインプログラムで 計算のフレームワークが完成したので 一度このままで動作させてみます。 下図のようにブレークポイントを設定して、Microsoft Visual C# の「ディバッグ」−「ディバッグ開始」からディバッグモードで動作を開始させます。

      

 ブレークポイントは、INPUT()メソッド内、CalControl()内に設定しておきます。 

     

 ブレークポイントでプログラム実行が中断したとこでメンバー変数の値等を調べながら、意図した通りの動作をしているかを確認します。

     



<派生クラスの作成>

 メインプログラムと親クラスを使用した計算のフレームワークが完成したので、次に具体的に派生クラスの処理を実装します。
 既に作成済みの「DynamicWaveModel」と「KinematicWaveModel」を改造します。 両方のクラスの親クラスを「河川モデル」にします。 派生クラス側で「河川モデルDLL」を参照するために、各派生モデルプロジェクトの参照設定を追加します。 プロジェクトの参照設定を選択しマウスを右クリックしてポップアップメニューから 参照の追加を選択します。 表示された参照の追加ダイアローグから 親クラスのDLL「RiverModelDLL」を追加します。

           
     
 次に 派生クラス側のソースコードファイルに参照する親クラスのnamespace 名称と、派生クラスの親クラスを追記します。

using RiverModelDLL;

namespace DynamicWaveLine
{
    // DynamicWaveMode クラス
    public class DynamicWaveModel : RiverModel
    {
          。。。。。。。。。


 ついでに、プロパテイ情報を格納するクラス「DynamicWavePrptyInf」「KinematicWavePrptyInf」を 「PrptyInf」クラスの派生クラスにします。

using RiverModelDLL;
namespace DynamicWaveLine
{
    public class DynamicWavePrptyInf : PrptyInf
    {
     。。。。。。。。。


 派生関係を設定した後、派生クラス側でオーバーライドするメソッドを作成します。 DynamicWaveModelクラスでは メンバー変数:DT、TLが それぞれ、親クラスのメンバー変数: m_DT、m_GoalT に移動されました。 そこで、DynamicWaveModelクラスのDT、TLのメンバー変数定義箇所を削除します。 そしてコンパイルエラーとなった箇所をそれぞれ 親クラスのメンバー変数 m_DT、m_GoalT  に置換します。 また、シミュレーション時刻:m_T に相当する変数は DYNAMIC()メソッド内の for (long J = 0; J <= NT - 1; J++)ループ内の変数 T1ですので、この T1を 親クラスのメンバー変数:m_T に置換します。 また、OUTPUTメソッドの引数:Tもメンバー変数 m_T と等価なので メソッド内の変数:T を m_T に置き換えます。
 派生クラス側でオーバーライドするメソッドは次のように改造します。

・INPUTメソッド: オーバーライドするメソッドであるので override を追加し、引数を 「DynamicWavePrptyInf」の親クラスである「PrptyInf」に変更します。
・INITIALメソッド: オーバーライドするメソッドであるので override を追加します。
・OUTPUTメソッド: オーバーライドするメソッドであるので override を追加します。 また、親クラスのメソッドは protected 宣言されているので、このメソッドもprotected に変更します。 さらに、引数:Jは不要であり、もう一方の引数:T は 親クラスのメンバー変数:m_T と同じなのでこの引数も不要となります。
・COEFFメソッド: 外部には一切開放しないので アクセス権限を public から private に変更します。
・QINメソッドもCOEFFメソッドと同様の変更を行います。

public override void INPUT(PrptyInf csPrpty)
public override void INITIAL()
protected override void OUTPUT()
private double QIN(double T)
private void COEFF(long I,ref double FQ, ref double FV, ref double FP, ref double FF)


 DYNAMICメソッドの for (long J = 0; J <= NT - 1; J++)のループ内部に記述している内容が実際のモデルの計算を行う部分なので、
   protected override void Calculate()
 の 内部に移動させます。

        /// <summary>計算</summary>
        protected override void Calculate ()
        {
            //long J1 = J + 1;  ←この部分は不要
            //double m_T = (double)J1 * m_DT;  ←この部分は不要

            double dQIN = QIN(m_T);
            Q1[0] = dQIN;
            H1[0] = H[0] + (1.5 * Q[0] - 2.0 * Q[1] + 0.5 * Q[2]) * m_DT / DX;

            for (long I = 1; I <= NX; I++)
            {
                double FQ = 0.0;
                double FV = 0.0;
                double FP = 0.0;
                double FF = 0.0;
                COEFF(I, ref FQ, ref FV, ref FP, ref FF);

                H1[I] = H[I] - FQ * m_DT;
                Q1[I] = (Q1[I] + (FP - FV) * m_DT) / (1.0 + FF * m_DT);
            }

            for (long I = 0; I <= NX; I++)
            {
                H[I] = H1[I];
                Q[I] = Q1[I];
                V[I] = Q[I] / H[I];
            }

            //if ((NSKIP * (long)(J1 / NSKIP) - J1) == 0)  ←この部分は不要
            //{  ←この部分は不要
            //    OUTPUT(J1, m_T);  ←この部分は不要
            //}  ←この部分は不要
        }  


  DYNAMICメソッドは不要となるので削除します。
 OUTPUTメソッドは 出力する文字列を制作している処理のみを残し、ファイルに出力する部分は 親クラスの FileOutメソッドをコールするように修正します。

        protected override void OUTPUT ()
        {
            if ((3600 * (long)(m_T / 3600.0) - (long)m_T) == 0)
            {   // シミュレーション時刻が正時になったら出力する
                // ↓出力する文字列を制作する処理
                string sMsg = "";
                sMsg = m_T.ToString();
                double outQ = 0.0;
                for (long I = 0; I <= NX; I++)
                {
                    outQ = Q[I] * WIDTH;
                    sMsg += "," + outQ.ToString();
                }
                //  ↑出力する文字列を制作する処理

                FileOut(sMsg); // ファイル出力(親クラスのメソッドをコールする)
            }
        }


  計算制御に使用していたメンバー変数:NT、NSKIP もコメントアウトします。

 INPUT( )メソッドは、引数として 「DynamicWavePrptyInf」クラスの親クラス「PrptyInf」が与えられています。 しかしながら 派生クラス側は 引数として DynamicWavePrptyInfクラスが与えられることを期待しています。 一方、派生クラス側でも 親クラスで実装した m_GoalT、m_T への設定も行いたいと思います。 このような時には、次のような実装を行います。
 派生クラス側から親クラスのメソッドを呼び出して 親クラス側のメンバー変数を設定するために base.INPUT(csPrpty); とします。( 親クラスは base. とする事で呼び出せます。) 次に 引数を派生クラスと見なして処理を行うために 「キャスト」という操作を行います。キャストすると、親クラスを子クラスとして扱えるため、キャスト後は 以前に子クラス側で実装した内容がそのまま使用できます。

        public override void INPUT (PrptyInf csPrpty)
        {
            base.INPUT(csPrpty); // 親クラスの設定


            X = new double[NXX];
            H = new double[NXX];
            H1 = new double[NXX];
            Q = new double[NXX];
            Q1 = new double[NXX];
            V = new double[NXX];

            DynamicWavePrptyInf csPrptyInf = csPrpty as DynamicWavePrptyInf; // 派生クラス側にキャスト
            G = 9.8;
            // m_GoalT = 48.0 * 3600.0;    <-- 親クラス側で設定する(base.INPUT(csPrpty); 内で行われる)
            //NT = 2400;
            // m_DT = m_GoalT / ((double)NT);   <-- 親クラス側で設定する(base.INPUT(csPrpty); 内で行われる)
            //NSKIP = 240;

            //XL = 100.0 * 1000.0;
            XL = csPrptyInf.XL;
            // NX = 50;
            NX = csPrptyInf.NX;
            DX = XL / ((double)NX);

            //SLOPE = 1.0 / 2000.0;
            SLOPE = csPrptyInf.SLOPE;
            WIDTH = 200.0;
            //ROUGH = 0.03;
            ROUGH = csPrptyInf.ROUGH;

            QB = 200.0;
            QP = 2000.0;
            TP = 12.0 * 3600.0;
            CP = 10.0;

            m_sOutFileName = csPrptyInf.OutFileName;
        } 


  派生クラスも作成できたので、メインプログラムの 河川モデルを new している部分を 派生クラスを new する処理に置き換えて、動作を確認してみます。

            // モデルインスタンス生成
            //RiverModel csRvrMdl = new RiverModel(); // <-- コメントアウト
            RiverModel csRvrMdl = new DynamicWaveModel(); // <-- 派生クラスに置き換え
            
            // プロパティ情報インスタンス生成
            //PrptyInf csMdlPrp = new PrptyInf(); // <-- コメントアウト
            PrptyInf csMdlPrp = new DynamicWavePrptyInf(); // <-- 派生クラスに置き換え 
        ( 制作したプログラム: (RiverMDL1.lzh) )

  Microsoft Visual C# のディバッグ実行を行います。 すると、親クラス:RiverModel として宣言した RvrMdlが DlynamicWaveModelとして動作することが解ります。
 同様に RiverModelの派生クラス:KinematicWaveModelを作成し、
            // モデルインスタンス生成
            //RiverModel csRvrMdl = new RiverModel(); // <-- コメントアウト
            RiverModel csRvrMdl = new KinematicWaveModel(); // <-- 派生クラスに置き換え
             
            // プロパティ情報インスタンス生成
            //PrptyInf csMdlPrp = new PrptyInf(); // <-- コメントアウト
            PrptyInf csMdlPrp = new KinematicWavePrptyInf();  // <-- 派生クラスに置き換え
 とすると 今度は RvrMdlが KinematicWaveModel として動作します。





  これまで、FORTRANで制作されたモデルを C#を用いたオブジェクト指向対応モデルに改造して来ました。
 ところで、せっかくモデルをDLL化して作成したにもかかわらず、メイン側で使用するには、DLL内のモデルが何であるかを知り、そのインスタンスを new しなければなりません。 DLL化したのであれば、新しいDLLが追加された時にも コンパイル無しでそのDLLを取り込みたいと思います。 CommonMPもDLL化した要素モデルライブラリをコンパイルなしに取り込む事が可能です。 その為の仕組みとしてFactory クラスが定義されています。 次の節では Factoryクラスについて勉強します。

 


(前頁)<<      >>(次頁)

初版:2013/06/12 |