《OpenFOAM 源码深度剖析》系列-第 5 章 数值基元、维度系统与 `Field` 体系

第 5 章 数值基元、维度系统与 Field 体系

第 3、4 章讲的是"运行时框架"(字典、注册表、RTS)。从本章起进入数值底座 :OpenFOAM 如何表示一个标量、一个向量、一个带物理维度的量,以及一整片场(field)。这一层的设计决定了整个框架数值计算的表达力与性能。核心是三块:基元(primitives) 、维度系统(dimensionSet) 、场体系(Field / DimensionedField / FieldField)。

5.1 基元:从 scalar 到张量

5.1.1 标量与整数

src/OpenFOAM/primitives/ 下定义了 OpenFOAM 的"数值原子":

  • scalar:浮点标量。由 WM_PRECISION_OPTION 决定是 double(DP,默认)还是 float(SP)。这正是编译宏 -DWM_DP / -DWM_SP(见 wmake/rules/General/general 第 10--11 行)控制的东西。
  • label:整数索引类型。由 WM_LABEL_SIZE 决定是 int(32 位)还是 long(64 位)。网格规模超过 20 亿单元时才需要 64 位 label。
  • direction、uint、int32/int64 等辅助整型。

scalar 与 label 的关系是 OpenFOAM 性能与可移植性的关键:它们一个是物理量的类型,一个是索引/计数的类型,二者分离,使得"把精度从 double 换成 float"或"把索引从 32 位换成 64 位"都只需改环境变量重编,无需改代码。

5.1.2 向量空间类:CRTP 与 VectorSpace

OpenFOAM 的所有"多分量张量"都建立在一个统一的 CRTP(奇异递归模板)基类上。src/OpenFOAM/primitives/Vector/Vector.H(第 62--66 行)示范了这一模式:

cpp 复制代码
template<class Cmpt>
class Vector
:
    public VectorSpace<Vector<Cmpt>, Cmpt, 3>
{
public:
    typedef Vector<label> labelType;
    static constexpr direction rank = 1;
    enum components { X, Y, Z };
    ...
};
  • 第一个模板参数是派生类自己 (Vector<Cmpt>),第二个是分量类型,第三个是分量个数(3)。
  • VectorSpace<Vec, Cmpt, N> 提供所有"可逆/可编译期循环"的通用运算:逐分量加减乘除、内积(&)、外积(^)、标量乘除、范数、cmpt() 访问、以及 rank/nComponents/cmptType 等编译期常量。

在此之上,OpenFOAM 定义了完整的张量家族:

类型 分量数 rank 说明
scalar 1 0 标量
vector(Vector<scalar>) 3 1 向量
tensor(Tensor<scalar>) 9 2 二阶张量
symmTensor 6 2 对称二阶张量(存 xx,yy,zz,xy,xz,yz)
sphericalTensor 1 2 球张量(仅一个分量,表示 λI)
diagTensor 3 2 对角张量
symmTensor4thOrder 6 4 四阶对称张量(某些固体/各向异性场景)

注意 point 只是 Vector<scalar> 的一个 typedef(Vector 的 centre() 成员是为了让 point 能在 shape 体系里与其它几何形状一致)。张量的具体分量语义(tensor 的 9 个分量 xx,xy,xz,yx,...)在 Tensor.H 中定义。

5.1.3 pTraits:编译期特性查询

src/OpenFOAM/primitives/traits/pTraits.H(第 28--37 行)说明 pTraits 的用途:

cpp 复制代码
Description
    A traits class, which is primarily used for primitives and vector-space.
    All primitives need a specialised version of this class.

pTraits<T> 为每个基元提供编译期信息:typeName、zero、one、rank、nComponents、cmptType 等。基础模板本身是"直接继承 Base"的透传实现(第 67--87 行),而对多分量类型有特化。

此外还有一套更"健壮"的探测模板(第 94--147 行):pTraits_rank、pTraits_cmptType、pTraits_nComponents,用 std::void_t 探测某类型是否定义了对应成员,缺省回退到 0/1。这让未知类型也能安全参与泛型代码。

pTraits 是 OpenFOAM 泛型编程的"胶水":Field<Type> 的 cmptType、表达式模板的分量类型推导、DimensionedField 的维度检查,全部依赖它。

