読者です 読者をやめる 読者になる 読者になる

倭算数理研究所

科学・数学・学習関連の記事を、「倭マン日記」とは別に書いていくのだ!

データ出力 DataOutputter

Groops

今回はデータ出力

データ出力は、前回の反復条件と同様に、よく使いそうなものは Groops 側でサポートします。 ただし、データを独自にビジュアライズしたい場合もあると思うので、自作のデータ出力もある程度簡単に作成が可能なようにできればいいなと思ってます。 まぁこれはおいおい考えることにしましょう。 データ出力に関しては、出力方法も大切ですが、それと同じくらい出力のタイミング、つまりどの時点で観測量を計算して出力するのかというのも気にかける必要があります。 Groops ではいくつかの出力タイミングを決め打ちでサポートしていて*1、シミュレーション構築時に DataOutputter のプロパティとして簡単に設定できるようにしています。 この設定方法(というかプロパティの設定方法)は次回にやる予定ですが、今回は決め打ちでサポートしている出力タイミングなどを見ていきます。

DataOutputter インターフェース


まずは DataOutputter インターフェースを見てみましょう。 独自のデータ出力を作成する場合でなければこのインターフェースの詳細を知っておく必要はありませんが、そんなに込み入った宣言でもないと思います:

public interface DataOutputter extends SimulationComponent{

    OutputTiming getOutputTiming();
    void setOutputTiming(OutputTiming timing);

    void output(DataStore dataStore) throws SimulationException;
}

スーパーインターフェースの SimulationComponent は IterationCondition インターフェースのときも出てきましたが、いくつかのフック・メソッドが定義されています:

  • onIterationStart() / onIterationEnd()
  • onEvolutionStart() / onEvolutionEnd()

実際には他にもいくつかメソッドが宣言されてますが、詳細はそのうちに。 outputTiming プロパティへのアクセッサメソッド(getOutputTiming() / setOutputTiming()) は出力のタイミングを設定するためのメソッドです。 出力タイミングについては後ほど。 最後はoutput() メソッド。 このメソッドにデータ出力の仕方を実装します。 引数の DataStore オブジェクトは観測量の値をストックしていているオブジェクトです。 DataStore クラスは今までも何度か出て来てますね。

出力のタイミング


では、出力タイミングの基本概念(ちょっと大仰な言い方かな?)を見ていきましょう。 前回見たように、プッシュ・シミュレートでは、イメージとして以下のような反復を行っているのでした:

IterationCondition ic = ...
DataStore store = ...

onIterationStart()
while(ic.continueIteration(store)){
    onEvolutionStart()
    // 状態更新
    onEvolutionEnd()
}
onIterationEnd()

onXxxxStart() / onXxxxEnd() メソッドは処理を柔軟にするためのフック・メソッドでした。 前回はこれらのメソッドは IterationCondition オブジェクトに対して呼び出していましたが、それを拡張して、登録されている全ての DataOutputter に対しても呼ばれるようにします。 で、これらのフック・メソッドの呼び出し位置でデータ出力も行われるようにします*2。 ただし、これら4つの位置を好き勝手に指定してデータ出力すると1つの状態が2度出力されることがあるので*3、これを防ぐために、以下のようによく使いそうな出力タイミングを Groops 側で定義して、それをユーザーが指定するようにします:

出力タイミング onIterationStart onEvolutionStart onEvolutionEnd onIterationEnd
ALL
FINAL
INITIAL_FINAL
AFTER_EVOLUTION
OBSERVE_ALL
OBSERVE_FINAL
OBSERVE_INITIAL_FINAL
NONE

これらは列挙型 OutputTiming に定義されています。 一応、それぞれの出力タイミングを説明しておくと

出力タイミング 説明
ALL 全ての状態を出力する
FINAL 終状態を出力する
INITIAL_FINAL 始状態、終状態を出力する
AFTER_EVOLUTION 状態を更新した後のみ出力する
OBSERVE_ALL 物理量の値によって反復条件を決めている場合に、全ての状態を出力する
OBSERVE_FINAL 物理量の値によって反復条件を決めている場合に、終状態を出力する
OBSERVE_FINAL 物理量の値によって反復条件を決めている場合に、始状態、終状態を出力する
NONE 出力しない

OBSERVE_XXXX を別途定義しているのは、状態を更新した後に反復条件が満たされなくなった場合は、その前の状態が終状態であるためです。 上記の表中の△は出力するタイミングとその時の物理系の状態がずれていることを示しています。 これ以上は実装の詳細なので省略。

サポート予定のデータ出力


では、Groops でサポート予定のデータ出力をいくつか見ていきましょう。 普通に考えて、データをチャート(グラフ)にして出力するのが一番必要そうな気がしますが、結構 GUI が絡んできてゴチャゴチャするので、そのうち別途 Griffon ででも実装します。 ってことで、サポート予定のデータ出力は以下のようなものを想定しています:

タイプ 説明
console 標準出力への出力
csv CSV (comma-separated value) 形式でファイルへ出力
ssv SSV (space-separated value) 形式でファイルへ出力
tsv TSV (tab-separated value) 形式でファイルへ出力
map java.util.Map オブジェクトへデータをセット
database データベースへ出力

以下で、それぞれについて設定できるプロパティを整理しておきます。 ただし、全ての DataOutputter に対して共通のプロパティ outputTiming がありますが、これについては省略しています。

標準出力への出力 : console

まずは標準出力へ出力する console。 設定できるプロパティはこんなの:

