クラスを使用して数値アルゴリズムをカプセル化することに固有の利点と欠点は何ですか?


13

科学計算で使用される多くのアルゴリズムは、ソフトウェア工学のあまり数学集約型ではないと一般的に考えられているアルゴリズムとは異なる固有の構造を持っています。特に、個々の数学アルゴリズムは非常に複雑になる傾向があり、多くの場合、数百行または数千行のコードが含まれますが、状態は含まれません(つまり、複雑なデータ構造に作用しません)。インターフェイス-配列(または2つ)に作用する単一の関数へ。

このことは、クラスではなく、関数が科学計算で遭遇するほとんどのアルゴリズムへの自然なインターフェースであることを示唆しています。しかし、この議論は、複雑なマルチパートアルゴリズムの実装がどのように処理されるべきかについてほとんど洞察を提供しません。

従来のアプローチは、他の多くの関数を呼び出す1つの関数を使用して、関連する引数を途中で渡すだけでしたが、OOPはアルゴリズムをクラスとしてカプセル化できる別のアプローチを提供します。明確にするために、アルゴリズムをクラスにカプセル化するということは、アルゴリズム入力がクラスコンストラクターに入力され、パブリックメソッドが呼び出されて実際にアルゴリズムを呼び出すクラスを作成することを意味します。このようなC ++ psuedocodeでのマルチグリッドの実装は次のようになります。

class multigrid {
    private:
        x_, b_
        [grid structure]

        restrict(...)
        interpolate(...)
        relax(...)
    public:
        multigrid(x,b) : x_(x), b_(b) { }
        run()
}

multigrid::run() {
     [call restrict, interpolate, relax, etc.]
}

私の質問は次のとおりです。クラスを使用しない従来のアプローチと比較して、この種のプラクティスの利点と欠点は何ですか?拡張性や保守性の問題はありますか?明確にするために、私は意見を求めるつもりはありませんが、そのようなコーディング手法を採用することのダウンストリーム効果(つまり、コードベースが非常に大きくなるまで生じない効果)をよりよく理解するつもりです。


2
クラス名が名詞ではなく形容詞の場合、常に悪い兆候です。
デビッドケッチャソン

