C++: 基於四叉樹資料結構的自適應網格
二叉樹是一種典型的非線性儲存資料結構,查詢效率可以達到\(O(log_2N)\),同樣,這類樹狀結構存在許多種變體,詳細參考鄧俊輝老師的《資料結構C++》課程。在這裡不詳細介紹樹狀資料結構的具體特性,只是初步嘗試下基於四叉樹資料結構如何實現\(CFD\)計算網格的自適應功能。
四叉樹資料資料結構
四叉樹資料結構與二維空間網格對應關係如下圖所示
基於四叉樹資料結構,容易實現對於二維空間區域的區域性細化,這對於\(CFD\)計算是十分有用的。一般來說,網格的好壞能夠很大程度上影響\(CFD\)數值計算過結果的準確性,網格越精細,流場空間解析度也就越高,能夠描述更為細緻的流場,同時也意味著,需要求解的線性方程組更加龐大。
自適應網格就是為了優化此類問題而提出的,在流場內,物理量梯度較大的地方,設定更加細緻的網格,流場平緩的區域,網格可以適當稀疏。然而,一般的多面體、多邊形網格劃分難度較大,為了劃分合適的貼體網格,精確擬合物體表面,網格劃分過程中計算量同樣巨大。因此,自適應網格一般都是基於簡單的幾何體,規整地四分或者八分網格,以此達到網格劃分。著名的\(CFD\)開源求解程式\(Gerris\),便擁有基於四/八叉樹建立的網格自適應功能。
demo
例子:在一塊寬度為\(\pi\)的方形區域中間,定義一條曲線\(z=sin(x+t)\),在曲線周圍設定網格加密最大至7層,曲線上下0.5為加密區域。效果如下
#include <iostream>
#include "QuadTree.h"
#include "PanelTree.h"
using namespace std;
#define PI 3.1415926
double Eta(const Panel& e, double t) {
double x=e.Centroid(0, 0);
double y=e.Centroid(1, 0);
double z = e.Centroid(2, 0);
return sin(x + t);
}
int main(){
Panel patch({ -PI,0,-PI }, { -PI,0,PI }, { PI,0,PI }, { PI,0,-PI });
PanelTree tree(patch);
char buff[100];
for (int i = 0; i < 100; i++) {
sprintf_s(buff, "leafsfile_%04d.txt", i);
//最大加密至7層,曲線上下0.5為加密區域
tree.Expand_demo(tree.root, 7, i/30., Eta, 0.5, -0.5);
tree.WriteLeafsNode(buff);
}
return 0;
}
四叉樹模板類 ADT
四叉樹模板類
/*QuadTree.h
* Include; TreeNode Class, QuadTree Class
*/
#pragma once
#include <iostream>
#include <vector>
#include <iomanip>
#include <fstream>
#define FormatOut(string,X) std::cout<< std::setw(20) << std::left <<string<<":"<< (#X)<<" = "<<X<<std::endl
template<class T> using vector = std::vector<T>;
template<class T> class TreeNode;
template<class T> using TreeNodePosi = TreeNode<T>*;
#define HasChild(p) (p->c1st!=NULL||p->c2nd!=NULL||p->c3rd!=NULL||p->c4th!=NULL)
#define HasParent(p) (p->parent!=NULL)
template<class T>
class TreeNode {
public:
T element;
TreeNodePosi<T> parent, c1st, c2nd, c3rd, c4th;
int height;
TreeNode() :parent(NULL), c1st(NULL), c2nd(NULL), c3rd(NULL), c4th(NULL), height(1) {}
TreeNode(T e) : element(e), parent(NULL), c1st(NULL), c2nd(NULL), c3rd(NULL), c4th(NULL), height(1) {}
TreeNode(T e, TreeNodePosi<T> p) :element(e), parent(p), c1st(NULL), c2nd(NULL), c3rd(NULL), c4th(NULL) {
this->height = 1 + p->height;
}
void insertAsc1st(T const& e) { this->c1st = new TreeNode(e, this); this->c1st->height = this->height + 1; }
void insertAsc2nd(T const& e) { this->c2nd = new TreeNode(e, this); this->c2nd->height = this->height + 1; }
void insertAsc3rd(T const& e) { this->c3rd = new TreeNode(e, this); this->c3rd->height = this->height + 1; }
void insertAsc4th(T const& e) { this->c4th = new TreeNode(e, this); this->c4th->height = this->height + 1; }
};
template<class T>
class QuadTree {
public:
int Leaf_Num;
vector<TreeNodePosi<T>> Leafs;
int size;
TreeNodePosi<T> root;
QuadTree() :root(NULL), size(0), Leaf_Num() {}
QuadTree(const T &root_node) :size(1), Leaf_Num(1) {
root = new TreeNode<T> (root_node);
}
virtual void Expand(TreeNodePosi<T> p); //Expend 1 Level
virtual void Expand(TreeNodePosi<T> p, int Level); //Expand to Level
void DeleteTreeNode(TreeNodePosi<T>& p) { delete p; p = NULL;
} //delete TreeNode
void DeleteTree(TreeNodePosi<T>& p); //delete Tree at p
~QuadTree() {
DeleteTree(this->root);
delete root;
root = NULL;
std::cout << "QuadTree deconstructor" << std::endl;
}
void Travers(TreeNodePosi<T> p, vector<TreeNodePosi<T>>& S);
void Travers();
void WriteLeafsNode(const char* filename);
};
template<class T>
void QuadTree<T>::Expand(TreeNodePosi<T> p) {
T c1_sub, c2_sub, c3_sub, c4_sub;
p->insertAsc1st(c1_sub);
p->insertAsc2nd(c2_sub);
p->insertAsc3rd(c3_sub);
p->insertAsc4th(c4_sub);
size += 4;
}
template<class T>
void QuadTree<T>::Expand(TreeNodePosi<T> p, int Level){
if (p->height >= Level) return;
else {
this->Expand(p);
this->Expand(p->c1st, Level);
this->Expand(p->c2nd, Level);
this->Expand(p->c3rd, Level);
this->Expand(p->c4th, Level);
}
}
template<class T>
inline void QuadTree<T>::DeleteTree(TreeNodePosi<T>& p){
if (p == NULL) return;
if (HasChild(p)) {
DeleteTree(p->c1st); DeleteTreeNode(p->c1st);
DeleteTree(p->c2nd); DeleteTreeNode(p->c2nd);
DeleteTree(p->c3rd); DeleteTreeNode(p->c3rd);
DeleteTree(p->c4th); DeleteTreeNode(p->c4th);
}
else {
DeleteTreeNode(p);
}
}
template<class T>
inline void QuadTree<T>::Travers(TreeNodePosi<T> p, vector<TreeNodePosi<T>>& S){
if (p == NULL) return;
if (!HasChild(p)) {
S.push_back(p);
}
else {
Travers(p->c1st, S);
Travers(p->c2nd, S);
Travers(p->c3rd, S);
Travers(p->c4th, S);
}
}
template<class T>
inline void QuadTree<T>::Travers(){
Leafs.clear();
Travers(this->root, this->Leafs);
Leaf_Num = Leafs.size();
FormatOut("Leaf Size", Leaf_Num);
}
//output file!!! Note: the class T should overload << operator
template<class T>
void QuadTree<T>::WriteLeafsNode(const char* filename){
this->Travers();
std::ofstream fout(filename);
for (TreeNodePosi<T> p : this->Leafs)
fout << p->element;
fout.close();
}
PanelTree 類
PanelTree類繼承自QuadTree類,資料成員為自定義的Panel結構體,原始碼依賴開源矩陣庫\(Eigen\)
/*
*PanelTree.h
*/
#pragma once
#include "QuadTree.h"
#include <iostream>
#include <iomanip>
#include <fstream>
#include "Eigen/Dense"
#define FOUT_V(name) out << std::setw(20) << std::scientific << std::setprecision(4) << e.##name<<","
#define FOUT(name) out << std::setw(20) << std::scientific << std::setprecision(4) << e.##name(0, 0)<<","\
<< std::setw(20) << std::scientific << std::setprecision(4) << e.##name(1, 0)<<","\
<< std::setw(20) << std::scientific << std::setprecision(4) << e.##name(2, 0)<<","
template<class T> using Mat3 = Eigen::Matrix<T, 3, 1>;
using string = std::string;
using ofstream = std::ofstream;
struct Panel {
public:
Mat3<double> P1 = { 0,0,0 };
Mat3<double> P2 = { 0,0,0 };
Mat3<double> P3 = { 0,0,0 };
Mat3<double> P4 = { 0,0,0 };
Mat3<double> Centroid = { 0,0,0 };
Mat3<double> Normal = { 0,0,0 };
double ds, Fraction;
Panel(Mat3<double> p1, Mat3<double> p2, Mat3<double> p3, Mat3<double> p4);
Panel() {};
Panel(const Panel& e); //copy constructor
Panel& operator =(const Panel& e); //overload =
void Cal();
void Cal_Fraction(double eta);
friend ofstream& operator << (ofstream& out, const Panel& e) {
FOUT(Centroid); FOUT(Normal);
FOUT_V(ds); FOUT_V(Fraction);
FOUT(P1); FOUT(P2); FOUT(P3); FOUT(P4);
out << std::endl;
return out;
}
};
class PanelTree : public QuadTree<Panel>
{
public:
PanelTree(const Panel &root) :QuadTree<Panel>(root) {}
public:
bool IsContainFreeSurface(Panel& e, double t, double (*Eta)(const Panel&, double));
bool IsNearFreeSurface(Panel& e, double t, double (*Eta)(const Panel&, double), double up, double down);
//bool IsContainCircle(Panel& e, double t, Mat3<double>(*Circle)(const Panel&, double));
//bool IsNearCircle(Panel& e, double t, Mat3<double>(*Circle)(const Panel&, double), double r0);
virtual void Expand(TreeNodePosi<Panel> p) override;
void Expand_demo(TreeNodePosi<Panel> p, int Level, double t, double (*Eta)(const Panel&, double), double up, double down);
};
/*
*PanelTree.cpp
*/
#include "PanelTree.h"
Panel::Panel(Mat3<double> p1, Mat3<double> p2, Mat3<double> p3, Mat3<double> p4) {
this->P1 = p1; this->P2 = p2; this->P3 = p3; this->P4 = p4;
this->Cal();//計算中心、ds、法相向量等引數;
this->Fraction = 0;
}
Panel::Panel(const Panel& e) {
this->P1 = e.P1; this->P2 = e.P2; this->P3 = e.P3; this->P4 = e.P4;
this->Centroid = e.Centroid;
this->Normal = e.Normal;
this->Fraction = e.Fraction;
this->ds = e.ds;
}
Panel& Panel::operator =(const Panel& e) {
this->P1 = e.P1; this->P2 = e.P2; this->P3 = e.P3; this->P4 = e.P4;
this->Centroid = e.Centroid;
this->Normal = e.Normal;
this->Fraction = e.Fraction;
this->ds = e.ds;
return *this;
}
void Panel::Cal() {
Mat3<double> n1 = { 0,0,0 }, n2 = { 0,0,0 };
double Costheta = 0, Sintheta = 0;
Mat3<double> tmp = { 0,0,0 };
n1 = P1 - P3;
n2 = P2 - P4;
Costheta = n1.dot(n2) / (n1.norm() * n2.norm());
Sintheta = sqrt(1 - pow(Costheta, 2));
ds = 0.5 * (n1.norm() * n2.norm() * Sintheta);
Centroid = (P1 + P2 + P3 + P4) / 4.;
tmp = n1.cross(n2);
Normal = tmp.normalized();
}
void Panel::Cal_Fraction(double eta) {
double f[4]{};
f[0] = this->P1(2, 0) - eta;
f[1] = this->P2(2, 0) - eta;
f[2] = this->P3(2, 0) - eta;
f[3] = this->P4(2, 0) - eta;
double fN = 0, fP = 0;
if (f[0] <= 0 && f[1] <= 0 && f[2] <= 0 && f[3] <= 0)
this->Fraction = 1.;
else {
if (f[0] >= 0 && f[1] >= 0 && f[2] >= 0 && f[3] >= 0)
this->Fraction = 0.;
else {
for (int i = 0; i < 4; ++i) {
if (f[i] >= 0) fP += f[i];
else fN += f[i];
}
this->Fraction = abs(fN) / (abs(fP) + abs(fN));
}
}
}
bool PanelTree::IsContainFreeSurface(Panel& e,double t,double(*Eta)(const Panel&, double)){
double eta = Eta(e, t);
e.Cal_Fraction(eta);
return !((abs(e.Fraction - 1) < 1.0e-5) || (abs(e.Fraction - 0) < 1.0e-5));
}
bool PanelTree::IsNearFreeSurface(Panel& e, double t, double(*Eta)(const Panel&, double), double up, double down){
double eta = Eta(e, t);
e.Cal_Fraction(eta);
double z = e.Centroid(2, 0);
return ((z - eta) >= down && (z - eta) <= up);
}
//bool PanelTree::IsContainCircle(Panel& e, double t, Mat3<double>(*Circle)(const Panel&, double))
//{
// return false;
//}
//
//bool PanelTree::IsNearCircle(Panel& e, double t, Mat3<double> (*Circle)(const Panel&, double), double r0)
//{
// Mat3<double> pos = Circle(e, t);
// double dis = (e.Centroid - pos).norm();
// return abs(dis)<=r0;
//}
void PanelTree::Expand(TreeNodePosi<Panel> p){
Panel subP1, subP2, subP3, subP4;
Mat3<double> c12 = { 0,0,0 };
Mat3<double> c23 = { 0,0,0 };
Mat3<double> c34 = { 0,0,0 };
Mat3<double> c41 = { 0,0,0 };
c12 = (p->element.P1 + p->element.P2) / 2.;
c23 = (p->element.P2 + p->element.P3) / 2.;
c34 = (p->element.P3 + p->element.P4) / 2.;
c41 = (p->element.P4 + p->element.P1) / 2.;
//subP1
subP1.P1 = p->element.P1;
subP1.P2 = c12;
subP1.P3 = p->element.Centroid;
subP1.P4 = c41;
//subP2
subP2.P1 = p->element.P2;
subP2.P2 = c23;
subP2.P3 = p->element.Centroid;
subP2.P4 = c12;
//subP3
subP3.P1 = p->element.P3;
subP3.P2 = c34;
subP3.P3 = p->element.Centroid;
subP3.P4 = c23;
//subP4
subP4.P1 = p->element.P4;
subP4.P2 = c41;
subP4.P3 = p->element.Centroid;
subP4.P4 = c34;
subP1.Cal(); subP2.Cal(); subP3.Cal(); subP4.Cal();
p->insertAsc1st(subP1); p->insertAsc2nd(subP2);
p->insertAsc3rd(subP3); p->insertAsc4th(subP4);
Leaf_Num += 3;
size += 4;
}
void PanelTree::Expand_demo(TreeNodePosi<Panel> p, int Level, double t, double (*Eta)(const Panel&, double), double up, double down)
{
if (p->height >= Level) return;
bool IsMin = p->height <= 4;
bool IsContain = this->IsContainFreeSurface(p->element, t, Eta);
bool IsNear = this->IsNearFreeSurface(p->element, t, Eta, up, down);
if (IsMin||IsNear||IsContain) {
this->Expand(p);
this->Expand_demo(p->c1st, Level, t, Eta, up, down);
this->Expand_demo(p->c2nd, Level, t, Eta, up, down);
this->Expand_demo(p->c3rd, Level, t, Eta, up, down);
this->Expand_demo(p->c4th, Level, t, Eta, up, down);
}
}