プロパティ デフォルト値 プロパティの説明
outputHeader boolean true ヘッダを出力するかどうか
delimiter String ' '(半角スペース) データの区切り文字列
lineSeparator String System.getProperty("line.separator") 行の区切り文字列
dataEntries List 登録されている物理量全て データとして出力する物理量のリスト
additionalWriters List<Writer> 空リスト 標準出力以外に付加的に出力したい Writer

ファイルへの出力 : csv, ssv, tsv

次は csv, ssv, tsv タイプの出力。 console と同じものもいくつか含まれています。

プロパティ デフォルト値 プロパティの説明
fileName String - 出力するファイルの名前
encoding String System.getProperty("file.encoding") ファイルのエンコーディング
overwrite boolean false ファイルが既に存在する場合に上書きするかどうか
outputHeader boolean true ヘッダを出力するかどうか
lineSeparator String System.getProperty("line.separator") 行の区切り文字列
dataEntries List 登録されている物理量全て データとして出力する物理量のリスト

Map オブジェクトへの出力 : map

観測データをオブジェクトとして取得し、別途プログラム中で処理したい場合などに map 出力を使用します。 Map オブジェクト自体はユーザー側がインスタンス化してセットする必要があります。

プロパティ デフォルト値 プロパティの説明
target Map<String, Object> - データをセットする Map オブジェクト
dataEntries List 登録されている物理量全て データとして出力する物理量のリスト
containsEntries boolean false dataEntries が指定されていないとき、出力するデータにエイリアス(別名)も含めるか

データベースへの出力 : database

最後はデータベースへの出力。 これはデータベースプログラミングの練習に実装したようなもので、本格的な使用は想定してません ^ ^;) パフォーマンスもさることながら、パスワードの扱いや SQL インジェクションのようなセキュリティ関連の事項には注意を払ってません。 「とりあえず動く」って程度のモノです。 一応、プロパティの一覧:

プロパティ デフォルト値 プロパティの説明
url String - データベースの URL
user String - データベースアクセスのためのユーザー名
password String - データベースアクセスのためのパスワード
driver String (Class) - ドライバクラス名
table String - テーブル名
createTable String - データを作成する SQL
dataEntries Map 登録されている物理量全て データとして出力する物理量の名前と型のマップ
overwrite boolean false 既にテーブルが存在する場合に上書き(というより削除して新規に作成)するかどうか

おそらくデータベース出力に関してはもう触れないので、先走っていくつかの使い方を載せておきます。 以下のようにデータベースにアクセスするとしましょう:

プロパティ 説明
url jdbc:hsqldb:mem:groops データベースの URL
user waman ユーザー名
password (空文字列) パスワード
driver org.hsqldb.jdbcDriver ドライバークラス
table test テーブル

これを踏まえて、データベースへ出力へ出力する DataOutputter の生成方法を見てみましょう。 以下のコードで SimulationBuilder はシミュレーションを構築するビルダーです(次回にでも真面目にやります)。 SimulationBuilder に対して database ノードによってデータベース出力を生成できます:

@Grab('org.hsqldb:hsqldb:2.0.0')

// import 文省略

new SimulationBuilder().simulation{

    // その1 : createTable ノードによってテーブルを作成するバージョン
    database(url:'jdbc:hsqldb:mem:groops', user:'waman', password:'', driver:'org.hsqldb.jdbcDriver'){
        createTable '''
            DROP TABLE test IF EXISTS;
            CREATE TABLE test (step INTEGER, time DOUBLE, T DOUBLE);
        '''
    }

    // その2 : dataEntries プロパティによってテーブルを作成するバージョン (1)
    database(url:'jdbc:hsqldb:mem:groops', user:'waman', password:'', driver:'org.hsqldb.jdbcDriver',
                 table:'test', overwrite:true, dataEntries:[step:'INTEGER', time:'DOUBLE', T:'DOUBLE'])

    // その3 : dataEntries プロパティによってテーブルを作成するバージョン (2)
    database(url:'jdbc:hsqldb:mem:groops', user:'waman', password:'', driver:'org.hsqldb.jdbcDriver',
                 table:'test', overwrite:true, dataEntries:[step:Types.INTEGER, time:Types.DOUBLE])

    // その4 : 登録されている観測量全てを観測するバージョン
    database(url:'jdbc:hsqldb:mem:groops', user:'waman', password:'', driver:'org.hsqldb.jdbcDriver',
                 table:'test', overwrite:true)
}

JDBC ドライバは別途インストール*4が必要です。 これにて反復シミュレーションを構成するコンポーネントは大雑把に見てきたので、次回にこれらのコンポーネントを組み立てて反復シミュレーションを構築する方法を見ていきます。

計算物理学入門

計算物理学入門

  • 作者: ハーベイゴールド,ジャントボチニク,Harvey Gould,Jan Tobochnik,鈴木増雄,石川正勝,溜渕継博,宮島佐介
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2000/12
  • メディア: 単行本
  • クリック: 5回
  • この商品を含むブログ (45件) を見る

*1:若干、融通が利かないかも。

*2:独自の DataOutputter クラスを作成する際にこれらの呼び出しを自分で実装する必要はありません。 データ出力は output() メソッドを実装するだけでいいようにしています。

*3:例えば onIterationStart() と onEvolutionStart() で出力を行うと、1番最初の状態が2度出力されてしまいます。

*4:Jar ファイルをクラスパス内に配置するだけです。