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

每个几何形状都有不同的坐标系,所以我的积分器和横坐标如下所示:

积分器.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;
};

集成器实现将像这样集成:

积分器.cpp

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

到目前为止,一切都很好。让我向您展示我的插值器类。

插值器.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 * 对象的向下转换。我通过将模板类与非虚拟接口模式结合使用来实现这一点。

插值器_模板.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;
};

插值器_模板.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 )
}

我确信这段代码包含错误,因为我只需要复制和粘贴我认为理解我的观点所必需的内容,以及执行修改。但是,我希望我的想法能够正确地通过。

我知道向下转换通常是一种代码味道,所以在我开始为有限元中的所有几何形状实现积分器和插值器之前,我想听听你的意见。

我之前的尝试

这是我实现的最后一个设计模式。我将在下面解释我尝试过的其他设计;但是,您可以跳过阅读本节。

  • 一种双重调度设计模式(特别是访问者模式),其中积分器是派生的而不是插值器。例如,我有一个 Integrator_Quad_04 而不是 Interpolator_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;
    };
    
  • 我尝试使用多次调度来做一些事情,但是所有形状所需的函数数量增长得非常快。

  • 双调度+模板的多种变体。

  • 我在这里找到了我正在使用的当前设计模式:

    面向对象的设计问题,Liskov 替换原则

结论

正如您可能从代码中推断的那样,我正在使用 C++11 进行编译。

您可能想知道为什么我不简单地将积分器和插值器组合成一个类,答案是因为积分器可能在四边形的子域上操作。例如,我可以在四边形内引入一个虚构的三角形并将积分点放在三角形内,但我仍然会使用四边形插值来近似三角形点内的解。当然,我需要在三角形和四边形坐标之间实现一些映射,但这是另一天的问题。

我想知道你是否认为向下转换不是解决这个问题的好方法,或者我是否遗漏了一些东西。也许我不了解解决此问题的设计模式,或者我的多态架构不正确。

任何反馈表示赞赏。谢谢!

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 {
}

convert编辑 2:添加到Integrator类的示例实现。

于 2014-09-03T00:29:42.623 回答