In OpenFOAM, the solvers and numerical models are separated. Most of the numerical models are located in the $FOAM_SRC
path, and being compiled to a dynamic library “.so” binary file, which can be loaded either by linking it during the solver compiling process or loading it by specify the “.so” file in the case controlDict. For example, we develop a new combustion model, named PaSR
, and compile it to a “libPaSR.so”. When we run a case, we just need to add a line libs ("libPaSR.so");
in controlDict
. Then, we can see and use this model in the reactingFoam
solver.
How does the “libPaSR.so” file is loaded, and why the solver knows that a new model “PaSR” is available? This blog will explain it.
Before digging into OpenFOAM, let’s see how does the dynamic library is loaded in C++. There are three famous function named dlopen
, dlsym
and dlclose
, which are used to open, find function address and close library, in your operating system.
I wrote a short code to demonstrate how does it work, see https://github.com/ZmengXu/dynamicLibloading/tree/master/ch01
First, we define two models WSR and PaSR (which are two simple combustion model) in two .C
files. Each file has only one simple function, named get_library_name
. Compiling these two .C
files to two libraries, libWSR.so
and libPaSR.so
.
1 | // In WSR.C |
1 | // In PaSR.C |
Second, we write a solver named solverFoam
to call get_library_name
function in libWSR.so
and libPaSR.so
.
1 |
|
In this code, we get the librarypath from the solver argument, and try to find the dynamic library, load the library to computer memory and get a handle libhandle using dlopen. We use dlsym to find the function _Z16get_library_namev
in this library and call this function to print its information.
you can use Allrun in https://github.com/ZmengXu/dynamicLibloading/blob/master/Allrun to compile and run it.
1 | solverFoam "libWSR.so" |
For example, typing the above commands after compiling the library and solver. It will find the “libWSR.so” in $LD_LIBRARY_PATH
and printing the following results.
1 | libWSR.so is found. |
Here, we have two things need to be explained:
typedef char* (*funcType)(void);
is an alias, define a function pointer type. This function has a return value of char*
and a void
argument. We call this kind of function as funcType
. Now you can understand that Line 32: funcType funcPtr = (funcType)voidPtr;
means that we are about to convert voidPtr
to a function pointer and assign it to a new funcType
type function pointer funcPtr
. Thus, it can be used as funcPtr()
in Line 34.
You must be curious that the function name we defined in PaSR.C
is “get_library_name”, but why we are looking for the function name “_Z16get_library_namev”. This is due to the name mangling (also called name decoration) in C++ compiler. We can use objdump -tT $FOAM_USER_LIBBIN/libPaSR.so
or nm -D $FOAM_USER_LIBBIN/libPaSR.so
to see the function list in the libPaSR.so
dynamic libray.
This is an example for nm -D $FOAM_USER_LIBBIN/libPaSR.so
result, you can find _Z16get_library_namev
in the optput.
1 | 0000000000201040 B __bss_start |
The name mangling is a common issue when you want to use Hybrid Programming in OpenFOAM, like “C” and “C++”, or “fortune” and “C++”. You can use the keyword extern C
to avoid the compiler converting function name. For example, we can change WSR.C to:
1 |
|
compile it and use nm -D $FOAM_USER_LIBBIN/libPaSR.so
you can get this. The function name is not decorated anymore.
1 | 0000000000201020 B __bss_start |
In OpenFOAM, the dynamic library are loaded and unloaded implicitly in the dlLibraryTable
class, see $FOAM_SRC/OpenFOAM/db/dynamicLibrary/dlLibraryTable
. I created a tutorial code to demonstrate how does it work, see https://github.com/ZmengXu/dynamicLibloading/tree/master/ch02
In the dlLibraryTable class, it has a constructor using a specific dictionary to look through all the libraries, load the libraries to computer memory and store the library names and pointers to libNames_ and libPtrs_ list. When the dlLibraryTable object is deleted, it will call the deconstructor function to unload these libraries.
1 | // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // |
In the solverFoam, we create a dictionary named controlDict, which will read a dictionary file controlDict, and providing the “libs” as the libsEntry. Then the libraies in the controlDict.libs will be loaded and unloaded automatically. This work is done in the Time
class in $FOAM_SRC/OpenFOAM/db/Time
and be called from #include "createTime.H"
by each CFD solver. This is the reason why the libraries we wrote in the “libs” of controlDict will be loaded automaticlly.
In terms of the libraries, a base class combustionModelBase and a derived class WSR in the combustionModel folder are compiled to libcombustModel.so file.
In the base class, a static HashTable pointer member WordConstructorTablePtr_ is created. A template class addWordConstructorToTable is defined to operate this pointer. In the addWordConstructorToTable constructor, the static HashTable pointer member WordConstructorTablePtr_ is created and insert a “debug value - typeName” pair into the table. Once the constructor is called, it will print “Try to insert typeName” in the constructor, if the typeName exist in the table, it will print “Duplicate entry typeName in runtime selection table”.
1 | //In combustionModel.H |
1 | template< class Type > |
WSR is inherited from the base class, so it also has the template class addWordConstructorToTable, in the WSR.C, an object addWSRWordConstructorTocombustionModelBaseTable_ is defined, which will call the constructor, and get the above output. We will see when the table is created. This is very important.
1 | //In WSR.C |
Apart from that, a static function “New” is defined in combustionModelNew.C to search the typeName from this table.
1 | // static Factory Method (selector) |
In the same way, PaSR is defined, the difference is that it is compiled separately to libPaSR.so file. The libcombustModel.so is written in the solverFoam’s Make/options and linked to the solverFoam during the compiling stage. while the libPaSR.so file is loaded when we run the solverFoam. You can use Allrun in https://github.com/ZmengXu/dynamicLibloading/blob/master/Allrun to compile and run it. Before the libPaSR.so is created, we can not find it in the $LD_LIBRARY_PATH
, thus only one model WSR
is available in the WordConstructorTable. After libPaSR.so is created, you will see the following outputs.
1 | Try to insert WSR into the WordConstructorTablePtr_ |
It is interesting that WSR is inserted into the WordConstructorTablePtr_ before entring main() function, PaSR is inserted into the WordConstructorTablePtr_ after the libPaSR.so is loaded. This is because that libcombust.so is loaded before the main() function, while libPaSR.so is loaded during the run time. Once the dynamic library is loaded, the objects addWSRWordConstructorTocombustionModelBaseTable_ and addPaSRWordConstructorTocombustionModelBaseTable_ are constructed automatically in the computer memory as global variables. If we load one library twice, the insert function will reject the inserting. This is why we see the Duplicate entry before the OpenFOAM output header.
]]>OpenFOAM最近发布了最新的两个版本OpenFOAM-8和OpenFOAMv2006, 这篇博客简单介绍一下这两个版本的区别,然后讲一下如何在Ubuntu和Windows系统下安装这两个版本的OpenFOAM。
OpenFOAM有不少分支,其中最常用的有两个版本: OpenFOAM基金会版和ESI集团版。
这个是OpenFOAM的分支图,OpenFOAM基金会版和ESI集团版在2016年开始分家了,有兴趣的朋友可以看一下OpenFOAM的发展史。
相比它们的发展史,其实我们更关心的是这两个版本功能上差别大不大。总地来说,两者主体内容差别不大,代码风格和计算效率相近。但是最近的几个版本差别开始变大了,有时候同一套代码在一个上编译没问题但在同期的另一个版本却编译不通。ESI版本更新得更激进一些,会有一些OpenFOAM基金会版没有的求解器和算例。
OpenFOAM最初是开发在Linux操作系统上的,在Linux上安装OpenFOAM也更方便一些。以Ubuntu为例,讲一下OpenFOAM的两种安装方式。
最快最方便,但需要联网,需要sudo权限
开机进入Ubuntu,快捷键Ctrl+Alt+T
(或者依次点击 应用, 附件, 终端)启动终端,复制下面四行,按照提示输入密码然后确认安装。
1 | sudo sh -c "wget -O - https://dl.openfoam.org/gpg.key | apt-key add -" |
上面四行做的事情分别是添加软件包密钥,添加软件仓库地址,更新软件仓库,下载并安装openfoam-8。OpenFOAM会被安装在/opt
目录下,默认paraview
也会一并安装。
参考https://openfoam.org/download/8-ubuntu/ 。
大约需要30分钟到6个小时,可以不联网也不用sudo权限
同样,先开机进入Ubuntu,快捷键Ctrl+Alt+T
(或者依次点击 应用, 附件, 终端)启动终端,依次复制粘贴下面的命令。
创建OpenFOAM安装目录,建议装在$HOME/OpenFOAM
下
1 | mkdir $HOME/OpenFOAM |
下载OpenFOAM和ThirdParty源码放在$HOME/OpenFOAM
目录下。有网的话直接在终端输入下面两行下载。
1 | wget -P $HOME/OpenFOAM https://sourceforge.net/projects/openfoam/files/v2006/OpenFOAM-v2006.tgz |
解压到安装目录
1 | tar -xzf OpenFOAM-v2006.tgz -C $HOME/OpenFOAM |
索引OpenFOAM
安装环境
1 | source ~/OpenFOAM/OpenFOAM-v2006/etc/bashrc |
进入$WM_PROJECT_DIR
目录并检查安装环境是否满足要求,ubuntu
新系统是满足的,如果不满足则需要补充安装相应的库。
1 | foam |
接下来正式编译OpenFOAM
,这个过程比较耗时,大约需要30分钟到6个小时
1 | ./Allwmake -s -l -k -j |
这里解释一下./Allwmake
后面的几个选项的含义,-l
是自动记录log文件, -s
是在编译过程中废话少说减少log文件大小,-k
是跳过编译错误即使编译出错也继续编译完不要停,-j
是并行编译可以减少编译时间,后面可以接数字指定编译核数,默认是用所有的核。
编译完以后我们再执行一遍./Allwmake
,这一次去掉-k
选项,查看编译过程是否出错(这一步操作非必须,可跳过)。
1 | ./Allwmake -s -l -j |
一切正常的话会有输出提示,告诉你编译已经完成,接下来测试OpenFOAM。
复制cavity
算例到$FOAM_RUN
目录,测试blockMesh
网格工具和icoFoam
求解器
1 | run |
参考https://www.openfoam.com/download/install-source.php
仅适用于Windows 10系统
windows 10 提供了适用于Linux子系统WSL (Windows Subsystem for Linux),你可以在这上面安装Ubuntu子系统,然后参考上一节在Ubuntu上安装OpenFOAM的方法apt或者编译安装OpenFOAM。
开启WSL
参考这个帖子https://zhuanlan.zhihu.com/p/34133795
在微软商店里中搜索Ubuntu
,安装最新版(目前是Ubuntu 20.04 LTS
)。
装好后快捷Win+R
打开系统自带的运行命令,输入bash
回车就进入Ubuntu
的终端了,接下来参考apt安装。
如果你是windows 7或者windows XP系统,也可以选择安装VMware虚拟机,具体参考李东岳的教程。他提供了VMware虚拟机文件,里面有Ubuntu系统和编译好的OpenFOAM,比较方便。
]]>Git
is the open source distributed version control system that facilitates GitHub activities on your laptop or desktop.sudo
rights.Github
is a platform for hosting and collaborating on Git repositories.1 | apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \ |
1 | conda install -c anaconda git |
Sets the name you want atached to your commit transactions
1 | git config --global user.name "[name]" |
Sets the email you want atached to your commit transactions
1 | git config --global user.email "[email address]" |
Enables helpful colorization of command line output
1 | git config --global color.ui auto |
Downloads a project and its entire version history from github
1 | git clone [url] |
Creates a new floder named [project-name]
and init it as a local repository
1 | git init [project-name] |
or init the current folder
1 | git init |
stage files (in the current folder) into the repository
1 | git add [file] |
Unstages the file, but preserve its contents
Like un-add one of the file
1 | git reset [file] |
Deletes the file from the working directory and stages the deletion
–cached is “Removes the file from version control but preserves the file locally”
1 | git rm [file] |
Changes the file name and prepares it for commit
1 | git mv [file-original] [file-renamed] |
record the staged files permanently in version history
DO COMMITS
1 | git commit -m "[descriptive message]" |
REDO COMMITS
Erase mistakes and craf replacement history
Undoes all commits afer [commit], preserving changes locally
1 | git reset [commit] |
Discards all history and changes back to the specified commit
1 | git reset --hard [commit] |
Browse and inspect the evolution of project files
List all of the files in the current repository
A text file named .gitignore suppresses accidental versioning of
files and paths matching the specified paterns
1 | git ls-files |
Lists all new or modified files to be commited
1 | git status |
Shows file differences not yet staged
It works only when you have add this file
but now you change it without adding again
exit the environment.
1 | git diff |
If it is staged (git add), you can not see
the difference anymore. But you can use this
command to see the file differences between staging and the last file version
1 | git diff --staged |
Shows content differences between two branches
1 | git diff [first-branch]...[second-branch] |
Lists version history for the current branch
List the log in breaf
Lists version history for a file, including renames
1 | git log |
Outputs metadata and content changes of the specified commit
1 | git show [commit] |
Name a series of commits and combine completed efforts
The master branch is created when you record (commit) the first change
Lists all local branches in the current repository
1 | git branch |
Jump to another branch, or creates a new branch and jump (if not exist)
1 | git checkout [branch-name] |
Combines the specified branch’s history into the current branch
1 | git merge [branch] |
Deletes the specified branch
1 | git branch -d [branch-name] |
Shelve and restore incomplete changes
Temporarily stores all modified tracked files
1 | git stash |
Restores the most recently stashed files
1 | git stash pop |
Lists all stashed changesets
1 | git stash list |
Discards the most recently stashed changeset
1 | git stash drop |
Register a repository bookmark and exchange version history
Downloads all history from the repository bookmark
1 | git fetch [bookmark] |
Combines bookmark’s branch into current local branch
1 | git merge [bookmark]/[branch] |
Uploads all local branch commits to GitHub
1 | git push [alias] [branch] |
Uploads a specific branch (dev) to GitHub, if there is no dev branch in GitHub, a new branch will be created
1 | git push origin dev |
Downloads bookmark history and incorporates changes
1 | git pull |
git
: an open source, distributed version-control systemGitHub
: a platform for hosting and collaborating on Git repositoriescommit
: a Git object, a snapshot of your entire repository compressed into a SHAbranch
: a lightweight movable pointer to a commitclone
: a local version of a repository, including all commits and branchesremote
: a common repository on GitHub that all team member use to exchange their changesfork
: a copy of a repository on GitHub owned by a different userpull request
: a place to compare and discuss the differences introduced on a branch with reviews, comments, integrated tests, and moreHEAD
: representing your current working directory, the HEAD pointer can be moved to different branches, tags, or commits when using git checkout
2019 年腾讯开源了自己的物联网(internet of thing, IoT) 操作系统 TencentOS tiny,我加入了他们的官方讨论群,关注了很久。最近疫情在家把TencentOS tiny移植到了自己的一块STM32F407的开发板上,移植还算顺利,这里记录一下过程。
腾讯官方移植指南没有具体型号MCU的介绍,网上我只找到了这篇博客 https://juejin.im/post/5da7289d51882553690284a3 介绍了STM32F103的MCU的移植。我自己的开发板STM32F407的,网上没有,有必要记录下来供参考。
我用的是这个STM32F407VET6 black board开发板,ARM Cortex-M4 内核,相同内核的开发板都可以参考。
先找到开发板的前后台系统工程模板,一般买开发板的时候学习资料里就有,也可以在我的github上下载,这是下载链接https://github.com/ZmengXu/STM32ProjectTemplate.git。
用Keil软件(也可以在linux或者mac上写个Makefile文件通过arm-none-eabi-gcc来编译,后面我会写个这样的教程)打开工程模板中USER/Template.uvprojx
文件, 我用的是Keil μVision5,打开后是这样的
F7
编译工程文件,如果下载的是我GitHub上提供的文件,编译是可以直接通过的,并且会自动新建一个OBJ
文件夹,并在该文件夹下生成Template.hex
的16进制文件。
接下来将Template.hex
文件写入STM32F407
开发板。我用的是ST-LINK V2 采用SW方式连接开发板,打开魔法棒Options for Target
按钮,选Debug
, 右侧Use
选项打开下拉菜单选ST-Link Debugger
,然后点击setting
,修改ort
选项为SW
,连接好开发板插入ST-LINK
,这时候右侧SW Device
就可以看到电脑找到了刚刚插入的设备。
回到桌面,点击Download
,刚刚的16进制文件Template.hex
就被写入开发板了。连接上串口,打开串口调试助手,按一下开发板上的复位,调试助手就可以收到这样的输入信息了。
去GitHub上https://github.com/Tencent/TencentOS-tiny或者去腾讯工蜂开源仓https://git.code.tencent.com/Tencent_Open_Source/TencentOS-tiny下载。我是在GitHub上下的,参考杰杰的建议后者会快些。
下载下来后有很多子目录,但我们只需要下面几个目录。
一级目录 | 二级目录 | 说明 |
---|---|---|
arch | arm/arm-v7m | TencentOS tiny适配的IP核架构(含M核中断、调度、tick相关代码) |
board | STM32F407VGT6_discovery/TOS_CONFIG | 移植目标芯片的工程文件的配置文件,根据情况选一个最接近自己开发板的 |
kernel | core | TencentOS tiny内核源码 |
pm | tiny低功耗模块源码,本次移植暂不使用 | |
osal | cmsis_os | TencentOS tiny提供的cmsis os 适配 |
复制一个模板工程目录,重命名为Demo_MultiTask
,在该目录下新建一个文件夹,TencentOS
,然后把arm-v7m
,core
和cmsis_os
目录复制到TencentOS
中。
创建一个TOS_CONFIG
子文件夹,并创建一个tos_config.h
文件,下面是我用到的,附有腾讯官方解释
1 |
打开USER/Template.uvprojx
文件,依次添加arch平台代码和内核源码。具体地
点击File Extensions
新建工程分组tos/arch
。点击右侧的Add Files
添加TencentOS\arm-v7m\cortex-m4\common
里的tos_cpu.c
文件和TencentOS\arm-v7m\cortex-m4\armcc
里的port_c.c
和port_c.S
。同样新建工程分组和tos/kernel
。添加TencentOS\kernel\core
里的所有.c
文件。另外,pm是内核中的低功耗组件,基础移植的时候可以不添加。
打开魔法棒Options for Target
按钮,选C/C++
, 右侧Use
选项打开下拉菜
点击添加的头文件目录如下:
1 | ..\TencentOS\arm-v7m\common\include |
//在C/C++
界面上一定要勾上c99,否则编译的时候出underfined reference 的错误(好像不是这个原因,是我忘了添加tos_cpu.c
文件了,不需要)
修改SYSTEM/USART/usart.c第29行,_sys_exit(int x) 改为 void _sys_exit(int x)
修改stm32f4xx_it.c
#include "tos_k.h"
头文件PendSV_Handler
函数SysTick_Handler
函数中添加TencentOS tiny的调度处理函数stm32f4xx_it.c
长这样1 | #include "stm32f4xx_it.h" |
这里参考官方测试代码,原链接在这里 https://github.com/Tencent/TencentOS-tiny/blob/master/doc/10.Porting_Manual_for_KEIL.md
1 | #include "sys.h" |
编译和下载与模板例子模板例子一样,F7
编译工程文件,没有错误的话连接ST-LINK V2和开发板,点击Download下载程序到开发板。
找一个USB转串口连接上电脑和开发板的UART1端口,记得TX-RX,RX-TX交替连接。打开串口调试助手,选择波特率115200,然后复位开发板,就可以收到上面的信息了。task1和task2交替打印,就说明TencentOS-tiny的任务调度功能实现了,移植成功。
我放在github上了https://github.com/ZmengXu/STM32ProjectTemplate.git。如果访问不了,可以发邮件找我要。
]]>很久之前就把网站搭建好了,但一直没往里写东西。最近被一位神秘的大佬催更,写篇文章吧。
无意中看到了一个之前写的后处理工具,决定就拿这个作第一篇博客。
OpenFOAM不能对计算结果进行周向平均处理。所以就写了个后处理程序,用于对轴向对称流场的三维结果进行周向平均。
确定一个周向平均操作需要给定两个量,基点basePoint和旋转向量spanwiseVector。接下来可以通过两种方法实现周向平均过程。
对流场的所有网格遍历,将所有位于同一圆周线上的点加和求平均。可以这么实现:对原始坐标系进行坐标变换,将原来的三维坐标(x,y,z)映射到原点为basePoint高度方向为spanwiseVector的圆柱坐标系(r,z,theta)中;然后在theta方向上求平均,即将所有具有相同(r,z)的节点上的物理量取平均。
取有限个经过基点basePoint和旋转向量spanwiseVector的截面,将各截面上的物理量值求平均。这么实现:过基点和旋转向量选取任意一个截面,提取截面上各点的物理量值;将该初始截面旋转一个角度dTheta,对旋转后的新的截面也提取一次。如此,旋转一周均匀截取n个截面。最终对这n个截面上的物理量值取平均,得到周向平均的结果。
显然,当截面数n比较小的情况下,方案二的计算量显著小于方案一;和理论值之间的偏差取决于截面数量,精度可控。所以接下来就按照方案二的思路来介绍,代码是基础OpenFOAM-4.1写的。
实现方法上参考了这篇cfd-online帖子。
值得一提的是,周向平均并不是一个普适性的后处理过程,它对计算域的几何形状有要求。有些几何形状并不是轴向对称,或者不是关于用户提供的基点basePoint和旋转向量normVector轴向对称的,从原理上讲本就无法进行周向平均。那么这样的几何形状怎么处理,这里涉及到一个容错的问题,这在后面会提到,这应该也是为什么OpenFOAM官方没有提供周向平均后处理程序的一个原因。
本着尽可能用OpenFOAM现有类和函数的原则,我们找到了cuttingPlane类和sampledSurface类,前者可以根据基点basePoint和法向量normVector截取一个初始截面并获得在该截面上的点的坐标,后者则是用来获取这些点的物理量值的。显然,我们直接继承这两个类就可以完成步骤1的主要内容了,而OpenFOAM恰好有一个很好的例子,就是sampledPlan类,同时继承了上面提到的两个类,它在这里
1 | $FOAM_SRC/sampling/sampledSurface/sampledPlan |
所以我们就按照sampledPlan类改写,新建一个新的类sampledPlaneSpanwise,这里截取一段类的声明,新增了采样点个数nPoints_和旋转向量spanwiseVector_. 基点basePoint和初始截面的法向量normVector就不需要了,因为他们可以直接从cuttingPlane类继承来。
sampledPlaneSpanwise/sampledPlaneSpanwise.H
1 | class sampledPlaneSpanwise |
在sampledPlaneSpanwise类的构造函数中,有这么一段,用于判断初始截面的法向量normVector和旋转向量spanwiseVector是否垂直,这是容错机制的一部分。
sampledPlaneSpanwise/sampledPlaneSpanwise.C
1 | const vector& normal = spanwiseVector_; |
圆周采样类circleSet,可以给定圆心,半径和旋转轴在圆周方向上均匀采样,得到一组采样点的坐标。这么一来,圆柱坐标变换和旋转坐标变换的步骤也省了,所以说还是用OpenFOAM现有类和函数好啊。
circleSet类的声明和定义在这里:
1 | $FOAM_SRC/sampling/sampledSet/circle |
所以,旋转采样就可以这么实现:为每个初始截面上的点currentPoint,创建一个circleSet对象,其中包含nPoints_个采样点,相邻两个采样点之间的夹角是dTheta。
sampledPlaneSpanwise/sampledPlaneSpanwiseTemplates.C
1 | // Extract the circle in the homogeneous direction |
circleSet在创建新对象时会调用findCell函数查找改采样点是否在计算域内,不在的话会有输出警告。这也是前面提到的容错机制的一种,只有选取了合适的基点basePoint法向量normVector和旋转向量spanwiseVector才能保证所有的采样点都在计算域内。
参考 $FOAM_SRC/sampling/sampledSurface/sampledPlan/sampledPlanTemplates.C 当中的实现方法,调用interpolate函数,就可以得到圆周采样点上的物理量值。实现如下
sampledPlaneSpanwise/sampledPlaneSpanwiseTemplates.C
1 | forAll(line, lineI) |
得到圆周采样点上的物理量值后,对nPoints_个点求平均即可得到改点处的周向平均值。对初始截面上每个点在每个物理量场(p, U, T …)上作相同地采样和平均处理,即可得到初始截面上的所有点的周向平均值,输出即可。而输出的格式因为继承了sampling类,既可以导出vtk格式又可以得到行列式的raw格式。具体参考sampledPlan的使用方法一样。文末提供的下载链接中也提供了一个算例作参考。
我放在github上了https://github.com/ZmengXu/sampledAveragePlane。如果访问不了,可以发邮件找我要。
]]>