2. "Implementing QuantLib"の和訳
Chapter VIII The Finite-Difference Framework (有限差分法のフレームワーク)
8.1 The Old Framework (古いフレームワーク)
8.1.5 The FiniteDifferenceModel Class : 有限差分モデルクラス
FiniteDifferenceModelクラスの実装内容を下記 Listing 8.8 に示します。このクラスは、Evolution Scheme(時間軸の差分スキーム)を使って、将来の時点 t1 から手前の時点 t2 に向かって時間を遡り、その間、モデルに加えられた様々な条件をチェックし、また、特定の事象が発生する時点で、一旦立ち止まってその事象に対応する操作を行います。
前もって注意しておきますが、この章を書くために、プログラムコードを何年か振りに見直し、その結果いくつかのプログラムは、別の書き方があったかも知れない点に気づきました。但し、その別の方法が、必ずしもより良い方法という訳ではありませんが。何年も経っているので、当時なぜそのようなプログラムの書き方を選択したか、推察するしかありません。このクラスの説明をしていく中で、そのような別の書き方についても指摘し、それについての私の推察も述べます。
Listing 8.8 : FiniteDifferenceModelクラステンプレートのインターフェース
template<class Evolver>
class FiniteDifferenceModel {
public:
typedef typename Evolver::traits traits;
typedef typename traits::operator_type operator_type;
typedef typename traits::array_type array_type;
typedef typename traits::bc_set bc_set;
typedef typename traits::condition_type condition_type;
FiniteDifferenceModel(
const Evolver& evolver,
const std::vector<Time>& stoppingTimes =
std::vector<Time>());
FiniteDifferenceModel(
const operator_type& L,
const bc_set& bcs,
const std::vector<Time>& stoppingTimes =
std::vector<Time>());
const Evolver& evolver() const{ return evolver_; }
void rollback(array_type& a,
Time from,
Time to,
Size steps) {
rollbackImpl(a,from,to,steps, (const condition_type*) 0);
}
void rollback(array_type& a,
Time from,
Time to,
Size steps,
const condition_type& condition) {
rollbackImpl(a,from,to,steps,&condition);
}
private:
void rollbackImpl(array_type& a,
Time from,
Time to,
Size steps,
const condition_type* condition);
Evolver evolver_;
std::vector<Time> stoppingTimes_;
};
まず簡単に触れますが、このクラスは Evolution Scheme(時間軸方向の差分演算子)をテンプレート引数として取っており、traitsを使って、そこからいくつかのデータ型を取りだしています(プログラムコードでもそうですが、以後、Evolution Schemeを略してEvolverと呼びます)。traitsを使ったこの手法は、これまでの章で既に何度か見てきました。
まず最初に、コンストラクターの説明をします。コードを見ての通り、2つ用意されています。1つめは、当然予想される通り、引数として Evolution Scheme(Evolver)のインスタンスと Stopping Time(何等かの事象が発生する時点)の配列を取り、それぞれをメンバー変数に保存します。2つめは、Evolverの部品を引数として取り、自ら Evolverのインスタンスを生成します。
2つめのコンストラクターを用意した理由は、利便性の為かと思われます。すなわち、下記のような若干重複する書き方を避ける為でしょう。(訳注:ユーザーがこのクラスを使う場合、下記のコードのようにSomeSchemeを2回記述する必要がある。)
FiniteDifferenceModel<SomeScheme>(SomeScheme(L, bcs), ts);
しかし、その利便性が、このプログラムコードを維持管理していく手間に見合うものか疑わしいです。(ところで、このコンストラクターの書き方では、一部の Evolverでしか使えません。例えば、MixedSchemeベースクラスは、この2つの引数だけを取るコンストラクターを用意していません) この(2つ目の)コンストラクターは、QuantLibライブラリーで用意されている typedefsのひとつを使えば、もっと意味のあるものになるかも知れません。例えば StandardFiniteDifferenceModelです。この型は、使われている Evolution Schemeを隠しているからです。しかし、ユーザーは、どの Evolution Schemeを使うかは、いずれ知る必要があります。なぜなら、状態変数のグリッド間隔や時間軸のステップの間隔などは、Evolution Schemeに依存するからです。 (注:同じ理由から、StandardFiniteDifferenceModelの typedefは、それを隠す為にあるのではなく、どのスキームを使うかの参考として使うべきです。)
このクラスの中心となるロジックは、rollback( ) と rollbackImpl( )メソッドの中にコード化されています。rollback( )は、publicなインターフェースを提供しており、2種類のオーバーロードされたメソッドが用意されています。ひとつは、引数として、①rollback動作の初期値を格納する配列、②スタート時、③最終期日、および④使用されるステップ数、を取り、もう一方は、それに加えて、各時間の Nodeで使われる⑤StepConditionのインスタンスを引数として取ります。いずれも、関数の中で rollbackImpl( )を呼び出し、そのメソッドは Step Conditionインスタンスへのポインターを最後の引数として取ります。1つめのメソッドでは、そのポインターはNullとしており、2つめのメソッドでは Step Conditionインスタンスのアドレスを含んでいます。
さて、この方法(訳注:StepCondition引数として、Nullポインター用と、具体的なインスタンスの参照を渡す用と、2種類のオーバーロード関数を用意する方法)は、QuantLib Libraryでの通常の方法とは異なります。大半のケースでは、publicなインターフェースをひとつだけ用意し、Null値にデフォールト設定された shared_ptrを引数として取るようにします。思うに、上記の方法は、ユーザーが client codeを開発する際に、Step Conditionインスタンスを単純に stackメモリーに置き、それをメソッドに渡すだけで済む様にする為に、取られたのでしょう。そうでないと、別途shared pointerを生成するコードの記述が必要になります(それは即ち、その StepConditionインスタンスを他のメソッドとシェアするなら初めて意味がある事です。それを単に、メソッドに渡し、使い、使い終わったたら忘れてしまっていいのなら shared pointerを使う必要はありません)。 申し上げておきますが、privateなインターフェースにおいて(shared_ptrではない)生のポインターを使う事は、Library全体では基本的に Smart Pointerを使っている事とは矛盾しません。我々は、ここで生のポインター用のメモリー領域を取得しているのでは無く、Stackメモリー上に既に存在するオブジェクトのアドレスを渡しているだけです。Stackメモリー上なら、そのオブジェクトの生成消滅は、きちんと管理されているはずです。
インターフェースについて、最後の注意点を。我々は、Boundary Condition と Step Conditionを、異なった方法で取扱っています。前者はモデルクラスのコンストラクターに渡されますが、後者は rollback( )メソッドに渡されます。(別の相違点としては、我々は境界条件については明示的に指定された条件の集合を使いますが、Step Conditionはひとつのオブジェクトとして、その中に複数のタイプの条件を含めるように使っています。) なぜそのようにしたのか、考えられるひとつの理由は、time step毎に rollback( )を呼び出す際、別々の Step Conditionオブジェクトを使える様にする為かと思われます。しかし、同じ事は、(複数の条件を持つ)Step Condition Compositeの中でも出来るはずです。なぜなら、Step Conditionオブジェクトには、どの時点にいるかの情報が渡されているからです。(もし、境界条件とStep Conditionの異なった取扱いを)より汎用的な枠組みにしようとすれば、境界条件も rollback( )メソッドに渡すのがひとつの方法でしょう。なぜなら、今のままでは、rollback計算の最中に、特定の時点で境界条件を変更する事は不可能だからです。従って、汎用化するには Evolverのインターフェースも変更する必要があるでしょう。その場合、境界条件は、Evolverのコンストラクターにではなく、step( )メソッドに渡されるべきでしょう。
最後に、rollbackImpl( )メソッドの実装内容です。
Listing 8.9:FiniteDifferenceModel::rollbackImpl( )メソッドの実装内容
void FiniteDifferenceModel::rollbackImpl(
array_type& a,
Time from,
Time to,
Size steps,
const condition_type* condition) {
Time dt = (from-to)/steps, t = from;
evolver_.setStep(dt);
if (!stoppingTimes_.empty() && stoppingTimes_.back()==from) {
if (condition)
condition->applyTo(a,from);
}
for (Size i=0; i<steps; ++i, t -= dt) {
Time now = t, next = t-dt;
if (std::fabs(to-next) < std::sqrt(QL_EPSILON))
next = to;
bool hit = false;
for (Integer j = stoppingTimes_.size()-1; j >= 0 ; --j) {
if (next <= stoppingTimes_[j]
&& stoppingTimes_[j] < now) {
hit = true;
evolver_.setStep(now-stoppingTimes_[j]);
evolver_.step(a,now);
if (condition)
condition->applyTo(a,stoppingTimes_[j]);
now = stoppingTimes_[j];
}
}
if (hit) {
if (now > next) {
evolver_.setStep(now - next);
evolver_.step(a,now);
if (condition)
condition->applyTo(a,next);
}
evolver_.setStep(dt);
} else {
evolver_.step(a,now);
if (condition)
condition->applyTo(a, next);
}
}
}
ここでは、時間軸を将来の時間 from から手前の時間 to へ、引数で与えられた step数だけ遡っていきます。それらの値から、各stepの時間間隔 dt が計算でき、その値を Evolverに渡します。もし from の時点において、何等かの条件が発生している場合は、その時点における Step Conditionが適用されます (その条件が引数として渡された場合に限ります。その場合は、以降の step毎にずっと同じ事が繰り返されていきます)。そこから、step毎に時間を遡っていきます。各時点を t とし次の時点は t – dt となります。そこで注意しないといけないのは、最後まで遡った時点が実際に引数で渡された to と一致するかどうか確認します。その時、浮動小数点の計算により微妙なずれが発生する可能性があり、その場合は正しい値に修正します。
もし、Stopping Timeの条件をヒットする事象が無ければ、実装内容は極めてシンプルです。実際の所、loopの中のコードは最後のelse以下の{ }の中にある以下のたった三行だけで済むでしょう。
evolver_.step(a,now);
if (condition)
condition->applyTo(a, next);
すなわち、Evolverインスタンスに、(TimeStep=)nowにおける配列 a の値から1step先の(時間軸は戻る)値を計算させ、そこで (TimeStep=nextの) Step Conditionを適用するという操作を行います。実際に、now と next の間で Stopping Time条件 にヒットしていない場合は、この動作のみが行われ、制御変数である hit の値は false のままにしておきます。
仮に、あるstep間で Stopping Time条件がヒットする場合は、動作はやや複雑になります。次の図の上のラインを見てください。ここでは、from から to までの期間を等間隔で離散化した時点と、それらの時点の中間に Stopping-Time s1 と s2 が発生しているケースを示しています。
前セクションで説明したように、上の図において、今 \(t_7\) から \(t_6\) に stepが進んで(遡って)、そこからさらに \(t_5\) に進むとします。しかし、プログラムコードが、Stopping Timeである \(s_2\) が now と nextの間に存在する事をキャッチします。従って、制御変数である hit を trueにセットし、Evolverの使う時間間隔を、より狭い \(t_6-s_2\) 2にして、次のStepがStopping Timeと同じになるようにし、Evolverが計算を行います(もし Conditionがあれば、それを適用します)。もしひとつの Time Stepの間に2つ以上の Stopping Timeの事象があれば、プログラムコードは、Stepをその次の Stopping Timeまでとします。最後に、プログラムは if(hit) 文に進み、そこから通常の手順に戻ります。そこで残りのStep \(s_2-t_5\)の期間に対応する処理を行い、その後 Evolverは Stepの間隔を元の値に戻します。そこから通常の Step間の処理を \(t_4\) に向けて行います。
最後の注意点として、少しだけ Treeモデルに戻ってみましょう。Chapter 7において、Latticeオブジェクトの Time Gridは、Stopping Timeを含めて構築されていると述べました。その場合、Time Gridは図の下の段のようになるはずです。ご覧の通り、Stopping Timeである \(s_1\ と\ s_2\) は、離散時間のグリッドポイントに含まれており、そうする為に \(t_o\ から\ s_1\) までの時間間隔は少し狭く、\(s_2\ から\ from\) までの時間間隔は少し広くしています(注:この場合、時間間隔を変えても、最終的にステップ数は有限差分グリッド数と同じになります。但し常にそうなる訳では無く、Treeの Time Gridは、有限差分グリッド数より 2ステップ分多くなる事もあります)。この2つの方法(訳注:上記図の(a)と(b)の時間間隔の取り方)のいずれを使っても、互いに劣る点は無いと思いますが、それに不同意な専門家の方もおられるかもしれません。その場合は、修正する用意は出来ています。いずれにしても、有限差分モデルで、TreeモデルのTime Gridと同じ方法を取るのであれば、rollback( )を何回か(時間間隔が変わる度に)呼び出し、明示的に Stopping Timeから Stopping Timeへ進むように指示してやれば出来ると思います。
<ライセンス表示>
QuantLibのソースコードを使う場合は、ライセンス表示とDisclaimerの表示が義務付けられているので、添付します。 ライセンス