1

有限要素コードを実装しています。

問題の説明

有限要素法では、積分器と補間器が必要です。積分器は、四角形、三角形などの幾何学的オブジェクトで数値積分を実行するオブジェクトです。積分器は、幾何学的オブジェクト内に複数の積分点または横座標を配置し、補間器を使用して次の値を近似します。これらの統合点での関数。

たとえば、四角形のオブジェクトは、4 つの積分点を使用する積分器を使用できます。

* ------------- *
|               |
|    @     @    |
|               |
|               |
|    @     @    |
|               |
* ------------- *

ここで、@ は積分点の位置を表します。補間器は、* で表されるコーナー ノードの値を使用して、これらの積分点での関数の値を概算します。@ の各値は、すべての * の値の一種の平均であると考えることができます。

ポリモーフィズムの概要

次の図は、便宜上、この質問で使用されるさまざまなクラス間の接続を示しています。

               Interpolator
                    |
                    | is a base for:
                    v
          Interpolator_Template
                    |
                    | is a base for:
                    |
    ------------------------------------------
    |                              |         |
    |                              |    More shapes
    V                              V
Interpolator_Quad         Interpolator_Tria
    |    |                         |
    |   ... More QUADs         More TRIAs
    v
Interpolator_Quad_04


               Integrator
                    |
                    | has member variable
                    v
         vector<Abscissa*> abscissa
                    |
                    | Abscissa is a base class for:
                    |
    --------------------------------
    |               |              |
    |          More shapes         |
    V                              V
Abscissa_Quad               Abscissar_Tria

各幾何学的形状には異なる座標系があるため、積分器と横座標は次のようになります。

Integrator.hpp

class Integrator {

public:

    void
    integrate();

private:

    /**
     * An interpolator class.
     */
    Interpolator * _interpolator;

    /**
     * List of abscissae (for the quadrilateral shown above, _abscissae.size() == 4).
     */
    std::vector< Abscissa * > _abscissae;
};

すべての自然座標横軸の基底クラス。

横座標.hpp

class Abscissa {

};

四角形の横座標は、ξ と η の自然座標に作用します。

Abscissa_Quad.hpp

class Abscissa_Quad final : public Abscissa {

public:

    const double xi;

    const double eta;
};

三角形の横座標は、ζ1、ζ2、および ζ3 自然座標に作用します。

Abscissa_Tria.hpp

class Abscissa_Tria final : public Abscissa {

public:

    const double zeta_1;

    const double zeta_2;

    const double zeta_3;
};

インテグレーターの実装は、次のように統合されます。

Integrator.cpp

void
Integrator::integrate()
{
    for ( Abscissa * abscissa : _abscissae ) {
        _intepolator->eval_det_J( abscissa );
    }
}

ここまでは順調ですね。私の補間クラスをお見せしましょう。

Interpolator.hpp

class Interpolator {

public:

    /**
     * Evaluate the determinant of the Jacobian (important for numerical integration).
     *
     * @note Common interface for all abscissa types.
     *
     * @param[in] abscissa Integration abscissa.
     * @return Shape functions.
     */
    virtual double
    eval_det_J(
            const Abscissa * abscissa ) const = 0;
};

補間器から、すべての幾何学的形状のクラスを派生させます。Interpolator_Template クラスをベースとして使用していることにお気付きかもしれません。詳細は後ほど説明しますので、今は無視してください。このクラスには、すべての四角形に共通の関数が含まれています。

Interpolator_Quad.hpp

class Interpolator_Quad : public Interpolator_Template< Abscissa_Quad > {

public:

    // ... More functions common to all quadrilaterals.
};

この派生クラスは、この質問の最初に描かれた四角形に対応します。これが導出される理由は、より多くの補間ノードを持つ四角形が存在する可能性があるためです。このクラスは QUAD_04 要素 (4 つの補間ノードを持つ四角形) を実装しますが、有限要素には QUAD_08、QUAD_09 などもあります。

Interpolator_Quad_04.hpp

class Interpolator_Quad_04 final : public Interpolator_Quad {

public:

    double
    eval_det_J(
            const Abscissa_Quad * abscissa ) const;
};

Interpolator_Quad_04.cpp

double
Interpolator_Quad_04::eval_det_J(
    const Abscissa_Quad * abscissa ) const
{
    // Important! Get coordinates from an Abscissa_Quad object.
    const double xi = abscissa.xi;
    const double eta = abscissa.eta;

    double det_J = ...
    // ... Perform some computations and return the determinant of the Jacobian.

    return det_J;
}

前に説明し損ねた Interpolator_Template クラスに戻りましょう。コードのある時点で、Abscissa * から Abscissa_Quad * オブジェクトへのダウンキャストを実行します。テンプレート クラスを非仮想インターフェイス パターンと組み合わせて使用​​することで、これを実現しました。

Interpolator_Template.hpp

template< class Abscissa_Derived >
class Interpolator_Template : public Interpolator {

public:

    /**
     * Implements Interpolator::eval_det_J.
     */
    double
    eval_det_J(
            const Abscissa * abscissa ) const;

protected:

    /**
     * Implemented by Interpolator_Quad_04 in this example.
     */
    virtual double
    eval_det_J(
            const Abscissa_Derived * abscissa ) const = 0;

private:

    Abscissa_Derived *
    eval_abscissa(
            const Abscissa * abscissa ) const;
};

Interpolator_Template.cpp

template< class Abscissa_Derived >
double
Interpolator_Template< Abscissa_Derived >::eval_det_J(
        const Abscissa * abscissa ) const
{
    Abscissa_Derived * abscissa_type = this->eval_abscissa( abscissa );

    double det_J = this->eval_det_J( abscissa_type );

    return det_J;
}

