HArD::Core3D
Hybrid Arbitrary Degree::Core 3D - Library to implement 3D schemes with vertex, edge, face and cell polynomials as unknowns
HHO_LocVarDiff.hpp
Go to the documentation of this file.
1 // Implementation of the HHO scheme in 3D for the diffusion equation
2 //
3 // { -div(K \grad(u)) = f, inside Omega
4 // { K \grad(u) . nTF = g, on GammaN
5 // { u = g, on GammaD
6 //
7 // At the moment, only pure Neumann or pure Dirichlet
8 //
9 // Author: Jerome Droniou (jerome.droniou@monash.edu)
10 //
11 
12 /*
13  *
14  * This implementation of HHO was developped following the principles described in
15  * Appendix B of the book
16  *
17  * The Hybrid High-Order Method for Polytopal Meshes: Design, Analysis, and Applications.
18  * D. A. Di Pietro and J. Droniou. 2019, 516p.
19  * url: https://hal.archives-ouvertes.fr/hal-02151813.
20  *
21  * If you use this code or part of it for a scientific publication, please cite the book
22  * above as a reference for the implementation.
23  *
24  */
25 
26 #ifndef _HHO_LOCVARDIFF_HPP
27 #define _HHO_LOCVARDIFF_HPP
28 
29 #include <functional>
30 #include <utility>
31 #include <iostream>
32 
33 #include <boost/timer/timer.hpp>
34 
35 // Matrices and linear solvers
36 #include <Eigen/Sparse>
37 #include <Eigen/Dense>
38 //#include "Eigen/MA41.cpp"
39 
40 #include <hybridcore.hpp>
41 #include <elementquad.hpp>
42 #include <parallel_for.hpp>
43 #include <TestCase/TestCase.hpp>
45 
51 namespace HArDCore3D {
52 
57  // ----------------------------------------------------------------------------
58  // Class definition
59  // ----------------------------------------------------------------------------
60 
62 
64  public:
67  HybridCore& hho,
68  size_t K,
69  int L,
70  CellFType<MatrixRd> kappa,
71  size_t deg_kappa,
72  CellFType<double> source,
74  FType<double> exact_solution,
75  CellFType<VectorRd> grad_exact_solution,
76  std::string solver_type,
77  bool use_threads,
78  std::ostream & output = std::cout
79  );
80 
82  void assemble(HybridCore& hho);
83  UVector solve(HybridCore& hho);
84 
86  double EnergyNorm(HybridCore& hho, const UVector Xh);
87 
89  inline double get_assembly_time() const
90  {
91  return double(_assembly_time) * pow(10, -9);
92  };
94  inline double get_solving_time() const
95  {
96  return double(_solving_time) * pow(10, -9);
97  };
99  inline double get_solving_error() const
100  {
101  return _solving_error;
102  };
104  inline double get_itime(size_t idx) const
105  {
106  return double(_itime[idx]) * pow(10, -9);
107  };
108 
110  const inline size_t get_nlocal_cell_dofs() { return m_nlocal_cell_dofs; }
112  const inline size_t get_nlocal_face_dofs() { return m_nlocal_face_dofs; }
114  const inline size_t get_nhighorder_dofs() { return m_nhighorder_dofs; }
116  const inline size_t get_ntotal_cell_dofs() { return m_ntotal_cell_dofs; }
118  const inline size_t get_ntotal_face_dofs() { return m_ntotal_face_dofs; }
120  const inline size_t get_ndir_face_dofs() { return m_ndir_face_dofs; }
122  const inline size_t get_ntotal_dofs() { return m_ntotal_dofs; }
123 
124  private:
126  Eigen::MatrixXd diffusion_operator(HybridCore& hho, const size_t iT, const ElementQuad& elquad) const;
127 
129  Eigen::VectorXd load_operator(HybridCore& hho, const size_t iT, const ElementQuad &elquad) const;
130 
131  // Reference to the HybridCore structure
132  HybridCore& m_hho;
133 
134  // Degrees on faces and in cells
135  size_t m_K;
136  int m_L;
137  size_t m_Ldeg;
138 
139  // Data
140  const CellFType<MatrixRd> kappa;
141  size_t _deg_kappa;
142  const CellFType<double> source;
143  const BoundaryConditions m_BC;
144  const FType<double> exact_solution;
145  const CellFType<VectorRd> grad_exact_solution;
146  const std::string solver_type;
147  const bool m_use_threads;
148  std::ostream & m_output;
149 
150  // DOFs
151  const size_t m_nlocal_cell_dofs;
152  const size_t m_nlocal_face_dofs;
153  const size_t m_nhighorder_dofs;
154  const size_t m_ntotal_cell_dofs;
155  const size_t m_ntotal_face_dofs;
156  const size_t m_ndir_face_dofs;
157  const size_t m_nnondir_face_dofs;
158  const size_t m_ntotal_dofs;
159 
160  // Local bilinear forms
161  std::vector<Eigen::MatrixXd> aT;
162  // Global system matrix
163  Eigen::SparseMatrix<double> GlobMat;
164  // If static condensation (L>=0): matrix to recover cell unknowns
165  // If barycentric elimination (L=-1): matrix to recover cell unknowns
166  Eigen::SparseMatrix<double> ScBeMat;
167  // Source terms for the system, and for recovering cell unknowns from static condensation
168  Eigen::VectorXd GlobRHS;
169  Eigen::VectorXd ScRHS;
170 
171  // Computation statistics
172  size_t _assembly_time;
173  size_t _solving_time;
174  double _solving_error;
175  mutable std::vector<size_t> _itime = std::vector<size_t>(10, 0);
176 
177  };
178 
179  HHO_LocVarDiff::HHO_LocVarDiff(HybridCore& hho, size_t K, int L, CellFType<MatrixRd> kappa, size_t deg_kappa, CellFType<double> source, BoundaryConditions BC, FType<double> exact_solution, CellFType<VectorRd> grad_exact_solution, std::string solver_type, bool use_threads, std::ostream & output)
180  : m_hho(hho),
181  m_K(K),
182  m_L(L),
183  m_Ldeg(std::max(L,0)),
184  kappa(kappa),
185  _deg_kappa(deg_kappa),
186  source(source),
187  m_BC(BC),
188  exact_solution(exact_solution),
189  grad_exact_solution(grad_exact_solution),
190  solver_type(solver_type),
191  m_use_threads(use_threads),
192  m_output(output),
193  m_nlocal_cell_dofs(DimPoly<Cell>(m_Ldeg)),
194  m_nlocal_face_dofs(DimPoly<Face>(m_K)),
195  m_nhighorder_dofs(DimPoly<Cell>(m_K+1)),
196  m_ntotal_cell_dofs(m_nlocal_cell_dofs * m_hho.get_mesh()->n_cells()),
197  m_ntotal_face_dofs(m_nlocal_face_dofs * m_hho.get_mesh()->n_faces()),
198  m_ndir_face_dofs(m_nlocal_face_dofs * m_BC.n_dir_faces()),
199  m_nnondir_face_dofs(m_nlocal_face_dofs * m_hho.get_mesh()->n_faces() - m_ndir_face_dofs),
200  m_ntotal_dofs(m_ntotal_cell_dofs + m_ntotal_face_dofs),
201  GlobRHS(Eigen::VectorXd::Zero(m_ntotal_face_dofs)),
202  ScRHS(Eigen::VectorXd::Zero(m_ntotal_cell_dofs))
203  {
204  m_output << "[HHO_LocVarDiff] Initializing" << std::endl;
205  GlobMat.resize(m_ntotal_face_dofs, m_ntotal_face_dofs);
206  ScBeMat.resize(m_ntotal_cell_dofs, m_ntotal_face_dofs);
207  // Do nothing
208  }
209 
211 
212  boost::timer::cpu_timer timer; // Time the matrix assembly
213  timer.start();
214  const Mesh* mesh = hho.get_mesh();
215 
216  //--------------- PREPARE SYSTEM ------------------------//
217 
218  // Global triplets for: system matrix, static condensaion/barycentric elimination
219  std::vector<Eigen::Triplet<double>> triplets_GlobMat;
220  std::vector<Eigen::Triplet<double>> triplets_ScBe;
221 
222  // Local bilinear form, triplets and source term (one per cell)
223  aT.resize(mesh->n_cells());
224  std::vector<std::vector<Eigen::Triplet<double>>> cell_triplets_GlobMat;
225  std::vector<std::vector<Eigen::Triplet<double>>> cell_triplets_ScBe;
226  std::vector<Eigen::VectorXd> cell_source(mesh->n_cells());
227  cell_triplets_GlobMat.resize(mesh->n_cells());
228  cell_triplets_ScBe.resize(mesh->n_cells());
229  size_t size_triplets_GlobMat = 0;
230  size_t size_triplets_ScBe = 0;
231 
232  //-------------- ASSEMBLE LOCAL CONTRIBUTIONS -------------//
233 
234  // Function to create local contribution between cell start and cell end-1
235  std::function<void(size_t, size_t)> construct_all_local_contributions
236  = [&](size_t start, size_t end)->void
237  {
238  for (size_t iT = start; iT < end; iT++) {
239  Cell* iCell = mesh->cell(iT);
240 
241  // Total number of face degrees of freedom local to this cell (adjacent faces to the cell)
242  size_t nlocal_faces = iCell->n_faces();
243  size_t face_dofs = nlocal_faces * m_nlocal_face_dofs;
244 
245  // Local bilinear form and source term
246  size_t doeT = std::max( std::max(m_K,m_Ldeg) + m_K+1 , 2*m_K + _deg_kappa );
247  size_t doeF = 2*m_K + 1;
248  ElementQuad elquad(hho, iT, doeT, doeF);
249 
250  aT[iT] = diffusion_operator(hho, iT, elquad);
251  Eigen::VectorXd bT = load_operator(hho, iT, elquad);
252 
253  // Local matrix and right-hand side on the face unknowns
254  Eigen::MatrixXd MatF = Eigen::MatrixXd::Zero(face_dofs,face_dofs);
255  cell_source[iT] = Eigen::VectorXd::Zero(face_dofs);
256 
257  if (m_L>=0) {
258  // STATIC CONDENSATION OF ELEMENT UNKNOWNS
259 
260  // Perform static condensation
261  Eigen::MatrixXd ATT = aT[iT].topLeftCorner(m_nlocal_cell_dofs, m_nlocal_cell_dofs);
262  Eigen::MatrixXd ATF = aT[iT].topRightCorner(m_nlocal_cell_dofs, face_dofs);
263  Eigen::MatrixXd AFF = aT[iT].bottomRightCorner(face_dofs, face_dofs);
264 
265  Eigen::PartialPivLU<Eigen::MatrixXd> invATT;
266  invATT.compute(ATT);
267 
268  Eigen::MatrixXd invATT_ATF = invATT.solve(ATF);
269  Eigen::VectorXd invATT_bTcell = invATT.solve(bT.head(m_nlocal_cell_dofs));
270  MatF = AFF - ATF.transpose() * invATT_ATF;
271 
272  cell_source[iT] = bT.tail(face_dofs) - ATF.transpose() * invATT_bTcell;
273 
274  // Assemble local triplets for static condensation operator
275  ScRHS.segment(iT * m_nlocal_cell_dofs, m_nlocal_cell_dofs) = invATT_bTcell;
276  for (size_t i = 0; i < m_nlocal_cell_dofs; i++) {
277  for (size_t jlF = 0; jlF < nlocal_faces; jlF++) {
278  const size_t jF = iCell->face(jlF)->global_index();
279  for (size_t jk = 0; jk < m_nlocal_face_dofs; jk++) {
280  const size_t jLocal = jlF * m_nlocal_face_dofs + jk;
281  const size_t jGlobal = jF * m_nlocal_face_dofs + jk;
282  cell_triplets_ScBe[iT].emplace_back(iT * m_nlocal_cell_dofs + i, jGlobal, invATT_ATF(i, jLocal));
283  }
284  }
285  }
286  size_triplets_ScBe += cell_triplets_ScBe[iT].size();
287 
288  } else {
289  // BARYCENTRIC ELIMINATION OF ELEMENT UNKNOWNS
290  // Create reduction matrix: 1+nlocal_faces * nlocal_faces matrix with the coefficients on the first row, and the identity below. When multiplied by the face unknowns, return cell and face unknowns
291  // Note that the basis functions are constant, but not necessarily assumed to be one (which is not the case after orthonormalisation for example), which is why we have to adjust the first row.
292  Eigen::MatrixXd red_matT = Eigen::MatrixXd::Zero(1+nlocal_faces,nlocal_faces);
293  red_matT.row(0) = hho.compute_weights(iT);
294  VectorRd xT = iCell->center_mass();
295  double phiT_cst = hho.CellBasis(iT).function(0, xT);
296  for (size_t ilF = 0; ilF < nlocal_faces; ilF++){
297  VectorRd xF = iCell->face(ilF)->center_mass();
298  size_t iF = iCell->face(ilF)->global_index();
299  double phiF_cst = hho.FaceBasis(iF).function(0, xF);
300  red_matT(0,ilF) *= phiF_cst / phiT_cst;
301  }
302  red_matT.bottomRightCorner(nlocal_faces,nlocal_faces) = Eigen::MatrixXd::Identity(nlocal_faces,nlocal_faces);
303 
304  cell_source[iT] = red_matT.transpose() * bT;
305  MatF = red_matT.transpose() * aT[iT] * red_matT;
306 
307  // Assemble local triplets for barycentric combination to recover cell unknown
308  for (size_t jlF = 0; jlF < nlocal_faces; jlF++) {
309  const size_t jF = iCell->face(jlF)->global_index();
310  const size_t jGlobal = jF * m_nlocal_face_dofs;
311  cell_triplets_ScBe[iT].emplace_back(iT, jGlobal, red_matT(0,jlF));
312  }
313  size_triplets_ScBe += cell_triplets_ScBe[iT].size();
314 
315  }
316 
317  // Assemble local triplets for scheme's matrix and source term
318  for (size_t ilF = 0; ilF < nlocal_faces; ilF++) {
319  const size_t iF = iCell->face(ilF)->global_index();
320  for (size_t ik = 0; ik < m_nlocal_face_dofs; ik++) {
321  const size_t iLocal = ilF * m_nlocal_face_dofs + ik;
322  const size_t iGlobal = iF * m_nlocal_face_dofs + ik;
323  for (size_t jlF = 0; jlF < nlocal_faces; jlF++) {
324  const size_t jF = iCell->face(jlF)->global_index();
325  for (size_t jk = 0; jk < m_nlocal_face_dofs; jk++) {
326  const size_t jLocal = jlF * m_nlocal_face_dofs + jk;
327  const size_t jGlobal = jF * m_nlocal_face_dofs + jk;
328  cell_triplets_GlobMat[iT].emplace_back(iGlobal, jGlobal, MatF(iLocal, jLocal));
329  }
330  }
331  }
332  }
333  size_triplets_GlobMat += cell_triplets_GlobMat[iT].size();
334 
335  }
336  }; // End function to construct local contributions
337 
338  // Running the local constructions in parallel
339  parallel_for(mesh->n_cells(), construct_all_local_contributions, m_use_threads);
340 
341  // Assemble local contribution into global matrix
342  triplets_ScBe.reserve(size_triplets_ScBe);
343  triplets_GlobMat.reserve(size_triplets_GlobMat);
344  for (size_t iT = 0; iT < mesh->n_cells(); iT++){
345  for (size_t i = 0; i < cell_triplets_ScBe[iT].size(); i++){
346  triplets_ScBe.push_back(cell_triplets_ScBe[iT][i]);
347  }
348  for (size_t i = 0; i < cell_triplets_GlobMat[iT].size(); i++){
349  triplets_GlobMat.push_back(cell_triplets_GlobMat[iT][i]);
350  }
351  Cell& T = *mesh->cell(iT);
352  for (size_t ilF = 0; ilF < T.n_faces(); ilF++) {
353  const size_t iF = T.face(ilF)->global_index();
354  for (size_t ik = 0; ik < m_nlocal_face_dofs; ik++) {
355  const size_t iLocal = ilF * m_nlocal_face_dofs + ik;
356  const size_t iGlobal = iF * m_nlocal_face_dofs + ik;
357  GlobRHS(iGlobal) += cell_source[iT](iLocal);
358  }
359  }
360  }
361 
362  if (m_BC.name()=="Neumann"){
363  // Neumann BC: remove a row in the matrix and fix the first degree of freedom
364  triplets_GlobMat.erase(std::remove_if(std::begin(triplets_GlobMat), std::end(triplets_GlobMat),
365  [](const auto& x) { return (x.row() == 0); }), std::end(triplets_GlobMat));
366  triplets_GlobMat.emplace_back(0, 0, 1);
367  GlobRHS(0) = 0;
368  }
369 
370  // Assemble the global linear system (without BC), and matrix to recover statically-condensed cell dofs
371  GlobMat.setFromTriplets(std::begin(triplets_GlobMat), std::end(triplets_GlobMat));
372  ScBeMat.setFromTriplets(std::begin(triplets_ScBe), std::end(triplets_ScBe));
373 
374  // Record assembly time
375 // _assembly_time = timer.elapsed().user + timer.elapsed().system;
376  _assembly_time = timer.elapsed().wall;
377 
378  }
379 
381  const Mesh* mesh = hho.get_mesh();
382  boost::timer::cpu_timer timer; // Time the matrix assembly
383  timer.start();
384 
385  //-------------- TREATMENT OF BOUNDARY CONDITIONS -------------//
386 
387  // If Dirichlet, the final system is only posed on the interior face unknowns and we have to subtract from the source
388  // term the contribution of the boundary values
389  // If Neumann, the final system is posed on all face unknowns
390 
391  size_t n_unknowns = 0;
392  size_t n_fixed_dofs = 0;
393  Eigen::SparseMatrix<double> A;
394  Eigen::VectorXd B;
395  Eigen::VectorXd UDir;
396 
397  if (m_BC.name() != "Neumann"){
398  // Dirichlet boundary conditions
399  n_unknowns = m_nnondir_face_dofs;
400  n_fixed_dofs = m_ndir_face_dofs;
401  A = GlobMat.topLeftCorner(n_unknowns, n_unknowns);
402 
403  // Boundary value: UDir corresponds to the L2 projection of the exact solution on the polynomial spaces on the Dirichlet faces (last BC.n_dir_faces() faces)
404  UDir = Eigen::VectorXd::Zero(n_fixed_dofs);
405 
406  size_t n_dir_faces = m_BC.n_dir_faces();
407  size_t n_nondir_faces = mesh->n_faces() - n_dir_faces;
408  for (size_t idF = 0; idF < n_dir_faces; idF++){
409  Face* face = mesh->face(n_nondir_faces + idF);
410  size_t iF = face->global_index();
411  QuadratureRule quadF = generate_quadrature_rule(*face, 2*m_K+2);
412  boost::multi_array<double, 2> phiF_quadF = evaluate_quad<Function>::compute(hho.FaceBasis(iF), quadF);
413  UDir.segment(idF * m_nlocal_face_dofs, m_nlocal_face_dofs) = l2_projection<HybridCore::PolyFaceBasisType>(exact_solution, hho.FaceBasis(iF), quadF, phiF_quadF);
414  }
415 
416  B = GlobRHS.segment(0, n_unknowns) - GlobMat.topRightCorner(n_unknowns, n_fixed_dofs) * UDir;
417 
418  } else {
419  // We will solve the complete system
420  n_unknowns = m_ntotal_face_dofs;
421  A = GlobMat;
422  B = GlobRHS;
423  UDir = Eigen::VectorXd::Zero(n_fixed_dofs);
424  }
425 
426  //-------------- SOLVE CONDENSED SYSTEM -------------//
427 
428  Eigen::VectorXd xF = Eigen::VectorXd::Zero(n_unknowns);
429 
430  // if (solver_type == "ma41") {
431  // Eigen::MA41<Eigen::SparseMatrix<double>, Eigen::VectorXd> solver;
432  // solver.analyzePattern(A);
433  // solver.factorize(A);
434  // xF = solver.solve(B);
435  // } else {
436  Eigen::BiCGSTAB<Eigen::SparseMatrix<double> > solver;
437  solver.compute(A);
438  xF = solver.solve(B);
439  std::cout << " [solver] #iterations: " << solver.iterations() << ", estimated error: " << solver.error() << std::endl;
440  // }
441  _solving_error = (A * xF - B).norm();
442  // Recover the fixed boundary values, cell unknowns (from static condensation/barycentric elimination)
443  Eigen::VectorXd Xh = Eigen::VectorXd::Zero(m_ntotal_dofs);
444  Xh.tail(n_fixed_dofs) = UDir;
445  Xh.segment(m_ntotal_cell_dofs, n_unknowns) = xF;
446  if (m_L>=0) {
447  Xh.head(m_ntotal_cell_dofs) = ScRHS - ScBeMat * Xh.tail(m_ntotal_face_dofs);
448  } else {
449  Xh.head(m_ntotal_cell_dofs) = ScBeMat * Xh.tail(m_ntotal_face_dofs);
450  }
451 
452  // Only Neumann: translate to get the proper average
453  if (m_BC.name()=="Neumann"){
454  // Compute average to translate
455  double average = 0.;
456  double total_measure = 0;
457  for (size_t iT = 0; iT < mesh->n_cells(); iT++){
458  Cell& T = *mesh->cell(iT);
459  total_measure += T.measure();
460  HybridCore::PolyCellBasisType basisT = hho.CellBasis(iT);
461  QuadratureRule quadT = generate_quadrature_rule(T, 2*m_K);
462  boost::multi_array<double, 2> phiT_quadT = evaluate_quad<Function>::compute(basisT, quadT);
463  for (size_t i = 0; i < basisT.dimension(); i++){
464  for (size_t iqn = 0; iqn < quadT.size(); iqn++){
465  average += quadT[iqn].w * Xh(iT * m_nlocal_cell_dofs + i) * basisT.function(i, quadT[iqn].vector());
466  }
467  }
468  }
469  double average_exact_sol = 0.0;
470  for (auto& T : mesh->get_cells()){
471  QuadratureRule quadT = generate_quadrature_rule(*T, 2 * m_Ldeg + 2);
472  for (QuadratureNode& qT : quadT){
473  average_exact_sol += qT.w * exact_solution(qT.vector());
474  }
475  }
476  average_exact_sol /= total_measure;
477 
478  // Translate the cells and faces
479  // We compute the interpolant of the constant function "average_exact_sol - average"
480  // and we translate Xh by that amount
481  std::function<double(VectorRd)> AveDiff = [&average_exact_sol,&average](VectorRd x)->double
482  { return average_exact_sol - average;};
483 
484  UVector Cst = hho.interpolate(AveDiff, m_L, m_K, 2*m_K+3);
485  Xh += Cst.asVectorXd();
486  }
487 
488  _solving_time = timer.elapsed().user + timer.elapsed().system; // Record the final solving time
489 
490  return UVector(Xh, *hho.get_mesh(), m_L, m_K);
491  }
492 
493  //********************************
494  // local diffusion matrix
495  //********************************
496 
497  Eigen::MatrixXd HHO_LocVarDiff::diffusion_operator(HybridCore &hho, const size_t iT, const ElementQuad &elquad) const
498  {
499 
500  boost::timer::cpu_timer timeint;
501 
502  const auto mesh = hho.get_mesh();
503  const size_t dimPKcell = DimPoly<Cell>(m_K);
504  const size_t dimPKcell_vec = mesh->dim() * dimPKcell;
505  Cell* cell = mesh->cell(iT);
506  const size_t nfacesT = cell->n_faces();
507 
508  // Total number of degrees of freedom local to this cell (cell and its adjacent faces)
509  size_t local_dofs = m_nlocal_cell_dofs + nfacesT * m_nlocal_face_dofs;
510 
511  //------------------- Initialisatons: quadratures, mass matrices... --------------------//
512 
513  // Note: representing the gradient reconstruction GT supposes a basis for (P^k)^d. This basis can be
514  // built from the basis (phi_i)_i of P^k, by working component-by-component:
515  // (phi_1 e_1, ..., phi_N e_1, phi_1 e_2, ..., phi_N e_2, ...),
516  // where e_1=[1 0], e_2=[0 1]... is the canonical basis of R^d
517  // Hence, the first dimPKcell functions are on the first component of R^d, the next dimPKcell on the second
518  // component etc.
519  // We do not explicitly build this basis function, but we compute the values of these functions at the
520  // quadrature nodes, using the values computed for the scalar basis functions
521 
522 
523  // QUADRATURES
524  // Cell quadrature nodes, and values of cell basis functions (up to degree K+1) and gradients thereof.
525  QuadratureRule quadT = elquad.get_quadT();
526  boost::multi_array<double, 2> phiT_quadT = elquad.get_phiT_quadT();
527  boost::multi_array<VectorRd, 2> dphiT_quadT = elquad.get_dphiT_quadT();
528 
529  // Vector basis functions (up to degree K) at the quadrature nodes.
530  boost::multi_array<VectorRd, 2> vec_phiT_quadT = elquad.get_vec_phiT_quadT(m_K);
531 
532  // Diffusion tensor at the quadrature nodes
533  std::vector<MatrixRd> kappaT_quadT(quadT.size());
534  std::transform(quadT.begin(), quadT.end(), kappaT_quadT.begin(),
535  [this,&cell](QuadratureNode qr) -> Eigen::MatrixXd { return kappa(qr.vector(), cell); });
536 
537  // MASS MATRICES
538  // Scalar cell mass matrix (phi_i,phi_j)_T up to degree max(L,K) * (K+1), and
539  // Vector cell mass matrix (Phi_i,Phi_j)_T up to degree K*K [this one is block diagonal]
540  size_t maxdimPKL = std::max(m_nlocal_cell_dofs, dimPKcell);
541 
542  _itime[0] += timeint.elapsed().user + timeint.elapsed().system;
543  timeint.start();
544 
545  Eigen::MatrixXd MTT = compute_gram_matrix(phiT_quadT, phiT_quadT, quadT, maxdimPKL, m_nhighorder_dofs, "sym");
546  Eigen::MatrixXd VecMTT = Eigen::MatrixXd::Zero(dimPKcell_vec, dimPKcell_vec);
547  for (size_t r=0; r < mesh->dim(); r++){
548  VecMTT.block(r*dimPKcell, r*dimPKcell, dimPKcell, dimPKcell) = MTT.topLeftCorner(dimPKcell, dimPKcell);
549  }
550 
551  // Face mass matrices:
552  // MFF[ilF]: face-face mass on face with local number ilF, up to degree K*K
553  // MFT[ilF]: face-cell mass on face with local number ilF, up to degree K*(K+1)
554  // MTT_on_F[ilF]: cell-cell mass on face with local number ilF, up to degree K*L
555  std::vector<Eigen::MatrixXd> MFF(nfacesT, Eigen::MatrixXd::Zero(m_nlocal_face_dofs, m_nlocal_face_dofs));
556  std::vector<Eigen::MatrixXd> MFT(nfacesT, Eigen::MatrixXd::Zero(m_nlocal_face_dofs, m_nhighorder_dofs));
557  std::vector<Eigen::MatrixXd> MTT_on_F(nfacesT, Eigen::MatrixXd::Zero(dimPKcell, m_nlocal_cell_dofs));
558 
559  for (size_t ilF = 0; ilF < nfacesT; ilF++) {
560 
561  // Face quadrature nodes and values of cell and face basis functions (and gradients) at these nodes
562  auto quadF = elquad.get_quadF(ilF);
563  boost::multi_array<double, 2> phiT_quadF = elquad.get_phiT_quadF(ilF);
564  boost::multi_array<double, 2> phiF_quadF = elquad.get_phiF_quadF(ilF);
565 
566  // Mass matrices
567  MFF[ilF] = compute_gram_matrix(phiF_quadF, phiF_quadF, quadF, "sym");
568  MFT[ilF] = compute_gram_matrix(phiF_quadF, phiT_quadF, quadF, "nonsym");
569  MTT_on_F[ilF] = compute_gram_matrix(phiT_quadF, phiT_quadF, quadF, dimPKcell, m_nlocal_cell_dofs, "nonsym");
570  }
571 
572  _itime[1] += timeint.elapsed().user + timeint.elapsed().system;
573  timeint.start();
574 
575  // STIFNESS mass-matrices (and the like): (\nabla phi_i,\nabla phi_j)_T up to degree (K+1)*(K+1)
576  // and (\nabla phi_i, Phi_j)_T up to degree (K+1)*K
577  Eigen::MatrixXd StiffT = compute_gram_matrix(dphiT_quadT, dphiT_quadT, quadT, "sym");
578  Eigen::MatrixXd MdphiT_PhiT = compute_gram_matrix(dphiT_quadT, vec_phiT_quadT, quadT, "nonsym");
579 
580  _itime[2] += timeint.elapsed().user + timeint.elapsed().system;
581  timeint.start();
582 
583  //-------------------- Compute GT, matrix of full gradient reconstruction ---------//
584 
585  // Right-hand side, starting with volumetric term (Phi_i, \nabla phi_j)_T
586  Eigen::MatrixXd RHS_GT = Eigen::MatrixXd::Zero(dimPKcell_vec, local_dofs);
587  RHS_GT.topLeftCorner(dimPKcell_vec, m_nlocal_cell_dofs) = (MdphiT_PhiT.topLeftCorner(m_nlocal_cell_dofs, dimPKcell_vec)).transpose();
588 
589  // Boundary terms
590  for (size_t r=0; r < mesh->dim(); r++){
591  for (size_t ilF = 0; ilF < nfacesT; ilF++) {
592  // Offset for face unknowns
593  const size_t offset_F = m_nlocal_cell_dofs + ilF * m_nlocal_face_dofs;
594  const auto& nTF = cell->face_normal(ilF);
595 
596  // Contribution of cell unknowns, and then face unknowns on F
597  RHS_GT.block(r*dimPKcell, 0, dimPKcell, m_nlocal_cell_dofs) -= nTF(r) * MTT_on_F[ilF];
598  RHS_GT.block(r*dimPKcell, offset_F, dimPKcell, m_nlocal_face_dofs) +=
599  nTF(r) * (MFT[ilF].topLeftCorner(m_nlocal_face_dofs, dimPKcell)).transpose();
600  }
601  }
602 
603  // Compute GT
604  Eigen::MatrixXd GT = (VecMTT.ldlt()).solve(RHS_GT);
605 
606  //------------- Consistent contribution (K GT, GT)_T to the local bilinear form ------//
607 
608 
609  // Weighted mass matrix (K Phi_i, Phi_j)_T of the basis (Phi_i)_i of (P^k)^d
610  // We start by creating K Phi_i at the quadrature nodes
611  boost::multi_array<VectorRd, 2> kappa_vec_phiT_quadT;
612  kappa_vec_phiT_quadT.resize( boost::extents[dimPKcell_vec][quadT.size()] );
613  for (size_t i = 0; i < dimPKcell_vec; i++){
614  for (size_t iqn = 0; iqn < quadT.size(); iqn++){
615  kappa_vec_phiT_quadT[i][iqn] = kappaT_quadT[iqn] * vec_phiT_quadT[i][iqn];
616  }
617  }
618  _itime[3] += timeint.elapsed().user + timeint.elapsed().system;
619  timeint.start();
620 
621  Eigen::MatrixXd kappaVecMTT = compute_gram_matrix(kappa_vec_phiT_quadT, vec_phiT_quadT, quadT, vec_phiT_quadT.shape()[0], vec_phiT_quadT.shape()[0], "sym");
622 
623  _itime[4] += timeint.elapsed().user + timeint.elapsed().system;
624  timeint.start();
625 
626  Eigen::MatrixXd ATF = GT.transpose() * kappaVecMTT * GT;
627 
628 
629  //------------- Compute PT, matrix of potential reconstruction, using GT ------//
630 
631  // We write that \nabla pT = projection on \nabla P^{k+1} of GT, and add the closure relation:
632  // (nabla pT v, nabla w)_T + lambda_T(p_T v,1)_T(w,1)_T = (GT v,\nabla w)_T + lambda_T(v_T,1)_T(w,1)_T
633 
634  // Right-hand side, starting with volumetric term
635  Eigen::MatrixXd RHS_PT = MdphiT_PhiT * GT;
636 
637  // Vector LT of (phi_j,1)_T for phi_j up to degree K+1, and LT^t*LT, for the closure relation
638  Eigen::VectorXd LT = (MTT.row(0)).transpose();
639  Eigen::MatrixXd LTtLT = LT * (LT.transpose());
640  double scalT = StiffT.trace() / std::pow(LT.norm(), 2);
641 
642  // Add closure relation and compute PT
643  RHS_PT.topLeftCorner(m_nhighorder_dofs, m_nlocal_cell_dofs) +=
644  scalT * LTtLT.topLeftCorner(m_nhighorder_dofs, m_nlocal_cell_dofs);
645  Eigen::MatrixXd PT = ((StiffT + scalT*LTtLT).ldlt()).solve(RHS_PT);
646 
647 
648  //-------------------- Compute stabilisation term sT ---------//
649 
650  Eigen::MatrixXd STF = Eigen::MatrixXd::Zero(local_dofs, local_dofs);
651 
652  // Cell residual delta_T^l = pi_T^l (rT uT) - u_T
653 
654  Eigen::MatrixXd MTT_LKp1 = MTT.topLeftCorner(m_nlocal_cell_dofs, m_nhighorder_dofs);
655  Eigen::MatrixXd MTT_LL = MTT.topLeftCorner(m_nlocal_cell_dofs, m_nlocal_cell_dofs);
656  Eigen::MatrixXd deltaTL = MTT_LL.ldlt().solve( MTT_LKp1 * PT );
657  deltaTL.topLeftCorner(m_nlocal_cell_dofs, m_nlocal_cell_dofs) -= Eigen::MatrixXd::Identity(m_nlocal_cell_dofs, m_nlocal_cell_dofs);
658 
659  for (size_t ilF = 0; ilF < nfacesT; ilF++) {
660  // Two options for stabilisation: diameter of face, or ratio measure cell/measure face
661  double dTF = cell->face(ilF)->diam();
662  // double dTF = cell->measure() / cell->face(ilF)->measure();
663 
664  VectorRd xF = cell->face(ilF)->center_mass();
665 
666  // auto kappa_TF = kappa(xF, cell).trace();
667 
668  const VectorRd &nTF = cell->face_normal(ilF);
669  const double kappa_TF = (kappa(xF, cell) * nTF).dot(nTF);
670 
671  // Face residual delta_TF^k = pi_F^k (rT uT) - u_F
672  Eigen::MatrixXd MFFinv = MFF[ilF].inverse();
673  Eigen::MatrixXd deltaTFK = MFFinv * MFT[ilF] * PT;
674  deltaTFK.block(0, m_nlocal_cell_dofs + ilF * m_nlocal_face_dofs, m_nlocal_face_dofs, m_nlocal_face_dofs) -=
675  Eigen::MatrixXd::Identity(m_nlocal_face_dofs, m_nlocal_face_dofs);
676 
677  // Stabilisation term: here, we actually project deltaTL on P^k(F) so, for l=k+1, it actually corresponds to the stabilisation used in HDG methods (see Section 5.1.6 of HHO book)
678  Eigen::MatrixXd deltaTFK_minus_deltaTL = deltaTFK - MFFinv * MFT[ilF].topLeftCorner(m_nlocal_face_dofs, m_nlocal_cell_dofs) * deltaTL;
679 
680  STF += (kappa_TF / dTF) * deltaTFK_minus_deltaTL.transpose() * MFF[ilF] * deltaTFK_minus_deltaTL;
681 
682  }
683 
684  _itime[5] += timeint.elapsed().user + timeint.elapsed().system;
685 
686 
687 
688  // Adjust local bilinear form with stabilisation term
689  ATF += mesh->dim() * STF;
690 
691  return ATF;
692 
693  }
694 
695 
696  //********************************
697  // local load term
698  //********************************
699 
700  Eigen::VectorXd HHO_LocVarDiff::load_operator(HybridCore &hho, const size_t iT, const ElementQuad &elquad) const {
701  // Load for the cell DOFs (first indices) and face DOFs (last indices)
702  const auto mesh = hho.get_mesh();
703  Cell* cell = mesh->cell(iT);
704  size_t cell_face_dofs = m_nlocal_cell_dofs + cell->n_faces()*m_nlocal_face_dofs;
705  Eigen::VectorXd b = Eigen::VectorXd::Zero(cell_face_dofs);
706 
707  // Quadrature nodes and values of cell basis functions at these nodes
708  auto quadT = elquad.get_quadT();
709  size_t nbq = quadT.size();
710  boost::multi_array<double, 2> phiT_quadT = elquad.get_phiT_quadT();
711 
712  // Value of source times quadrature weights at the quadrature nodes
713  Eigen::ArrayXd weight_source_quad = Eigen::ArrayXd::Zero(nbq);
714  for (size_t iqn = 0; iqn < nbq; iqn++){
715  weight_source_quad(iqn) = quadT[iqn].w * source(quadT[iqn].vector(), cell);
716  }
717 
718  for (size_t i=0; i < m_nlocal_cell_dofs; i++){
719  for (size_t iqn = 0; iqn < quadT.size(); iqn++){
720  b(i) += weight_source_quad[iqn] * phiT_quadT[i][iqn];
721  }
722  }
723 
724  // Boundary values, if we have a boundary cell
725  if (cell->is_boundary()){
726  // Boundary values only on boundary Neumann faces
727  for (size_t ilF = 0; ilF < cell->n_faces(); ilF++) {
728  Face* F = cell->face(ilF);
729  if (m_BC.type(*F)=="neu"){
730  const size_t iF = F->global_index();
731  // BC on boundary faces
732  if (F->is_boundary()){
733  // Offset for face unknowns
734  const size_t offset_F = m_nlocal_cell_dofs + ilF * m_nlocal_face_dofs;
735  // Normal to the face and bases function
736  const auto& nTF = cell->face_normal(ilF);
737  const auto& basisF = hho.FaceBasis(iF);
738  // for each DOF of the boundary face
739  for (size_t i = 0; i < m_nlocal_face_dofs; i++){
740  QuadratureRule quadF = generate_quadrature_rule(*F, 2*m_K+2);
741  std::function<double(VectorRd)> Kgrad_n = [&](VectorRd p){
742  return nTF.dot(kappa(p,cell) * grad_exact_solution(p,cell)) * basisF.function(i,p);
743  };
744  for (QuadratureNode& qF : quadF){
745  b(offset_F + i) += qF.w * Kgrad_n(qF.vector());
746  }
747  }
748  }
749  }
750  }
751  }
752 
753  return b;
754  }
755 
757  const auto mesh = hho.get_mesh();
758  double value = 0.0;
759 
760  for (size_t iT = 0; iT < mesh->n_cells(); iT++) {
761  Eigen::VectorXd XTF = Xh.restr(iT);
762  value += XTF.transpose() * aT[iT] * XTF;
763  }
764 
765  return sqrt(value);
766  }
767 
768 
770 } // end of namespace HArDCore3D
771 
772 #endif //_HHO_LOCVARDIFF_HPP
The BoundaryConditions class provides definition of boundary conditions.
Definition: BoundaryConditions.hpp:43
const std::string type(Face &face) const
Test the boundary condition of an face.
Definition: BoundaryConditions.cpp:31
const std::string name() const
Returns the complete name of the boundary condition.
Definition: BoundaryConditions.hpp:68
const size_t n_dir_faces() const
Returns the number of Dirichlet faces.
Definition: BoundaryConditions.hpp:63
Definition: elementquad.hpp:55
Family of functions expressed as linear combination of the functions of a given basis.
Definition: basis.hpp:388
The HHO_LocVarDiff class provides tools to implement the HHO method for the diffusion problem.
Definition: HHO_LocVarDiff.hpp:63
Definition: hybridcore.hpp:174
Definition: hybridcore.hpp:87
Class to describe a mesh.
Definition: MeshND.hpp:17
static boost::multi_array< typename detail::basis_evaluation_traits< BasisType, BasisFunction >::ReturnValue, 2 > compute(const BasisType &basis, const QuadratureRule &quad)
Generic basis evaluation.
Definition: basis.hpp:2189
FunctionValue function(size_t i, const VectorRd &x) const
Evaluate the i-th function at point x.
Definition: basis.hpp:427
std::function< T(const VectorRd &, const Cell *)> CellFType
type for function of point. T is the return type of the function
Definition: basis.hpp:57
std::function< T(const VectorRd &)> FType
type for function of point. T is the return type of the function
Definition: basis.hpp:54
size_t dimension() const
Dimension of the family. This is actually the number of functions in the family, not necessarily line...
Definition: basis.hpp:421
Eigen::MatrixXd compute_gram_matrix(const boost::multi_array< VectorRd, 2 > &B1, const boost::multi_array< double, 2 > &B2, const QuadratureRule &qr)
Compute the Gram-like matrix given a family of vector-valued and one of scalar-valued functions by te...
Definition: basis.cpp:339
static void parallel_for(unsigned nb_elements, std::function< void(size_t start, size_t end)> functor, bool use_threads=true)
Generic function to execute threaded processes.
Definition: parallel_for.hpp:58
bool use_threads
Definition: HHO_DiffAdvecReac.hpp:47
size_t L
Definition: HHO_DiffAdvecReac.hpp:46
size_t K
Definition: HHO_DiffAdvecReac.hpp:46
double get_solving_time() const
cpu time to solve the scheme
Definition: HHO_LocVarDiff.hpp:94
UVector solve(HybridCore &hho)
Definition: HHO_LocVarDiff.hpp:380
double EnergyNorm(HybridCore &hho, const UVector Xh)
Discrete energy norm (associated to the diffusion operator) of an hybrid function.
Definition: HHO_LocVarDiff.hpp:756
double get_solving_error() const
residual after solving the scheme
Definition: HHO_LocVarDiff.hpp:99
double get_assembly_time() const
cpu time to assemble the scheme
Definition: HHO_LocVarDiff.hpp:89
const size_t get_nlocal_face_dofs()
Number of DOFs on each face.
Definition: HHO_LocVarDiff.hpp:112
void assemble(HybridCore &hho)
Assemble and solve the scheme.
Definition: HHO_LocVarDiff.hpp:210
const size_t get_nhighorder_dofs()
Number of DOFs per cell for high-order (K+1) polynomials.
Definition: HHO_LocVarDiff.hpp:114
const size_t get_ntotal_cell_dofs()
Total number of cell DOFs over the entire mesh.
Definition: HHO_LocVarDiff.hpp:116
const size_t get_ntotal_dofs()
Total number of degrees of freedom over the entire mesh.
Definition: HHO_LocVarDiff.hpp:122
const size_t get_nlocal_cell_dofs()
Number of DOFs in each cell.
Definition: HHO_LocVarDiff.hpp:110
const size_t get_ndir_face_dofs()
Total number of face DOFs for Dirichlet faces.
Definition: HHO_LocVarDiff.hpp:120
double get_itime(size_t idx) const
various intermediate assembly times
Definition: HHO_LocVarDiff.hpp:104
const size_t get_ntotal_face_dofs()
Total number of face DOFs over the entire mesh.
Definition: HHO_LocVarDiff.hpp:118
HHO_LocVarDiff(HybridCore &hho, size_t K, int L, CellFType< MatrixRd > kappa, size_t deg_kappa, CellFType< double > source, BoundaryConditions BC, FType< double > exact_solution, CellFType< VectorRd > grad_exact_solution, std::string solver_type, bool use_threads, std::ostream &output=std::cout)
Constructor of the class.
Definition: HHO_LocVarDiff.hpp:179
size_t const DimPoly(int m)
Eigen::VectorXd compute_weights(size_t iT) const
Computes the weights to get cell values from face values when l=-1.
Definition: hybridcore.cpp:175
Eigen::VectorXd restr(size_t iT) const
Extract the restriction of the unknowns corresponding to cell iT and its faces.
Definition: hybridcore.cpp:28
const boost::multi_array< double, 2 > & get_phiT_quadF(size_t ilF) const
Returns values of cell basis functions at face quadrature nodes, for face with local number ilF.
Definition: elementquad.hpp:91
boost::multi_array< VectorRd, 2 > get_vec_phiT_quadT(size_t degree) const
Builds on the fly the values of vector cell basis functions at cell quadrature nodes....
Definition: elementquad.cpp:61
const boost::multi_array< VectorRd, 2 > & get_dphiT_quadT() const
Returns values of gradients of cell basis functions at cell quadrature nodes.
Definition: elementquad.hpp:85
const boost::multi_array< double, 2 > get_phiF_quadF(size_t ilF) const
Returns values of face basis functions at face quadrature nodes, for face with local number ilF.
Definition: elementquad.hpp:97
const PolyFaceBasisType & FaceBasis(size_t iF) const
Return face basis for face with global index iF.
Definition: hybridcore.hpp:215
const QuadratureRule & get_quadF(size_t ilF) const
Returns quadrature rules on face with local number ilF.
Definition: elementquad.hpp:73
const boost::multi_array< double, 2 > & get_phiT_quadT() const
Returns values of cell basis functions at cell quadrature nodes.
Definition: elementquad.hpp:79
const size_t DimPoly< Cell >(const int m)
Compute the size of the basis of 3-variate polynomials up to degree m.
Definition: hybridcore.hpp:61
Eigen::VectorXd & asVectorXd() const
Return the values as an Eigen vector.
Definition: hybridcore.hpp:97
UVector interpolate(const ContinuousFunction &f, const int deg_cell, const size_t deg_face, size_t doe) const
Compute the interpolant in the discrete space of a continuous function.
Definition: hybridcore.hpp:298
const QuadratureRule & get_quadT() const
Returns quadrature rules in cell.
Definition: elementquad.hpp:67
const Mesh * get_mesh() const
Returns a pointer to the mesh.
Definition: hybridcore.hpp:198
const PolyCellBasisType & CellBasis(size_t iT) const
Return cell basis for element with global index iT.
Definition: hybridcore.hpp:207
std::vector< Cell< dimension > * > get_cells() const
lists the cells in the mesh.
Definition: MeshND.hpp:88
std::size_t n_faces() const
number of faces in the mesh.
Definition: MeshND.hpp:59
Face< dimension > * face(std::size_t index) const
get a constant pointer to a face using its global index
Definition: MeshND.hpp:196
std::size_t n_cells() const
number of cells in the mesh.
Definition: MeshND.hpp:60
Cell< dimension > * cell(std::size_t index) const
get a constant pointer to a cell using its global index
Definition: MeshND.hpp:197
QuadratureRule generate_quadrature_rule(const Cell &T, const int doe, const bool force_split)
Generate quadrature rule on mesh element.
Definition: quadraturerule.cpp:11
std::vector< QuadratureNode > QuadratureRule
Definition: quadraturerule.hpp:61
Definition: ddr-magnetostatics.hpp:40
MeshND::VectorRd< 2 > VectorRd
Definition: Mesh2D.hpp:14
MeshND::Face< 2 > Face
Definition: Mesh2D.hpp:12
MeshND::Cell< 2 > Cell
Definition: Mesh2D.hpp:13
Description of one node and one weight from a quadrature rule.
Definition: quadraturerule.hpp:47