倭算数理研究所

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

観測量 ObservableSet

今回は物理量(観測可能量)。

物理量は前回見た物理系に対して、支配 状態変数の値を使って計算できる量です。 物理量は状態変数そのものでも構いません。 何らかのデータを出力したい場合は(大抵そうでしょうけど)、まずそのデータに対応する物理量を定義する必要があります。

物理量は物理系自体の特性として、それを表すクラス内にフィールドとして定義してもプログラムとしては問題ないことが多いですが、物理系の状態を指定する支配変数や物理系の性質を表すシミュレーション・パラメータと区別しておく方が、自分も他人もシミュレーションの全体像を把握しやすいでしょう。 また、状態更新のロジックとデータ計算のロジックを分離しておけるのも理解の把握を容易にすると思います。

ちなみに、Observable という用語はデザイン・パターンの Observer パターンとは関係なく(よって java.util.Observable クラスとも無関係)、量子力学観測可能量から拝借しました。 量子力学では系の状態を決めるのは波動関数ですが、波動関数自体は観測できず、粒子の位置や運動量のような測定できる量(物理量、観測可能量)は対応する演算子波動関数からその期待値が計算できるのみ、という枠組みになっています。 まぁもし可能なら、そのうち量子系のシミュレーションをやりたいと思います*1

ObservableSet インターフェース


では、もう少し具体的な話に入っていきましょう。 物理量を表すインターフェースは Observable ではなく ObservableSet とします。 これは物理量1つ1つに対してクラスを定義するのが面倒なので、まとめて計算できるようにするためです。 また、複数の物理量を一緒に計算した方がパフォーマンス(Groops ではあまり気にしませんが)が上がることがよくありそうだからでもあります。 ObservableSet インターフェースには、物理量に関する情報を取得するメソッドなどを定義しますが、最も重要なのは

  • 物理量を計算する対象の物理系に対するアクセッサ・メソッド
  • 観測量を計算するメソッド

でしょう。 ObservableSet インターフェースの定義はこんな感じ:

import java.util.List;
import java.util.Map;

public interface ObservableSet<PS extends PhysicalSystem>{

    /** 物理量を計算する対象の物理系に対するアクセッサ */
    PS getPhysicalSystem();
    void setPhysicalSystem(PhysicalSystem system);

    boolean isObserveAllStates();
    
    List<String> getDataEntries();
    Map<String, String> getDataEntryAliases();
    boolean supportDataEntry(String name);
    Class getDatatype(String name);

    /** 物理量を計算するメソッド */
    void observe(DataStore store)throws ObservationException;
}

物理系 PhysicalSystem に対するジェネリクスなんかも使ってます。 物理系に対するアクセッサ以外にもいくつか getter メソッドを定義してますが、あまり気にしなくて構いません。 ObservableSet のサブクラスを作成する際に、後で見る AnnotationObservableSet クラスや AnnotationObservable クラスを使えば実装する必要はありません。

物理量を計算する observe() メソッドの引数にある DataStore オブジェクトは、計算した物理量をセットするデータの入れ物です。 計算した物理量の値は忘れずこのオブジェクトにセットしましょう。 このオブジェクトは Map のようなメソッドを提供していて(Map インターフェースを実装してるわけではありませんが)、Groovy ではプロパティ・アクセスや [] 演算子(putAt() メソッド)を使って値のセットを行えます。 簡単な使い方は実装例の項参照。

実装例


では実装例。 物理量を計算する物理系は、前回作成した、ニュートンの冷却の法則に従う CoolSystem です。 ObservableSet を実装するクラスを作成するのに

  • AnnotationObservableSet クラス
  • AnnotationObservable クラス

を使用する例を1つずつ見ていきます。

AnnotationObservableSet クラスを使用した実装

まずは物理量「温度」を計算する ObservableSet のサブクラスを、AnnotationObserbableSet クラスを用いて作成してみましょう。 クラス名は Temperature とします。 物理量の計算をするメソッドは ObservableSet インターフェースのものと少しシグニチャが変わってますが、これは対象となる物理系にアクセスしやすくするためです。 observe() メソッド内では特に何らかの計算を行っているわけではありませんが、DataStore オブジェクトに値をセットしているのが胆です。

