test.cpp 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. #include "periodize.hpp"
  2. /**
  3. * Background flow with unit pressure gradient along X-axis.
  4. */
  5. template <class Real> sctl::Vector<Real> bg_flow(const sctl::Vector<Real>& X) {
  6. const sctl::Long N = X.Dim()/3;
  7. sctl::Vector<Real> U(N*3);
  8. for (sctl::Long i = 0; i < N; i++) {
  9. const auto x = X.begin() + i*3;
  10. U[i*3+0] = -((x[1]-0.5)*(x[1]-0.5) + (x[2]-0.5)*(x[2]-0.5))/4;
  11. U[i*3+1] = 0;
  12. U[i*3+2] = 0;
  13. }
  14. return U;
  15. }
  16. /**
  17. * Reference solution for checking error.
  18. */
  19. template <class Real> sctl::Vector<Real> u_ref(const sctl::Vector<Real>& X) {
  20. const sctl::Long N = X.Dim()/3;
  21. sctl::Vector<Real> U(N*3);
  22. for (sctl::Long i = 0; i < N; i++) {
  23. const auto x = X.begin() + i*3;
  24. U[i*3+0] = 1e-2 - ((x[1]-0.4)*(x[1]-0.4) + (x[2]-0.3)*(x[2]-0.3))/4;
  25. U[i*3+1] = 0;
  26. U[i*3+2] = 0;
  27. }
  28. return U;
  29. }
  30. // Stokes combined field operator (S+D)
  31. template <sctl::Long SL_scal> struct Stokes3D_CF_ {
  32. static const std::string& Name() {
  33. // Name determines what quadrature tables to use.
  34. // Single-layer quadrature tables also works for combined fields.
  35. static const std::string name = "Stokes3D-FxU";
  36. return name;
  37. }
  38. static constexpr sctl::Integer FLOPS() {
  39. return 50;
  40. }
  41. template <class Real> static constexpr Real uKerScaleFactor() {
  42. return 1 / (8 * sctl::const_pi<Real>());
  43. }
  44. template <sctl::Integer digits, class VecType>
  45. static void uKerMatrix(VecType (&u)[3][3], const VecType (&r)[3], const VecType (&n)[3], const void* ctx_ptr) {
  46. using Real = typename VecType::ScalarType;
  47. const auto SL_scal_ = VecType((Real)SL_scal);
  48. const auto r2 = r[0]*r[0] + r[1]*r[1] + r[2]*r[2];
  49. const auto rinv = sctl::approx_rsqrt<digits>(r2, r2 > VecType::Zero()); // Compute inverse square root
  50. const auto rinv2 = rinv * rinv;
  51. const auto rinv3 = rinv2 * rinv;
  52. const auto rinv5 = rinv3 * rinv2;
  53. const auto rdotn = r[0] * n[0] + r[1] * n[1] + r[2] * n[2];
  54. const auto rdotn_rinv5_6 = VecType((Real)6) * rdotn * rinv5;
  55. for (sctl::Integer i = 0; i < 3; i++) {
  56. for (sctl::Integer j = 0; j < 3; j++) {
  57. const auto ri_rj = r[i] * r[j];
  58. const auto ker_dl = ri_rj * rdotn_rinv5_6; // Double-layer kernel
  59. const auto ker_sl = (i == j ? rinv + ri_rj * rinv3 : ri_rj * rinv3); // Single-layer kernel
  60. u[i][j] = ker_dl + ker_sl * SL_scal_; // Combine kernels
  61. }
  62. }
  63. }
  64. };
  65. using Stokes3D_CF = sctl::GenericKernel<Stokes3D_CF_<1>>;
  66. /**
  67. * Visualize volume inside SlenderElemList.
  68. */
  69. template <class Real> class VolumeVis {
  70. static constexpr sctl::Integer COORD_DIM = 3;
  71. static constexpr sctl::Integer s_order = 20;
  72. static constexpr sctl::Integer t_order = 60;
  73. static constexpr sctl::Integer r_order = 12;
  74. public:
  75. VolumeVis() = default;
  76. /**
  77. * @brief Construct a new VolumeVis object.
  78. *
  79. * @param elem_lst the geometry.
  80. * @param comm MPI communicator.
  81. */
  82. VolumeVis(const sctl::SlenderElemList<Real>& elem_lst, const sctl::Comm& comm = sctl::Comm::Self()) : comm_(comm) {
  83. Nelem = elem_lst.Size();
  84. sctl::Vector<Real> s_param, sin_theta, cos_theta;
  85. for (sctl::Long i = 0; i < s_order; i++) {
  86. const Real t = i/(Real)(s_order-1);
  87. s_param.PushBack(t);
  88. }
  89. for (sctl::Long i = 0; i < t_order; i++) {
  90. const Real t = i/(Real)t_order;
  91. sin_theta.PushBack(sctl::sin<Real>(2*sctl::const_pi<Real>()*t));
  92. cos_theta.PushBack(sctl::cos<Real>(2*sctl::const_pi<Real>()*t));
  93. }
  94. for (sctl::Long elem_idx = 0; elem_idx < Nelem; elem_idx++) {
  95. const Real t_order_inv = 1/(Real)t_order;
  96. const Real r_order_inv = (1-1e-6)/(Real)(r_order-1);
  97. sctl::Vector<Real> X_, Xc(COORD_DIM);
  98. elem_lst.GetGeom(&X_, nullptr, nullptr, nullptr, nullptr, s_param, sin_theta, cos_theta, elem_idx);
  99. for (sctl::Long i = 0; i < s_order; i++) {
  100. Xc = 0;
  101. for (sctl::Long j = 0; j < t_order; j++) {
  102. for (sctl::Long l = 0; l < COORD_DIM; l++) {
  103. Xc[l] += X_[(i*t_order+j)*COORD_DIM+l] * t_order_inv;
  104. }
  105. }
  106. for (sctl::Long j = 0; j < t_order; j++) {
  107. for (sctl::Long k = 0; k < r_order; k++) {
  108. for (sctl::Long l = 0; l < COORD_DIM; l++) {
  109. coord.PushBack((X_[(i*t_order+j)*COORD_DIM+l]-Xc[l])*k*r_order_inv + Xc[l]);
  110. }
  111. }
  112. }
  113. }
  114. }
  115. }
  116. /**
  117. * @brief Get the coordinates of the discretization points.
  118. *
  119. * @return const Vector<Real>& Vector containing the coordinates.
  120. */
  121. const sctl::Vector<Real>& GetCoord() const {
  122. return coord;
  123. }
  124. /**
  125. * @brief Write the volume to a VTK file.
  126. *
  127. * @param fname File name.
  128. * @param F Data associated with the discretization points.
  129. */
  130. void WriteVTK(const std::string& fname, const sctl::Vector<Real>& F) const {
  131. sctl::VTUData vtu_data;
  132. GetVTUData(vtu_data, F);
  133. vtu_data.WriteVTK(fname, comm_);
  134. }
  135. /**
  136. * @brief Get VTU data.
  137. *
  138. * @param vtu_data VTU data object.
  139. * @param F Data associated with the discretization points.
  140. */
  141. void GetVTUData(sctl::VTUData& vtu_data, const sctl::Vector<Real>& F) const {
  142. for (const auto& x : coord) vtu_data.coord.PushBack((float)x);
  143. for (const auto& x : F) vtu_data.value.PushBack((float)x);
  144. for (sctl::Long l = 0; l < Nelem; l++) {
  145. const sctl::Long offset = l * s_order*t_order*r_order;
  146. for (sctl::Long i = 0; i < s_order-1; i++) {
  147. for (sctl::Long j = 0; j < t_order; j++) {
  148. for (sctl::Long k = 0; k < r_order-1; k++) {
  149. auto idx = [this,&offset](sctl::Long i, sctl::Long j, sctl::Long k) {
  150. return offset+(i*t_order+(j%t_order))*r_order+k;
  151. };
  152. vtu_data.connect.PushBack(idx(i+0,j+0,k+0));
  153. vtu_data.connect.PushBack(idx(i+0,j+0,k+1));
  154. vtu_data.connect.PushBack(idx(i+0,j+1,k+1));
  155. vtu_data.connect.PushBack(idx(i+0,j+1,k+0));
  156. vtu_data.connect.PushBack(idx(i+1,j+0,k+0));
  157. vtu_data.connect.PushBack(idx(i+1,j+0,k+1));
  158. vtu_data.connect.PushBack(idx(i+1,j+1,k+1));
  159. vtu_data.connect.PushBack(idx(i+1,j+1,k+0));
  160. vtu_data.offset.PushBack(vtu_data.connect.Dim());;
  161. vtu_data.types.PushBack(12);
  162. }
  163. }
  164. }
  165. }
  166. }
  167. private:
  168. sctl::Comm comm_;
  169. sctl::Long Nelem;
  170. sctl::Vector<Real> coord;
  171. };
  172. template <class Real> void test() {
  173. using KerType = Stokes3D_CF;
  174. //using KerType = sctl::Stokes3D_FxU; // unstable if gmres_tol is too small
  175. const Real tol = 1e-14;
  176. const Real gmres_tol = 1e-11;
  177. const sctl::Long Nelem = 4;
  178. const sctl::Long ElemOrder = 10;
  179. const sctl::Long FourierOrder = 28;
  180. const sctl::Comm comm = sctl::Comm::Self();
  181. const KerType stokes_ker;
  182. const auto build_elem_lst_nbr = [](const sctl::Long Nelem, const sctl::Long ElemOrder, const sctl::Long FourierOrder, const sctl::Integer nbr_range){
  183. sctl::Vector<Real> Xc, eps, orient;
  184. sctl::Vector<sctl::Long> ElemOrderVec, FourierOrderVec;
  185. for (sctl::Long k0 = -nbr_range; k0 <=nbr_range; k0++) {
  186. for (sctl::Long k1 = -nbr_range; k1 <=nbr_range; k1++) {
  187. for (sctl::Long k2 = -nbr_range; k2 <=nbr_range; k2++) {
  188. for (sctl::Long i = 0; i < Nelem; i++) {
  189. ElemOrderVec.PushBack(ElemOrder);
  190. FourierOrderVec.PushBack(FourierOrder);
  191. const sctl::Vector<Real>& nodes = sctl::SlenderElemList<Real>::CenterlineNodes(ElemOrderVec[i]);
  192. for (sctl::Long j = 0; j < ElemOrderVec[i]; j++) {
  193. const Real x = (i+nodes[j])/Nelem;
  194. Xc.PushBack(k0+x);
  195. Xc.PushBack(k1+0.4);
  196. Xc.PushBack(k2+0.3);
  197. eps.PushBack(0.2);
  198. orient.PushBack(0);
  199. orient.PushBack(0);
  200. orient.PushBack(1);
  201. }
  202. }
  203. }
  204. }
  205. }
  206. sctl::SlenderElemList<Real> elem_lst(ElemOrderVec, FourierOrderVec, Xc, eps, orient);
  207. return elem_lst;
  208. };
  209. const auto elem_lst0 = build_elem_lst_nbr(Nelem, ElemOrder, FourierOrder, 0); // geometry in the unit box [0,1]^3
  210. const auto elem_lst_nbr = build_elem_lst_nbr(Nelem, ElemOrder, FourierOrder, 1); // geometry with one set of images in each direction
  211. const sctl::Long Nrepeat = elem_lst_nbr.Size() / elem_lst0.Size(); // should be 3^3 = 27
  212. //elem_lst_nbr.WriteVTK("vis/S-nbr");
  213. sctl::Vector<Real> X0; // target coordinates
  214. elem_lst0.GetNodeCoord(&X0, nullptr, nullptr);
  215. const auto X_proxy = Periodize<Real>::GetProxySurf(); // proxy points coordinates
  216. sctl::BoundaryIntegralOp<Real, KerType> LayerPotenOp0(stokes_ker); // potential from elem_lst_nbr to X0
  217. LayerPotenOp0.AddElemList(elem_lst_nbr);
  218. LayerPotenOp0.SetTargetCoord(X0);
  219. LayerPotenOp0.SetAccuracy(tol);
  220. sctl::BoundaryIntegralOp<Real, KerType> LayerPotenOp_proxy(stokes_ker); // potential from elem_lst0 to proxy points
  221. LayerPotenOp_proxy.AddElemList(elem_lst0);
  222. LayerPotenOp_proxy.SetTargetCoord(X_proxy);
  223. LayerPotenOp_proxy.SetAccuracy(tol);
  224. // periodized layer potential operator
  225. const auto BIO = [&LayerPotenOp0,&LayerPotenOp_proxy,&X0,&Nrepeat](sctl::Vector<Real>* U, const sctl::Vector<Real>& sigma) {
  226. const sctl::Long N = sigma.Dim();
  227. sctl::Vector<Real> sigma_nbr(Nrepeat*N); // repeat sigma Nrepeat times
  228. for (sctl::Long k = 0; k < Nrepeat; k++) {
  229. for (sctl::Long i = 0; i < N; i++) {
  230. sigma_nbr[k*N+i] = sigma[i];
  231. }
  232. }
  233. U->SetZero();
  234. LayerPotenOp0.ComputePotential(*U, sigma_nbr);
  235. if (U->Dim() == N && std::is_same<KerType,Stokes3D_CF>::value) (*U) -= sigma*0.5; // for double-layer
  236. { // Add far-field
  237. sctl::Vector<Real> U_proxy, U_far;
  238. LayerPotenOp_proxy.ComputePotential(U_proxy, sigma);
  239. Periodize<Real>::EvalFarField(U_far, X0, U_proxy);
  240. (*U) += U_far;
  241. }
  242. };
  243. // Solve for sigma to satisfy no-slip boundary conditions: BIO(sigma) + bg_flow = 0
  244. sctl::Vector<Real> sigma;
  245. sctl::GMRES<Real> solver(comm);
  246. solver(&sigma, BIO, -bg_flow(X0), gmres_tol);
  247. elem_lst0.WriteVTK("vis/sigma", sigma);
  248. { // Evaluate in interior, compute error and write visualization
  249. VolumeVis<Real> cube(elem_lst0, comm);
  250. X0 = cube.GetCoord(); // set new target coordinates
  251. LayerPotenOp0.SetTargetCoord(X0);
  252. sctl::Vector<Real> U;
  253. BIO(&U, sigma);
  254. U += bg_flow(X0);
  255. Real max_err = 0;
  256. const auto err = U - u_ref(X0);
  257. for (const auto e : err) max_err = std::max<Real>(max_err, fabs(e));
  258. std::cout<<"Max error = "<<max_err<<'\n';
  259. cube.WriteVTK("vis/U", U);
  260. cube.WriteVTK("vis/err", err);
  261. }
  262. }
  263. int main(int argc, char** argv) {
  264. using Real = double;
  265. test<Real>();
  266. return 0;
  267. }