求解二阶偏微分方程c语言,科学网—求解偏微分方程开源有限元软件deal.II学习--Step 3 - 亓欣波的博文...-程序员宅基地

技术标签: 求解二阶偏微分方程c语言  

完整版见:qixinbo.info.

deal.II的程序结构

fem-4-3.jpeg

deal.II采用面向对象编程,其中包含了很多的Modules,各自实现不同的功能,并有机地结合起来。如上图所示。具体为:Triangulation

Triangulations是单元及其更低维度的边界的集合。triangulation存储了网格的几何和拓扑性质:单元怎样接触,它们的顶点在哪里。triangulation不知道将要在它上面使用的有限元的任何信息,甚至不知道它自己的单元的形状,它只知道在二维情形下有4条线段和4个顶点,三维下有6个面、12条线段和8个顶点。不过其他所有信息都定义在映射类mapping中,由该类将参考单元的坐标映射到真实单元的坐标上,通常采用线性映射。

当需要访问triangulation的性质和数据时,通过使用iterators迭代器对单元进行循环。

Finite Element

Finite element类用来描述定义在参考单元上的有限元空间的性质,比如在单元的顶点、线段或内部各有多少自由度,此外还给出节点上的形函数的值及其梯度。

Quadrature

跟Finite element相同,Quadrature也定义在单元上,用来描述参考单元上积分点的位置及其权重。

DoFHandler

DoFHandler对象是triangulations和finite elements的汇合点:finite element类描述了triangulation单元的点、线或内部需要多少自由度,而DoFHandler分配了这种空间,从而使得点、线或内部都有正确的数目,同时也给这些自由度统一编号。

也可以这样理解:triangulation和finite element描述了有限元空间的具体性质(有限元空间就是用来得到离散解的空间),DoFHandler则枚举了该空间的基本框架,使得我们可以用一系列有序的系数Uj来表示离散解uh(x)=∑jUjϕi(x)。

正如triangulation对象,DoFHandler也可以通过iterators迭代器对单元进行循环,从而得到具体的信息,比如单元的几何和拓扑信息(这些也可以由triangulation迭代获得,其实两个类是派生关系),以及当前单元上的自由度数目和数值。需要注意的是,跟triangulation一样,DoFHandler也不知道从参考单元到它上面的真实单元的映射,也不知道对应于它所管理的自由度的形函数。

Mapping

Mapping类就是建立从参考单元到triangulation的单元的映射,包括形函数、积分点和积分权重,同时提供了这种派生的梯度和Jacobian行列式。

FEValues

这一步就是真正地取出某个单元,计算它上面在积分点上的形函数值及其梯度。注意,有限元空间不是连续的,积分都是在特定积分点上计算。

Linear Systems

一旦知道了怎样使用FEValues在单个单元上计算形函数值及其梯度,同时知道怎样使用DoFHandler获得自由度的全局标识,那么就可以使用矩阵类和向量类来存储和管理矩阵和向量的元素,从而形成线性系统。

Linear Solvers

构建好线性系统后,就可以使用求解器来求解该系统。

Output

求解完毕后,就可以输出结果到可视化软件中进行后处理。源码解析头文件1

2#include

#include

这两个头文件分别处理triangulation和自由度。1#include

该文件用于生成网格。1

2

3#include

#include

#include

这三个文件用来对单元进行循环并获得单元上的信息。1#include

该文件包含拉格朗日插值的描述。1

2

3

4

5

6

7

8

9

10

11

12

13

14

15#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

以上头文件还需仔细理解。step3类

跟之前两个例子不同,这次将信息都封装在了一个类里面。1

2

3

4

5class Step3

{

public:

Step3 ();

voidrun();

这是类的public部分,包含一个构造函数和一个run函数,用来说明执行顺序。1

2

3

4

5

6private:

voidmake_grid();

voidsetup_system();

voidassemble_system();

voidsolve();

voidoutput_results()const;

这是类的private部分的成员函数,函数名说明了其要实现的功能。1

2

3

4

5

6

7

8Triangulation<2> triangulation;

FE_Q<2> fe;

DoFHandler<2> dof_handler;

SparsityPattern sparsity_pattern;

SparseMatrix system_matrix;

Vector solution;

Vector system_rhs;

};

还有成员变量,用来存储各种信息。

以下是step3类的各个成员函数的详解。1

2

3

4

5Step3::Step3 ()

:

fe (1),

dof_handler (triangulation)

{}

