二叉树是一种典型的非线性存储数据结构,查找效率可以达到\(O(log_2N)\),同样,这类树状结构存在许多种变体,详细参考邓俊辉老师的《数据结构C++》课程。在这里不详细介绍树状数据结构的具体特性,只是初步尝试下基于四叉树数据结构如何实现\(CFD\)计算网格的自适应功能。
四叉树数据结构与二维空间网格对应关系如下图所示
基于四叉树数据结构,容易实现对于二维空间区域的局部细化,这对于\(CFD\)计算是十分有用的。一般来说,网格的好坏能够很大程度上影响\(CFD\)数值计算过结果的准确性,网格越精细,流场空间分辨率也就越高,能够描述更为细致的流场,同时也意味着,需要求解的线性方程组更加庞大。
自适应网格就是为了优化此类问题而提出的,在流场内,物理量梯度较大的地方,设置更加细致的网格,流场平缓的区域,网格可以适当稀疏。然而,一般的多面体、多边形网格划分难度较大,为了划分合适的贴体网格,精确拟合物体表面,网格划分过程中计算量同样巨大。因此,自适应网格一般都是基于简单的几何体,规整地四分或者八分网格,以此达到网格划分。著名的\(CFD\)开源求解程序\(Gerris\),便拥有基于四/八叉树建立的网格自适应功能。
源码项目地址(还没更新)
例子:在一块宽度为\(\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; }
四叉树模板类
/*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类继承自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); } }