import org.waman.groops.simulation.DataStore;
import org.waman.groops.simulation.observable.*;

class Temperature extends AnnotationObservableSet<CoolSystem>{

    @DataEntry(Double.class)
    static final String TEMPERATURE = 'Temperature'

    @Override
    protected void observe(CoolSystem system, DataStore store){
        store[TEMPERATURE] = system.T
    }
}

DataStore に値をセットする際に指定しいるキー「TEMPERATURE」は public static final な String フィールドとして定義しています。 普通の Java クラスや Groovy クラスではこれは任意ですが(observe() メソッド内に String リテラルの形で用いても良い)、ObservableSet のサブクラスではこれは必須で、さらに @DataEntry アノテーションによって値の型を指定しないといけないようになってます。 少々面倒に感じるかもれませんが、ObservableSet インターフェースに宣言してある getter メソッドのデフォルト実装を提供するために必要なのでご了承を。 また複数の物理量を計算する場合は、それぞれの物理量に対して String フィールドを宣言してください。

AnnotationObservable クラスを使用した実装

次は AnnotationObservable クラスを用いた例。 時間「Time」を計算するサンプルを作成します。 この AnnotationObservable クラスは、1つの物理量を計算したい場合に使ってください。 実装が必要なのは

  • public static final な String 型のフィールド。 この値の文字列は物理量の値を取得するキーとなる
  • 物理量の値を取得するキーとなる String と物理量の値の型を表す Class オブジェクトを指定したコンストラクタ
  • 計算した物理量を返す observeData() メソッド

String フィールドの定義は AnnotationObservableSet クラスの場合と同様。 コンストラクタの第1引数にはこのフィールドを渡してください。 実装はこんな感じ:

import org.waman.groops.simulation.DataStore;
import org.waman.groops.simulation.observable.*;

class Time extends AnnotationObservable<CoolSystem>{

    @DataEntry(BigDecimal.class)
    static final String TIME = 'Time'

    Time(){
        super(TIME, BigDecimal.class)
    }

    @Override
    protected BigDecimal observeData(CoolSystem system){
        return system.time
    }
}

累積値を計算したい場合

ある物理量の分散などのモーメントを計算したい場合、シミュレーションを通して値を加えていくような累積値の計算が必要になることがあります(まともなシミュレーションには必須かな?)。 この場合は、クラスに @ObserveAllStates アノテーションを付加しておいてください。 なくてもうまくいく場合もありますが、保証はできません*2。 実際の実装はこんな感じにします:

import org.waman.groops.simulation.DataStore;
import org.waman.groops.simulation.observable.*;

@ObserveAllStates
class Temperature extends AbstractObservableSet<CoolSystem>{

    @DataEntry(Double.class)
    static final String TEMPERATURE = 'Temperature'

    @DataEntry(Double.class)
    static final String TEMPERATURE2 = 'Square Sum of Temperature'

    private double sum_T2 = 0d;

    @Override
    protected void observe(CoolSystem system, DataStore store){
        store[TEMPERATURE] = system.T

        this.sum_T2 += system.T * system.T
        store[TEMPERATURE2] = this.sum_T2
    }
}

追記


Groops 0.1-beta から少々互換性のない変更を加えました:

  • @DataEntry のパッケージを org.waman.groops.simulation.observable に変更
  • AbstractObservableSet クラス → AnnotationObservableSet クラス*3
  • AbstractObservable クラス → AnnotationObservable クラス*4

計算物理学入門

計算物理学入門

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

*1:そのように波動関数が収束するかどうかわかりませんが・・・

*2:物理量を定義しても、実際にその物理量が計算されるのはデータを出力する場合だけなので、出力タイミングの設定によっては累積が計算されない場合があります。

*3:パッケージは org.waman.groops.simulation.observable

*4:パッケージは org.waman.groops.simulation.observable