这是step3类的构造函数,它没有执行具体操作,只是调用了成员初始器对fe和dof_handler进行了初始化。fe是一个finite element对象,它接收1,1是多项式的次数,表明使用的是双线性插值的形函数。

之前的triangulation也被传递给了dof_handler。注意此时triangulation还没有具体建立网格,但dof_handler不介意,它只有在具体分配自由度时才关心网格。

下一步必须做的就是对计算域进行剖分,然后对每个顶点分配自由度。1

2

3

4

5

6

7

8void Step3::make_grid ()

{

GridGenerator::hyper_cube (triangulation, -1, 1);

triangulation.refine_global (5);

std::cout << "Number of active cells: "

<< triangulation.n_active_cells()

<< std::endl;

}

这个函数做的是第一步,即对计算域进行剖分,建立网格。计算域是[−1,1]x[−1,1]。

因为初次建立时只有一个单元,所以细化5次,形成1024个单元,这里用n_active_cells()验证一下个数。注意这里用的不是n_cells()函数,因为其还包涵父单元的概念。

下一步就是分配自由度:1

2

3

4

5

6void Step3::setup_system ()

{

dof_handler.distribute_dofs (fe);

std::cout << "Number of degrees of freedom: "

<< dof_handler.n_dofs()

<< std::endl;

这里使用distribute_dofs()函数,接收的是fe,因为fe是线性插值,所以自由度是每个顶点上有一个。用n_dofs()输出一下,显示是1089。这是因为我们有32x32个网格,那么对应是33x33个节点。

然后创建稀疏矩阵1

2

3

4DynamicSparsityPattern dsp(dof_handler.n_dofs());

DoFTools::make_sparsity_pattern (dof_handler, dsp);

sparsity_pattern.copy_from(dsp);

system_matrix.reinit (sparsity_pattern);

注意,SparsityPattern和SparseMatrix不同,前者只存储元素的位置,后者则存储具体的数值。

然后建立右端项向量和解向量:1

2

3solution.reinit (dof_handler.n_dofs());

system_rhs.reinit (dof_handler.n_dofs());

}

下一步就是计算线性系统中的矩阵中的元素以及右端项的元素,这是每个有限元程序的核心部分!

组装矩阵和向量通常的方法是对所有单元进行循环,然后在每个单元上进行积分运算,得到该单元对整体的贡献。需要注意的是此时需要知道在每个真实单元上形函数在积分点位置上的值,但是!!形函数和积分点都是仅仅定义在参考单元上的,因此这些东西基本没用。那么关键问题来了,就是怎样将数据从参考单元上映射到真实单元上。这个活是由Mapping类来完成的,更加智能的是通常不需要人为指定它怎么做,它自动按标准双线性映射来操作。

现在我们需要处理三类东西:有限元finite element、积分quadrature和映射mapping。这些概念太多了,这里有一个类FEValues来将三者有机地整合起来。给它传进去这三个东西,它就能告诉你在真实单元上的形函数的值和梯度。

那么现在就开干吧:1

2

3void Step3::assemble_system ()