5.2 维度系统:编译期无法做到,就运行期做

物理量必须带量纲。OpenFOAM 选择了运行期维度检查(而非 C++ 的编译期维度类型),换来更好的可扩展性与字典可读性。

5.2.1 七基本量

src/OpenFOAM/dimensionSet/dimensionSet.H(第 78--115 行):

cpp 复制代码
class dimensionSet
{
public:
    typedef FixedList<scalar,7> list_type;

    static constexpr int nDimensions = 7;

    enum dimensionType
    {
        MASS,               // kg
        LENGTH,             // m
        TIME,               // s
        TEMPERATURE,        // K
        MOLES,              // mol
        CURRENT,            // A
        LUMINOUS_INTENSITY  // cd
    };

    static const scalar smallExponent;

private:
    list_type exponents_;   // FixedList<scalar,7>
};

要点:

  1. 维度用 7 个浮点指数 表示(FixedList<scalar,7>)。为什么指数是 scalar 而不是 int?因为要对指数做分数幂运算(如 sqrt 会让指数减半),所以必须支持非整数。
  2. smallExponent 是一个容差,用于把"接近 0 的指数"舍入为 0,避免浮点误差累积。
  3. 维度检查函数是 dimensionSet::checkDims(dimensionSet.C 第 47--64 行):两个 dimensionSet 不一致时打印错误并终止。

这套设计的代价是维度错误在运行期才暴露(且往往要等到算到某行代码才报),收益是"任何合法的量纲组合都能自然表达,且字典里可读地写出量纲"。

5.2.2 预定义维度与单位换算

src/OpenFOAM/dimensionSet/dimensionSets.H(第 51--81 行)预定义了一大批常用维度:

cpp 复制代码
extern const dimensionSet dimless;          // 无量纲
extern const dimensionSet dimMass;          // kg
extern const dimensionSet dimLength;        // m
extern const dimensionSet dimTime;          // s
extern const dimensionSet dimTemperature;   // K
extern const dimensionSet dimMoles;         // mol
extern const dimensionSet dimCurrent;       // A
extern const dimensionSet dimLuminousIntensity; // cd

extern const dimensionSet dimArea;          // m^2
extern const dimensionSet dimVolume;        // m^3
extern const dimensionSet dimDensity;       // kg/m^3
extern const dimensionSet dimForce;         // N
extern const dimensionSet dimEnergy;        // J
extern const dimensionSet dimPower;         // W
extern const dimensionSet dimVelocity;      // m/s
extern const dimensionSet dimAcceleration;  // m/s^2
extern const dimensionSet dimPressure;      // Pa
extern const dimensionSet dimCompressibility;
extern const dimensionSet dimGasConstant;
extern const dimensionSet dimSpecificHeatCapacity;
extern const dimensionSet dimViscosity;         // 运动粘度 m^2/s
extern const dimensionSet dimDynamicViscosity;  // 动力粘度 Pa·s

还有 dimensionSets 类(第 89--100 行)负责单位换算 :内部用 PtrList<dimensionedScalar> units_ 与一个 scalarSquareMatrix conversion_(及其 LU 分解 conversionPivots_)在"用户单位"与"SI 单位"之间换算(如把 psi 转 Pa)。

5.2.3 dimensioned<Type>:带维度的量

src/OpenFOAM/dimensionedTypes/dimensionedType/dimensionedType.H(第 73--99 行):

cpp 复制代码
template<class Type>
class dimensioned
{
    // Private Data
        word name_;              // 变量名
        dimensionSet dimensions_; // 维度集
        Type value_;             // 值
    ...
};

dimensioned<Type> 就是"名字 + 维度 + 值"。常用的 dimensionedScalar、dimensionedVector、dimensionedTensor 都是它的 typedef。

其运算规则是:乘除时维度指数相加/相减,加减时维度必须一致 。加减的不一致由 checkDims 捕获。读取时(initialize/readEntry,第 90--117 行)支持如下语法:

cpp 复制代码
// [name] [dims] value
nu              1e-05;          // 名字 + 值(维度已知时省略)
nu  [0 2 -1 0 0 0 0]  1e-05;    // 名字 + 方括号维度 + 值

