deal.II version 9.7.0
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
step-62.h
Go to the documentation of this file.
1) const
1020 *   {
1021 * @endcode
1022 *
1023 * The speed of sound is defined by
1024 * @f[
1025 * c = \frac{K_e}{\rho}
1026 * @f]
1027 * where @f$K_e@f$ is the effective elastic constant and @f$\rho@f$ the density.
1028 * Here we consider the case in which the waveguide width is much smaller
1029 * than the wavelength. In this case it can be shown that for the two
1030 * dimensional case
1031 * @f[
1032 * K_e = 4\mu\frac{\lambda +\mu}{\lambda+2\mu}
1033 * @f]
1034 * and for the three dimensional case @f$K_e@f$ is equal to the Young's modulus.
1035 * @f[
1036 * K_e = \mu\frac{3\lambda +2\mu}{\lambda+\mu}
1037 * @f]
1038 *
1039 * @code
1040 *   double elastic_constant;
1041 *   if (dim == 2)
1042 *   {
1043 *   elastic_constant = 4 * mu * (lambda + mu) / (lambda + 2 * mu);
1044 *   }
1045 *   else if (dim == 3)
1046 *   {
1047 *   elastic_constant = mu * (3 * lambda + 2 * mu) / (lambda + mu);
1048 *   }
1049 *   else
1050 *   DEAL_II_NOT_IMPLEMENTED();
1051 *  
1052 *   const double material_a_speed_of_sound =
1053 *   std::sqrt(elastic_constant / material_a_rho);
1054 *   const double material_a_wavelength =
1055 *   material_a_speed_of_sound / cavity_resonance_frequency;
1056 *   const double material_b_speed_of_sound =
1057 *   std::sqrt(elastic_constant / material_b_rho);
1058 *   const double material_b_wavelength =
1059 *   material_b_speed_of_sound / cavity_resonance_frequency;
1060 *  
1061 * @endcode
1062 *
1063 * The density @f$\rho@f$ takes the following form
1064 * <img alt="Phononic superlattice cavity"
1065 * src="https://www.dealii.org/images/steps/developer/step-62.04.svg"
1066 * height="200" />
1067 * where the brown color represents material_a and the green color
1068 * represents material_b.
1069 *
1070 * @code
1071 *   for (unsigned int idx = 0; idx < nb_mirror_pairs; ++idx)
1072 *   {
1073 *   const double layer_transition_center =
1074 *   material_a_wavelength / 2 +
1075 *   idx * (material_b_wavelength / 4 + material_a_wavelength / 4);
1076 *   if (std::abs(p[0]) >=
1077 *   (layer_transition_center - average_rho_width / 2) &&
1078 *   std::abs(p[0]) <= (layer_transition_center + average_rho_width / 2))
1079 *   {
1080 *   const double coefficient =
1081 *   (std::abs(p[0]) -
1082 *   (layer_transition_center - average_rho_width / 2)) /
1083 *   average_rho_width;
1084 *   return (1 - coefficient) * material_a_rho +
1085 *   coefficient * material_b_rho;
1086 *   }
1087 *   }
1088 *  
1089 * @endcode
1090 *
1091 * Here we define the
1092 * [subpixel
1093 * smoothing](https://meep.readthedocs.io/en/latest/Subpixel_Smoothing/)
1094 * which improves the precision of the simulation.
1095 *
1096 * @code
1097 *   for (unsigned int idx = 0; idx < nb_mirror_pairs; ++idx)
1098 *   {
1099 *   const double layer_transition_center =
1100 *   material_a_wavelength / 2 +
1101 *   idx * (material_b_wavelength / 4 + material_a_wavelength / 4) +
1102 *   material_b_wavelength / 4;
1103 *   if (std::abs(p[0]) >=
1104 *   (layer_transition_center - average_rho_width / 2) &&
1105 *   std::abs(p[0]) <= (layer_transition_center + average_rho_width / 2))
1106 *   {
1107 *   const double coefficient =
1108 *   (std::abs(p[0]) -
1109 *   (layer_transition_center - average_rho_width / 2)) /
1110 *   average_rho_width;
1111 *   return (1 - coefficient) * material_b_rho +
1112 *   coefficient * material_a_rho;
1113 *   }
1114 *   }
1115 *  
1116 * @endcode
1117 *
1118 * then the cavity
1119 *
1120 * @code
1121 *   if (std::abs(p[0]) <= material_a_wavelength / 2)
1122 *   {
1123 *   return material_a_rho;
1124 *   }
1125 *  
1126 * @endcode
1127 *
1128 * the material_a layers
1129 *
1130 * @code
1131 *   for (unsigned int idx = 0; idx < nb_mirror_pairs; ++idx)
1132 *   {
1133 *   const double layer_center =
1134 *   material_a_wavelength / 2 +
1135 *   idx * (material_b_wavelength / 4 + material_a_wavelength / 4) +
1136 *   material_b_wavelength / 4 + material_a_wavelength / 8;
1137 *   const double layer_width = material_a_wavelength / 4;
1138 *   if (std::abs(p[0]) >= (layer_center - layer_width / 2) &&
1139 *   std::abs(p[0]) <= (layer_center + layer_width / 2))
1140 *   {
1141 *   return material_a_rho;
1142 *   }
1143 *   }
1144 *  
1145 * @endcode
1146 *
1147 * the material_b layers
1148 *
1149 * @code
1150 *   for (unsigned int idx = 0; idx < nb_mirror_pairs; ++idx)
1151 *   {
1152 *   const double layer_center =
1153 *   material_a_wavelength / 2 +
1154 *   idx * (material_b_wavelength / 4 + material_a_wavelength / 4) +
1155 *   material_b_wavelength / 8;
1156 *   const double layer_width = material_b_wavelength / 4;
1157 *   if (std::abs(p[0]) >= (layer_center - layer_width / 2) &&
1158 *   std::abs(p[0]) <= (layer_center + layer_width / 2))
1159 *   {
1160 *   return material_b_rho;
1161 *   }
1162 *   }
1163 *  
1164 * @endcode
1165 *
1166 * and finally the default is material_a.
1167 *
1168 * @code
1169 *   return material_a_rho;
1170 *   }
1171 *  
1172 *  
1173 *  
1174 * @endcode
1175 *
1176 *
1177 * <a name="step_62-TheParametersclassimplementation"></a>
1178 * <h4>The `Parameters` class implementation</h4>
1179 *
1180
1181 *
1182 * The constructor reads all the parameters from the HDF5::Group `data` using
1183 * the HDF5::Group::get_attribute() function.
1184 *
1185 * @code
1186 *   template <int dim>
1187 *   Parameters<dim>::Parameters(HDF5::Group &data)
1188 *   : data(data)
1189 *   , simulation_name(data.get_attribute<std::string>("simulation_name"))
1190 *   , save_vtu_files(data.get_attribute<bool>("save_vtu_files"))
1191 *   , start_frequency(data.get_attribute<double>("start_frequency"))
1192 *   , stop_frequency(data.get_attribute<double>("stop_frequency"))
1193 *   , nb_frequency_points(data.get_attribute<int>("nb_frequency_points"))
1194 *   , lambda(data.get_attribute<double>("lambda"))
1195 *   , mu(data.get_attribute<double>("mu"))
1196 *   , dimension_x(data.get_attribute<double>("dimension_x"))
1197 *   , dimension_y(data.get_attribute<double>("dimension_y"))
1198 *   , nb_probe_points(data.get_attribute<int>("nb_probe_points"))
1199 *   , grid_level(data.get_attribute<int>("grid_level"))
1200 *   , probe_start_point(data.get_attribute<double>("probe_pos_x"),
1201 *   data.get_attribute<double>("probe_pos_y") -
1202 *   data.get_attribute<double>("probe_width_y") / 2)
1203 *   , probe_stop_point(data.get_attribute<double>("probe_pos_x"),
1204 *   data.get_attribute<double>("probe_pos_y") +
1205 *   data.get_attribute<double>("probe_width_y") / 2)
1206 *   , right_hand_side(data)
1207 *   , pml(data)
1208 *   , rho(data)
1209 *   {}
1210 *  
1211 *  
1212 *  
1213 * @endcode
1214 *
1215 *
1216 * <a name="step_62-TheQuadratureCacheclassimplementation"></a>
1217 * <h4>The `QuadratureCache` class implementation</h4>
1218 *
1219
1220 *
1221 * We need to reserve enough space for the mass and stiffness matrices and the
1222 * right hand side vector.
1223 *
1224 * @code
1225 *   template <int dim>
1226 *   QuadratureCache<dim>::QuadratureCache(const unsigned int dofs_per_cell)
1227 *   : dofs_per_cell(dofs_per_cell)
1228 *   , mass_coefficient(dofs_per_cell, dofs_per_cell)
1229 *   , stiffness_coefficient(dofs_per_cell, dofs_per_cell)
1230 *   , right_hand_side(dofs_per_cell)
1231 *   {}
1232 *  
1233 *  
1234 *  
1235 * @endcode
1236 *
1237 *
1238 * <a name="step_62-ImplementationoftheElasticWaveclass"></a>
1239 * <h3>Implementation of the `ElasticWave` class</h3>
1240 *
1241
1242 *
1243 *
1244 * <a name="step_62-Constructor"></a>
1245 * <h4>Constructor</h4>
1246 *
1247
1248 *
1249 * This is very similar to the constructor of @ref step_40 "step-40". In addition we create
1250 * the HDF5 datasets `frequency_dataset`, `position_dataset` and
1251 * `displacement`. Note the use of the `template` keyword for the creation of
1252 * the HDF5 datasets. It is a C++ requirement to use the `template` keyword in
1253 * order to treat `create_dataset` as a dependent template name.
1254 *
1255 * @code
1256 *   template <int dim>
1257 *   ElasticWave<dim>::ElasticWave(const Parameters<dim> &parameters)
1258 *   : parameters(parameters)
1259 *   , mpi_communicator(MPI_COMM_WORLD)
1260 *   , triangulation(mpi_communicator,
1261 *   typename Triangulation<dim>::MeshSmoothing(
1262 *   Triangulation<dim>::smoothing_on_refinement |
1263 *   Triangulation<dim>::smoothing_on_coarsening))
1264 *   , quadrature_formula(2)
1265 *   , fe(FE_Q<dim>(1) ^ dim)
1266 *   , dof_handler(triangulation)
1267 *   , frequency(parameters.nb_frequency_points)
1268 *   , probe_positions(parameters.nb_probe_points, dim)
1269 *   , frequency_dataset(parameters.data.template create_dataset<double>(
1270 *   "frequency",
1271 *   std::vector<hsize_t>{parameters.nb_frequency_points}))
1272 *   , probe_positions_dataset(parameters.data.template create_dataset<double>(
1273 *   "position",
1274 *   std::vector<hsize_t>{parameters.nb_probe_points, dim}))
1275 *   , displacement(
1276 *   parameters.data.template create_dataset<std::complex<double>>(
1277 *   "displacement",
1278 *   std::vector<hsize_t>{parameters.nb_probe_points,
1279 *   parameters.nb_frequency_points}))
1280 *   , pcout(std::cout,
1281 *   (Utilities::MPI::this_mpi_process(mpi_communicator) == 0))
1282 *   , computing_timer(mpi_communicator,
1283 *   pcout,
1284 *   TimerOutput::never,
1285 *   TimerOutput::wall_times)
1286 *   {}
1287 *  
1288 *  
1289 *  
1290 * @endcode
1291 *
1292 *
1293 * <a name="step_62-ElasticWavesetup_system"></a>
1294 * <h4>ElasticWave::setup_system</h4>
1295 *
1296
1297 *
1298 * There is nothing new in this function, the only difference with @ref step_40 "step-40" is
1299 * that we don't have to apply boundary conditions because we use the PMLs to
1300 * truncate the domain.
1301 *
1302 * @code
1303 *   template <int dim>
1304 *   void ElasticWave<dim>::setup_system()
1305 *   {
1306 *   TimerOutput::Scope t(computing_timer, "setup");
1307 *  
1308 *   dof_handler.distribute_dofs(fe);
1309 *  
1310 *   locally_owned_dofs = dof_handler.locally_owned_dofs();
1311 *   locally_relevant_dofs =
1313 *  
1314 *   locally_relevant_solution.reinit(locally_owned_dofs,
1315 *   locally_relevant_dofs,
1316 *   mpi_communicator);
1317 *  
1318 *   system_rhs.reinit(locally_owned_dofs, mpi_communicator);
1319 *  
1320 *   constraints.clear();
1321 *   constraints.reinit(locally_relevant_dofs);
1322 *   DoFTools::make_hanging_node_constraints(dof_handler, constraints);
1323 *  
1324 *   constraints.close();
1325 *  
1326 *   DynamicSparsityPattern dsp(locally_relevant_dofs);
1327 *  
1328 *   DoFTools::make_sparsity_pattern(dof_handler, dsp, constraints, false);
1330 *   locally_owned_dofs,
1331 *   mpi_communicator,
1332 *   locally_relevant_dofs);
1333 *  
1334 *   system_matrix.reinit(locally_owned_dofs,
1335 *   locally_owned_dofs,
1336 *   dsp,
1337 *   mpi_communicator);
1338 *   }
1339 *  
1340 *  
1341 *  
1342 * @endcode
1343 *
1344 *
1345 * <a name="step_62-ElasticWaveassemble_system"></a>
1346 * <h4>ElasticWave::assemble_system</h4>
1347 *
1348
1349 *
1350 * This function is also very similar to @ref step_40 "step-40", though there are notable
1351 * differences. We assemble the system for each frequency/omega step. In the
1352 * first step we set `calculate_quadrature_data = True` and we calculate the
1353 * mass and stiffness matrices and the right hand side vector. In the
1354 * subsequent steps we will use that data to accelerate the calculation.
1355 *
1356 * @code
1357 *   template <int dim>
1358 *   void ElasticWave<dim>::assemble_system(const double omega,
1359 *   const bool calculate_quadrature_data)
1360 *   {
1361 *   TimerOutput::Scope t(computing_timer, "assembly");
1362 *  
1363 *   FEValues<dim> fe_values(fe,
1364 *   quadrature_formula,
1367 *   const unsigned int dofs_per_cell = fe.n_dofs_per_cell();
1368 *   const unsigned int n_q_points = quadrature_formula.size();
1369 *  
1370 *   FullMatrix<std::complex<double>> cell_matrix(dofs_per_cell, dofs_per_cell);
1371 *   Vector<std::complex<double>> cell_rhs(dofs_per_cell);
1372 *  
1373 *   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1374 *  
1375 * @endcode
1376 *
1377 * Here we store the value of the right hand side, rho and the PML.
1378 *
1379 * @code
1380 *   std::vector<Vector<double>> rhs_values(n_q_points, Vector<double>(dim));
1381 *   std::vector<double> rho_values(n_q_points);
1382 *   std::vector<Vector<std::complex<double>>> pml_values(
1383 *   n_q_points, Vector<std::complex<double>>(dim));
1384 *  
1385 * @endcode
1386 *
1387 * We calculate the stiffness tensor for the @f$\lambda@f$ and @f$\mu@f$ that have
1388 * been defined in the Jupyter Notebook. Note that contrary to @f$\rho@f$ the
1389 * stiffness is constant among for the whole domain.
1390 *
1391 * @code
1392 *   const SymmetricTensor<4, dim> stiffness_tensor =
1393 *   get_stiffness_tensor<dim>(parameters.lambda, parameters.mu);
1394 *  
1395 * @endcode
1396 *
1397 * We use the same method of @ref step_20 "step-20" for vector-valued problems.
1398 *
1399 * @code
1400 *   const FEValuesExtractors::Vector displacement(0);
1401 *  
1402 *   for (const auto &cell : dof_handler.active_cell_iterators())
1403 *   if (cell->is_locally_owned())
1404 *   {
1405 *   cell_matrix = 0;
1406 *   cell_rhs = 0;
1407 *  
1408 * @endcode
1409 *
1410 * We have to calculate the values of the right hand side, rho and
1411 * the PML only if we are going to calculate the mass and the
1412 * stiffness matrices. Otherwise we can skip this calculation which
1413 * considerably reduces the total calculation time.
1414 *
1415 * @code
1416 *   if (calculate_quadrature_data)
1417 *   {
1418 *   fe_values.reinit(cell);
1419 *  
1420 *   parameters.right_hand_side.vector_value_list(
1421 *   fe_values.get_quadrature_points(), rhs_values);
1422 *   parameters.rho.value_list(fe_values.get_quadrature_points(),
1423 *   rho_values);
1424 *   parameters.pml.vector_value_list(
1425 *   fe_values.get_quadrature_points(), pml_values);
1426 *   }
1427 *  
1428 * @endcode
1429 *
1430 * We have done this in @ref step_18 "step-18". Get a pointer to the quadrature
1431 * cache data local to the present cell, and, as a defensive
1432 * measure, make sure that this pointer is within the bounds of the
1433 * global array:
1434 *
1435 * @code
1436 *   QuadratureCache<dim> *local_quadrature_points_data =
1437 *   reinterpret_cast<QuadratureCache<dim> *>(cell->user_pointer());
1438 *   Assert(local_quadrature_points_data >= &quadrature_cache.front(),
1439 *   ExcInternalError());
1440 *   Assert(local_quadrature_points_data <= &quadrature_cache.back(),
1441 *   ExcInternalError());
1442 *   for (unsigned int q = 0; q < n_q_points; ++q)
1443 *   {
1444 * @endcode
1445 *
1446 * The quadrature_data variable is used to store the mass and
1447 * stiffness matrices, the right hand side vector and the value
1448 * of `JxW`.
1449 *
1450 * @code
1451 *   QuadratureCache<dim> &quadrature_data =
1452 *   local_quadrature_points_data[q];
1453 *  
1454 * @endcode
1455 *
1456 * Below we declare the force vector and the parameters of the
1457 * PML @f$s@f$ and @f$\xi@f$.
1458 *
1459 * @code
1460 *   Tensor<1, dim> force;
1462 *   std::complex<double> xi(1, 0);
1463 *  
1464 * @endcode
1465 *
1466 * The following block is calculated only in the first frequency
1467 * step.
1468 *
1469 * @code
1470 *   if (calculate_quadrature_data)
1471 *   {
1472 * @endcode
1473 *
1474 * Store the value of `JxW`.
1475 *
1476 * @code
1477 *   quadrature_data.JxW = fe_values.JxW(q);
1478 *  
1479 *   for (unsigned int component = 0; component < dim; ++component)
1480 *   {
1481 * @endcode
1482 *
1483 * Convert vectors to tensors and calculate xi
1484 *
1485 * @code
1486 *   force[component] = rhs_values[q][component];
1487 *   s[component] = pml_values[q][component];
1488 *   xi *= s[component];
1489 *   }
1490 *  
1491 * @endcode
1492 *
1493 * Here we calculate the @f$\alpha_{mnkl}@f$ and @f$\beta_{mnkl}@f$
1494 * tensors.
1495 *
1496 * @code
1499 *   for (unsigned int m = 0; m < dim; ++m)
1500 *   for (unsigned int n = 0; n < dim; ++n)
1501 *   for (unsigned int k = 0; k < dim; ++k)
1502 *   for (unsigned int l = 0; l < dim; ++l)
1503 *   {
1504 *   alpha[m][n][k][l] = xi *
1505 *   stiffness_tensor[m][n][k][l] /
1506 *   (2.0 * s[n] * s[k]);
1507 *   beta[m][n][k][l] = xi *
1508 *   stiffness_tensor[m][n][k][l] /
1509 *   (2.0 * s[n] * s[l]);
1510 *   }
1511 *  
1512 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
1513 *   {
1514 *   const Tensor<1, dim> phi_i =
1515 *   fe_values[displacement].value(i, q);
1516 *   const Tensor<2, dim> grad_phi_i =
1517 *   fe_values[displacement].gradient(i, q);
1518 *  
1519 *   for (unsigned int j = 0; j < dofs_per_cell; ++j)
1520 *   {
1521 *   const Tensor<1, dim> phi_j =
1522 *   fe_values[displacement].value(j, q);
1523 *   const Tensor<2, dim> grad_phi_j =
1524 *   fe_values[displacement].gradient(j, q);
1525 *  
1526 * @endcode
1527 *
1528 * calculate the values of the @ref GlossMassMatrix "mass matrix".
1529 *
1530 * @code
1531 *   quadrature_data.mass_coefficient[i][j] =
1532 *   rho_values[q] * xi * phi_i * phi_j;
1533 *  
1534 * @endcode
1535 *
1536 * Loop over the @f$mnkl@f$ indices of the stiffness
1537 * tensor.
1538 *
1539 * @code
1540 *   std::complex<double> stiffness_coefficient = 0;
1541 *   for (unsigned int m = 0; m < dim; ++m)
1542 *   for (unsigned int n = 0; n < dim; ++n)
1543 *   for (unsigned int k = 0; k < dim; ++k)
1544 *   for (unsigned int l = 0; l < dim; ++l)
1545 *   {
1546 * @endcode
1547 *
1548 * Here we calculate the stiffness matrix.
1549 * Note that the stiffness matrix is not
1550 * symmetric because of the PMLs. We use the
1551 * gradient function (see the
1552 * [documentation](https://www.dealii.org/current/doxygen/deal.II/group__vector__valued.html))
1553 * which is a <code>Tensor@<2,dim@></code>.
1554 * The matrix @f$G_{ij}@f$ consists of entries
1555 * @f[
1556 * G_{ij}=
1557 * \frac{\partial\phi_i}{\partial x_j}
1558 * =\partial_j \phi_i
1559 * @f]
1560 * Note the position of the indices @f$i@f$ and
1561 * @f$j@f$ and the notation that we use in this
1562 * tutorial: @f$\partial_j\phi_i@f$. As the
1563 * stiffness tensor is not symmetric, it is
1564 * very easy to make a mistake.
1565 *
1566 * @code
1567 *   stiffness_coefficient +=
1568 *   grad_phi_i[m][n] *
1569 *   (alpha[m][n][k][l] * grad_phi_j[l][k] +
1570 *   beta[m][n][k][l] * grad_phi_j[k][l]);
1571 *   }
1572 *  
1573 * @endcode
1574 *
1575 * We save the value of the stiffness matrix in
1576 * quadrature_data
1577 *
1578 * @code
1579 *   quadrature_data.stiffness_coefficient[i][j] =
1580 *   stiffness_coefficient;
1581 *   }
1582 *  
1583 * @endcode
1584 *
1585 * and the value of the right hand side in
1586 * quadrature_data.
1587 *
1588 * @code
1589 *   quadrature_data.right_hand_side[i] =
1590 *   phi_i * force * fe_values.JxW(q);
1591 *   }
1592 *   }
1593 *  
1594 * @endcode
1595 *
1596 * We loop again over the degrees of freedom of the cells to
1597 * calculate the system matrix. These loops are really quick
1598 * because we have already calculated the stiffness and mass
1599 * matrices, only the value of @f$\omega@f$ changes.
1600 *
1601 * @code
1602 *   for (unsigned int i = 0; i < dofs_per_cell; ++i)
1603 *   {
1604 *   for (unsigned int j = 0; j < dofs_per_cell; ++j)
1605 *   {
1606 *   std::complex<double> matrix_sum = 0;
1607 *   matrix_sum += -Utilities::fixed_power<2>(omega) *
1608 *   quadrature_data.mass_coefficient[i][j];
1609 *   matrix_sum += quadrature_data.stiffness_coefficient[i][j];
1610 *   cell_matrix(i, j) += matrix_sum * quadrature_data.JxW;
1611 *   }
1612 *   cell_rhs(i) += quadrature_data.right_hand_side[i];
1613 *   }
1614 *   }
1615 *   cell->get_dof_indices(local_dof_indices);
1616 *   constraints.distribute_local_to_global(cell_matrix,
1617 *   cell_rhs,
1618 *   local_dof_indices,
1619 *   system_matrix,
1620 *   system_rhs);
1621 *   }
1622 *  
1623 *   system_matrix.compress(VectorOperation::add);
1624 *   system_rhs.compress(VectorOperation::add);
1625 *   }
1626 *  
1627 * @endcode
1628 *
1629 *
1630 * <a name="step_62-ElasticWavesolve"></a>
1631 * <h4>ElasticWave::solve</h4>
1632 *
1633
1634 *
1635 * This is even more simple than in @ref step_40 "step-40". We use the parallel direct solver
1636 * MUMPS which requires less options than an iterative solver. The drawback is
1637 * that it does not scale very well. It is not straightforward to solve the
1638 * Helmholtz equation with an iterative solver. The shifted Laplacian
1639 * multigrid method is a well known approach to precondition this system, but
1640 * this is beyond the scope of this tutorial.
1641 *
1642 * @code
1643 *   template <int dim>
1644 *   void ElasticWave<dim>::solve()
1645 *   {
1646 *   TimerOutput::Scope t(computing_timer, "solve");
1647 *   LinearAlgebraPETSc::MPI::Vector completely_distributed_solution(
1648 *   locally_owned_dofs, mpi_communicator);
1649 *  
1650 *   SolverControl solver_control;
1651 *   PETScWrappers::SparseDirectMUMPS solver(solver_control, mpi_communicator);
1652 *   solver.solve(system_matrix, completely_distributed_solution, system_rhs);
1653 *  
1654 *   pcout << " Solved in " << solver_control.last_step() << " iterations."
1655 *   << std::endl;
1656 *   constraints.distribute(completely_distributed_solution);
1657 *   locally_relevant_solution = completely_distributed_solution;
1658 *   }
1659 *  
1660 * @endcode
1661 *
1662 *
1663 * <a name="step_62-ElasticWaveinitialize_position_vector"></a>
1664 * <h4>ElasticWave::initialize_position_vector</h4>
1665 *
1666
1667 *
1668 * We use this function to calculate the values of the position vector.
1669 *
1670 * @code
1671 *   template <int dim>
1672 *   void ElasticWave<dim>::initialize_probe_positions_vector()
1673 *   {
1674 *   for (unsigned int position_idx = 0;
1675 *   position_idx < parameters.nb_probe_points;
1676 *   ++position_idx)
1677 *   {
1678 * @endcode
1679 *
1680 * Because of the way the operator + and - are overloaded to subtract
1681 * two points, the following has to be done:
1682 * `Point_b<dim> + (-Point_a<dim>)`
1683 *
1684 * @code
1685 *   const Point<dim> p =
1686 *   (position_idx / ((double)(parameters.nb_probe_points - 1))) *
1687 *   (parameters.probe_stop_point + (-parameters.probe_start_point)) +
1688 *   parameters.probe_start_point;
1689 *   probe_positions[position_idx][0] = p[0];
1690 *   probe_positions[position_idx][1] = p[1];
1691 *   if (dim == 3)
1692 *   {
1693 *   probe_positions[position_idx][2] = p[2];
1694 *   }
1695 *   }
1696 *   }
1697 *  
1698 * @endcode
1699 *
1700 *
1701 * <a name="step_62-ElasticWavestore_frequency_step_data"></a>
1702 * <h4>ElasticWave::store_frequency_step_data</h4>
1703 *
1704
1705 *
1706 * This function stores in the HDF5 file the measured energy by the probe.
1707 *
1708 * @code
1709 *   template <int dim>
1710 *   void
1711 *   ElasticWave<dim>::store_frequency_step_data(const unsigned int frequency_idx)
1712 *   {
1713 *   TimerOutput::Scope t(computing_timer, "store_frequency_step_data");
1714 *  
1715 * @endcode
1716 *
1717 * We store the displacement in the @f$x@f$ direction; the displacement in the
1718 * @f$y@f$ direction is negligible.
1719 *
1720 * @code
1721 *   const unsigned int probe_displacement_component = 0;
1722 *  
1723 * @endcode
1724 *
1725 * The vector coordinates contains the coordinates in the HDF5 file of the
1726 * points of the probe that are located in locally owned cells. The vector
1727 * displacement_data contains the value of the displacement at these points.
1728 *
1729 * @code
1730 *   std::vector<hsize_t> coordinates;
1731 *   std::vector<std::complex<double>> displacement_data;
1732 *  
1733 *   const auto &mapping = get_default_linear_mapping(triangulation);
1734 *   GridTools::Cache<dim, dim> cache(triangulation, mapping);
1735 *   typename Triangulation<dim, dim>::active_cell_iterator cell_hint{};
1736 *   std::vector<bool> marked_vertices = {};
1737 *   const double tolerance = 1.e-10;
1738 *  
1739 *   for (unsigned int position_idx = 0;
1740 *   position_idx < parameters.nb_probe_points;
1741 *   ++position_idx)
1742 *   {
1743 *   Point<dim> point;
1744 *   for (unsigned int dim_idx = 0; dim_idx < dim; ++dim_idx)
1745 *   {
1746 *   point[dim_idx] = probe_positions[position_idx][dim_idx];
1747 *   }
1748 *   bool point_in_locally_owned_cell = false;
1749 *   {
1750 *   auto cell_and_ref_point = GridTools::find_active_cell_around_point(
1751 *   cache, point, cell_hint, marked_vertices, tolerance);
1752 *   if (cell_and_ref_point.first.state() == IteratorState::valid)
1753 *   {
1754 *   cell_hint = cell_and_ref_point.first;
1755 *   point_in_locally_owned_cell =
1756 *   cell_and_ref_point.first->is_locally_owned();
1757 *   }
1758 *   }
1759 *   if (point_in_locally_owned_cell)
1760 *   {
1761 * @endcode
1762 *
1763 * Then we can store the values of the displacement in the points of
1764 * the probe in `displacement_data`.
1765 *
1766 * @code
1767 *   Vector<std::complex<double>> tmp_vector(dim);
1768 *   VectorTools::point_value(dof_handler,
1769 *   locally_relevant_solution,
1770 *   point,
1771 *   tmp_vector);
1772 *   coordinates.emplace_back(position_idx);
1773 *   coordinates.emplace_back(frequency_idx);
1774 *   displacement_data.emplace_back(
1775 *   tmp_vector(probe_displacement_component));
1776 *   }
1777 *   }
1778 *  
1779 * @endcode
1780 *
1781 * We write the displacement data in the HDF5 file. The call
1782 * HDF5::DataSet::write_selection() is MPI collective which means that all
1783 * the processes have to participate.
1784 *
1785 * @code
1786 *   if (coordinates.size() > 0)
1787 *   {
1788 *   displacement.write_selection(displacement_data, coordinates);
1789 *   }
1790 * @endcode
1791 *
1792 * Therefore even if the process has no data to write it has to participate
1793 * in the collective call. For this we can use HDF5::DataSet::write_none().
1794 * Note that we have to specify the data type, in this case
1795 * `std::complex<double>`.
1796 *
1797 * @code
1798 *   else
1799 *   {
1800 *   displacement.write_none<std::complex<double>>();
1801 *   }
1802 *  
1803 * @endcode
1804 *
1805 * If the variable `save_vtu_files` in the input file equals `True` then all
1806 * the data will be saved as vtu. The procedure to write `vtu` files has
1807 * been described in @ref step_40 "step-40".
1808 *
1809 * @code
1810 *   if (parameters.save_vtu_files)
1811 *   {
1812 *   std::vector<std::string> solution_names(dim, "displacement");
1813 *   std::vector<DataComponentInterpretation::DataComponentInterpretation>
1814 *   interpretation(
1816 *  
1817 *   DataOut<dim> data_out;
1818 *   data_out.add_data_vector(dof_handler,
1819 *   locally_relevant_solution,
1820 *   solution_names,
1821 *   interpretation);
1822 *   Vector<float> subdomain(triangulation.n_active_cells());
1823 *   for (unsigned int i = 0; i < subdomain.size(); ++i)
1824 *   subdomain(i) = triangulation.locally_owned_subdomain();
1825 *   data_out.add_data_vector(subdomain, "subdomain");
1826 *  
1827 *   std::vector<Vector<double>> force(
1828 *   dim, Vector<double>(triangulation.n_active_cells()));
1829 *   std::vector<Vector<double>> pml(
1830 *   dim, Vector<double>(triangulation.n_active_cells()));
1831 *   Vector<double> rho(triangulation.n_active_cells());
1832 *  
1833 *   for (auto &cell : triangulation.active_cell_iterators())
1834 *   {
1835 *   if (cell->is_locally_owned())
1836 *   {
1837 *   for (unsigned int dim_idx = 0; dim_idx < dim; ++dim_idx)
1838 *   {
1839 *   force[dim_idx](cell->active_cell_index()) =
1840 *   parameters.right_hand_side.value(cell->center(), dim_idx);
1841 *   pml[dim_idx](cell->active_cell_index()) =
1842 *   parameters.pml.value(cell->center(), dim_idx).imag();
1843 *   }
1844 *   rho(cell->active_cell_index()) =
1845 *   parameters.rho.value(cell->center());
1846 *   }
1847 * @endcode
1848 *
1849 * And on the cells that we are not interested in, set the
1850 * respective value to a bogus value in order to make sure that if
1851 * we were somehow wrong about our assumption we would find out by
1852 * looking at the graphical output:
1853 *
1854 * @code
1855 *   else
1856 *   {
1857 *   for (unsigned int dim_idx = 0; dim_idx < dim; ++dim_idx)
1858 *   {
1859 *   force[dim_idx](cell->active_cell_index()) = -1e+20;
1860 *   pml[dim_idx](cell->active_cell_index()) = -1e+20;
1861 *   }
1862 *   rho(cell->active_cell_index()) = -1e+20;
1863 *   }
1864 *   }
1865 *  
1866 *   for (unsigned int dim_idx = 0; dim_idx < dim; ++dim_idx)
1867 *   {
1868 *   data_out.add_data_vector(force[dim_idx],
1869 *   "force_" + std::to_string(dim_idx));
1870 *   data_out.add_data_vector(pml[dim_idx],
1871 *   "pml_" + std::to_string(dim_idx));
1872 *   }
1873 *   data_out.add_data_vector(rho, "rho");
1874 *  
1875 *   data_out.build_patches();
1876 *  
1877 *   std::stringstream frequency_idx_stream;
1878 *   const unsigned int nb_number_positions =
1879 *   ((unsigned int)std::log10(parameters.nb_frequency_points)) + 1;
1880 *   frequency_idx_stream << std::setw(nb_number_positions)
1881 *   << std::setfill('0') << frequency_idx;
1882 *   const std::string filename = (parameters.simulation_name + "_" +
1883 *   frequency_idx_stream.str() + ".vtu");
1884 *   data_out.write_vtu_in_parallel(filename, mpi_communicator);
1885 *   }
1886 *   }
1887 *  
1888 *  
1889 *  
1890 * @endcode
1891 *
1892 *
1893 * <a name="step_62-ElasticWaveoutput_results"></a>
1894 * <h4>ElasticWave::output_results</h4>
1895 *
1896
1897 *
1898 * This function writes the datasets that have not already been written.
1899 *
1900 * @code
1901 *   template <int dim>
1902 *   void ElasticWave<dim>::output_results()
1903 *   {
1904 * @endcode
1905 *
1906 * The vectors `frequency` and `position` are the same for all the
1907 * processes. Therefore any of the processes can write the corresponding
1908 * `datasets`. Because the call HDF5::DataSet::write is MPI collective, the
1909 * rest of the processes will have to call HDF5::DataSet::write_none.
1910 *
1911 * @code
1912 *   if (Utilities::MPI::this_mpi_process(mpi_communicator) == 0)
1913 *   {
1914 *   frequency_dataset.write(frequency);
1915 *   probe_positions_dataset.write(probe_positions);
1916 *   }
1917 *   else
1918 *   {
1919 *   frequency_dataset.write_none<double>();
1920 *   probe_positions_dataset.write_none<double>();
1921 *   }
1922 *   }
1923 *  
1924 *  
1925 *  
1926 * @endcode
1927 *
1928 *
1929 * <a name="step_62-ElasticWavesetup_quadrature_cache"></a>
1930 * <h4>ElasticWave::setup_quadrature_cache</h4>
1931 *
1932
1933 *
1934 * We use this function at the beginning of our computations to set up initial
1935 * values of the cache variables. This function has been described in @ref step_18 "step-18".
1936 * There are no differences with the function of @ref step_18 "step-18".
1937 *
1938 * @code
1939 *   template <int dim>
1940 *   void ElasticWave<dim>::setup_quadrature_cache()
1941 *   {
1942 *   triangulation.clear_user_data();
1943 *  
1944 *   {
1945 *   std::vector<QuadratureCache<dim>> tmp;
1946 *   quadrature_cache.swap(tmp);
1947 *   }
1948 *  
1949 *   quadrature_cache.resize(triangulation.n_locally_owned_active_cells() *
1950 *   quadrature_formula.size(),
1951 *   QuadratureCache<dim>(fe.n_dofs_per_cell()));
1952 *   unsigned int cache_index = 0;
1953 *   for (const auto &cell : triangulation.active_cell_iterators())
1954 *   if (cell->is_locally_owned())
1955 *   {
1956 *   cell->set_user_pointer(&quadrature_cache[cache_index]);
1957 *   cache_index += quadrature_formula.size();
1958 *   }
1959 *   Assert(cache_index == quadrature_cache.size(), ExcInternalError());
1960 *   }
1961 *  
1962 *  
1963 *  
1964 * @endcode
1965 *
1966 *
1967 * <a name="step_62-ElasticWavefrequency_sweep"></a>
1968 * <h4>ElasticWave::frequency_sweep</h4>
1969 *
1970
1971 *
1972 * For clarity we divide the function `run` of @ref step_40 "step-40" into the functions
1973 * `run` and `frequency_sweep`. In the function `frequency_sweep` we place the
1974 * iteration over the frequency vector.
1975 *
1976 * @code
1977 *   template <int dim>
1978 *   void ElasticWave<dim>::frequency_sweep()
1979 *   {
1980 *   for (unsigned int frequency_idx = 0;
1981 *   frequency_idx < parameters.nb_frequency_points;
1982 *   ++frequency_idx)
1983 *   {
1984 *   pcout << parameters.simulation_name + " frequency idx: "
1985 *   << frequency_idx << '/' << parameters.nb_frequency_points - 1
1986 *   << std::endl;
1987 *  
1988 *  
1989 *  
1990 *   setup_system();
1991 *   if (frequency_idx == 0)
1992 *   {
1993 *   pcout << " Number of active cells : "
1994 *   << triangulation.n_active_cells() << std::endl;
1995 *   pcout << " Number of degrees of freedom : "
1996 *   << dof_handler.n_dofs() << std::endl;
1997 *   }
1998 *  
1999 *   if (frequency_idx == 0)
2000 *   {
2001 * @endcode
2002 *
2003 * Write the simulation parameters only once
2004 *
2005 * @code
2006 *   parameters.data.set_attribute("active_cells",
2007 *   triangulation.n_active_cells());
2008 *   parameters.data.set_attribute("degrees_of_freedom",
2009 *   dof_handler.n_dofs());
2010 *   }
2011 *  
2012 * @endcode
2013 *
2014 * We calculate the frequency and omega values for this particular step.
2015 *
2016 * @code
2017 *   const double current_loop_frequency =
2018 *   (parameters.start_frequency +
2019 *   frequency_idx *
2020 *   (parameters.stop_frequency - parameters.start_frequency) /
2021 *   (parameters.nb_frequency_points - 1));
2022 *   const double current_loop_omega =
2023 *   2 * numbers::PI * current_loop_frequency;
2024 *  
2025 * @endcode
2026 *
2027 * In the first frequency step we calculate the mass and stiffness
2028 * matrices and the right hand side. In the subsequent frequency steps
2029 * we will use those values. This improves considerably the calculation
2030 * time.
2031 *
2032 * @code
2033 *   assemble_system(current_loop_omega,
2034 *   (frequency_idx == 0) ? true : false);
2035 *   solve();
2036 *  
2037 *   frequency[frequency_idx] = current_loop_frequency;
2038 *   store_frequency_step_data(frequency_idx);
2039 *  
2040 *   computing_timer.print_summary();
2041 *   computing_timer.reset();
2042 *   pcout << std::endl;
2043 *   }
2044 *   }
2045 *  
2046 *  
2047 *  
2048 * @endcode
2049 *
2050 *
2051 * <a name="step_62-ElasticWaverun"></a>
2052 * <h4>ElasticWave::run</h4>
2053 *
2054
2055 *
2056 * This function is very similar to the one in @ref step_40 "step-40".
2057 *
2058 * @code
2059 *   template <int dim>
2060 *   void ElasticWave<dim>::run()
2061 *   {
2062 *   if constexpr (running_in_debug_mode())
2063 *   pcout << "Debug mode" << std::endl;
2064 *   else
2065 *   pcout << "Release mode" << std::endl;
2066 *  
2067 *   {
2068 *   Point<dim> p1;
2069 *   p1(0) = -parameters.dimension_x / 2;
2070 *   p1(1) = -parameters.dimension_y / 2;
2071 *   if (dim == 3)
2072 *   {
2073 *   p1(2) = -parameters.dimension_y / 2;
2074 *   }
2075 *   Point<dim> p2;
2076 *   p2(0) = parameters.dimension_x / 2;
2077 *   p2(1) = parameters.dimension_y / 2;
2078 *   if (dim == 3)
2079 *   {
2080 *   p2(2) = parameters.dimension_y / 2;
2081 *   }
2082 *   std::vector<unsigned int> divisions(dim);
2083 *   divisions[0] = int(parameters.dimension_x / parameters.dimension_y);
2084 *   divisions[1] = 1;
2085 *   if (dim == 3)
2086 *   {
2087 *   divisions[2] = 1;
2088 *   }
2090 *   divisions,
2091 *   p1,
2092 *   p2);
2093 *   }
2094 *  
2095 *   triangulation.refine_global(parameters.grid_level);
2096 *  
2097 *   setup_quadrature_cache();
2098 *  
2099 *   initialize_probe_positions_vector();
2100 *  
2101 *   frequency_sweep();
2102 *  
2103 *   output_results();
2104 *   }
2105 *   } // namespace step62
2106 *  
2107 *  
2108 *  
2109 * @endcode
2110 *
2111 *
2112 * <a name="step_62-Themainfunction"></a>
2113 * <h4>The main function</h4>
2114 *
2115
2116 *
2117 * The main function is very similar to the one in @ref step_40 "step-40".
2118 *
2119 * @code
2120 *   int main(int argc, char *argv[])
2121 *   {
2122 *   try
2123 *   {
2124 *   using namespace dealii;
2125 *   const unsigned int dim = 2;
2126 *  
2127 *   Utilities::MPI::MPI_InitFinalize mpi_initialization(argc, argv, 1);
2128 *  
2129 *   HDF5::File data_file("results.h5",
2131 *   MPI_COMM_WORLD);
2132 *   auto data = data_file.create_group("data");
2133 *  
2134 * @endcode
2135 *
2136 * Each of the simulations (displacement and calibration) is stored in a
2137 * separate HDF5 group:
2138 *
2139 * @code
2140 *   const std::array<std::string, 2> group_names{
2141 *   {"displacement", "calibration"}};
2142 *   for (const std::string &group_name : group_names)
2143 *   {
2144 * @endcode
2145 *
2146 * For each of these two group names, we now create the group and put
2147 * attributes into these groups.
2148 * Specifically, these are:
2149 * - The dimensions of the waveguide (in @f$x@f$ and @f$y@f$ directions)
2150 * - The position of the probe (in @f$x@f$ and @f$y@f$ directions)
2151 * - The number of points in the probe
2152 * - The global refinement level
2153 * - The cavity resonance frequency
2154 * - The number of mirror pairs
2155 * - The material properties
2156 * - The force parameters
2157 * - The PML parameters
2158 * - The frequency parameters
2159 *
2160
2161 *
2162 *
2163 * @code
2164 *   auto group = data.create_group(group_name);
2165 *  
2166 *   group.set_attribute<double>("dimension_x", 2e-5);
2167 *   group.set_attribute<double>("dimension_y", 2e-8);
2168 *   group.set_attribute<double>("probe_pos_x", 8e-6);
2169 *   group.set_attribute<double>("probe_pos_y", 0);
2170 *   group.set_attribute<double>("probe_width_y", 2e-08);
2171 *   group.set_attribute<unsigned int>("nb_probe_points", 5);
2172 *   group.set_attribute<unsigned int>("grid_level", 1);
2173 *   group.set_attribute<double>("cavity_resonance_frequency", 20e9);
2174 *   group.set_attribute<unsigned int>("nb_mirror_pairs", 15);
2175 *  
2176 *   group.set_attribute<double>("poissons_ratio", 0.27);
2177 *   group.set_attribute<double>("youngs_modulus", 270000000000.0);
2178 *   group.set_attribute<double>("material_a_rho", 3200);
2179 *  
2180 *   if (group_name == "displacement")
2181 *   group.set_attribute<double>("material_b_rho", 2000);
2182 *   else
2183 *   group.set_attribute<double>("material_b_rho", 3200);
2184 *  
2185 *   group.set_attribute(
2186 *   "lambda",
2187 *   group.get_attribute<double>("youngs_modulus") *
2188 *   group.get_attribute<double>("poissons_ratio") /
2189 *   ((1 + group.get_attribute<double>("poissons_ratio")) *
2190 *   (1 - 2 * group.get_attribute<double>("poissons_ratio"))));
2191 *   group.set_attribute("mu",
2192 *   group.get_attribute<double>("youngs_modulus") /
2193 *   (2 * (1 + group.get_attribute<double>(
2194 *   "poissons_ratio"))));
2195 *  
2196 *   group.set_attribute<double>("max_force_amplitude", 1e26);
2197 *   group.set_attribute<double>("force_sigma_x", 1e-7);
2198 *   group.set_attribute<double>("force_sigma_y", 1);
2199 *   group.set_attribute<double>("max_force_width_x", 3e-7);
2200 *   group.set_attribute<double>("max_force_width_y", 2e-8);
2201 *   group.set_attribute<double>("force_x_pos", -8e-6);
2202 *   group.set_attribute<double>("force_y_pos", 0);
2203 *  
2204 *   group.set_attribute<bool>("pml_x", true);
2205 *   group.set_attribute<bool>("pml_y", false);
2206 *   group.set_attribute<double>("pml_width_x", 1.8e-6);
2207 *   group.set_attribute<double>("pml_width_y", 5e-7);
2208 *   group.set_attribute<double>("pml_coeff", 1.6);
2209 *   group.set_attribute<unsigned int>("pml_coeff_degree", 2);
2210 *  
2211 *   group.set_attribute<double>("center_frequency", 20e9);
2212 *   group.set_attribute<double>("frequency_range", 0.5e9);
2213 *   group.set_attribute<double>(
2214 *   "start_frequency",
2215 *   group.get_attribute<double>("center_frequency") -
2216 *   group.get_attribute<double>("frequency_range") / 2);
2217 *   group.set_attribute<double>(
2218 *   "stop_frequency",
2219 *   group.get_attribute<double>("center_frequency") +
2220 *   group.get_attribute<double>("frequency_range") / 2);
2221 *   group.set_attribute<unsigned int>("nb_frequency_points", 400);
2222 *  
2223 *   if (group_name == "displacement")
2224 *   group.set_attribute<std::string>("simulation_name",
2225 *   "phononic_cavity_displacement");
2226 *   else
2227 *   group.set_attribute<std::string>("simulation_name",
2228 *   "phononic_cavity_calibration");
2229 *  
2230 *   group.set_attribute<bool>("save_vtu_files", false);
2231 *   }
2232 *  
2233 *   {
2234 * @endcode
2235 *
2236 * Displacement simulation. The parameters are read from the
2237 * displacement HDF5 group and the results are saved in the same HDF5
2238 * group.
2239 *
2240 * @code
2241 *   auto displacement = data.open_group("displacement");
2242 *   step62::Parameters<dim> parameters(displacement);
2243 *  
2244 *   step62::ElasticWave<dim> elastic_problem(parameters);
2245 *   elastic_problem.run();
2246 *   }
2247 *  
2248 *   {
2249 * @endcode
2250 *
2251 * Calibration simulation. The parameters are read from the calibration
2252 * HDF5 group and the results are saved in the same HDF5 group.
2253 *
2254 * @code
2255 *   auto calibration = data.open_group("calibration");
2256 *   step62::Parameters<dim> parameters(calibration);
2257 *  
2258 *   step62::ElasticWave<dim> elastic_problem(parameters);
2259 *   elastic_problem.run();
2260 *   }
2261 *   }
2262 *   catch (std::exception &exc)
2263 *   {
2264 *   std::cerr << std::endl
2265 *   << std::endl
2266 *   << "----------------------------------------------------"
2267 *   << std::endl;
2268 *   std::cerr << "Exception on processing: " << std::endl
2269 *   << exc.what() << std::endl
2270 *   << "Aborting!" << std::endl
2271 *   << "----------------------------------------------------"
2272 *   << std::endl;
2273 *  
2274 *   return 1;
2275 *   }
2276 *   catch (...)
2277 *   {
2278 *   std::cerr << std::endl
2279 *   << std::endl
2280 *   << "----------------------------------------------------"
2281 *   << std::endl;
2282 *   std::cerr << "Unknown exception!" << std::endl
2283 *   << "Aborting!" << std::endl
2284 *   << "----------------------------------------------------"
2285 *   << std::endl;
2286 *   return 1;
2287 *   }
2288 *  
2289 *   return 0;
2290 *   }
2291 * @endcode
2292<a name="step_62-Results"></a><h1>Results</h1>
2293
2294
2295<a name="step_62-Resonancefrequencyandbandgap"></a><h3>Resonance frequency and bandgap</h3>
2296
2297
2298The results are analyzed in the
2299[Jupyter Notebook](https://github.com/dealii/dealii/blob/master/examples/step-62/step-62.ipynb)
2300with the following code
2301@code{.py}
2302h5_file = h5py.File('results.h5', 'r')
2303data = h5_file['data']
2304
2305# Gaussian function that we use to fit the resonance
2306def resonance_f(freq, freq_m, quality_factor, max_amplitude):
2307 omega = 2 * constants.pi * freq
2308 omega_m = 2 * constants.pi * freq_m
2309 gamma = omega_m / quality_factor
2310 return max_amplitude * omega_m**2 * gamma**2 / (((omega_m**2 - omega**2)**2 + gamma**2 * omega**2))
2311
2312frequency = data['displacement']['frequency'][...]
2313# Average the probe points
2314displacement = np.mean(data['displacement']['displacement'], axis=0)
2315calibration_displacement = np.mean(data['calibration']['displacement'], axis=0)
2316reflection_coefficient = displacement / calibration_displacement
2317reflectivity = (np.abs(np.mean(data['displacement']['displacement'][...]**2, axis=0))/
2318 np.abs(np.mean(data['calibration']['displacement'][...]**2, axis=0)))
2319
2320try:
2321 x_data = frequency
2322 y_data = reflectivity
2323 quality_factor_guess = 1e3
2324 freq_guess = x_data[np.argmax(y_data)]
2325 amplitude_guess = np.max(y_data)
2326 fit_result, covariance = scipy.optimize.curve_fit(resonance_f, x_data, y_data,
2327 [freq_guess, quality_factor_guess, amplitude_guess])
2328 freq_m = fit_result[0]
2329 quality_factor = np.abs(fit_result[1])
2330 max_amplitude = fit_result[2]
2331 y_data_fit = resonance_f(x_data, freq_m, quality_factor, max_amplitude)
2332
2333 fig = plt.figure()
2334 plt.plot(frequency / 1e9, reflectivity, frequency / 1e9, y_data_fit)
2335 plt.xlabel('frequency (GHz)')
2336 plt.ylabel('amplitude^2 (a.u.)')
2337 plt.title('Transmission\n' + 'freq = ' + "%.7g" % (freq_guess / 1e9) + 'GHz Q = ' + "%.6g" % quality_factor)
2338except:
2339 fig = plt.figure()
2340 plt.plot(frequency / 1e9, reflectivity)
2341 plt.xlabel('frequency (GHz)')
2342 plt.ylabel('amplitude^2 (a.u.)')
2343 plt.title('Transmission')
2344
2345fig = plt.figure()
2346plt.plot(frequency / 1e9, np.angle(reflection_coefficient))
2347plt.xlabel('frequency (GHz)')
2348plt.ylabel('phase (rad)')
2349plt.title('Phase (transmission coefficient)\n')
2350
2351plt.show()
2352h5_file.close()
2353@endcode
2354
2355A phononic cavity is characterized by the
2356[resonance frequency](https://en.wikipedia.org/wiki/Resonance) and the
2357[the quality factor](https://en.wikipedia.org/wiki/Q_factor).
2358The quality factor is equal to the ratio between the stored energy in the resonator and the energy
2359dissipated energy per cycle, which is approximately equivalent to the ratio between the
2360resonance frequency and the
2361[full width at half maximum (FWHM)](https://en.wikipedia.org/wiki/Full_width_at_half_maximum).
2362The FWHM is equal to the bandwidth over which the power of vibration is greater than half the
2363power at the resonant frequency.
2364@f[
2365Q = \frac{f_r}{\Delta f} = \frac{\omega_r}{\Delta \omega} =
23662 \pi \times \frac{\text{energy stored}}{\text{energy dissipated per cycle}}
2367@f]
2368
2369The square of the amplitude of the mechanical resonance @f$a^2@f$ as a function of the frequency
2370has a gaussian shape
2371@f[
2372a^2 = a_\textrm{max}^2\frac{\omega^2\Gamma^2}{(\omega_r^2-\omega^2)^2+\Gamma^2\omega^2}
2373@f]
2374where @f$f_r = \frac{\omega_r}{2\pi}@f$ is the resonance frequency and @f$\Gamma=\frac{\omega_r}{Q}@f$ is the dissipation rate.
2375We used the previous equation in the Jupyter Notebook to fit the mechanical resonance.
2376
2377Given the values we have chosen for the parameters, one could estimate the resonance frequency
2378analytically. Indeed, this is then confirmed by what we get in this program:
2379the phononic superlattice cavity exhibits a mechanical resonance at 20GHz and a quality factor of 5046.
2380The following images show the transmission amplitude and phase as a function of frequency in the
2381vicinity of the resonance frequency:
2382
2383<img alt="Phononic superlattice cavity" src="https://www.dealii.org/images/steps/developer/step-62.05.png" height="400" />
2384<img alt="Phononic superlattice cavity" src="https://www.dealii.org/images/steps/developer/step-62.06.png" height="400" />
2385
2386The images above suggest that the periodic structure has its intended effect: It really only lets waves of a very
2387specific frequency pass through, whereas all other waves are reflected. This is of course precisely what one builds
2388these sorts of devices for.
2389But it is not quite this easy. In practice, there is really only a "band gap", i.e., the device blocks waves other than
2390the desired one at 20GHz only within a certain frequency range. Indeed, to find out how large this "gap" is within
2391which waves are blocked, we can extend the frequency range to 16 GHz through the appropriate parameters in the
2392input file. We then obtain the following image:
2393
2394<img alt="Phononic superlattice cavity" src="https://www.dealii.org/images/steps/developer/step-62.07.png" height="400" />
2395
2396What this image suggests is that in the range of around 18 to around 22 GHz, really only the waves with a frequency
2397of 20 GHz are allowed to pass through, but beyond this range, there are plenty of other frequencies that can pass
2398through the device.
2399
2400<a name="step_62-Modeprofile"></a><h3>Mode profile</h3>
2401
2402
2403We can inspect the mode profile with Paraview or VisIt.
2404As we have discussed, at resonance all the mechanical
2405energy is transmitted and the amplitude of motion is amplified inside the cavity.
2406It can be observed that the PMLs are quite effective to truncate the solution.
2407The following image shows the mode profile at resonance:
2408
2409<img alt="Phononic superlattice cavity" src="https://www.dealii.org/images/steps/developer/step-62.08.png" height="400" />
2410
2411On the other hand, out of resonance all the mechanical energy is
2412reflected. The following image shows the profile at 19.75 GHz.
2413Note the interference between the force pulse and the reflected wave
2414at the position @f$x=-8\mu\textrm{m}@f$.
2415
2416<img alt="Phononic superlattice cavity" src="https://www.dealii.org/images/steps/developer/step-62.09.png" height="400" />
2417
2418<a name="step_62-Experimentalapplications"></a><h3>Experimental applications</h3>
2419
2420
2421Phononic superlattice cavities find application in
2422[quantum optomechanics](https://journals.aps.org/rmp/abstract/10.1103/RevModPhys.86.1391).
2423Here we have presented the simulation of a 2D superlattice cavity,
2424but this code can be used as well to simulate "real world" 3D devices such as
2425[micropillar superlattice cavities](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.99.060101),
2426which are promising candidates to study macroscopic quantum phenomena.
2427The 20GHz mode of a micropillar superlattice cavity is essentially a mechanical harmonic oscillator that is very well isolated
2428from the environment. If the device is cooled down to 20mK in a dilution fridge, the mode would then become a
2429macroscopic quantum harmonic oscillator.
2430
2431
2432<a name="step_62-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
2433
2434
2435Instead of setting the parameters in the C++ file we could set the parameters
2436using a Python script and save them in the HDF5 file that we will use for
2437the simulations. Then the deal.II program will read the parameters from the
2438HDF5 file.
2439
2440@code{.py}
2441import numpy as np
2442import h5py
2443import matplotlib.pyplot as plt
2444import subprocess
2445import scipy.constants as constants
2446import scipy.optimize
2447
2448# This considerably reduces the size of the svg data
2449plt.rcParams['svg.fonttype'] = 'none'
2450
2451h5_file = h5py.File('results.h5', 'w')
2452data = h5_file.create_group('data')
2453displacement = data.create_group('displacement')
2454calibration = data.create_group('calibration')
2455
2456# Set the parameters
2457for group in [displacement, calibration]:
2458 # Dimensions of the domain
2459 # The waveguide length is equal to dimension_x
2460 group.attrs['dimension_x'] = 2e-5
2461 # The waveguide width is equal to dimension_y
2462 group.attrs['dimension_y'] = 2e-8
2463
2464 # Position of the probe that we use to measure the flux
2465 group.attrs['probe_pos_x'] = 8e-6
2466 group.attrs['probe_pos_y'] = 0
2467 group.attrs['probe_width_y'] = 2e-08
2468
2469 # Number of points in the probe
2470 group.attrs['nb_probe_points'] = 5
2471
2472 # Global refinement
2473 group.attrs['grid_level'] = 1
2474
2475 # Cavity
2476 group.attrs['cavity_resonance_frequency'] = 20e9
2477 group.attrs['nb_mirror_pairs'] = 15
2478
2479 # Material
2480 group.attrs['poissons_ratio'] = 0.27
2481 group.attrs['youngs_modulus'] = 270000000000.0
2482 group.attrs['material_a_rho'] = 3200
2483 if group == displacement:
2484 group.attrs['material_b_rho'] = 2000
2485 else:
2486 group.attrs['material_b_rho'] = 3200
2487 group.attrs['lambda'] = (group.attrs['youngs_modulus'] * group.attrs['poissons_ratio'] /
2488 ((1 + group.attrs['poissons_ratio']) *
2489 (1 - 2 * group.attrs['poissons_ratio'])))
2490 group.attrs['mu']= (group.attrs['youngs_modulus'] / (2 * (1 + group.attrs['poissons_ratio'])))
2491
2492 # Force
2493 group.attrs['max_force_amplitude'] = 1e26
2494 group.attrs['force_sigma_x'] = 1e-7
2495 group.attrs['force_sigma_y'] = 1
2496 group.attrs['max_force_width_x'] = 3e-7
2497 group.attrs['max_force_width_y'] = 2e-8
2498 group.attrs['force_x_pos'] = -8e-6
2499 group.attrs['force_y_pos'] = 0
2500
2501 # PML
2502 group.attrs['pml_x'] = True
2503 group.attrs['pml_y'] = False
2504 group.attrs['pml_width_x'] = 1.8e-6
2505 group.attrs['pml_width_y'] = 5e-7
2506 group.attrs['pml_coeff'] = 1.6
2507 group.attrs['pml_coeff_degree'] = 2
2508
2509 # Frequency sweep
2510 group.attrs['center_frequency'] = 20e9
2511 group.attrs['frequency_range'] = 0.5e9
2512 group.attrs['start_frequency'] = group.attrs['center_frequency'] - group.attrs['frequency_range'] / 2
2513 group.attrs['stop_frequency'] = group.attrs['center_frequency'] + group.attrs['frequency_range'] / 2
2514 group.attrs['nb_frequency_points'] = 400
2515
2516 # Other parameters
2517 if group == displacement:
2518 group.attrs['simulation_name'] = 'phononic_cavity_displacement'
2519 else:
2520 group.attrs['simulation_name'] = 'phononic_cavity_calibration'
2521 group.attrs['save_vtu_files'] = False
2522
2523h5_file.close()
2524@endcode
2525
2526In order to read the HDF5 parameters we have to use the
2527HDF5::File::FileAccessMode::open flag.
2528@code{.py}
2529 HDF5::File data_file("results.h5",
2531 MPI_COMM_WORLD);
2532 auto data = data_file.open_group("data");
2533@endcode
2534 *
2535 *
2536<a name="step_62-PlainProg"></a>
2537<h1> The plain program</h1>
2538@include "step-62.cc"
2539*/
void write(const Container &data)
Definition hdf5.h:2000
void write_selection(const Container &data, const std::vector< hsize_t > &coordinates)
Definition hdf5.h:2032
void write_none()
Definition hdf5.h:2190
Definition point.h:113
constexpr bool running_in_debug_mode()
Definition config.h:78
#define Assert(cond, exc)
TriaActiveIterator< CellAccessor< dim, spacedim > > active_cell_iterator
Definition tria.h:1581
void loop(IteratorType begin, std_cxx20::type_identity_t< IteratorType > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(std_cxx20::type_identity_t< DOFINFO > &, std_cxx20::type_identity_t< DOFINFO > &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, AssemblerType &assembler, const LoopControl &lctrl=LoopControl())
Definition loop.h:564
const bool IsBlockVector< VectorType >::value
void make_hanging_node_constraints(const DoFHandler< dim, spacedim > &dof_handler, AffineConstraints< number > &constraints)
void make_sparsity_pattern(const DoFHandler< dim, spacedim > &dof_handler, SparsityPatternBase &sparsity_pattern, const AffineConstraints< number > &constraints={}, const bool keep_constrained_dofs=true, const types::subdomain_id subdomain_id=numbers::invalid_subdomain_id)
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
@ update_quadrature_points
Transformed quadrature points.
const Mapping< dim, spacedim > & get_default_linear_mapping(const Triangulation< dim, spacedim > &triangulation)
Definition mapping.cc:316
MappingQ< dim, spacedim > StaticMappingQ1< dim, spacedim >::mapping
Definition mapping_q1.h:104
const Event initial
Definition event.cc:68
IndexSet extract_locally_relevant_dofs(const DoFHandler< dim, spacedim > &dof_handler)
void subdivided_hyper_rectangle(Triangulation< dim, spacedim > &tria, const std::vector< unsigned int > &repetitions, const Point< dim > &p1, const Point< dim > &p2, const bool colorize=false)
void scale(const double scaling_factor, Triangulation< dim, spacedim > &triangulation)
std::pair< typename MeshType< dim, spacedim >::active_cell_iterator, Point< dim > > find_active_cell_around_point(const Mapping< dim, spacedim > &mapping, const MeshType< dim, spacedim > &mesh, const Point< spacedim > &p, const std::vector< bool > &marked_vertices={}, const double tolerance=1.e-10)
Definition hdf5.h:344
@ valid
Iterator points to a valid object.
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
constexpr types::blas_int one
PETScWrappers::MPI::Vector Vector
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const FEValuesBase< dim > &fetest, const ArrayView< const std::vector< double > > &velocity, const double factor=1.)
Definition advection.h:74
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Definition utilities.cc:193
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
Tensor< 2, dim, Number > l(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
void apply(const Kokkos::TeamPolicy< MemorySpace::Default::kokkos_space::execution_space >::member_type &team_member, const Kokkos::View< Number *, MemorySpace::Default::kokkos_space > shape_data, const ViewTypeIn in, ViewTypeOut out)
void distribute_sparsity_pattern(DynamicSparsityPattern &dsp, const IndexSet &locally_owned_rows, const MPI_Comm mpi_comm, const IndexSet &locally_relevant_rows)
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
Definition mpi.cc:120
constexpr T fixed_power(const T t)
Definition utilities.h:943
void point_value(const DoFHandler< dim, spacedim > &dof, const VectorType &fe_function, const Point< spacedim, double > &point, Vector< typename VectorType::value_type > &value)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
int(& functions)(const void *v1, const void *v2)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
Definition loop.h:70
constexpr double PI
Definition numbers.h:239