第2回目となる今回は、大量のデータを扱う構造解析プログラミング際に押さえておくと役に立つポイントや、
計算式を扱うプログラミング全般の特徴について説明します。さらに、アプリケーションに面内解析機能が付加できる
開発キット、「FRAME(面内)SDK」の活用方法についても紹介します。
|
構造解 析プログラミングで扱うデータ量は巨大になりがちなので、まとめて管理したほうがよい場合もあります。格点が大量にある場合などは、それぞれ「格点番号の
配列」「X座標の配列」「Y座標の配列」とするより、格点番号とX座標、Y座標の情報をまとめて1つの格点情報の配列とした方が扱いやすくなります。デー
タを個別に扱うと、たとえば「格点番号の配列は10番、X座標の配列は11番」というように異なったデータを取得するミスが発生する可能性もあるので、で
きるだけまとめたほうがよいでしょう。
このように1つにまとめられたデータを、レコード(構造体)と呼び、type節で定義を行います。また、レコード内の各変数のことをフィールドと呼びます。
TNODE = packed record
NodeNo : integer;
XPoint : double;
Point : double;
end; |
レコードの配列を定義するには、通常の配列と同様に以下のように定義します。
NodeData : array[1..100] of TNODE; |
配列の型の部分に定義したレコードの名前を指定します。データにアクセスするには「.」を使用します。
NodeData.NodeNo := 1;
NodeData.XPoint := 0.0;
NodeData.YPoint := 0.0 |
構造解析プログラミングでは、対象とする構造物の種類によって計算に必要となる格点数や部材数などが大きく変化し、データ量が著しく巨大になる場合もあ
ります。ここでは、そういった場合に重要となる、パソコンのメモリを確保する方法について説明します。メモリの確保方法には、大きく分けて静的確保と動的
確保の2つの方法があります。
静的確保 :あらかじめ決められたデータの数しか使用しない場合にメモリを確保する方法
動的確保 :プログラム実行時に必要なデータ数分だけメモリを確保する方法
プログラミングの難易度という点では、一般的に静的確保の方が容易な方法といえます。また、必要と考えられる最大格点数や部材数をあらかじめ想定して数値
を固定化し、計算を行うというのも1つの方法ですが、その場合は必要のない格点や部材の分までメモリを消費してしまう恐れもあります。
最近では記録メディアが以前より安価になり、大容量のメモリも購入しやすくなっているとはいえ、必要な分だけメモリを確保するというのはプログラミングにおける基本でもあります。
また実際には、データ作成段階まで最大値が決まらないという場合もしばしばです。
以下に、それぞれの方法について詳しく見ていきましょう
■静的確保の方法
これまで説明してきた配列の宣言に相当します。前ページの例と同様に格点情報のレコードの配列を静的確保すると、以下のようになります。
この記述例では、配列の添え字で1 〜 100までの100個の格点情報の配列を定義しています。
■動的確保の方法
使用するデータ数はプログラム実行時に決定され、プログラミングにおいて宣言を行う時点では確定していません。しかし、データを利用するためには宣言を行っておく必要があります。
この場合の宣言は、前述の格点情報宣言では以下のようになります。これまで説明してきた配列の宣言に相当します。前ページの例と同様に格点情報のレコードの配列を静的確保すると、以下のようになります。
まず、数を指定しない状態で宣言を行います。次に、数が確定した段階で配列の数分のメモリを確保します
SetLength( NodeData, 100 ); |
ここまでで100個の格点情報の配列を作成できました。静的確保とは異なり、配列の添え字は0〜99までとなっている点に注意しましょう。動的確保の別
の方法としては、ポインタを利用するものがあります。ポインタとは、メモリアドレスを指す変数のことです。プログラム実行時に、プログラムの領域とデータ
の領域がメモリ上に確保されて動作し、そのメモリ領域のメモリの位置(メモリアドレス)を指し示す変数がポインタとなります。ポインタを用いてメモリを動
的に確保するには、ポインタの型をtype節で定義する必要があります。
TNODE = packed record
NodeNo : integer;
XPoint : double;
YPoint : double;
end;
PNODE = ^TNODE; |
レコード型のポインタを定義するにはレコード型の定義にキャレット「^」を付けて定義します。このPNODE
がレコード型のポインタとなります。これを使用してメモリを確保するには、以下のように記述します
var
List : TList;
pNodeData : PNODE;
begin
List := TList.Create; // リストの作成
New( pNodeData ); // 格点データの作成
pNodeData^.NodeNo := 1; // 格点番号の代入
pNodeData^.XPoint := 0.0; // 格点X 座標の代入
pNodeData^.YPoint := 0.0; // 格点Y 座標の代入
List.Add( pNodeData ); // リストに追加
New( pNodeData );
pNodeData^.NodeNo := 2;
pNodeData^.XPoint := 5.0;
pNodeData^.YPoint := 0.0;
List.Add( pNodeData );
for i:=0 to 1 do
begin
Dispose(List[i]); // 格点データの解放
end; // リストの解放
List.Free;
end; |
ポインタ変数を定義し、New手続きでメモリを確保すると、メモリ領域にレコードのサイズ分の領域が確保されます。その後、ポインタ変数にキャレット
「^」を付けることで、レコードのフィールドを指定し、値の出し入れが行えます。Newで確保したメモリはプログラマ自身で解放する必要があり、そのため
にはDispose手続きを呼び出す必要があります。
■計算式の単位に注意する
これは構造解析だけに該当する話ではありませんが、計算を行うプログラミングの場合には、単位に細心の注意を払う習慣を身につけておくとよいでしょう。計
算式を処理する関数を書いたものの、その計算式に渡すデータの単位がバラバラである場合など、結果がとんでもない値になってしまうこともあります。また、
決められた単位で計算を行う式の場合は、計算前に単位変換した値を渡す必要があります。この場合、計算後の値を自分が使用したい単位に単位変換してから使
用するという流れになります。
■ゼロの値による計算式エラーを避ける
計算を実行するプログラムの場合、「ゼロ」という値は非常にやっかいなものとなります。割り算が存在する式においてゼロで除した場合は、エラーとなる
か、数値ではない値(NaN)となってしまいます。ゼロを必要としない計算式の場合には、誤ってゼロが入力されることのないよう、あらかじめ設定しておく
ようにすれば、このようなエラーが発生する危険性が低くなります。
■実数を整数と同様の扱いで比較しない
実数を扱う計算プログラムを作成する場合、整数と同様の考え方で扱うと正しく動作しないことがあります。以下のように、フォームに「整数判定」と「実数判定」の2つのボタンを配置して検証してみます。
まずは整数を用いた計算を行います。整数では以下のプログラムで期待通りの結果が得られます。
procedure TForm1.Button1Click(Sender: TObject);
var
i : integer;
sum : integer;
begin
sum := 0;
for i := 1 to 10 do
sum := sum + 1;
if sum=10 then
ShowMessage( ' 正解' )
else
ShowMessage( ' 不正解' );
end; |
実行すると、0.1を10回足し合わせた合計値がsumに入り、if文の判定でTrueとなりそうなところですが、実際には不正解のメッセージが表示さ
れます。これは、0.1という値が2進数を使用するコンピュータの世界では表現しきれず、正確な0.1ではない近似値で足し算が行われた結果生じる現象で
す。実生活で使用されている10進数の場合であっても、たとえば3分の1という数字は表現しきれないため、「0.333」のようにある程度の桁数以降を打
ち切った値でしか表現できません。この打ち切った値を3回足し合わせても0.999となり1とはならないのは明らかです。
では、上記のプログラムを動作させるにはどうしたらよいでしょう。それには、「sumと1.0との差の絶対値がイコールと認められる程度に小さければOK」とみなす方法があります。
if Abs(sum-1.0)<0.00001 then
ShowMessage( ' 正解' )
else
ShowMessage( ' 不正解' );
end; |
この他には、整数として計算を行い結果の段階で桁を戻すという方法もありますが、ここでは説明を省略します。
■計算誤差の種類について
コンピュータの計算では、先の例で取り上げた以外にも、計算結果に誤差が生じる場合があります。以下に、計算プログラムによる計算誤差の種類について紹介します。
丸め誤差
計算結果の値の桁数が多く表現しきれない場合、表現可能な値に丸める必要があります。この丸めた値と本来の値との誤差を「丸め誤差」と呼びます。
情報落ち
コンピュータの計算では有効桁数が限られるため、大きな値と小さな値を加減算した際に、小さな値が反映されない場合があります。このような現象を「情報落ち」と呼びます。
桁落ち
ほぼ同じ値の2つの値を減算した場合に、有効桁数が少なくなることを「桁落ち」呼びます。有効桁数5桁とした場合、0.12345と0.12344を減算
すると0.00001となり、結果は有効数字が1桁となります。このため、不足した桁数が自動的に0で埋められて誤差が発生します。
打切り誤差
コンピュータで計算結果を求めるために、計算を繰り返し実行することで解に近づく方法をとった場合、無限に計算を続けられないためある時点で打ち切る必要があります。この計算を打ち切ったことによって生じる正解値との誤差を「打切り誤差」と呼びます。
■FRAME面内SDKの活用
ここでは、アプリケーションに面内解析機能が付加できる開発キット、FRAME面内SDKの活用方法を紹介します。ファイルの入力から計算、結果の出力ま
で、本書の付録DVD-ROMに収録されたサンプルソースを用いて解説を行っていきます。まずは、サンプルを実際に活用する前に押さえておくべきポイント
について説明します。
FRAME面内SDKは、面内荷重解析機能を備えた任意形の平面骨組解析ソフト、FRAME面内(フォーラムエイト社製)から、計算部のみをライブラリ
化した開発キット(SDK:Software Development Kit)です。本書の読者の方が、新規または既存のアプリケーションにおいて、このライブラリを利用して面内解析機能を付加できるようになっています
■DLLの関数を呼び出すには
アプリケーションからDLL内部の関数を呼び出すには、その関数の「エントリポイント」にアクセスする必要があります。これはプログラムの実行を開始する場所(ポイント)のことで、DLLでは外部にエクスポート宣言された関数ごとにエントリポイントを持っています。
DLLの関数をインポートする方法には静的リンクと動的リンクの2種類があります。静的リンクではDLLをプログラムの実行開始時点でメモリに取り込むのに対し、動的リンクでは文字通り任意のタイミングでDLLをメモリ上に取り込む点で異なります。
動的リンクは、DLL機能を利用するときのみDLLをメモリ上に取り込むためメモリを節約することができますが、一方でインポートするまでにいくつかの
手続きを踏む必要があります。そのため、プログラム内で頻繁に使用するような機能であれば静的リンクとしてしまった方が、プロセスも1回で済むためアクセ
スもより高速になります。
■ポインタとは
ポインタとは、プログラム内の変数の値がメモリ上のどのアドレスにあるかという情報を保存しておくものです。つまり、変数の値が格納されている領域の先頭のアドレスを示すためのもので、変数のデータ自体を持っているわけではありません。
ポインタが指し示すアドレスの値を取得する場合には、ポインタから値をもらうように記述する必要があります。では、Delphiで実際にポインタを扱う際の記述方法について簡単な例を挙げてみます。
var
a : Integer;
b : Integer;
p : ^Integer; // ("PInteger" でも可)
begin
a := 200; // a に値を代入
p := @a; // p にa のアドレスを代入
b := p^; // b にポインタp のデータを代入
end; |
上記は、整数型のポインタ変数pに整数型変数aのアドレスを代入し、ポインタpが指すアドレスの実データを変数bに代入している記述例ですが、4行目の
ように型の前に「^」(キャレット)をつけることで、その型のポインタ型として定義することができます。7行目では、ポインタpに変数aのアドレスを代入
していますが、変数名の手前に「@」をつけることでその変数の格納先のアドレスを取得できます。
最後に8行目で、ポインタpの示すアドレスから実データを取得して変数bに代入していますが、ポインタから データを逆参照する場合には、ポインタ変数
の後ろに「^」をつけることでデータを参照できます。また、ポインタの逆参照に対して値を書き込むこともできます。その場合は「p^:=400;」のよう
に記述しますが、ポインタ変数pと同じアドレスを指す変数aの値が同時に書き変わります。変数bはポインタ変数pのデータ部のみ代入しており、アドレスは
異なるために影響されることなく、200のままになります【下図】。
このように、ポインタとはデータへのアクセス方法の1つとなるわけです。
先に「Delphiではポイントという言葉をあまり耳にしない」と書いたのは、Delphi側でこうしたポインタまわりの処理を内部的に行ってくれるた
め、プログラマがポインタを意識することなくアプリケーションの開発ができるようになっているためです。たとえば、上記の例ではaという変数を宣言した時
点で、メモリの領域とそのアドレスが自動的に割り当てられています。
このため、Delphiを習得する上でポインタの知識が必須とまでは言いませんが、ポインタを使用することでいくつかのメリットがあることも確かです。
まず、ポインタはアドレスだけを格納するものなので、基本的にはどんな型の変数のアドレスも格納できます。また、ポインタが指すアドレスは同じプロセス
内で有効なので、外部ルーチンにパラメータを渡す際にも、ポインタを渡すことで外部ルーチンからメモリ上の値を直接読み書きできるようになるため、階層が
深かったり内部でさらにポインタを持つような複雑な構造体を渡す際も、パラメータはポインタ1つ指定するだけですみます。
上記の例では変数単位でのポインタの使用を例として解説していますが、構造解析プログラミングの場合は、ポインタは構造体のデータの受け渡しに使われる
ことが多くなります。後に紹介する開発キット「FRAME面内SDK」の関数も、このポインタでのデータの受け渡しを行いますので、ぜひとも習得しておき
ましょう。
if SaveDialog1.Execute then
begin
if FileExists( SaveDialog1.FileName ) then
if MessageDlg('File aleady exists. Save now?', mtWarning,
[mbYes, mbNo], 0, mbYes) = mrYes then
begin
Memo1.Lines.SaveToFile( SaveDialog1.FileName );
end;
end; |
SaveDialog もOpenDialog と同様に、Filter プロパティを設定することができます。
上記の記述を登録すると、ダイアログで表示されるファイルの種類がテキスト形式に制限されます。なお、DefaultExt
プロパティに補完したい拡張子(今回であれば「txt」)を登録しておくと、自動的に補完されます。
同様の方法で、「上書き保存」「印刷」「アプリケーションの設定」「書式」メニューなどを実装していきます。詳細は、冒頭でも紹介した書籍『土木建築エンジニアのプログラミング入門』に掲載していますので、興味を持った方は手に取ってみてください。
次回の誌上セミナーは、「構造解析プログラミング講座(2)」になります。FRAME面内SDKを活用して構造解析でのソルバー利用を行う際の、サンプルプログラムの使用方法について解説する予定です。ぜひご期待ください。
有償セミナーのお知らせ
エンジニアのプログラミング入門セミナー CPD認定 |
● 日時 |
2011年4月15日(金) 9:30〜17:00 |
● 受講費 |
1名様 15,750円(税込) |
● 本会場 |
フォーラムエイト東京本社 GTタワーセミナールーム
※TV会議システムにて東京・大阪・名古屋・福岡にて同時開催 |
|
|
(Up&Coming '11 新春号掲載) |
|
|
>> 製品総合カタログ
>> プレミアム会員サービス
>> ファイナンシャルサポート
|