{

QGauss<2> quadrature_formula(2);

先确定在单元上的一套积分规则,这里使用的是Gauss数值积分方法,每个方向上选两个积分点。这套积分规则满足现在的问题。然后就可以生成FEValues的对象了:1

2FEValues<2> fe_values (fe, quadrature_formula,

update_values | update_gradients | update_JxW_values);

第一个参数告诉它参考单元是谁,第二个参数告诉它积分点及其权重(实际是一个Qudrature对象),还有默认使用了双线性映射。最后告诉它需要在每个单元上计算什么,包括在积分点上的形函数值(为了计算右端项ϕi(xKq)f(xKq))、在积分点上的形函数梯度(为了计算矩阵元素∇ϕi(xKq)⋅∇ϕj(xKq))、Jacobian行列式。注意:这里都是积分点上的值。因为需要对单元进行循环,所有这些值需要更新,所以加上前缀update。这样显著地跟程序说明需要计算什么,就能加速运算,因为有的软件所有东西不管有用没用都一块计算,比如二阶导数、法向量等。注意最后用了按位或这一运算符,这在c语言中很常见,这里的意思就是我要计算谁“和”谁。1

2constunsignedint dofs_per_cell = fe.dofs_per_cell;

constunsignedint n_q_points = quadrature_formula.size();

然后定义两个“快捷名”来称呼常用的两个变量:每个单元上的自由度个数和积分点个数。

现在终于开始一个单元一个单元地组装整体刚度矩阵和向量了。一种方法是直接将结果写入整体矩阵中,但这样对于大型矩阵的运算通常是很慢的。所以,这里是先在当前单元上计算该单元的单元刚度矩阵,然后将它的贡献叠加到整体上。计算右端项向量时也是这样的。

首先先创建以上两种单元矩阵和单元向量:1

2FullMatrix cell_matrix (dofs_per_cell, dofs_per_cell);

Vector cell_rhs (dofs_per_cell);

当计算每个单元的贡献时,是对该单元上的自由度的局部标识进行循环,即从0到dofs_per_cell-1。

然而,当把结果传递给整体时,需要知道这些自由度的全局标识,这时需要一个临时的量来存储:1std::vector<:global_dof_index> local_dof_indices (dofs_per_cell);

来,现在真的要对单元进行循环:1

2

3

4

5DoFHandler<2>::active_cell_iterator

cell = dof_handler.begin_active(),

endc = dof_handler.end();

for (; cell!=endc; ++cell)

{

现在,我们整个人站在单元上,我们想要知道在积分点上的形函数的值及其梯度以及参考单元和真实单元之间变换的Jacobian行列式。因为这些值与每个单元的几何信息有关,所以必须在每个单元上都需要让FEValues重算一下这些东西:1fe_values.reinit (cell);

初始化单元的贡献为0,以便后面的赋值:1

2cell_matrix = 0;

cell_rhs = 0;

然后开始积分,对积分点进行循环:1

2for (unsignedint q_index=0; q_index

{

首先是对矩阵的组装:1

2

3

4

5for (unsignedint i=0; i

for (unsignedint j=0; j

cell_matrix(i,j) += (fe_values.shape_grad (i, q_index) *

fe_values.shape_grad (j, q_index) *

fe_values.JxW (q_index));

对于拉普拉斯方程,每个单元上的矩阵是形函数i和j的梯度的积分,程序中用quadrature代替integral,所以变成两个梯度的乘积乘以Jacobian行列式(这是为了从参考单元到真实单元)和权重。两个梯度是矢量,它们的乘积是一个点乘,是一个标量。

然后对右端项向量的组装:1

2

3

4

5for (unsignedint i=0; i

cell_rhs(i) += (fe_values.shape_value (i, q_index) *

1 *

fe_values.JxW (q_index));

}

这里的积分是形函数的值乘以f乘以JxW。

现在有了单元的矩阵,下一步就是把它组装到整体上。我们先得问问这个单元,它的某个局部标号的自由度的全局标识是多少:1cell->get_dof_indices (local_dof_indices);

后面就可以通过local_dof_indices[i]来获取全局标识。

然后再作循环叠加:1

2

3

4

5

6

7

8for (unsignedint i=0; i

for (unsignedint j=0; j

system_matrix.add (local_dof_indices[i],

local_dof_indices[j],

cell_matrix(i,j));

for (unsignedint i=0; i

system_rhs(local_dof_indices[i]) += cell_rhs(i);

}

这样,整个线性系统基本全做完了。But!还有一个重要的东西漏了:边界条件。事实上,如果该拉普拉斯方程不加上一个Dirichlet边界条件,它的解就不是惟一的,因为你可以在解上加一个任意的量。显然得解决这个问题:1

2

3

4

5std::map<:global_dof_index> boundary_values;

VectorTools::interpolate_boundary_values (dof_handler,

0,

ZeroFunction<2>(),

boundary_values);

这里使用的interpolate_boundary_values函数就是将函数值插入到特定位置的边界上,它需要的参数有:DoFHandler对象来获得边界上的自由度的全局标识;边界的一部分;边界上的值本身;输出对象。

”边界的一部分“意思是有时你只想在边界的一部分上赋予边界值,比如流体力学的入流和出流边界、变形体的固定部分等。这时可以对边界进行一部分一部分地标号,不同的标号施加不同的条件。默认条件下所有的边界标号都是0,这里也使用的是0,表明是对整个边界作用。描述边界值的函数这里用的是ZeroFunction,返回的是0,刚好适用现在的零边界条件。最后的输出对象boundary_values是一个列表,里面是成对的边界自由度全局标识及其对应的边界值。

知道上述信息后,再实际施加边界条件:1

2

3

4

5

6

7

8

9

10MatrixTools::apply_boundary_values (boundary_values,

system_matrix,

solution,

system_rhs);

}

完全建立好线性系统了,终于该求解了。这个问题的变量个数是1089,实际是很小的量,通常的问题一般是10万个变量这个量级。传统的求解线性代数的算法,如Gauss消去或LU分解,对于大型系统不适用,这里用的是共轭梯度算法,即CG算法。

```cpp

void Step3::solve ()

{

SolverControl solver_control(1000, 1e-12);

首先告诉CG算法何时停止计算,这里是创建一个SolverControl对象来控制:最多迭代1000步,精度为1e-12。通常这个精度值是迭代停止的判据。1SolverCG<> solver (solver_control);

然后创建一个CG的求解器。1

2

3solver.solve (system_matrix, solution, system_rhs,

PreconditionIdentity());

}

上面就是开始求解了。第四个参数是一个预条件子。求解完毕后,solution中就存储了解向量的离散值。

然后就是输出结果了:1

2

3void Step3::output_results () const

{

DataOut<2> data_out;

首先让它知道从哪里提取数据:1

2data_out.attach_dof_handler (dof_handler);

data_out.add_data_vector (solution, "solution");

知道了目标数据后,离后面的可输出数据还隔了一个“中间数据”,因为deal.II是前后端分离的,这个中间数据像是个缓冲层,得到它的语句为:1data_out.build_patches ();

然后再输出成最终的可被可视化软件读取的数据:1

2

3std::ofstream output("solution.gpl");

data_out.write_gnuplot (output);

}

上面一系列的成员函数通过run函数来按次序执行:1

2

3

4

5

6

7

8void Step3::run ()

{

make_grid ();

setup_system ();

assemble_system ();

solve ();

output_results ();

}

该程序的main函数为:1

2

3

4

5

6

7intmain()

{

deallog.depth_console (2);

Step3 laplace_problem;

laplace_problem.run ();

return0;

}

第一个语句是打印log信息到屏幕上,后面就是创建step3对象,然后执行。计算结果

输出到屏幕上的信息有:1

2

3

4Number of active cells: 1024

Number of degrees of freedom: 1089

DEAL:cg::Starting value 0.121094

DEAL:cg::Convergence step 48 value 5.33692e-13

即,说明了单元个数1024、自由度个数1089,CG算法的起始残差是0.12,经过47步迭代后满足精度要求。

图形结果为:

3-1.png

3-2.png

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39528843/article/details/117180095

智能推荐

攻防世界_难度8_happy_puzzle_攻防世界困难模式攻略图文-程序员宅基地

文章浏览阅读645次。这个肯定是末尾的IDAT了,因为IDAT必须要满了才会开始一下个IDAT,这个明显就是末尾的IDAT了。,对应下面的create_head()代码。,对应下面的create_tail()代码。不要考虑爆破,我已经试了一下,太多情况了。题目来源:UNCTF。_攻防世界困难模式攻略图文

达梦数据库的导出(备份)、导入_达梦数据库导入导出-程序员宅基地

文章浏览阅读2.9k次,点赞3次,收藏10次。偶尔会用到,记录、分享。1. 数据库导出1.1 切换到dmdba用户su - dmdba1.2 进入达梦数据库安装路径的bin目录,执行导库操作  导出语句:./dexp cwy_init/[email protected]:5236 file=cwy_init.dmp log=cwy_init_exp.log 注释:   cwy_init/init_123..._达梦数据库导入导出

js引入kindeditor富文本编辑器的使用_kindeditor.js-程序员宅基地

文章浏览阅读1.9k次。1. 在官网上下载KindEditor文件,可以删掉不需要要到的jsp,asp,asp.net和php文件夹。接着把文件夹放到项目文件目录下。2. 修改html文件,在页面引入js文件:<script type="text/javascript" src="./kindeditor/kindeditor-all.js"></script><script type="text/javascript" src="./kindeditor/lang/zh-CN.js"_kindeditor.js

STM32学习过程记录11——基于STM32G431CBU6硬件SPI+DMA的高效WS2812B控制方法-程序员宅基地

文章浏览阅读2.3k次,点赞6次,收藏14次。SPI的详情简介不必赘述。假设我们通过SPI发送0xAA,我们的数据线就会变为10101010,通过修改不同的内容,即可修改SPI中0和1的持续时间。比如0xF0即为前半周期为高电平,后半周期为低电平的状态。在SPI的通信模式中,CPHA配置会影响该实验,下图展示了不同采样位置的SPI时序图[1]。CPOL = 0,CPHA = 1:CLK空闲状态 = 低电平,数据在下降沿采样,并在上升沿移出CPOL = 0,CPHA = 0:CLK空闲状态 = 低电平,数据在上升沿采样,并在下降沿移出。_stm32g431cbu6

计算机网络-数据链路层_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输-程序员宅基地

文章浏览阅读1.2k次,点赞2次,收藏8次。数据链路层习题自测问题1.数据链路(即逻辑链路)与链路(即物理链路)有何区别?“电路接通了”与”数据链路接通了”的区别何在?2.数据链路层中的链路控制包括哪些功能?试讨论数据链路层做成可靠的链路层有哪些优点和缺点。3.网络适配器的作用是什么?网络适配器工作在哪一层?4.数据链路层的三个基本问题(帧定界、透明传输和差错检测)为什么都必须加以解决?5.如果在数据链路层不进行帧定界,会发生什么问题?6.PPP协议的主要特点是什么?为什么PPP不使用帧的编号?PPP适用于什么情况?为什么PPP协议不_接收方收到链路层数据后,使用crc检验后,余数为0,说明链路层的传输时可靠传输

软件测试工程师移民加拿大_无证移民,未受过软件工程师的教育(第1部分)-程序员宅基地

文章浏览阅读587次。软件测试工程师移民加拿大 无证移民,未受过软件工程师的教育(第1部分) (Undocumented Immigrant With No Education to Software Engineer(Part 1))Before I start, I want you to please bear with me on the way I write, I have very little gen...

随便推点

Thinkpad X250 secure boot failed 启动失败问题解决_安装完系统提示secureboot failure-程序员宅基地

文章浏览阅读304次。Thinkpad X250笔记本电脑,装的是FreeBSD,进入BIOS修改虚拟化配置(其后可能是误设置了安全开机),保存退出后系统无法启动,显示:secure boot failed ,把自己惊出一身冷汗,因为这台笔记本刚好还没开始做备份.....根据错误提示,到bios里面去找相关配置,在Security里面找到了Secure Boot选项,发现果然被设置为Enabled,将其修改为Disabled ,再开机,终于正常启动了。_安装完系统提示secureboot failure

C++如何做字符串分割(5种方法)_c++ 字符串分割-程序员宅基地

文章浏览阅读10w+次,点赞93次,收藏352次。1、用strtok函数进行字符串分割原型: char *strtok(char *str, const char *delim);功能:分解字符串为一组字符串。参数说明:str为要分解的字符串,delim为分隔符字符串。返回值:从str开头开始的一个个被分割的串。当没有被分割的串时则返回NULL。其它:strtok函数线程不安全,可以使用strtok_r替代。示例://借助strtok实现split#include <string.h>#include <stdio.h&_c++ 字符串分割

2013第四届蓝桥杯 C/C++本科A组 真题答案解析_2013年第四届c a组蓝桥杯省赛真题解答-程序员宅基地

文章浏览阅读2.3k次。1 .高斯日记 大数学家高斯有个好习惯:无论如何都要记日记。他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210后来人们知道,那个整数就是日期,它表示那一天是高斯出生后的第几天。这或许也是个好习惯,它时时刻刻提醒着主人:日子又过去一天,还有多少时光可以用于浪费呢?高斯出生于:1777年4月30日。在高斯发现的一个重要定理的日记_2013年第四届c a组蓝桥杯省赛真题解答

基于供需算法优化的核极限学习机(KELM)分类算法-程序员宅基地

文章浏览阅读851次,点赞17次,收藏22次。摘要:本文利用供需算法对核极限学习机(KELM)进行优化,并用于分类。

metasploitable2渗透测试_metasploitable2怎么进入-程序员宅基地

文章浏览阅读1.1k次。一、系统弱密码登录1、在kali上执行命令行telnet 192.168.26.1292、Login和password都输入msfadmin3、登录成功,进入系统4、测试如下:二、MySQL弱密码登录:1、在kali上执行mysql –h 192.168.26.129 –u root2、登录成功,进入MySQL系统3、测试效果:三、PostgreSQL弱密码登录1、在Kali上执行psql -h 192.168.26.129 –U post..._metasploitable2怎么进入

Python学习之路:从入门到精通的指南_python人工智能开发从入门到精通pdf-程序员宅基地

文章浏览阅读257次。本文将为初学者提供Python学习的详细指南,从Python的历史、基础语法和数据类型到面向对象编程、模块和库的使用。通过本文,您将能够掌握Python编程的核心概念,为今后的编程学习和实践打下坚实基础。_python人工智能开发从入门到精通pdf

推荐文章

热门文章

相关标签