template< class Abscissa_Derived >
Abscissa_Derived *
Interpolator_Template< Abscissa_Derived >::eval_abscissa(
        const Abscissa * abscissa ) const
{
    // Dynamic cast occurs here.
    // I will include some check later to check for nullptr.
    return dynamic_cast< Abscissa_Derived * >( abscissa )
}

このコードにはエラーが含まれているに違いありません。要点を理解するために必要だと思われるものだけをコピーして貼り付け、変更を加えなければならなかったからです。しかし、私の考えが正しく伝わることを願っています。

通常、ダウンキャスティングはコードのにおいがすることを知っているので、有限要素のすべての幾何学的形状の積分器と補間器の実装を始める前に、あなたの意見を聞きたいと思いました。

私の以前の試み

これは、私が実装した最後のデザイン パターンです。以下に、私が試みた他のデザインについて説明します。ただし、このセクションを読み飛ばしてもかまいません。

  • インターポレーターではなくインテグレーターが派生するダブル ディスパッチ デザイン パターン (具体的にはビジター パターン)。たとえば、Interpolator_Quad_04 ではなく Integrator_Quad_04 がありました。Integrator_Quad_04 には Abscissa_Quad がメンバー変数としてありました。これは、横座標が派生されなくなったためです。

    class Integrator_Quad_04 final : public Integrator {
    
    private:
    
        std::vector< Abscissa_Quad * > _abscissae;
    
    public:
    
        double
        eval_det_J(
            const std::size_t &  index,
            const Interpolator * interpolator ) const
        {
            // The interpolator acts as the visitor.
            interpolator->eval_det_J( _abscissa[index] );
        }
    }
    
    /// Abscissa_Quad is no longer derived from Abscissa.
    
    class Abscissa_Quad {
    
    public:
    
        const double xi;
    
        const double eta;
    };
    

    次に、インターポレーターはインテグレーター クラスのビジターになり、その _abscissae メンバー変数にアクセスします。この設計には従わないことにしました。これは、形状ではなく操作に基づいて補間器を実装する必要があったからです。

    class Interpolator {
    
        // ...
    
    };
    
    class Eval_Det_J : public Interpolator {
    
        double
        eval_det_J(
            const Abscissa_Quad * abscissa ) const;
    
        double
        eval_det_J(
            const Abscissa_Tria * abscissa ) const;
    };
    
  • 複数のディスパッチで何かをやってみましたが、すべての形状に必要な関数の数がかなり速く増えました。

  • ダブルディスパッチ+テンプレートの複数のバリエーション。

  • ここで使用している現在のデザインパターンを見つけました:

    オブジェクト指向設計問題、リスコフ置換原理

結論

コードから推測できるように、私は C++11 を使用してコンパイルしています。

積分器と補間器を単純に 1 つのクラスに結合しない理由を不思議に思われるかもしれません。その答えは、積分器が四辺形の部分領域で動作する可能性があるからです。たとえば、四角形の内部に架空の三角形を導入し、三角形の内部に積分点を配置することもできますが、それでも四角形の補間を使用して三角形の点内の解を近似します。もちろん、三角形と四角形の座標間のマッピングを実装する必要がありますが、それはまた別の機会にします。

ダウンキャストがこの問題の悪い解決策ではないと思うかどうか、または何かが足りないかどうかを知りたいです。おそらく、この問題を修正する設計パターンの知識がないか、ポリモーフィズム アーキテクチャが正しくない可能性があります。

フィードバックをお待ちしております。ありがとう!

4

1 に答える 1

0

コメントするには多すぎるため、これを回答として投稿しています。おそらく役立つでしょう。

Integratorテンプレートクラスを作成できます:

template<class Shape>
class Integrator {
     typedef typename Shape::AbscissaType AbscissaType;
public:
    void integrate() const {
        for (const AbscissaType& abscissa : _abscissae) {
            _intepolator->eval_det_J(abscissa);
        }
    }

    template<class OtherShape>
    Integrator<OtherShape> convert(/* maybe pass range of abscissae indices */) const {
        // Abscissae classes must have converting constructors like AbscissaTria(const AbscissaQuad&);            
        std::vector<typename OtherShape::AbscissaType> newAbscissae(_abscissae.begin(), _abscissae.end());
        // initialize resulting integrator with newAbscissae
    }

    Interpolator_Template<AbscissaType> _interpolator;
    std::vector<AbscissaType> _abscissae;
};

特定のインテグレータは、必要な場合は適切なベース テンプレートから継承されます。

class Integrator_Quad_04 : public Integrator<Quad> {
};

Interpolatorしたがって、基本クラスは必要なくeval_det_J、適切なタイプの横座標を受け入れる非仮想関数です。

template<class AbscissaType>
class Interpolator_Template {
public:
    double eval_det_J(const AbscissaType& abscissa) const;
}

横座標のタイプは形状に依存することを強調するためにここに追加Shapeしました。また、形状に依存する他のものもある場合があります。

struct Tria {
    typedef AbscissaTria AbscissaType;
    static const size_t NUM_EDGES = 3; // just example
};

struct Quad {
    typedef AbscissaQuad AbscissaType;
    static const size_t NUM_EDGES = 4; // just example
};

コードにはすでにそのようなクラスがあると思います。

また、ベースももう必要ないことに注意してくださいAbscissa。横座標のすべてのクラスは独立しています。

編集:横座標を別の型に変換する必要がある場合は、Integratorクラスに次の関数を実装できます。

template<class OtherShape>
Integrator<OtherShape> convert(/* maybe pass range of abscissae indices */) const {
}

EDIT 2:クラスconvertへのサンプル実装を追加しました。Integrator

于 2014-09-03T00:29:42.623 に答える