libMesh
claw_system.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2025 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 
4 // This library is free software; you can redistribute it and/or
5 // modify it under the terms of the GNU Lesser General Public
6 // License as published by the Free Software Foundation; either
7 // version 2.1 of the License, or (at your option) any later version.
8 
9 // This library is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 // Lesser General Public License for more details.
13 
14 // You should have received a copy of the GNU Lesser General Public
15 // License along with this library; if not, write to the Free Software
16 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 
18 // local includes
19 #include "claw_system.h"
20 
21 // LibMesh includes
22 #include "libmesh/dense_matrix.h"
23 #include "libmesh/dof_map.h"
24 #include "libmesh/equation_systems.h"
25 #include "libmesh/exodusII_io.h"
26 #include "libmesh/fe.h" // FEBase::buid()
27 #include "libmesh/fe_interface.h" // FEInterface::inverse_map()
28 #include "libmesh/getpot.h" // GetPot input file parsing
29 #include "libmesh/libmesh_logging.h" // LOG_SCOPE
30 #include "libmesh/linear_solver.h" // LinearSolver::reuse_preconditioner()
31 #include "libmesh/mesh_base.h" // MeshBase::active_local_element_ptr_range()
32 #include "libmesh/numeric_vector.h"
33 #include "libmesh/quadrature_gauss.h"
34 #include "libmesh/sparse_matrix.h"
35 
36 namespace libMesh
37 {
38 
40  const std::string & name,
41  const unsigned int number)
42  : Parent(es, name, number),
43  _mass_matrix(SparseMatrix<Number>::build(es.comm())),
44  _jump_matrix(SparseMatrix<Number>::build(es.comm())),
45  _temporal_discretization_type(ForwardEuler),
46  _delta_t(0.),
47  _n_time_steps(0),
48  _LxF_constant(1.),
49  _write_interval(1),
50  _time(0.)
51 {
52  // set assemble_before_solve flag to false
53  // so that we control matrix assembly.
54  this->assemble_before_solve = false;
55 
56 }
57 
58 
59 ClawSystem::~ClawSystem() = default;
60 
61 std::string ClawSystem::system_type () const
62 {
63  return "ClawSystem";
64 }
65 
67 ClawSystem::string_to_enum(std::string string_type)
68 {
69  if (string_type == "ForwardEuler")
70  return ForwardEuler;
71 
72  if (string_type == "RK4")
73  return RK4;
74 
75  // If we got here, there must be an error
76  libmesh_error_msg("Error: Invalid temporal discretization");
77 }
78 
79 std::string
81 {
82  switch (enum_type)
83  {
84  case ForwardEuler:
85  return "ForwardEuler";
86 
87  case RK4:
88  return "RK4";
89 
90  default:
91  libmesh_error_msg("Error: Invalid temporal discretization");
92  }
93 }
94 
96 {
97  // Set the ImplicitSystem "matrix" equal to the mass matrix. This is
98  // the only part of the explicit method that requires a "solve()",
99  // however we save the LU factorization from this and apply it
100  // during subsequent solve() steps.
101  this->matrix->zero();
102  this->matrix->close();
103  this->matrix->add(1., *_mass_matrix);
104 
105  this->set_time(0.);
106 
107  // For the first solve, make sure we generate a new preconditioner
108  linear_solver->reuse_preconditioner(false);
109 
110  auto old_solution = NumericVector<Number>::build(this->comm());
111  old_solution->init (this->n_dofs(), this->n_local_dofs(), false, libMeshEnums::PARALLEL);
112 
113  // Temporary and "stage" vectors - only initialized if doing RK timestepping
114  std::vector<std::unique_ptr<NumericVector<Number>>> k(4);
115  std::unique_ptr<NumericVector<Number>> temp;
117  {
118  temp = NumericVector<Number>::build(this->comm());
119  temp->init (this->n_dofs(), this->n_local_dofs(), false, libMeshEnums::PARALLEL);
120 
121  for (auto & vec : k)
122  {
123  vec = NumericVector<Number>::build(this->comm());
124  vec->init (this->n_dofs(), this->n_local_dofs(), false, libMeshEnums::PARALLEL);
125  }
126  }
127 
128  // Begin time stepping loop
129  for (unsigned int step=1; step<=_n_time_steps; step++)
130  {
131  *old_solution = *solution;
132 
134  {
135  case ForwardEuler:
136  {
137  this->assemble_claw_rhs(*old_solution);
138 
139  // For explicit timestepping schemes, the solve() step
140  // "inverts" the mass matrix and then back substitutes the rhs
141  // to update the solution. In practice, the mass matrix
142  // factorization is computed only once and then the factors
143  // are re-used at each step.
144  this->solve();
145 
146  // Tell the system to reuse the preconditioner (for the mass matrix)
147  if (step == 1)
148  linear_solver->reuse_preconditioner(true);
149 
150  solution->scale(_delta_t);
151  solution->add(*old_solution);
152 
153  break;
154  }
155 
156  case RK4:
157  {
158  // Compute stage 1 (explicit Euler step)
159  this->assemble_claw_rhs(*old_solution);
160  this->solve();
161  *k[0] = *solution;
162 
163  // Tell the system to reuse the preconditioner (for the mass matrix)
164  if (step==1)
165  linear_solver->reuse_preconditioner(true);
166 
167  // Compute stage 2
168  *temp = *old_solution;
169  temp->add(0.5*_delta_t, *k[0]);
170  this->assemble_claw_rhs(*temp);
171  this->solve();
172  *k[1] = *solution;
173 
174  // Compute stage 3
175  *temp = *old_solution;
176  temp->add(0.5*_delta_t, *k[1]);
177  this->assemble_claw_rhs(*temp);
178  this->solve();
179  *k[2] = *solution;
180 
181  // Compute stage 4
182  *temp = *old_solution;
183  temp->add(_delta_t, *k[2]);
184  this->assemble_claw_rhs(*temp);
185  this->solve();
186  *k[3] = *solution;
187 
188  // The updated solution is now a linear combination of the 4 "stages"
189  // u_{n+1} = u_n + dt*(k1/6 + k2/3 + k3/3 + k4/6)
190  *solution = *old_solution;
191  solution->add(_delta_t/6., *k[0]);
192  solution->add(_delta_t/3., *k[1]);
193  solution->add(_delta_t/3., *k[2]);
194  solution->add(_delta_t/6., *k[3]);
195 
196  break;
197  }
198 
199  default:
200  libmesh_error_msg("Error: Invalid temporal discretization in ClawSystem::solve_conservation_law");
201  }
202 
203  this->update();
204 
205  this->set_time(_time + _delta_t);
206 
207 #ifdef LIBMESH_HAVE_EXODUS_API
208  if (step % get_write_interval() == 0)
209  {
210  libMesh::out << std::endl << "plotting time step " << step << ", time = " << _time << std::endl;
211 
212  std::ostringstream file_name;
213  file_name << "claw_solution."
214  << std::setw(4)
215  << std::right
216  << std::setfill('0')
217  << step
218  << ".e";
219 
220  // We are writing to a separate file so we write each step as step 1.
221  // It would be better to append each new solution to an existing file...
223  file_name.str(), this->get_equation_systems(), /*step=*/1, _time);
224  }
225 #endif
226  } // end time stepping loop
227 
228  linear_solver->reuse_preconditioner(false);
229 }
230 
231 void ClawSystem::process_parameters_file (const std::string& parameters_filename)
232 {
233  // First read in data from parameters_filename
234  GetPot infile(parameters_filename);
235 
236  const std::string temporal_discretization_type_in = infile("temporal_discretization_type", "ForwardEuler");
237  const Real LxF_constant_in = infile("LxF_constant", 1.);
238  const Real delta_t_in = infile("delta_t", 0.05);
239  const unsigned int n_time_steps_in = infile("n_time_steps", 0);
240  const unsigned int write_interval_in = infile("write_interval", 1);
241 
242  this->set_temporal_discretization_type(ClawSystem::string_to_enum(temporal_discretization_type_in));
243  this->set_LxF_constant(LxF_constant_in);
244  this->set_delta_t(delta_t_in);
245  this->set_n_time_steps(n_time_steps_in);
246  this->set_write_interval(write_interval_in);
247 }
248 
250 {
251  // Print out info that describes the current setup
252  libMesh::out << std::endl << "==== ClawSystem ====" << std::endl;
253  libMesh::out << "system name: " << this->name() << std::endl;
254  libMesh::out << "LxF constant: " << get_LxF_constant() << std::endl;
255  libMesh::out << "delta_t: " << get_delta_t() << std::endl;
256  libMesh::out << "n_time_steps: " << get_n_time_steps() << std::endl;
257  libMesh::out << "temporal_discretization_type: " << ClawSystem::enum_to_string(this->get_temporal_discretization_type()) << std::endl;
258  libMesh::out << "write_interval: " << get_write_interval() << std::endl;
259  libMesh::out << std::endl;
260 }
261 
263 {
264  this->assemble_mass_matrix();
269 }
270 
272 {
273  LOG_SCOPE("assemble_mass_matrix()", "ClawSystem");
274 
275  // clear the mass matrix before filling
276  _mass_matrix->zero();
277 
278  // Convenient reference to the Mesh
279  const MeshBase & mesh = this->get_mesh();
280  const unsigned int mesh_dim = mesh.mesh_dimension();
281 
282  // Construct an FE object to be used for assembly
283  FEType fe_type = this->variable_type(0);
284  std::unique_ptr<FEBase> fe = FEBase::build(mesh_dim, fe_type);
285 
286  // Quadrature rules for numerical integration.
287  QGauss qrule (mesh_dim, fe_type.default_quadrature_order());
288  fe->attach_quadrature_rule (&qrule);
289 
290  // Pre-request JxW and shape function values at interior quadrature points.
291  const std::vector<Real> & JxW = fe->get_JxW();
292  const std::vector<std::vector<Real>> & phi = fe->get_phi();
293 
294  // Element mass matrix
296 
297  // DofMap reference and vector used for getting dof_indices on each Elem
298  const DofMap & dof_map = get_dof_map();
299  std::vector<dof_id_type> dof_indices;
300 
301  for (const auto & elem : mesh.active_local_element_ptr_range())
302  {
303  // Recompute shape function values, etc. on current Elem
304  fe->reinit(elem);
305 
306  // Get DOFs for current Elem
307  dof_map.dof_indices (elem, dof_indices);
308 
309  // The number of degrees of freedom on this element for variable 0.
310  const unsigned int n_q1_dofs = dof_indices.size();
311 
312  // Resize the Elem mass matrix for the current Elem
313  Ke.resize(n_q1_dofs, n_q1_dofs);
314 
315  // The number of quadrature points on the current Elem
316  unsigned int n_qpoints = qrule.n_points();
317 
318  // Assemble element mass matrix. Here we loop over vars but assume that
319  // every var has the same number of DOFs as variable 0.
320  for (auto qp : make_range(n_qpoints))
321  for (auto i : make_range(n_q1_dofs))
322  for (auto j : make_range(n_q1_dofs))
323  Ke(i,j) += JxW[qp] * phi[j][qp] * phi[i][qp];
324 
325  // Apply constraints, e.g. periodic constraints and "stamp" the
326  // local matrix into the global matrix
327  this->get_dof_map().constrain_element_matrix(Ke, dof_indices);
328  _mass_matrix->add_matrix (Ke, dof_indices);
329  }
330 
331  // Close the mass matrix now that we are done with assembly
332  _mass_matrix->close();
333 }
334 
336 {
337  LOG_SCOPE("assemble_advection_matrices()", "ClawSystem");
338 
339  // Zero all advection matrices before filling
340  for (auto & mat : _advection_matrices)
341  mat->zero();
342 
343  // Convenient reference to the Mesh
344  const MeshBase & mesh = this->get_mesh();
345  const unsigned int mesh_dim = mesh.mesh_dimension();
346 
347  // Check that the mesh dimension matches the number of advection
348  // matrices we are going to assemble.
349  libmesh_error_msg_if(
350  mesh_dim != _advection_matrices.size(),
351  "Error: Expected number of advection matrices to match mesh dimension.");
352 
353  // Construct an FE object to be used for assembly
354  FEType fe_type = this->variable_type(0);
355  std::unique_ptr<FEBase> fe = FEBase::build(mesh_dim, fe_type);
356 
357  // Quadrature rules for numerical integration.
358  QGauss qrule (mesh_dim, fe_type.default_quadrature_order());
359  fe->attach_quadrature_rule (&qrule);
360 
361  // Pre-request JxW and shape function values/derivatives at interior quadrature points
362  const std::vector<Real> & JxW = fe->get_JxW();
363  const std::vector<std::vector<Real>> & phi = fe->get_phi();
364  const std::vector<std::vector<RealGradient>> & dphi = fe->get_dphi();
365 
366  // We are going to build a separate element advection matrix for each direction.
367  std::vector<DenseMatrix<Number>> K_vec(mesh_dim);
368 
369  // DofMap reference and vector used for getting dof_indices on each Elem
370  const DofMap & dof_map = get_dof_map();
371  std::vector<dof_id_type> dof_indices;
372 
373  for (const auto & elem : mesh.active_local_element_ptr_range())
374  {
375  // Recompute shape function values, etc. on current Elem
376  fe->reinit(elem);
377 
378  // Get DOFs for current Elem
379  dof_map.dof_indices (elem, dof_indices);
380 
381  // The number of degrees of freedom on this element
382  const unsigned int n_q1_dofs = dof_indices.size();
383 
384  // Resize all the Elem advection matrices for the current Elem
385  for (auto & Ke : K_vec)
386  Ke.resize(n_q1_dofs, n_q1_dofs);
387 
388  // The number of quadrature points on the current Elem
389  unsigned int n_qpoints = qrule.n_points();
390 
391  // Assemble the convection operators. There are two separate
392  // operators in 2D (one for x-direction and one for y-direction).
393  // Note: in the FV (CONSTANT, MONOMIAL) case, these interior
394  // contributions are all zero because the spatial derivatives of
395  // the single constant "basis function" are all zero.
396  for (auto qp : make_range(n_qpoints))
397  for (auto i : make_range(n_q1_dofs))
398  for (auto j : make_range(n_q1_dofs))
399  for (auto dim : make_range(mesh_dim))
400  K_vec[dim](i,j) += JxW[qp] * phi[j][qp] * dphi[i][qp](dim);
401 
402  // Apply constraints to Elem matrices and "stamp" into the global matrices
403  for (auto dim : make_range(mesh_dim))
404  {
405  dof_map.constrain_element_matrix(K_vec[dim], dof_indices);
406  _advection_matrices[dim]->add_matrix(K_vec[dim], dof_indices);
407  }
408  }
409 
410  // Close all advection matrices now that we are done assembling
411  for (auto & mat : _advection_matrices)
412  mat->close();
413 }
414 
416 {
417  LOG_SCOPE("assemble_avg_coupling_matrices()", "ClawSystem");
418 
419  // Zero all average coupling matrices before filling
420  for (auto & mat : _avg_matrices)
421  mat->zero();
422 
423  // Convenient reference to the Mesh
424  const MeshBase & mesh = this->get_mesh();
425  const unsigned int mesh_dim = mesh.mesh_dimension();
426 
427  // Check that the mesh dimension matches the number of average coupling
428  // matrices we are going to assemble.
429  libmesh_error_msg_if(
430  mesh_dim != _avg_matrices.size(),
431  "Error: Expected number of average coupling matrices to match mesh dimension.");
432 
433  // Construct an side FE objects for both "current" and "neighbor" Elems
434  FEType fe_type = this->variable_type(0);
435  std::unique_ptr<FEBase> fe_elem_face = FEBase::build(mesh_dim, fe_type);
436  std::unique_ptr<FEBase> fe_neighbor_face =FEBase::build(mesh_dim, fe_type);
437 
438  // Quadrature rules for numerical integration.
439  QGauss qface(mesh_dim-1, fe_type.default_quadrature_order());
440  fe_elem_face->attach_quadrature_rule(&qface);
441  fe_neighbor_face->attach_quadrature_rule(&qface);
442 
443  // Here we define some references to cell-specific data that
444  // will be used to assemble the linear system.
445  // Data for surface integrals on the element boundary
446  const std::vector<std::vector<Real>> & phi_face = fe_elem_face->get_phi();
447  const std::vector<Real> & JxW_face = fe_elem_face->get_JxW();
448  const std::vector<Point> & qface_normals = fe_elem_face->get_normals();
449  const std::vector<Point> & qface_points = fe_elem_face->get_xyz();
450 
451  // Data for surface integrals on the neighbor boundary
452  const std::vector<std::vector<Real>> & phi_neighbor_face = fe_neighbor_face->get_phi();
453 
454  // Data structures to contain the element/neighbor contributions on
455  // interior faces. We store these separately for each space dimension.
456  std::vector<DenseMatrix<Number>> Kne_vec(mesh_dim);
457  std::vector<DenseMatrix<Number>> Ken_vec(mesh_dim);
458  std::vector<DenseMatrix<Number>> Kee_vec(mesh_dim);
459  std::vector<DenseMatrix<Number>> Knn_vec(mesh_dim);
460 
461  // DofMap reference and vectors used for getting dof_indices on each Elem
462  const DofMap & dof_map = get_dof_map();
463  std::vector<dof_id_type> dof_indices;
464  std::vector<dof_id_type> neighbor_dof_indices;
465 
466  for (const auto * elem : mesh.active_local_element_ptr_range())
467  {
468  dof_map.dof_indices (elem, dof_indices);
469  const unsigned int n_dofs = dof_indices.size();
470 
471  for (auto side : elem->side_index_range())
472  {
473  // Skip over boundary sides
474  if (elem->neighbor_ptr(side) != nullptr)
475  {
476  const Elem * neighbor = elem->neighbor_ptr(side);
477 
478  // Get the global id of the element and the neighbor
479  const unsigned int elem_id = elem->id();
480  const unsigned int neighbor_id = neighbor->id();
481 
482  // Compare element IDs since we don't want to compute the same contribution twice
483  if (elem_id < neighbor_id)
484  {
485  // Pointer to the element side
486  std::unique_ptr<const Elem> elem_side = elem->build_side_ptr(side);
487 
488  // Reinitialize shape functions on the element side
489  fe_elem_face->reinit(elem, side);
490 
491  // Find their locations on the neighbor
492  std::vector<Point> qface_neighbor_points;
494  neighbor->dim(),
495  neighbor,
496  /*physical_points*/qface_points,
497  /*reference_points*/qface_neighbor_points);
498 
499  // Calculate the neighbor element shape functions at those locations
500  fe_neighbor_face->reinit(neighbor, &qface_neighbor_points);
501 
502  // Get the degree of freedom indices for the
503  // neighbor. These define where in the global
504  // matrix this neighbor will contribute to.
505  dof_map.dof_indices (neighbor, neighbor_dof_indices);
506  const unsigned int n_neighbor_dofs = neighbor_dof_indices.size();
507 
508  // Zero the element matrices summing into them. We use the
509  // resize member here because the number of degrees of
510  // freedom might have changed from the last element or
511  // neighbor. Note that Kne and Ken are not square matrices
512  // if neighbor and element have a different p level
513  for (auto dim : make_range(mesh_dim))
514  {
515  Kne_vec[dim].resize(n_neighbor_dofs, n_dofs);
516  Ken_vec[dim].resize(n_dofs, n_neighbor_dofs);
517  Kee_vec[dim].resize(n_dofs, n_dofs);
518  Knn_vec[dim].resize(n_neighbor_dofs, n_neighbor_dofs);
519  }
520 
521  // Now we will build the "average" contribution due to the
522  // Lax-Friedrichs numerical flux term:
523  //
524  // "{{f(q)}}[v] \cdot n_e"
525  //
526  // For the advection problem, this is term is given by:
527  // 0.5 * ((\vec{u} q)_e + (\vec{u} q)_n) \cdot \hat{n}_e * [v]
528  //
529  // The assumption below is that the advective velocity is
530  // constant on the entire mesh, so it is pulled outside of
531  // the integration loop, hence the reason it does not appear
532  // explicitly below.
533  for (auto qp : make_range(qface.n_points()))
534  {
535  // Kee Matrix.
536  // This is the "q_e" contribution with v = phi^e_i.
537  for (auto i : make_range(n_dofs))
538  for (auto j : make_range(n_dofs))
539  for (auto dim : make_range(mesh_dim))
540  Kee_vec[dim](i,j) += 0.5 * JxW_face[qp] * phi_face[i][qp] * phi_face[j][qp] * qface_normals[qp](dim);
541 
542  // Knn Matrix.
543  // This is the "q_n" contribution with v = -phi^n_i.
544  // Hence, there is a negative sign.
545  for (auto i : make_range(n_neighbor_dofs))
546  for (auto j : make_range(n_neighbor_dofs))
547  for (auto dim : make_range(mesh_dim))
548  Knn_vec[dim](i,j) += -0.5 * JxW_face[qp] * phi_neighbor_face[i][qp] * phi_neighbor_face[j][qp] * qface_normals[qp](dim);
549 
550  // Kne Matrix.
551  // This is the "q_e" contribution with v = -phi^n_i.
552  // i goes over neighbor DOFs
553  // j goes over self DOFs
554  // Hence, there is a negative sign.
555  for (auto i : make_range(n_neighbor_dofs))
556  for (auto j : make_range(n_dofs))
557  for (auto dim : make_range(mesh_dim))
558  Kne_vec[dim](i,j) += -0.5 * JxW_face[qp] * phi_neighbor_face[i][qp] * phi_face[j][qp] * qface_normals[qp](dim);
559 
560  // Ken Matrix.
561  // This is the "q_n" contribution with v = phi^e_i.
562  // i goes over self DOFs
563  // j goes over neighbor DOFs
564  for (auto i : make_range(n_dofs))
565  for (auto j : make_range(n_neighbor_dofs))
566  for (auto dim : make_range(mesh_dim))
567  Ken_vec[dim](i,j) += 0.5 * JxW_face[qp] * phi_face[i][qp] * phi_neighbor_face[j][qp] * qface_normals[qp](dim);
568  }
569 
570  // The element and neighbor boundary matrix are now built
571  // for this side. Add them to the appropriate global matrices now.
572  for (auto dim : make_range(mesh_dim))
573  {
574  auto & mat = _avg_matrices[dim];
575  mat->add_matrix(Kne_vec[dim], neighbor_dof_indices, dof_indices);
576  mat->add_matrix(Ken_vec[dim], dof_indices, neighbor_dof_indices);
577  mat->add_matrix(Kee_vec[dim], dof_indices);
578  mat->add_matrix(Knn_vec[dim], neighbor_dof_indices);
579  }
580  }
581  }
582  }
583  }
584 
585  // Close all average coupling matrices now that we are done assembling
586  for (auto & mat : _avg_matrices)
587  mat->close();
588 }
589 
591 {
592  LOG_SCOPE("assemble_jump_coupling_matrix()", "ClawSystem");
593 
594  // clear the matrix
595  _jump_matrix->zero();
596 
597  const MeshBase & mesh = this->get_mesh();
598  const unsigned int mesh_dim = mesh.mesh_dimension();
599 
600  // Construct face FE objects for both "current" and "neighbor" Elems
601  FEType fe_type = variable_type(0);
602  std::unique_ptr<FEBase> fe_elem_face = FEBase::build(mesh_dim, fe_type);
603  std::unique_ptr<FEBase> fe_neighbor_face = FEBase::build(mesh_dim, fe_type);
604 
605  // Quadrature rules for numerical integration.
606  QGauss qface(mesh_dim-1, fe_type.default_quadrature_order());
607  fe_elem_face->attach_quadrature_rule(&qface);
608  fe_neighbor_face->attach_quadrature_rule(&qface);
609 
610  // Here we define some references to cell-specific data that
611  // will be used to assemble the linear system.
612  // Data for surface integrals on the element boundary
613  const std::vector<std::vector<Real>> & phi_face = fe_elem_face->get_phi();
614  const std::vector<Real> & JxW_face = fe_elem_face->get_JxW();
615  const std::vector<Point> & qface_points = fe_elem_face->get_xyz();
616 
617  // Data for surface integrals on the neighbor boundary
618  const std::vector<std::vector<Real>> & phi_neighbor_face = fe_neighbor_face->get_phi();
619 
620  // Data structures to contain the element and neighbor boundary matrix
621  // contribution. This matrices will do the coupling beetwen the dofs of
622  // the element and those of his neighbors.
623  // Ken: matrix coupling elem and neighbor dofs
628 
629  // DofMap reference and vectors used for storing dof_indices
630  const DofMap & dof_map = get_dof_map();
631  std::vector<dof_id_type> dof_indices;
632  std::vector<dof_id_type> neighbor_dof_indices;
633 
634  for (const auto & elem : mesh.active_local_element_ptr_range())
635  {
636  dof_map.dof_indices (elem, dof_indices);
637  const unsigned int n_dofs = dof_indices.size();
638 
639  for (auto side : elem->side_index_range())
640  {
641  // Skip over boundary sides
642  if (elem->neighbor_ptr(side) != nullptr)
643  {
644  const Elem * neighbor = elem->neighbor_ptr(side);
645 
646  // Get the global id of the element and the neighbor
647  const unsigned int elem_id = elem->id();
648  const unsigned int neighbor_id = neighbor->id();
649 
650  // Compare element IDs since we don't want to compute the same contribution twice
651  if (elem_id < neighbor_id)
652  {
653  // Pointer to the element side
654  std::unique_ptr<const Elem> elem_side = elem->build_side_ptr(side);
655 
656  // Reinitialize shape functions on the element side
657  fe_elem_face->reinit(elem, side);
658 
659  // Find side QP locations in neighbor side reference space
660  std::vector<Point> qface_neighbor_points;
662  neighbor->dim(),
663  neighbor,
664  /*physical_points*/qface_points,
665  /*reference_points*/qface_neighbor_points);
666 
667  // Calculate the neighbor element shape functions at those locations
668  fe_neighbor_face->reinit(neighbor, &qface_neighbor_points);
669 
670  // Get the degree of freedom indices for the
671  // neighbor. These define where in the global
672  // matrix this neighbor will contribute to.
673  dof_map.dof_indices (neighbor, neighbor_dof_indices);
674  const unsigned int n_neighbor_dofs = neighbor_dof_indices.size();
675 
676  // Zero the element and neighbor side matrix before
677  // summing them. We use the resize member here because
678  // the number of degrees of freedom might have changed from
679  // the last element or neighbor.
680  // Note that Kne and Ken are not square matrices if neighbor
681  // and element have a different p level
682  Kne.resize (n_neighbor_dofs, n_dofs);
683  Ken.resize (n_dofs, n_neighbor_dofs);
684  Kee.resize (n_dofs, n_dofs);
685  Knn.resize (n_neighbor_dofs, n_neighbor_dofs);
686 
687  // Now we will build the element and neighbor
688  // boundary matrices. This involves
689  // a double loop to integrate the test funcions
690  // (i) against the trial functions (j).
691  for (auto qp : make_range(qface.n_points()))
692  {
693  // Kee Matrix. This is the "q_e" contribution from the [q][v] term with v = phi^e_i
694  // (q_e - q_n) * phi^e_i
695  for (auto i : make_range(n_dofs))
696  for (auto j : make_range(n_dofs))
697  Kee(i,j) += 0.5 * JxW_face[qp] * phi_face[i][qp] * phi_face[j][qp];
698 
699  // Knn Matrix. This is the "q_n" contribution from the [q][v] term with v = -phi^n_i
700  // (q_e - q_n) * -phi^n_i
701  for (auto i : make_range(n_neighbor_dofs))
702  for (auto j : make_range(n_neighbor_dofs))
703  Knn(i,j) += 0.5 * JxW_face[qp] * phi_neighbor_face[i][qp] * phi_neighbor_face[j][qp];
704 
705  // Kne Matrix. This is the "q_e" contribution from the [q][v] term with v = -phi^n_i,
706  // so there is a negative sign.
707  // (q_e - q_n) * -phi^n_i
708  for (auto i : make_range(n_neighbor_dofs))
709  for (auto j : make_range(n_dofs))
710  Kne(i,j) -= 0.5 * JxW_face[qp] * phi_neighbor_face[i][qp] * phi_face[j][qp];
711 
712  // Ken Matrix. This is the "q_n" contribution from the [q][v] term with v = phi^e_i,
713  // so there is a negative sign.
714  // (q_e - q_n) * phi^e_i
715  for (auto i : make_range(n_dofs))
716  for (auto j : make_range(n_neighbor_dofs))
717  Ken(i,j) -= 0.5 * JxW_face[qp] * phi_face[i][qp] * phi_neighbor_face[j][qp];
718  }
719 
720  // The element and neighbor boundary matrix are now built
721  // for this side. Add them to the global matrix
722  // The \p SparseMatrix::add_matrix() members do this for us.
723  _jump_matrix->add_matrix(Kne, neighbor_dof_indices, dof_indices);
724  _jump_matrix->add_matrix(Ken, dof_indices, neighbor_dof_indices);
725  _jump_matrix->add_matrix(Kee, dof_indices);
726  _jump_matrix->add_matrix(Knn, neighbor_dof_indices);
727  }
728  }
729  }
730  }
731 
732  // Close the matrix now that we are done assembling it
733  _jump_matrix->close();
734 }
735 
737 {
738  LOG_SCOPE("assemble_boundary_condition_matrices()", "ClawSystem");
739 
740  // Zero all boundary condition matrices before filling
741  for (auto & mat : _boundary_condition_matrices)
742  mat->zero();
743 
744  // Construct FE objects for the cell and side domains
745  const MeshBase & mesh = this->get_mesh();
746  const unsigned int mesh_dim = mesh.mesh_dimension();
747 
748  // Check that the mesh dimension matches the number of boundary
749  // condition matrices we are going to assemble.
750  libmesh_error_msg_if(
751  mesh_dim != _boundary_condition_matrices.size(),
752  "Error: Expected number of boundary condition matrices to match mesh dimension.");
753 
754  // Construct side FE object to be used for assembly
755  FEType fe_type = variable_type(0);
756  std::unique_ptr<FEBase> fe_elem_face = FEBase::build(mesh_dim, fe_type);
757  QGauss qface(mesh_dim-1, fe_type.default_quadrature_order());
758 
759  // Tell the face finite element object to use our quadrature rule.
760  fe_elem_face->attach_quadrature_rule(&qface);
761 
762  // Here we define some references to cell-specific data that
763  // will be used to assemble the linear system.
764  // Data for surface integrals on the element boundary
765  const std::vector<std::vector<Real>> & phi_face = fe_elem_face->get_phi();
766  const std::vector<Real> & JxW_face = fe_elem_face->get_JxW();
767  const std::vector<Point> & qface_normals = fe_elem_face->get_normals();
768 
769  // We are going to build a separate element boundary condition matrix for each direction.
770  std::vector<DenseMatrix<Number>> K_vec(mesh_dim);
771 
772  // DofMap reference and vector used for getting dof_indices on each Elem
773  const DofMap & dof_map = get_dof_map();
774  std::vector<dof_id_type> dof_indices;
775 
776  for (const auto & elem : mesh.active_local_element_ptr_range())
777  {
778  dof_map.dof_indices (elem, dof_indices);
779  const unsigned int n_dofs = dof_indices.size();
780 
781  for (auto side : elem->side_index_range())
782  {
783  // skip interior sides
784  if (elem->neighbor_ptr(side) == nullptr)
785  {
786  // Reinitialize shape functions on the element side
787  fe_elem_face->reinit(elem, side);
788 
789  // Resize all the Elem boundary condition matrices for the current Elem
790  for (auto & Ke : K_vec)
791  Ke.resize(n_dofs, n_dofs);
792 
793  // Now we will build the element and neighbor
794  // boundary matrices. This involves
795  // a double loop to integrate the test funcions
796  // (i) against the trial functions (j).
797  for (auto qp : make_range(qface.n_points()))
798  for (auto i : make_range(n_dofs))
799  for (auto j : make_range(n_dofs))
800  for (auto dim : make_range(mesh_dim))
801  K_vec[dim](i,j) += JxW_face[qp] * phi_face[i][qp] * phi_face[j][qp] * qface_normals[qp](dim);
802 
803  // "Stamp" Elem boundary condition matrices into the global boundary condition matrices
804  for (auto dim : make_range(mesh_dim))
805  _boundary_condition_matrices[dim]->add_matrix(K_vec[dim], dof_indices);
806  }
807  }
808  }
809 
810  // Close all boundary condition matrices now that we are done assembling
811  for (auto & mat : _boundary_condition_matrices)
812  mat->close();
813 }
814 
817 {
818  return *_mass_matrix;
819 }
820 
823 {
824  libmesh_error_msg_if(
825  dim >= _advection_matrices.size(),
826  "Error: Invalid dimension " << dim << " requested.");
827 
828  return *_advection_matrices[dim];
829 }
830 
833 {
834  libmesh_error_msg_if(
835  dim >= _avg_matrices.size(),
836  "Error: Invalid dimension " << dim << " requested.");
837 
838  return *_avg_matrices[dim];
839 }
840 
843 {
844  return *_jump_matrix;
845 }
846 
849 {
850  libmesh_error_msg_if(
852  "Error: Invalid dimension " << dim << " requested.");
853 
855 }
856 
857 void
859 {
860  _delta_t = delta_t_in;
861 }
862 
863 Real
865 {
866  return _delta_t;
867 }
868 
869 void
870 ClawSystem::set_n_time_steps(unsigned int n_time_steps_in)
871 {
872  _n_time_steps = n_time_steps_in;
873 }
874 
875 unsigned int
877 {
878  return _n_time_steps;
879 }
880 
881 void
883 {
884  _LxF_constant = LxF_constant_in;
885 }
886 
887 Real
889 {
890  return _LxF_constant;
891 }
892 
893 void
895 {
897 }
898 
901 {
903 }
904 
905 void
906 ClawSystem::set_write_interval(unsigned int write_interval_in)
907 {
908  _write_interval = write_interval_in;
909 }
910 
911 unsigned int
913 {
914  return _write_interval;
915 }
916 
917 void
919 {
920  _time = time_in;
921 }
922 
923 Real
925 {
926  return _time;
927 }
928 
930 {
931  _mass_matrix->print_matlab("mass_matrix.m");
932  _jump_matrix->print_matlab("jump_matrix.m");
933 
934  for (auto i : index_range(_avg_matrices))
935  _avg_matrices[i]->print_matlab("avg_matrix_" + std::to_string(i+1) + ".m");
936 
937  for (auto i : index_range(_advection_matrices))
938  _advection_matrices[i]->print_matlab("advection_matrix_" + std::to_string(i+1) + ".m");
939 
941  _boundary_condition_matrices[i]->print_matlab("boundary_condition_matrix_" + std::to_string(i+1) + ".m");
942 }
943 
945 {
947 
948  // Allocate SparseMatrices before attaching them to the DofMap so
949  // that they all use the same sparsity pattern.
950  for (unsigned int i=0; i<2; ++i)
951  {
953  _avg_matrices.push_back(SparseMatrix<Number>::build(this->comm()));
955  }
956 
957  DofMap & dof_map = this->get_dof_map();
958 
959  // Helper lambda function that attaches the input matrix to the
960  // DofMap, initializes it, and finally zeros it.
961  auto prepare_matrix = [&](SparseMatrix<Number> & mat)
962  {
963  dof_map.attach_matrix(mat);
964  mat.init();
965  mat.zero();
966  };
967 
968  prepare_matrix(*_mass_matrix);
969 
970  for (auto & mat : _advection_matrices)
971  prepare_matrix(*mat);
972 
973  for (auto & mat : _avg_matrices)
974  prepare_matrix(*mat);
975 
976  prepare_matrix(*_jump_matrix);
977 
978  for (auto & mat : _boundary_condition_matrices)
979  prepare_matrix(*mat);
980 }
981 
982 } // namespace libMesh
std::string name(const ElemQuality q)
This function returns a string containing some name for q.
Definition: elem_quality.C:42
std::vector< std::unique_ptr< SparseMatrix< Number > > > _avg_matrices
The "flux average" element boundary matrices.
Definition: claw_system.h:185
class FEType hides (possibly multiple) FEFamily and approximation orders, thereby enabling specialize...
Definition: fe_type.h:196
void set_n_time_steps(unsigned int n_time_steps_in)
Set/get the number of time-steps.
Definition: claw_system.C:870
std::unique_ptr< SparseMatrix< Number > > _jump_matrix
The "jump" element boundary matrix.
Definition: claw_system.h:190
This is the EquationSystems class.
SparseMatrix< Number > & get_boundary_condition_matrix(unsigned int dim)
Definition: claw_system.C:848
void assemble_jump_coupling_matrix()
Definition: claw_system.C:590
void dof_indices(const Elem *const elem, std::vector< dof_id_type > &di) const
Definition: dof_map.C:2164
TemporalDiscretizationType get_temporal_discretization_type()
Definition: claw_system.C:900
unsigned int dim
static Point inverse_map(const unsigned int dim, const Elem *elem, const Point &p, const Real tolerance=TOLERANCE, const bool secure=true, const bool extra_checks=true)
Definition: fe_map.C:1628
The ExodusII_IO class implements reading meshes in the ExodusII file format from Sandia National Labs...
Definition: exodusII_io.h:52
Manages consistently variables, degrees of freedom, coefficient vectors, matrices and linear solvers ...
const EquationSystems & get_equation_systems() const
Definition: system.h:721
void attach_matrix(SparseMatrix< Number > &matrix)
Additional matrices may be attached to this DofMap.
Definition: dof_map.C:274
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
Real _time
The current time in the conservation law solve.
Definition: claw_system.h:227
const Parallel::Communicator & comm() const
Order default_quadrature_order() const
Definition: fe_type.h:371
void assemble_all_matrices()
Assemble the matrices we need to solve a conservation law.
Definition: claw_system.C:262
void set_time(Real time_in)
Set/get the current time in the conservation law solve.
Definition: claw_system.C:918
void assemble_mass_matrix()
Helper functions called by assemble_all_matrices().
Definition: claw_system.C:271
The libMesh namespace provides an interface to certain functionality in the library.
dof_id_type n_local_dofs() const
Definition: system.C:158
virtual void solve() override
Assembles & solves the linear system A*x=b.
const MeshBase & get_mesh() const
Definition: system.h:2358
virtual void init_data() override
Initialize the system (e.g.
Definition: claw_system.C:944
dof_id_type n_dofs() const
Definition: system.C:121
void set_temporal_discretization_type(TemporalDiscretizationType td_in)
Set/get the temporal discretization type.
Definition: claw_system.C:894
This is the MeshBase class.
Definition: mesh_base.h:75
void set_write_interval(unsigned int write_interval_in)
Set/get write_interval.
Definition: claw_system.C:906
virtual void add(const numeric_index_type i, const numeric_index_type j, const T value)=0
Add value to the element (i,j).
SparseMatrix< Number > & get_advection_matrix(unsigned int dim)
Definition: claw_system.C:822
TemporalDiscretizationType
Define an enumeration for temporal discretization types ForwardEuler = 1st-order Explicit Euler RK4 =...
Definition: claw_system.h:45
Generic sparse matrix.
Definition: vector_fe_ex5.C:46
This class handles the numbering of degrees of freedom on a mesh.
Definition: dof_map.h:179
virtual void init_data() override
Initializes new data members of the system.
virtual void zero()=0
Set all entries to 0.
virtual void process_parameters_file(const std::string &parameters_filename)
Set parameters for this system (e.g.
Definition: claw_system.C:231
dof_id_type id() const
Definition: dof_object.h:828
Real _delta_t
The time step size.
Definition: claw_system.h:207
SparseMatrix< Number > & get_avg_matrix(unsigned int dim)
Definition: claw_system.C:832
void assemble_boundary_condition_matrices()
Definition: claw_system.C:736
std::unique_ptr< NumericVector< Number > > solution
Data structure to hold solution values.
Definition: system.h:1593
static std::unique_ptr< FEGenericBase > build(const unsigned int dim, const FEType &type)
Builds a specific finite element type.
ClawSystem(EquationSystems &es, const std::string &name, const unsigned int number)
Constructor.
Definition: claw_system.C:39
static std::string enum_to_string(TemporalDiscretizationType enum_type)
Definition: claw_system.C:80
unsigned int get_n_time_steps()
Definition: claw_system.C:876
SparseMatrix< Number > & get_jump_matrix()
Definition: claw_system.C:842
std::vector< std::unique_ptr< SparseMatrix< Number > > > _boundary_condition_matrices
The "boundary condition" matrices.
Definition: claw_system.h:196
virtual void print_info()
Print out some info about the system&#39;s configuration.
Definition: claw_system.C:249
void solve_conservation_law()
Solve the conservation law.
Definition: claw_system.C:95
void set_delta_t(Real delta_t_in)
Set/get the time-step size.
Definition: claw_system.C:858
Real _LxF_constant
The constant C in the Lax-Friedrichs flux.
Definition: claw_system.h:217
void write_out_discretization_matrices()
Print discretization matrices to file in MATLAB-readable format.
Definition: claw_system.C:929
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2598
virtual void update()
Update the local values to reflect the solution on neighboring processors.
Definition: system.C:510
virtual void close()=0
Calls the SparseMatrix&#39;s internal assembly routines, ensuring that the values are consistent across p...
virtual ~ClawSystem()
Destructor.
const FEType & variable_type(const unsigned int i) const
Definition: system.h:2508
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
static TemporalDiscretizationType string_to_enum(std::string string_type)
Convert between string and enum for TemporalDiscretizationType.
Definition: claw_system.C:67
unsigned int _write_interval
The time step interval between writing out solutions.
Definition: claw_system.h:222
unsigned int _n_time_steps
The number of time steps.
Definition: claw_system.h:212
virtual unsigned short dim() const =0
void write_timestep(const std::string &fname, const EquationSystems &es, const int timestep, const Real time, const std::set< std::string > *system_names=nullptr)
Writes out the solution at a specific timestep.
Definition: exodusII_io.C:2017
OStreamProxy out
SparseMatrix< Number > * matrix
The system matrix.
void resize(const unsigned int new_m, const unsigned int new_n)
Resizes the matrix to the specified size and calls zero().
Definition: dense_matrix.h:895
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
static std::unique_ptr< NumericVector< T > > build(const Parallel::Communicator &comm, SolverPackage solver_package=libMesh::default_solver_package(), ParallelType parallel_type=AUTOMATIC)
Builds a NumericVector on the processors in communicator comm using the linear solver package specifi...
SparseMatrix< Number > & get_mass_matrix()
Get a reference to one of the discretization matrices.
Definition: claw_system.C:816
This class implements specific orders of Gauss quadrature.
std::unique_ptr< SparseMatrix< Number > > _mass_matrix
The mass matrix.
Definition: claw_system.h:175
void assemble_advection_matrices()
Definition: claw_system.C:335
virtual std::string system_type() const override
Definition: claw_system.C:61
void assemble_avg_coupling_matrices()
Definition: claw_system.C:415
virtual void assemble_claw_rhs(NumericVector< Number > &)=0
Assemble the right-hand side vector.
const std::string & name() const
Definition: system.h:2342
bool assemble_before_solve
Flag which tells the system to whether or not to call the user assembly function during each call to ...
Definition: system.h:1547
void set_LxF_constant(Real LxF_constant_in)
Set/get the Lax-Friedrichs constant.
Definition: claw_system.C:882
std::unique_ptr< LinearSolver< Number > > linear_solver
This class handles all the details of interfacing with various linear algebra packages like PETSc or ...
SparseMatrix< Number > & add_matrix(std::string_view mat_name, ParallelType type=PARALLEL, MatrixBuildType mat_build_type=MatrixBuildType::AUTOMATIC)
Adds the additional matrix mat_name to this system.
Definition: system.C:1010
const DofMap & get_dof_map() const
Definition: system.h:2374
unsigned int get_write_interval()
Definition: claw_system.C:912
auto index_range(const T &sizable)
Helper function that returns an IntRange<std::size_t> representing all the indices of the passed-in v...
Definition: int_range.h:117
TemporalDiscretizationType _temporal_discretization_type
String that defines the type of temporal discretization that we use.
Definition: claw_system.h:202
std::vector< std::unique_ptr< SparseMatrix< Number > > > _advection_matrices
The "advection" matrices.
Definition: claw_system.h:180
void constrain_element_matrix(DenseMatrix< Number > &matrix, std::vector< dof_id_type > &elem_dofs, bool asymmetric_constraint_rows=true) const
Constrains the element matrix.
Definition: dof_map.h:2317