3
クラスは、複雑さを管理するために関数を整理するためのステートレス名前空間として機能しますが、クラスを提供する言語の複雑さを管理する他の方法があります。(C ++のネーム
スペース

@GeoffOxberryこれが良いか悪いかを話すことはできません-そもそも私が尋ねている理由です-しかし、クラスは、名前空間やモジュールとは異なり、グリッドの階層などの「一時的な状態」も管理できます。マルチグリッドでは、アルゴリズムの完了時に破棄されます。
ベン

回答:


13

15年間数値ソフトウェアを実行してきたので、次のことを明確に述べることができます。

  • カプセル化は重要です。データストレージスキームを公開しているため、データへのポインターを渡すことはお勧めしません。ストレージスキームを公開した場合、プログラム全体でデータにアクセスするため、ストレージスキームを再度変更することはできません。これを回避する唯一の方法は、データをクラスのプライベートメンバー変数にカプセル化し、メンバー関数のみがそれを操作できるようにすることです。あなたの質問を読むと、行列の固有値をステートレスとして計算し、引数として行列エントリへのポインタを取り、何らかの方法で固有値を返す関数を考えるでしょう。これは間違った考え方だと思います。私の見解では、この関数はクラスの「定数」メンバー関数である必要があります。マトリックスを変更するためではなく、データを操作するためです。

  • ほとんどのオブジェクト指向プログラミング言語では、プライベートメンバー関数を使用できます。これは、1つの大きなアルゴリズムを小さなアルゴリズムに分解する方法です。たとえば、固有値の計算に必要なさまざまなヘルパー関数はまだマトリックスで動作するため、当然ながらマトリックスクラスのプライベートメンバー関数になります。

  • 他の多くのソフトウェアシステムと比較して、クラスの階層は、たとえばグラフィカルユーザーインターフェイスの場合ほど重要でないことが多いのは事実です。数値ソフトウェアには確かに目立つ場所があります-Jedはこのスレッドに対する別の答え、つまり行列(または、より一般的には、有限次元ベクトル空間での線形演算子)を表すことができる多くの方法の概要を説明しています。PETScはこれを非常に一貫して行い、行列に作用するすべての操作の仮想関数を使用します(「仮想関数」とは呼びませんが、それはそうです)。典型的な有限要素コードには、OOソフトウェアのこの設計原理を使用する他の領域があります。思い浮かぶのは、多くの種類の求積公式と多くの種類の有限要素です。これらはすべて、当然1つのインターフェイス/多くの実装として表されます。物質法の説明もこのグループに分類されます。しかし、それはそれについてであり、有限要素コードの残りの部分は、たとえばGUIで使用するほど広く継承を使用しないことは事実かもしれません。

これらの3つの点のみから、オブジェクト指向プログラミングは数値コードにも最も確実に適用可能であり、このスタイルの多くの利点を無視するのは愚かであることは明らかです。BLAS / LAPACKがこのパラダイムを使用していない(そして、MATLABによって公開されている通常のインターフェイスも使用していない)のは事実かもしれませんが、過去10年間に書かれたすべての成功した数値ソフトウェアは、実際には、オブジェクト指向。


16

カプセル化とデータの非表示は、科学計算の拡張可能なライブラリにとって非常に重要です。行列と線形ソルバーを2つの例として考えてください。ユーザーは、演算子が線形であることを知る必要がありますが、スパース性、カーネル、階層表現、テンソル積、またはSchur補数などの内部構造を持つ場合があります。すべての場合において、クリロフの方法はオペレーターの詳細に依存せず、ただのアクションに依存しますMatMult関数(およびおそらくその随伴関数)ます。同様に、線形ソルバーインターフェイス(非線形ソルバーなど)のユーザーは、線形問題が解決されることのみを考慮し、使用されるアルゴリズムを指定する必要はありません。実際、そのようなことを指定すると、非線形ソルバー(または他の外部インターフェイス)の機能が妨げられます。

インターフェイスは良いです。実装に依存するのは悪いことです。C ++クラス、Cオブジェクト、Haskell型クラス、またはその他の言語機能を使用してこれを達成するかどうかは重要ではありません。インターフェイスの機能、堅牢性、および拡張性は、科学図書館で重要なことです。


8

クラスは、コードの構造が階層構造の場合にのみ使用する必要があります。アルゴリズムについて言及しているため、それらの自然な構造はオブジェクトの階層ではなく、フローチャートです。

OpenFOAMの場合、アルゴリズム部分は、さまざまなタイプの数値スキームを使用して、さまざまなタイプのテンソルで動作する基本的に抽象的な関数である一般的な演算子(div、grad、curlなど)で実装されます。コードのこの部分は、基本的にクラスで動作する多くの汎用アルゴリズムから構築されています。これにより、クライアントは次のように記述できます。

solve(ddt(U) + div(phi, U)  == rho*g + ...);

輸送モデル、乱流モデル、差分スキーム、勾配スキーム、境界条件などの階層は、C ++クラスの観点から実装されます(ここでも、テンソル量の総称)。

さまざまなアルゴリズムが幾何学的情報とバンドルされた関数オブジェクトのグループとしてまとめられて幾何学的カーネル(クラス)を形成するCGALライブラリで同様の構造に気づきましたが、これは幾何学から操作を分離するために再び行われます(面、ポイントデータ型から)。

階層構造==>クラス

手続き型フローチャート==>アルゴリズム


5

これが古い質問であっても、ジュリアの特定の解決策に言及する価値があると思います。この言語が行うことは「クラスレスOOP」です。主な構成要素は型、つまり、struct継承関係が定義されるCのsに似た複合データオブジェクトです。型には「メンバー関数」はありませんが、各関数には型シグネチャがあり、サブタイプを受け入れます。たとえば、抽象Matrix型とサブタイプDenseMatrix、を持つことができ、特殊化されSparseMatrixた汎用メソッドdo_something(a::Matrix, b::Matrix)を持つことができますdo_something(a::SparseMatrix, b::SparseMatrix)複数のディスパッチを使用して、呼び出すのに最も適切なバージョンを選択します。

この方法は、クラスベースのOOPよりも強力です。クラスベースのOOPは、「メソッドはthis最初のパラメーターとしての関数である」という慣習を採用する場合にのみ、最初の引数の継承に基づくディスパッチと同等です(Pythonで一般的)。複数のディスパッチの形式は、C ++などでエミュレートできますが、かなりのゆがみがあります

主な違いは、メソッドはクラスに属していませんが、別々のエンティティとして存在し、すべてのパラメーターで継承が発生する可能性があることです。

いくつかの参照:

http://docs.julialang.org/en/release-0.4/manual/methods/

http://assoc.tumblr.com/post/71454527084/cool-things-you-can-do-in-julia

https://thenewphalls.wordpress.com/2014/03/06/understanding-object-oriented-programming-in-julia-inheritance-part-2/


1

オブジェクト指向アプローチの2つの利点は次のとおりです。

  • 異なる計算結果が必要な場合と必要ない場合がある長い計算。たとえば、β 最終出力ですが、中間結果に依存します αcalculate_alpha()キャッシュするメソッドを持つことができますαインスタンス内の結果。次に、あなたが呼ぶときcalculate_beta()、それはまた、呼び出していないcalculate_alpha()場合は何もα 結果はまだキャッシュされています。

  • 複数の入力がある計算。1つの入力が変更された場合、計算全体を必ずしも実行する必要はありません。たとえば、calculate_f()メソッドはfバツyz。その後、別の値の計算をやり直すことにした場合z、あなたが呼び出すことができますset_z()し、zパラメータは内部的に「ダーティ」としてマークされているため、calculate_f()再度呼び出すと、計算に依存する部分のみがz やり直しです。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.