方括号里的 7 个数字正是 MASS LENGTH TIME TEMPERATURE MOLES CURRENT LUMINOUS_INTENSITY 的指数。这是 OpenFOAM 字典里到处都是 [0 2 -1 0 0 0 0] 的原因 ------它是运动粘度的量纲 m²/s。

5.3 Field:连续数值数组

5.3.1 类定位

src/OpenFOAM/fields/Fields/Field/Field.H(第 28--48 行):

cpp 复制代码
Class
    Foam::Field

Description
    Generic templated field type that is much like a Foam::List
    except that it is expected to hold numerical content
    (not generic elements such as strings etc).

    In addition to methods specific to numerical fields
    (e.g., clamp, min, max, transpose, ...), it also has a reference
    counter that is used by Foam::tmp handling.

两个关键点:

  1. Field<Type> 本质是一维连续数组 ("much like a List"),但专为数值内容设计:clamp、min、max、transpose、逐分量运算、内积/外积等。
  2. 它自带引用计数 (FieldBase : public refCount,第 85--87 行),供 tmp 使用。

5.3.2 类层次

scss 复制代码
refCount                         (引用计数基类)
  └── FieldBase                  (Field/SubField 的公共部分)
        └── Field<Type>          (拥有数据的字段)
              └── SubField<Type> (字段的切片视图, 不拥有数据)
  • Field<Type> : public FieldBase, public List<Type>(拥有连续内存)。
  • SubField<Type> 是 Field 的切片视图 (slicing):指向某个 Field 的一段连续区间,不拷贝数据、不拥有内存。这是 OpenFOAM 里"零拷贝切片"的实现。
  • FieldBase 还带一批静态开关:allowConstructFromLargerSize、unifiedGeometricField、localBoundaryConsistency_ 等(第 96--110 行),用于控制解析容错与边界一致性检查。

5.3.3 字段代数与 ops.H

Field<Type> 的运算通过 src/OpenFOAM/fields/Fields/ 下的一批模板文件提供,核心是 FieldFunctions/FieldFunctionsM 与 ops.H:

  • + - * /(字段间、字段与标量间)、+= 等复合赋值;
  • 逐分量与整体的 mag、sqr、sqrt、pow;
  • &(内积)、^(外积)、*(矩阵-向量、矩阵-矩阵);
  • min、max、clamp、average、sum、gSum(并行求和)等归约。

这些运算通过模板 + 表达式模板(第 6 章)实现,既保证类型安全(如"标量字段 × 向量字段 → 向量字段"),又避免临时对象。

5.4 DimensionedField:给场加上维度

src/OpenFOAM/fields/DimensionedFields/DimensionedField/DimensionedField.H 引入 DimensionedField<Type, GeoMesh>:它在 Field<Type> 之上增加维度集(dimensionSet)与网格/几何信息。

这就是"带维度的场"。它的下标是"网格实体"(网格单元、面、点),而维度是物理量纲。GeometricField(第 8 章)再在它之上叠加边界场,形成完整的"场"。

层次关系:

python 复制代码
Field<Type>                                     纯数值数组
  └── DimensionedField<Type, GeoMesh>            + 维度 + 网格
        └── GeometricField<Type, PatchField, GeoMesh>  + 边界场  (第 8 章)

5.5 FieldField:场的场

src/OpenFOAM/fields/FieldFields/FieldField/FieldField.H(第 28--82 行):

cpp 复制代码
Class
    Foam::FieldField

Description
    A field of fields is a PtrList of fields with reference counting.

template<template<class> class Field, class Type>
class FieldField
:
    public refCount,
    public PtrList<Field<Type>>
{
public:
    typedef typename pTraits<Type>::cmptType cmptType;
    ...
};

FieldField<Field, Type> 是"一堆同类场 ",实现为 PtrList<Field<Type>> + 引用计数。它的第一层模板参数是"用哪种场模板"(Field、SubField 等)。

