/* Copyright 2012 SINTEF ICT, Applied Mathematics. This file is part of the Open Porous Media project (OPM). OPM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OPM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OPM. If not, see . */ #ifndef OPM_CELLQUADRATURE_HEADER_INCLUDED #define OPM_CELLQUADRATURE_HEADER_INCLUDED #include #include #include #include namespace Opm { namespace { /// Calculates the determinant of a 3 x 3 matrix, represented as /// three three-dimensional arrays. inline double determinantOf(const double* a0, const double* a1, const double* a2) { return a0[0] * (a1[1] * a2[2] - a2[1] * a1[2]) - a0[1] * (a1[0] * a2[2] - a2[0] * a1[2]) + a0[2] * (a1[0] * a2[1] - a2[0] * a1[1]); } /// Computes the volume of a tetrahedron consisting of 4 vertices /// with 3-dimensional coordinates inline double tetVolume(const double* p0, const double* p1, const double* p2, const double* p3) { double a[3] = { p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2] }; double b[3] = { p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2] }; double c[3] = { p3[0] - p0[0], p3[1] - p0[1], p3[2] - p0[2] }; return std::fabs(determinantOf(a, b, c) / 6.0); } /// Calculates the area of a triangle consisting of 3 vertices /// with 2-dimensional coordinates inline double triangleArea2d(const double* p0, const double* p1, const double* p2) { double a[2] = { p1[0] - p0[0], p1[1] - p0[1] }; double b[2] = { p2[0] - p0[0], p2[1] - p0[1] }; double a_cross_b = a[0]*b[1] - a[1]*b[0]; return 0.5*std::fabs(a_cross_b); } } // anonymous namespace /// A class providing numerical quadrature for cells. /// In general: \int_{cell} g(x) dx = \sum_{i=0}^{n-1} w_i g(x_i). /// Note that this class does multiply weights by cell volume, /// so weights always sum to cell volume. /// /// Degree 1 method: /// Midpoint (centroid) method. /// n = 1, w_0 = cell volume, x_0 = cell centroid /// /// Degree 2 method for 2d (but see the note): /// Based on subdivision of the cell into triangles, /// with the centroid as a common vertex, and the triangle /// edge midpoint rule. /// Triangle i consists of the centroid C, nodes N_i and N_{i+1}. /// Its area is A_i. /// n = 2 * nn (nn = num nodes in face) /// For i = 0..(nn-1): /// w_i = 1/3 A_i. /// w_{nn+i} = 1/3 A_{i-1} + 1/3 A_i /// x_i = (N_i + N_{i+1})/2 /// x_{nn+i} = (C + N_i)/2 /// All N and A indices are interpreted cyclic, modulus nn. /// Note: for simplicity of implementation, we currently use /// n = 3 * nn /// For i = 0..(nn-1): /// w_{3*i + {0,1,2}} = 1/3 A_i /// x_{3*i} = (N_i + N_{i+1})/2 /// x_{3*i + {1,2}} = (C + N_{i,i+1})/2 /// This is simpler, because we can implement it easily /// based on iteration over faces without requiring any /// particular (cyclic) ordering. /// /// Degree 2 method for 3d: /// Based on subdivision of each cell face into triangles /// with the face centroid as a common vertex, and then /// subdividing the cell into tetrahedra with the cell /// centroid as a common vertex. Then apply the tetrahedron /// rule with the following 4 nodes (uniform weights): /// a = 0.138196601125010515179541316563436 /// x_i has all barycentric coordinates = a, except for /// the i'th coordinate which is = 1 - 3a. /// This rule is from http://nines.cs.kuleuven.be/ecf, /// it is the second degree 2 4-point rule for tets, /// referenced to Stroud(1971). /// The tetrahedra are numbered T_{i,j}, and are given by the /// cell centroid C, the face centroid FC_i, and two nodes /// of face i: FN_{i,j}, FN_{i,j+1}. class CellQuadrature { public: CellQuadrature(const UnstructuredGrid& grid, const int cell, const int degree) : grid_(grid), cell_(cell), degree_(degree) { if (grid.dimensions > 3) { OPM_THROW(std::runtime_error, "CellQuadrature only implemented for up to 3 dimensions."); } if (degree > 2) { OPM_THROW(std::runtime_error, "CellQuadrature exact for polynomial degrees > 1 not implemented."); } } int numQuadPts() const { if (degree_ < 2 || grid_.dimensions == 1) { return 1; } // Degree 2 case. if (grid_.dimensions == 2) { return 3*(grid_.cell_facepos[cell_ + 1] - grid_.cell_facepos[cell_]); } assert(grid_.dimensions == 3); int sumnodes = 0; for (int hf = grid_.cell_facepos[cell_]; hf < grid_.cell_facepos[cell_ + 1]; ++hf) { const int face = grid_.cell_faces[hf]; sumnodes += grid_.face_nodepos[face + 1] - grid_.face_nodepos[face]; } return 4*sumnodes; } void quadPtCoord(const int index, double* coord) const { const int dim = grid_.dimensions; const double* cc = grid_.cell_centroids + dim*cell_; if (degree_ < 2) { std::copy(cc, cc + dim, coord); return; } // Degree 2 case. if (dim == 2) { if (index % 3 == 0) { // Boundary midpoint. This is the face centroid. const int hface = grid_.cell_facepos[cell_] + index/3; const int face = grid_.cell_faces[hface]; const double* fc = grid_.face_centroids + dim*face; std::copy(fc, fc + dim, coord); } else { // Interiour midpoint. This is the average of the // cell centroid and a face node (they should // always have two nodes in 2d). const int hface = grid_.cell_facepos[cell_] + index/3; const int face = grid_.cell_faces[hface]; const int nodeoff = (index % 3) - 1; // == 0 or 1 const int node = grid_.face_nodes[grid_.face_nodepos[face] + nodeoff]; const double* nc = grid_.node_coordinates + dim*node; for (int dd = 0; dd < dim; ++dd) { coord[dd] = 0.5*(nc[dd] + cc[dd]); } } return; } assert(dim == 3); int tetindex = index / 4; const int subindex = index % 4; const double* nc = grid_.node_coordinates; for (int hf = grid_.cell_facepos[cell_]; hf < grid_.cell_facepos[cell_ + 1]; ++hf) { const int face = grid_.cell_faces[hf]; const int nfn = grid_.face_nodepos[face + 1] - grid_.face_nodepos[face]; if (nfn <= tetindex) { // Our tet is not associated with this face. tetindex -= nfn; continue; } const double* fc = grid_.face_centroids + dim*face; const int* fnodes = grid_.face_nodes + grid_.face_nodepos[face]; const int node0 = fnodes[tetindex]; const int node1 = fnodes[(tetindex + 1) % nfn]; const double* n0c = nc + dim*node0; const double* n1c = nc + dim*node1; const double a = 0.138196601125010515179541316563436; // Barycentric coordinates of our point in the tet. double baryc[4] = { a, a, a, a }; baryc[subindex] = 1.0 - 3.0*a; for (int dd = 0; dd < dim; ++dd) { coord[dd] = baryc[0]*cc[dd] + baryc[1]*fc[dd] + baryc[2]*n0c[dd] + baryc[3]*n1c[dd]; } return; } OPM_THROW(std::runtime_error, "Should never reach this point."); } double quadPtWeight(const int index) const { if (degree_ < 2) { return grid_.cell_volumes[cell_]; } // Degree 2 case. const int dim = grid_.dimensions; const double* cc = grid_.cell_centroids + dim*cell_; if (dim == 2) { const int hface = grid_.cell_facepos[cell_] + index/3; const int face = grid_.cell_faces[hface]; const int* nptr = grid_.face_nodes + grid_.face_nodepos[face]; const double* nc0 = grid_.node_coordinates + dim*nptr[0]; const double* nc1 = grid_.node_coordinates + dim*nptr[1]; return triangleArea2d(nc0, nc1, cc)/3.0; } assert(dim == 3); int tetindex = index / 4; const double* nc = grid_.node_coordinates; for (int hf = grid_.cell_facepos[cell_]; hf < grid_.cell_facepos[cell_ + 1]; ++hf) { const int face = grid_.cell_faces[hf]; const int nfn = grid_.face_nodepos[face + 1] - grid_.face_nodepos[face]; if (nfn <= tetindex) { // Our tet is not associated with this face. tetindex -= nfn; continue; } const double* fc = grid_.face_centroids + dim*face; const int* fnodes = grid_.face_nodes + grid_.face_nodepos[face]; const int node0 = fnodes[tetindex]; const int node1 = fnodes[(tetindex + 1) % nfn]; const double* n0c = nc + dim*node0; const double* n1c = nc + dim*node1; return 0.25*tetVolume(cc, fc, n0c, n1c); } OPM_THROW(std::runtime_error, "Should never reach this point."); } private: const UnstructuredGrid& grid_; const int cell_; const int degree_; }; } // namespace Opm #endif // OPM_CELLQUADRATURE_HEADER_INCLUDED