我正在实现一个有限元代码。
问题描述
在有限元方法中,我们需要一个积分器和一个插值器。积分器是对几何对象进行数值积分的对象,例如四边形、三角形等。积分器在几何对象内部放置多个积分点或横坐标,然后使用插值器逼近这些集成点的功能。
例如,一个四边形对象可以使用一个使用 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; };
我尝试使用多次调度来做一些事情,但是所有形状所需的函数数量增长得非常快。
双调度+模板的多种变体。
我在这里找到了我正在使用的当前设计模式:
结论
正如您可能从代码中推断的那样,我正在使用 C++11 进行编译。
您可能想知道为什么我不简单地将积分器和插值器组合成一个类,答案是因为积分器可能在四边形的子域上操作。例如,我可以在四边形内引入一个虚构的三角形并将积分点放在三角形内,但我仍然会使用四边形插值来近似三角形点内的解。当然,我需要在三角形和四边形坐标之间实现一些映射,但这是另一天的问题。
我想知道你是否认为向下转换不是解决这个问题的好方法,或者我是否遗漏了一些东西。也许我不了解解决此问题的设计模式,或者我的多态架构不正确。
任何反馈表示赞赏。谢谢!