最典型的用途是边界场 :GeometricField 的边界不是一个连续数组,而是"每个 patch 一个场",即 FieldField<Field, Type>。线性代数里,interfaceBouCoeffs、interfaceIntCoeffs 也都是 FieldField<Field, scalar>(回到第 4 章的 LduMatrix.H,你会看到求解器接口的参数正是这个类型)。

5.6 连续内存与"为什么快"

OpenFOAM 高性能的一个重要来源是:Field(乃至 GeometricField 的内部场)在内存上是连续的一维数组。这带来三个直接好处:

  1. 缓存友好 :遍历一个 Field 是顺序访问,预取器友好。
  2. 向量化友好 :-O2 下编译器能自动向量化逐分量循环。
  3. 可与 SubField 切片协作做零拷贝操作。

与之配套的是 start()/cdata()/data() 等直接指针访问接口,以及 contiguous.H(Vector 头文件就 #include 了它)中对"可视为连续内存"的编译期声明。正是"连续内存 + 表达式模板 + tmp 零拷贝"三者合力,才让 OpenFOAM 在纯 C++ 模板框架下仍有可观的数值性能。

5.7 小结:数值底座的三层封装

把本章浓缩为一句话:OpenFOAM 用「基元(标量/向量/张量)→ 维度(dimensionSet/dimensioned)→ 场(Field/DimensionedField/FieldField)」三层封装,把物理量的"值、量纲、空间分布"完整地类型化。

  • 基元层:CRTP + VectorSpace + pTraits,统一表达任意张量。
  • 维度层:FixedList<scalar,7> + checkDims,运行期严格量纲检查。
  • 场层:Field(连续数组 + 引用计数)→ DimensionedField(+维度+网格)→ FieldField(场的数组)。

下一章将深入这套封装背后真正的性能引擎:内存管理(tmp)与表达式模板 / fused 算子融合。


本章要点回顾

  • scalar/label 分别由 WM_PRECISION_OPTION/WM_LABEL_SIZE 决定,精度与索引解耦。
  • 所有多分量量(vector/tensor/symmTensor/sphericalTensor...)都建立在 CRTP 基类 VectorSpace 之上,编译期常量由 pTraits 提供。
  • 维度系统用 FixedList<scalar,7> 存 7 个基本量的指数,运行期 checkDims 严格校验;[0 2 -1 0 0 0 0] 就是 m²/s。
  • dimensioned<Type> = 名字 + 维度 + 值;dimensionedScalar/dimensionedVector 是常用别名。
  • 场体系三层:Field(连续数组 + 引用计数)→ DimensionedField(+维度+网格)→ FieldField(PtrList<Field>,用于边界场与接口系数)。
  • 连续内存 + SubField 零拷贝切片,是性能的基础。
相关推荐
鶴哥只手遮天1 小时前
《OpenFOAM 源码深度剖析》系列-第 3 章 字典系统、IO 体系与对象注册表
算法·开源
鶴哥只手遮天1 小时前
《OpenFOAM 源码深度剖析》系列-第 2 章 wmake:OpenFOAM 自研构建系统与编译配置体系
算法·开源
用户81849100742161 小时前
我把工位上的“AI牛马”养成了:一个技术人的 AiPy 实战手记
算法
Tisfy1 小时前
LeetCode 3727.最大交替平方和:排序 (双指针可选)
算法·leetcode·题解·贪心·数组·排序
鶴哥只手遮天1 小时前
《OpenFOAM 源码深度剖析》系列-第 9 章 有限体积离散框架:`fvMatrix`、`fvSchemes` 与 `fvSolution`
算法·开源
鶴哥只手遮天1 小时前
《OpenFOAM 源码深度剖析》系列-第 7 章 网格与拓扑体系:`polyMesh`、`primitiveMesh` 与 patch
算法·开源
鶴哥只手遮天1 小时前
《OpenFOAM 源码深度剖析》系列-第 10 章 离散算子与插值格式:`grad` / `div` / `laplacian` / `ddt` 的实现
算法·开源
CHEEVEN_QY1 小时前
热仿真在液冷板设计中的应用价值与局限性客观评估
人工智能·算法
鶴哥只手遮天1 小时前
《OpenFOAM 源码深度剖析》系列-第 6 章 内存管理与表达式模板:`tmp`、`autoPtr` 与 fused 算子融合
算法·开源