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-81.h
Go to the documentation of this file.
1,
740 *   types::material_id material)
741 *   {
742 *   return (material == 1 ? epsilon_1 : epsilon_2);
743 *   }
744 *  
745 *   template <int dim>
746 *   std::complex<double> Parameters<dim>::mu_inv(const Point<dim> & /*x*/,
747 *   types::material_id material)
748 *   {
749 *   return (material == 1 ? mu_inv_1 : mu_inv_2);
750 *   }
751 *  
752 *   template <int dim>
753 *   typename Parameters<dim>::rank2_type
754 *   Parameters<dim>::sigma(const Point<dim> & /*x*/,
755 *   types::material_id left,
756 *   types::material_id right)
757 *   {
758 *   return (left == right ? rank2_type() : sigma_tensor);
759 *   }
760 *  
761 *   template <int dim>
762 *   typename Parameters<dim>::rank1_type
763 *   Parameters<dim>::J_a(const Point<dim> &point, types::material_id /*id*/)
764 *   {
765 *   rank1_type J_a;
766 *   const auto distance = (dipole_position - point).norm() / dipole_radius;
767 *   if (distance > 1.)
768 *   return J_a;
769 *   double scale = std::cos(distance * numbers::PI / 2.) *
770 *   std::cos(distance * numbers::PI / 2.) /
771 *   (numbers::PI / 2. - 2. / numbers::PI) / dipole_radius /
772 *   dipole_radius;
773 *   J_a = dipole_strength * dipole_orientation * scale;
774 *   return J_a;
775 *   }
776 *  
777 * @endcode
778 *
779 *
780 * <a name="step_81-PerfectlyMatchedLayerClass"></a>
781 * <h4>PerfectlyMatchedLayer Class</h4>
782 * The PerfectlyMatchedLayer class inherits ParameterAcceptor as well. It
783 * implements the transformation matrices used to modify the permittivity
784 * and permeability tensors supplied from the Parameters class. The
785 * actual transformation of the material tensors will be done in the
786 * assembly loop. The radii and the strength of the PML is specified, and
787 * the coefficients will be modified using transformation matrices within
788 * the PML region. The radii and strength of the PML are editable through
789 * a .prm file. The rotation function @f$T_{exer}@f$ is the same as
790 * introduced in the perfectly matched layer section of the introduction.
791 * Similarly, the matrices A, B and C are defined as follows
792 * @f[
793 * A = T_{e_xe_r}^{-1}
794 * \text{diag}\left(\frac{1}{\bar{d}^2},\frac{1}{d\bar{d}}\right)T_{e_xe_r},\qquad
795 * B = T_{e_xe_r}^{-1} \text{diag}\left(d,\bar{d}\right)T_{e_xe_r},\qquad
796 * C = T_{e_xe_r}^{-1} \text{diag}\left(\frac{1}{\bar{d}},\frac{1}{d}\right)
797 * T_{e_xe_r}.\qquad
798 * @f]
799 *
800
801 *
802 *
803 * @code
804 *   template <int dim>
805 *   class PerfectlyMatchedLayer : public ParameterAcceptor
806 *   {
807 *   public:
808 *   static_assert(dim == 2,
809 *   "The perfectly matched layer is only implemented in 2d.");
810 *  
811 *   Parameters<dim> parameters;
812 *  
813 *   using rank1_type = Tensor<1, dim, std::complex<double>>;
814 *  
815 *   using rank2_type = Tensor<2, dim, std::complex<double>>;
816 *  
817 *   PerfectlyMatchedLayer();
818 *  
819 *   std::complex<double> d(const Point<dim> point);
820 *  
821 *   std::complex<double> d_bar(const Point<dim> point);
822 *  
823 *  
824 *   rank2_type rotation(std::complex<double> d_1,
825 *   std::complex<double> d_2,
826 *   Point<dim> point);
827 *  
828 *   rank2_type a_matrix(const Point<dim> point);
829 *  
830 *   rank2_type b_matrix(const Point<dim> point);
831 *  
832 *   rank2_type c_matrix(const Point<dim> point);
833 *  
834 *   private:
835 *   double inner_radius;
836 *   double outer_radius;
837 *   double strength;
838 *   };
839 *  
840 *  
841 *   template <int dim>
842 *   PerfectlyMatchedLayer<dim>::PerfectlyMatchedLayer()
843 *   : ParameterAcceptor("PerfectlyMatchedLayer")
844 *   {
845 *   inner_radius = 12.;
846 *   add_parameter("inner radius",
847 *   inner_radius,
848 *   "inner radius of the PML shell");
849 *   outer_radius = 20.;
850 *   add_parameter("outer radius",
851 *   outer_radius,
852 *   "outer radius of the PML shell");
853 *   strength = 8.;
854 *   add_parameter("strength", strength, "strength of the PML");
855 *   }
856 *  
857 *  
858 *   template <int dim>
859 *   typename std::complex<double>
860 *   PerfectlyMatchedLayer<dim>::d(const Point<dim> point)
861 *   {
862 *   const auto radius = point.norm();
863 *   if (radius > inner_radius)
864 *   {
865 *   const double s =
866 *   strength * ((radius - inner_radius) * (radius - inner_radius)) /
867 *   ((outer_radius - inner_radius) * (outer_radius - inner_radius));
868 *   return {1.0, s};
869 *   }
870 *   else
871 *   {
872 *   return 1.0;
873 *   }
874 *   }
875 *  
876 *  
877 *   template <int dim>
878 *   typename std::complex<double>
879 *   PerfectlyMatchedLayer<dim>::d_bar(const Point<dim> point)
880 *   {
881 *   const auto radius = point.norm();
882 *   if (radius > inner_radius)
883 *   {
884 *   const double s_bar =
885 *   strength / 3. *
886 *   ((radius - inner_radius) * (radius - inner_radius) *
887 *   (radius - inner_radius)) /
888 *   (radius * (outer_radius - inner_radius) *
889 *   (outer_radius - inner_radius));
890 *   return {1.0, s_bar};
891 *   }
892 *   else
893 *   {
894 *   return 1.0;
895 *   }
896 *   }
897 *  
898 *  
899 *   template <int dim>
900 *   typename PerfectlyMatchedLayer<dim>::rank2_type
901 *   PerfectlyMatchedLayer<dim>::rotation(std::complex<double> d_1,
902 *   std::complex<double> d_2,
903 *   Point<dim> point)
904 *   {
905 *   rank2_type result;
906 *   result[0][0] = point[0] * point[0] * d_1 + point[1] * point[1] * d_2;
907 *   result[0][1] = point[0] * point[1] * (d_1 - d_2);
908 *   result[1][0] = point[0] * point[1] * (d_1 - d_2);
909 *   result[1][1] = point[1] * point[1] * d_1 + point[0] * point[0] * d_2;
910 *   return result;
911 *   }
912 *  
913 *  
914 *   template <int dim>
915 *   typename PerfectlyMatchedLayer<dim>::rank2_type
916 *   PerfectlyMatchedLayer<dim>::a_matrix(const Point<dim> point)
917 *   {
918 *   const auto d = this->d(point);
919 *   const auto d_bar = this->d_bar(point);
920 *   return invert(rotation(d * d, d * d_bar, point)) *
921 *   rotation(d * d, d * d_bar, point);
922 *   }
923 *  
924 *  
925 *   template <int dim>
926 *   typename PerfectlyMatchedLayer<dim>::rank2_type
927 *   PerfectlyMatchedLayer<dim>::b_matrix(const Point<dim> point)
928 *   {
929 *   const auto d = this->d(point);
930 *   const auto d_bar = this->d_bar(point);
931 *   return invert(rotation(d, d_bar, point)) * rotation(d, d_bar, point);
932 *   }
933 *  
934 *  
935 *   template <int dim>
936 *   typename PerfectlyMatchedLayer<dim>::rank2_type
937 *   PerfectlyMatchedLayer<dim>::c_matrix(const Point<dim> point)
938 *   {
939 *   const auto d = this->d(point);
940 *   const auto d_bar = this->d_bar(point);
941 *   return invert(rotation(1. / d_bar, 1. / d, point)) *
942 *   rotation(1. / d_bar, 1. / d, point);
943 *   }
944 *  
945 *  
946 * @endcode
947 *
948 *
949 * <a name="step_81-MaxwellClass"></a>
950 * <h4>Maxwell Class</h4>
951 * At this point we are ready to declare all the major building blocks of
952 * the finite element program which consists of the usual setup and
953 * assembly routines. Most of the structure has already been introduced
954 * in previous tutorial programs. The Maxwell class also holds private
955 * instances of the Parameters and PerfectlyMatchedLayers classes
956 * introduced above. The default values of these parameters are set to
957 * show us a standing wave with absorbing boundary conditions and a PML.
958 *
959
960 *
961 *
962 * @code
963 *   template <int dim>
964 *   class Maxwell : public ParameterAcceptor
965 *   {
966 *   public:
967 *   Maxwell();
968 *   void run();
969 *  
970 *   private:
971 *   /* run time parameters */
972 *   double scaling;
973 *   unsigned int refinements;
974 *   unsigned int fe_order;
975 *   unsigned int quadrature_order;
976 *   bool absorbing_boundary;
977 *  
978 *   void parse_parameters_callback();
979 *   void make_grid();
980 *   void setup_system();
981 *   void assemble_system();
982 *   void solve();
983 *   void output_results();
984 *  
985 *   Parameters<dim> parameters;
986 *   PerfectlyMatchedLayer<dim> perfectly_matched_layer;
987 *  
988 *   Triangulation<dim> triangulation;
989 *   DoFHandler<dim> dof_handler;
990 *  
991 *   std::unique_ptr<FiniteElement<dim>> fe;
992 *  
993 *   AffineConstraints<double> constraints;
994 *   SparsityPattern sparsity_pattern;
995 *   SparseMatrix<double> system_matrix;
996 *   Vector<double> solution;
997 *   Vector<double> system_rhs;
998 *   };
999 *  
1000 * @endcode
1001 *
1002 *
1003 * <a name="step_81-ClassTemplateDefinitionsandImplementation"></a>
1004 * <h3>Class Template Definitions and Implementation</h3>
1005 *
1006
1007 *
1008 *
1009 * <a name="step_81-TheConstructor"></a>
1010 * <h4>The Constructor</h4>
1011 * The Constructor simply consists of default initialization a number of
1012 * discretization parameters (such as the domain size, mesh refinement,
1013 * and the order of finite elements and quadrature) and declaring a
1014 * corresponding entry via ParameterAcceptor::add_parameter(). All of
1015 * these can be modified by editing the .prm file. Absorbing boundary
1016 * conditions can be controlled with the absorbing_boundary boolean. If
1017 * absorbing boundary conditions are disabled we simply enforce
1018 * homogeneous Dirichlet conditions on the tangential component of the
1019 * electric field. In the context of time-harmonic Maxwell's equations
1020 * these are also known as perfectly conducting boundary conditions.
1021 *
1022
1023 *
1024 *
1025 * @code
1026 *   template <int dim>
1027 *   Maxwell<dim>::Maxwell()
1028 *   : ParameterAcceptor("Maxwell")
1029 *   , dof_handler(triangulation)
1030 *   {
1031 *   ParameterAcceptor::parse_parameters_call_back.connect(
1032 *   [&]() { parse_parameters_callback(); });
1033 *  
1034 *   scaling = 20;
1035 *   add_parameter("scaling", scaling, "scale of the hypercube geometry");
1036 *  
1037 *   refinements = 8;
1038 *   add_parameter("refinements",
1039 *   refinements,
1040 *   "number of refinements of the geometry");
1041 *  
1042 *   fe_order = 0;
1043 *   add_parameter("fe order", fe_order, "order of the finite element space");
1044 *  
1045 *   quadrature_order = 1;
1046 *   add_parameter("quadrature order",
1047 *   quadrature_order,
1048 *   "order of the quadrature");
1049 *  
1050 *   absorbing_boundary = true;
1051 *   add_parameter("absorbing boundary condition",
1052 *   absorbing_boundary,
1053 *   "use absorbing boundary conditions?");
1054 *   }
1055 *  
1056 *  
1057 *   template <int dim>
1058 *   void Maxwell<dim>::parse_parameters_callback()
1059 *   {
1060 *   fe = std::make_unique<FESystem<dim>>(FE_NedelecSZ<dim>(fe_order), 2);
1061 *   }
1062 *  
1063 * @endcode
1064 *
1065 * The Maxwell::make_grid() routine creates the mesh for the
1066 * computational domain which in our case is a scaled square domain.
1067 * Additionally, a material interface is introduced by setting the
1068 * material id of the upper half (@f$y>0@f$) to 1 and of the lower half
1069 * (@f$y<0@f$) of the computational domain to 2.
1070 * We are using a block decomposition into real and imaginary matrices
1071 * for the solution matrices. More details on this are available
1072 * under the Results section.
1073 *
1074
1075 *
1076 *
1077 * @code
1078 *   template <int dim>
1079 *   void Maxwell<dim>::make_grid()
1080 *   {
1081 *   GridGenerator::hyper_cube(triangulation, -scaling, scaling);
1082 *   triangulation.refine_global(refinements);
1083 *  
1084 *   if (absorbing_boundary)
1085 *   {
1086 *   for (auto &face : triangulation.active_face_iterators())
1087 *   if (face->at_boundary())
1088 *   face->set_boundary_id(1);
1089 *   };
1090 *  
1091 *   for (auto &cell : triangulation.active_cell_iterators())
1092 *   if (cell->center()[1] > 0.)
1093 *   cell->set_material_id(1);
1094 *   else
1095 *   cell->set_material_id(2);
1096 *  
1097 *  
1098 *   std::cout << "Number of active cells: " << triangulation.n_active_cells()
1099 *   << std::endl;
1100 *   }
1101 *  
1102 * @endcode
1103 *
1104 * The Maxwell::setup_system() routine follows the usual routine of
1105 * enumerating all the degrees of freedom and setting up the matrix and
1106 * vector objects to hold the system data. Enumerating is done by using
1107 * DoFHandler::distribute_dofs().
1108 *
1109
1110 *
1111 *
1112 * @code
1113 *   template <int dim>
1114 *   void Maxwell<dim>::setup_system()
1115 *   {
1116 *   dof_handler.distribute_dofs(*fe);
1117 *   std::cout << "Number of degrees of freedom: " << dof_handler.n_dofs()
1118 *   << std::endl;
1119 *  
1120 *   solution.reinit(dof_handler.n_dofs());
1121 *   system_rhs.reinit(dof_handler.n_dofs());
1122 *  
1123 *   constraints.clear();
1124 *  
1125 *   DoFTools::make_hanging_node_constraints(dof_handler, constraints);
1126 *  
1127 *   VectorTools::project_boundary_values_curl_conforming_l2(
1128 *   dof_handler,
1129 *   0, /* real part */
1130 *   Functions::ZeroFunction<dim>(2 * dim),
1131 *   0, /* boundary id */
1132 *   constraints);
1133 *   VectorTools::project_boundary_values_curl_conforming_l2(
1134 *   dof_handler,
1135 *   dim, /* imaginary part */
1136 *   Functions::ZeroFunction<dim>(2 * dim),
1137 *   0, /* boundary id */
1138 *   constraints);
1139 *  
1140 *   constraints.close();
1141 *  
1142 *   DynamicSparsityPattern dsp(dof_handler.n_dofs(), dof_handler.n_dofs());
1143 *   DoFTools::make_sparsity_pattern(dof_handler,
1144 *   dsp,
1145 *   constraints,
1146 *   /* keep_constrained_dofs = */ true);
1147 *   sparsity_pattern.copy_from(dsp);
1148 *   system_matrix.reinit(sparsity_pattern);
1149 *   }
1150 *  
1151 * @endcode
1152 *
1153 * This is a helper function that takes the tangential component of a tensor.
1154 *
1155 * @code
1156 *   template <int dim>
1157 *   DEAL_II_ALWAYS_INLINE inline Tensor<1, dim, std::complex<double>>
1158 *   tangential_part(const Tensor<1, dim, std::complex<double>> &tensor,
1159 *   const Tensor<1, dim> &normal)
1160 *   {
1161 *   auto result = tensor;
1162 *   result[0] = normal[1] * (tensor[0] * normal[1] - tensor[1] * normal[0]);
1163 *   result[1] = -normal[0] * (tensor[0] * normal[1] - tensor[1] * normal[0]);
1164 *   return result;
1165 *   }
1166 *  
1167 *  
1168 * @endcode
1169 *
1170 * Assemble the stiffness matrix and the right-hand side:
1171 * \f{align*}{
1172 * A_{ij} = \int_\Omega (\mu_r^{-1}\nabla \times \varphi_j) \cdot
1173 * (\nabla\times\bar{\varphi}_i)\text{d}x
1174 * - \int_\Omega \varepsilon_r\varphi_j \cdot \bar{\varphi}_i\text{d}x
1175 * - i\int_\Sigma (\sigma_r^{\Sigma}(\varphi_j)_T) \cdot
1176 * (\bar{\varphi}_i)_T\text{do}x
1177 * - i\int_{\partial\Omega} (\sqrt{\mu_r^{-1}\varepsilon}(\varphi_j)_T) \cdot
1178 * (\nabla\times(\bar{\varphi}_i)_T)\text{d}x, \f} \f{align}{
1179 * F_i = i\int_\Omega J_a \cdot \bar{\varphi_i}\text{d}x - \int_\Omega
1180 * \mu_r^{-1} \cdot (\nabla \times \bar{\varphi_i}) \text{d}x.
1181 * \f}
1182 * In addition, we will be modifying the coefficients if the position of the
1183 * cell is within the PML region.
1184 *
1185
1186 *
1187 *
1188 * @code
1189 *   template <int dim>
1190 *   void Maxwell<dim>::assemble_system()
1191 *   {
1192 *   const QGauss<dim> quadrature_formula(quadrature_order);
1193 *   const QGauss<dim - 1> face_quadrature_formula(quadrature_order);
1194 *  
1195 *   FEValues<dim, dim> fe_values(*fe,
1196 *   quadrature_formula,
1197 *   update_values | update_gradients |
1198 *   update_quadrature_points |
1199 *   update_JxW_values);
1200 *   FEFaceValues<dim, dim> fe_face_values(*fe,
1201 *   face_quadrature_formula,
1202 *   update_values | update_gradients |
1203 *   update_quadrature_points |
1204 *   update_normal_vectors |
1205 *   update_JxW_values);
1206 *  
1207 *   const unsigned int dofs_per_cell = fe->dofs_per_cell;
1208 *  
1209 *   const unsigned int n_q_points = quadrature_formula.size();
1210 *   const unsigned int n_face_q_points = face_quadrature_formula.size();
1211 *  
1212 *   FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
1213 *   Vector<double> cell_rhs(dofs_per_cell);
1214 *   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1215 *  
1216 * @endcode
1217 *
1218 * Next, let us assemble on the interior of the domain on the left hand
1219 * side. So we are computing
1220 * \f{align*}{
1221 * \int_\Omega (\mu_r^{-1}\nabla \times \varphi_i) \cdot
1222 * (\nabla\times\bar{\varphi}_j)\text{d}x
1223 * -
1224 * \int_\Omega \varepsilon_r\varphi_i \cdot \bar{\varphi}_j\text{d}x
1225 * \f}
1226 * and
1227 * \f{align}{
1228 * i\int_\Omega J_a \cdot \bar{\varphi_i}\text{d}x
1229 * - \int_\Omega \mu_r^{-1} \cdot (\nabla \times \bar{\varphi_i})
1230 * \text{d}x.
1231 * \f}
1232 * In doing so, we need test functions @f$\varphi_i@f$ and @f$\varphi_j@f$, and the
1233 * curl of these test variables. We must be careful with the signs of the
1234 * imaginary parts of these complex test variables. Moreover, we have a
1235 * conditional that changes the parameters if the cell is in the PML region.
1236 *
1237 * @code
1238 *   const FEValuesExtractors::Vector real_part(0);
1239 *   const FEValuesExtractors::Vector imag_part(dim);
1240 *   for (const auto &cell : dof_handler.active_cell_iterators())
1241 *   {
1242 *   fe_values.reinit(cell);
1243 *  
1244 *   cell_matrix = 0.;
1245 *   cell_rhs = 0.;
1246 *  
1247 *   cell->get_dof_indices(local_dof_indices);
1248 *   const auto id = cell->material_id();
1249 *  
1250 *   const auto &quadrature_points = fe_values.get_quadrature_points();
1251 *  
1252 *   for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
1253 *   {
1254 *   const Point<dim> &position = quadrature_points[q_point];
1255 *  
1256 *   auto mu_inv = parameters.mu_inv(position, id);
1257 *   auto epsilon = parameters.epsilon(position, id);
1258 *   const auto J_a = parameters.J_a(position, id);
1259 *  
1260 *   const auto A = perfectly_matched_layer.a_matrix(position);
1261 *   const auto B = perfectly_matched_layer.b_matrix(position);
1262 *   const auto d = perfectly_matched_layer.d(position);
1263 *  
1264 *   mu_inv = mu_inv / d;
1265 *   epsilon = invert(A) * epsilon * invert(B);
1266 *  
1267 *   for (const auto i : fe_values.dof_indices())
1268 *   {
1269 *   constexpr std::complex<double> imag{0., 1.};
1270 *  
1271 *   const auto phi_i =
1272 *   fe_values[real_part].value(i, q_point) -
1273 *   imag * fe_values[imag_part].value(i, q_point);
1274 *   const auto curl_phi_i =
1275 *   fe_values[real_part].curl(i, q_point) -
1276 *   imag * fe_values[imag_part].curl(i, q_point);
1277 *  
1278 *   const auto rhs_value =
1279 *   (imag * scalar_product(J_a, phi_i)) * fe_values.JxW(q_point);
1280 *   cell_rhs(i) += rhs_value.real();
1281 *  
1282 *   for (const auto j : fe_values.dof_indices())
1283 *   {
1284 *   const auto phi_j =
1285 *   fe_values[real_part].value(j, q_point) +
1286 *   imag * fe_values[imag_part].value(j, q_point);
1287 *   const auto curl_phi_j =
1288 *   fe_values[real_part].curl(j, q_point) +
1289 *   imag * fe_values[imag_part].curl(j, q_point);
1290 *  
1291 *   const auto temp =
1292 *   (scalar_product(mu_inv * curl_phi_j, curl_phi_i) -
1293 *   scalar_product(epsilon * phi_j, phi_i)) *
1294 *   fe_values.JxW(q_point);
1295 *   cell_matrix(i, j) += temp.real();
1296 *   }
1297 *   }
1298 *   }
1299 *  
1300 * @endcode
1301 *
1302 * Now we assemble the face and the boundary. The following loops will
1303 * assemble
1304 * \f{align*}{
1305 * - i\int_\Sigma (\sigma_r^{\Sigma}(\varphi_i)_T) \cdot
1306 * (\bar{\varphi}_j)_T\text{do}x \f} and \f{align}{
1307 * - i\int_{\partial\Omega} (\sqrt{\mu_r^{-1}\varepsilon}(\varphi_i)_T)
1308 * \cdot (\nabla\times(\bar{\varphi}_j)_T)\text{d}x,
1309 * \f}
1310 * respectively. The test variables and the PML are implemented
1311 * similarly as the domain.
1312 *
1313
1314 *
1315 * If we are at the domain boundary @f$\partial\Omega@f$ and absorbing
1316 * boundary conditions are set (<code>id == 1</code>) we assemble
1317 * the corresponding boundary term:
1318 *
1319
1320 *
1321 *
1322 * @code
1323 *   const FEValuesExtractors::Vector real_part(0);
1324 *   const FEValuesExtractors::Vector imag_part(dim);
1325 *   for (const auto &face : cell->face_iterators())
1326 *   {
1327 *   if (face->at_boundary())
1328 *   {
1329 *   const auto id = face->boundary_id();
1330 *   if (id == 1)
1331 *   {
1332 *   fe_face_values.reinit(cell, face);
1333 *  
1334 *   for (unsigned int q_point = 0; q_point < n_face_q_points;
1335 *   ++q_point)
1336 *   {
1337 *   const auto &position = quadrature_points[q_point];
1338 *  
1339 *   auto mu_inv = parameters.mu_inv(position, id);
1340 *   auto epsilon = parameters.epsilon(position, id);
1341 *  
1342 *   const auto A =
1343 *   perfectly_matched_layer.a_matrix(position);
1344 *   const auto B =
1345 *   perfectly_matched_layer.b_matrix(position);
1346 *   const auto d = perfectly_matched_layer.d(position);
1347 *  
1348 *   mu_inv = mu_inv / d;
1349 *   epsilon = invert(A) * epsilon * invert(B);
1350 *  
1351 *   const auto normal =
1352 *   fe_face_values.normal_vector(q_point);
1353 *  
1354 *   for (const auto i : fe_face_values.dof_indices())
1355 *   {
1356 *   constexpr std::complex<double> imag{0., 1.};
1357 *  
1358 *   const auto phi_i =
1359 *   fe_face_values[real_part].value(i, q_point) -
1360 *   imag *
1361 *   fe_face_values[imag_part].value(i, q_point);
1362 *   const auto phi_i_T = tangential_part(phi_i, normal);
1363 *  
1364 *   for (const auto j : fe_face_values.dof_indices())
1365 *   {
1366 *   const auto phi_j =
1367 *   fe_face_values[real_part].value(j, q_point) +
1368 *   imag *
1369 *   fe_face_values[imag_part].value(j, q_point);
1370 *   const auto phi_j_T =
1371 *   tangential_part(phi_j, normal) *
1372 *   fe_face_values.JxW(q_point);
1373 *  
1374 *   const auto prod = mu_inv * epsilon;
1375 *   const auto sqrt_prod = prod;
1376 *  
1377 *   const auto temp =
1378 *   -imag * scalar_product((sqrt_prod * phi_j_T),
1379 *   phi_i_T);
1380 *   cell_matrix(i, j) += temp.real();
1381 *   } /* j */
1382 *   } /* i */
1383 *   } /* q_point */
1384 *   }
1385 *   }
1386 *   else
1387 *   {
1388 * @endcode
1389 *
1390 * We are on an interior face:
1391 *
1392 * @code
1393 *   const auto face_index = cell->face_iterator_to_index(face);
1394 *  
1395 *   const auto id1 = cell->material_id();
1396 *   const auto id2 = cell->neighbor(face_index)->material_id();
1397 *  
1398 *   if (id1 == id2)
1399 *   continue; /* skip this face */
1400 *  
1401 *   fe_face_values.reinit(cell, face);
1402 *  
1403 *   for (unsigned int q_point = 0; q_point < n_face_q_points;
1404 *   ++q_point)
1405 *   {
1406 *   const auto &position = quadrature_points[q_point];
1407 *  
1408 *   auto sigma = parameters.sigma(position, id1, id2);
1409 *  
1410 *   const auto B = perfectly_matched_layer.b_matrix(position);
1411 *   const auto C = perfectly_matched_layer.c_matrix(position);
1412 *   sigma = invert(C) * sigma * invert(B);
1413 *  
1414 *   const auto normal = fe_face_values.normal_vector(q_point);
1415 *  
1416 *   for (const auto i : fe_face_values.dof_indices())
1417 *   {
1418 *   constexpr std::complex<double> imag{0., 1.};
1419 *  
1420 *   const auto phi_i =
1421 *   fe_face_values[real_part].value(i, q_point) -
1422 *   imag * fe_face_values[imag_part].value(i, q_point);
1423 *   const auto phi_i_T = tangential_part(phi_i, normal);
1424 *  
1425 *   for (const auto j : fe_face_values.dof_indices())
1426 *   {
1427 *   const auto phi_j =
1428 *   fe_face_values[real_part].value(j, q_point) +
1429 *   imag *
1430 *   fe_face_values[imag_part].value(j, q_point);
1431 *   const auto phi_j_T = tangential_part(phi_j, normal);
1432 *  
1433 *   const auto temp =
1434 *   -imag *
1435 *   scalar_product((sigma * phi_j_T), phi_i_T) *
1436 *   fe_face_values.JxW(q_point);
1437 *   cell_matrix(i, j) += temp.real();
1438 *   } /* j */
1439 *   } /* i */
1440 *   } /* q_point */
1441 *   }
1442 *   }
1443 *  
1444 *   constraints.distribute_local_to_global(
1445 *   cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
1446 *   }
1447 *   }
1448 *  
1449 * @endcode
1450 *
1451 * We use a direct solver from the SparseDirectUMFPACK to solve the system
1452 *
1453 * @code
1454 *   template <int dim>
1455 *   void Maxwell<dim>::solve()
1456 *   {
1457 *   SparseDirectUMFPACK A_direct;
1458 *   A_direct.initialize(system_matrix);
1459 *   A_direct.vmult(solution, system_rhs);
1460 *   }
1461 *  
1462 * @endcode
1463 *
1464 * The output is written into a vtk file with 4 components
1465 *
1466 * @code
1467 *   template <int dim>
1468 *   void Maxwell<dim>::output_results()
1469 *   {
1470 *   DataOut<2> data_out;
1471 *   data_out.attach_dof_handler(dof_handler);
1472 *   data_out.add_data_vector(solution,
1473 *   {"real_Ex", "real_Ey", "imag_Ex", "imag_Ey"});
1474 *   data_out.build_patches();
1475 *   const std::string filename = "solution.vtk";
1476 *   std::ofstream output(filename);
1477 *   data_out.write_vtk(output);
1478 *   std::cout << "Output written to " << filename << std::endl;
1479 *   }
1480 *  
1481 *  
1482 *   template <int dim>
1483 *   void Maxwell<dim>::run()
1484 *   {
1485 *   make_grid();
1486 *   setup_system();
1487 *   assemble_system();
1488 *   solve();
1489 *   output_results();
1490 *   }
1491 *  
1492 *   } // namespace Step81
1493 *  
1494 * @endcode
1495 *
1496 * The following main function calls the class @ref step_81 "step-81"(), initializes the
1497 * ParameterAcceptor, and calls the run() function.
1498 *
1499
1500 *
1501 *
1502 * @code
1503 *   int main()
1504 *   {
1505 *   try
1506 *   {
1507 *   using namespace dealii;
1508 *  
1509 *   Step81::Maxwell<2> maxwell_2d;
1510 *   ParameterAcceptor::initialize("parameters.prm");
1511 *   maxwell_2d.run();
1512 *   }
1513 *   catch (std::exception &exc)
1514 *   {
1515 *   std::cerr << std::endl
1516 *   << std::endl
1517 *   << "----------------------------------------------------"
1518 *   << std::endl;
1519 *   std::cerr << "Exception on processing: " << std::endl
1520 *   << exc.what() << std::endl
1521 *   << "Aborting!" << std::endl
1522 *   << "----------------------------------------------------"
1523 *   << std::endl;
1524 *   return 1;
1525 *   }
1526 *   catch (...)
1527 *   {
1528 *   std::cerr << std::endl
1529 *   << std::endl
1530 *   << "----------------------------------------------------"
1531 *   << std::endl;
1532 *   std::cerr << "Unknown exception!" << std::endl
1533 *   << "Aborting!" << std::endl
1534 *   << "----------------------------------------------------"
1535 *   << std::endl;
1536 *   return 1;
1537 *   }
1538 *   return 0;
1539 *   }
1540 * @endcode
1541<a name="step_81-Results"></a><h1>Results</h1>
1542
1543
1544The solution is written to a .vtk file with four components. These are the
1545real and imaginary parts of the @f$E_x@f$ and @f$E_y@f$ solution waves. With the
1546default setup, the output should read
1547
1548@code
1549Number of active cells: 65536
1550Number of degrees of freedom: 263168
1551Output written to solution.vtk
1552@endcode
1553
1554<a name="step_81-AbsorbingboundaryconditionsandthePML"></a><h3> Absorbing boundary conditions and the PML </h3>
1555
1556
1557The following images are the outputs for the imaginary @f$E_x@f$ without the
1558interface and with the dipole centered at @f$(0,0)@f$. In order to remove the
1559interface, the surface conductivity is set to 0. First, we turn off the
1560absorbing boundary conditions and the PML. Second, we want to see the
1561effect of the PML when absorbing boundary conditions apply. So we set
1562absorbing boundary conditions to true and leave the PML strength to 0.
1563Lastly, we increase the strength of the PML to 4. Change the following in
1564the .prm file :
1565
1566@code
1567# use absorbing boundary conditions?
1568 set absorbing boundary condition = false
1569
1570# position of the dipole
1571 set dipole position = 0, 0
1572
1573# strength of the PML
1574 set strength = 0
1575
1576# surface conductivity between material 1 and material 2
1577 set sigma = 0, 0; 0, 0| 0, 0; 0, 0
1578@endcode
1579
1580Following are the output images:
1581
1582<table width="80%" align="center">
1583 <tr>
1584 <td align="center">
1585 <img src="https://www.dealii.org/images/steps/developer/step-81-nointerface_noabs_PML0.png" alt="Visualization of the solution of step-81 with no interface, Dirichlet boundary conditions and PML strength 0" height="210"/>
1586 <p> Solution with no interface, Dirichlet boundary conditions and PML strength 0.</p>
1587 </td>
1588 <td></td>
1589 <td align="center">
1590 <img src="https://www.dealii.org/images/steps/developer/step-81-nointerface_abs_PML0.png" alt="Visualization of the solution of step-81 with no interface, absorbing boundary conditions and PML strength 0" height="210">
1591 <p> Solution with no interface, absorbing boundary conditions and PML strength 0.</p>
1592 </td>
1593 <td></td>
1594 <td align="center">
1595 <img src="https://www.dealii.org/images/steps/developer/step-81-nointerface_abs_PML4.png" alt="Visualization of the solution of step-81 with no interface, absorbing boundary conditions and PML strength 4" height="210">
1596 <p> Solution with no interface, absorbing boundary conditions and PML strength 4.</p>
1597 </td>
1598 </tr>
1599</table>
1600
1601We observe that with absorbing boundary conditions and in absence of the
1602PML, there is a lot of distortion and resonance (the real parts will not be
1603generated without a PML). This is, as we stipulated, due to reflection from
1604infinity. As we see, a much more coherent image is generated with an
1605appropriate PML.
1606
1607<a name="step_81-SurfacePlasmonPolariton"></a><h3> Surface Plasmon Polariton </h3>
1608
1609Now, let's generate a standing wave by adding an interface at the center.
1610In order to observe this effect, we offset the center of the dipole to @f$(0,
16110.8)@f$ and set the surface conductivity back to @f$(0.001, 0.2)@f$:
1612
1613@code
1614# position of the dipole
1615 set dipole position = 0, 0.8
1616
1617# surface conductivity between material 1 and material 2
1618 set sigma = 0.001, 0.2; 0, 0| 0, 0; 0.001, 0.2
1619@endcode
1620
1621Once again, we will visualize the output with absorbing boundary conditions
1622and PML strength 0 and with absorbing boundary conditions and PML strength
16234. The following tables are the imaginary part of @f$E_x@f$ and the real part
1624of @f$E_x@f$.
1625
1626<table width="80%" align="center">
1627 <tr>
1628 <td align="center">
1629 <img src="https://www.dealii.org/images/steps/developer/step-81-imagEx_noabs_PML0.png" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1630 <p> Solution with an interface, Dirichlet boundary conditions and PML strength 0.</p>
1631 </td>
1632 <td></td>
1633 <td align="center">
1634 <img src="https://www.dealii.org/images/steps/developer/step-81-imagEx_abs_PML0.png" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1635 <p> Solution with an interface, absorbing boundary conditions and PML strength 0.</p>
1636 </td>
1637 <td></td>
1638 <td align="center">
1639 <img src="https://www.dealii.org/images/steps/developer/step-81-imagEx_abs_PML4.png" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 4" height="210">
1640 <p> Solution with an interface, absorbing boundary conditions and PML strength 4.</p>
1641 </td>
1642 </tr>
1643</table>
1644
1645
1646<table width="80%" align="center">
1647 <tr>
1648 <td align="center">
1649 <img src="https://www.dealii.org/images/steps/developer/step-81-realEx_noabs_PML0.png" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1650 <p> Solution with an interface, Dirichlet boundary conditions and PML strength 0.</p>
1651 </td>
1652 <td></td>
1653 <td align="center">
1654 <img src="https://www.dealii.org/images/steps/developer/step-81-realEx_abs_PML0.png" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1655 <p> Solution with an interface, absorbing boundary conditions and PML strength 0.</p>
1656 </td>
1657 <td></td>
1658 <td align="center">
1659 <img src="https://www.dealii.org/images/steps/developer/step-81-realEx_abs_PML4.png" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 4" height="210">
1660 <p> Solution with an interface, absorbing boundary conditions and PML strength 4.</p>
1661 </td>
1662 </tr>
1663</table>
1664
1665The SPP is confined near the interface that we created, however without
1666absorbing boundary conditions, we don't observe a dissipation effect. On
1667adding the absorbing boundary conditions, we observe distortion and
1668resonance and we still don't notice any dissipation. As expected, the PML
1669removes the distortion and resonance. The standing wave is also dissipating
1670and getting absorbed within the PML, and as we increase the PML strength,
1671the standing wave will dissipate more within the PML ring.
1672
1673Here are some animations to demonstrate the effect of the PML
1674<table width="80%" align="center">
1675 <tr>
1676 <td align="center">
1677 <img src="https://www.dealii.org/images/steps/developer/step-81-dirichlet_Ex.gif" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1678 <p> Solution with an interface, Dirichlet boundary conditions and PML strength 0.</p>
1679 </td>
1680 <td></td>
1681 <td align="center">
1682 <img src="https://www.dealii.org/images/steps/developer/step-81-absorbing_Ex.gif" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1683 <p> Solution with an interface, absorbing boundary conditions and PML strength 0.</p>
1684 </td>
1685 <td></td>
1686 <td align="center">
1687 <img src="https://www.dealii.org/images/steps/developer/step-81-perfectly_matched_layer_Ex.gif" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 4" height="210">
1688 <p> Solution with an interface, absorbing boundary conditions and PML strength 4.</p>
1689 </td>
1690 </tr>
1691</table>
1692
1693
1694<table width="80%" align="center">
1695 <tr>
1696 <td align="center">
1697 <img src="https://www.dealii.org/images/steps/developer/step-81-dirichlet_Ey.gif" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1698 <p> Solution with an interface, Dirichlet boundary conditions and PML strength 0.</p>
1699 </td>
1700 <td></td>
1701 <td align="center">
1702 <img src="https://www.dealii.org/images/steps/developer/step-81-absorbing_Ey.gif" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 0" height="210">
1703 <p> Solution with an interface, absorbing boundary conditions and PML strength 0.</p>
1704 </td>
1705 <td></td>
1706 <td align="center">
1707 <img src="https://www.dealii.org/images/steps/developer/step-81-perfectly_matched_layer_Ey.gif" alt="Visualization of the solution of step-81 with an interface, absorbing boundary conditions and PML strength 4" height="210">
1708 <p> Solution with an interface, absorbing boundary conditions and PML strength 4.</p>
1709 </td>
1710 </tr>
1711</table>
1712
1713<a name="step_81-Notes"></a><h3> Notes </h3>
1714
1715
1716<a name="step_81-RealandComplexMatrices"></a><h4> Real and Complex Matrices </h4>
1717
1718As is evident from the results, we are splitting our solution matrices into
1719the real and the imaginary components. We started off using the @f$H^{curl}@f$
1720conforming Nédélec Elements, and we made two copies of the Finite Elements
1721in order to represent the real and the imaginary components of our input
1722(FE_NedelecSZ was used instead of FE_Nedelec to avoid the sign conflicts
1723issues present in traditional Nédélec elements). In the assembly, we create
1724two vectors of dimension @f$dim@f$ that assist us in extracting the real and
1725the imaginary components of our finite elements.
1726
1727
1728<a name="step_81-RotationsandScaling"></a><h4> Rotations and Scaling </h4>
1729
1730As we see in our assembly, our finite element is rotated and scaled as
1731follows:
1732
1733@code
1734const auto phi_i = real_part.value(i, q_point) - 1.0i * imag_part.value(i, q_point);
1735@endcode
1736
1737This @f$\phi_i@f$ variable doesn't need to be scaled in this way, we may choose
1738any arbitrary scaling constants @f$a@f$ and @f$b@f$. If we choose this scaling, the
1739@f$\phi_j@f$ must also be modified with the same scaling, as follows:
1740
1741@code
1742const auto phi_i = a*real_part.value(i, q_point) -
1743 bi * imag_part.value(i, q_point);
1744
1745const auto phi_j = a*real_part.value(i, q_point) +
1746 bi * imag_part.value(i, q_point);
1747@endcode
1748
1749Moreover, the cell_rhs need not be the real part of the rhs_value. Say if
1750we modify to take the imaginary part of the computed rhs_value, we must
1751also modify the cell_matrix accordingly to take the imaginary part of temp.
1752However, making these changes to both sides of the equation will not affect
1753our solution, and we will still be able to generate the surface plasmon
1754polariton.
1755
1756@code
1757cell_rhs(i) += rhs_value.imag();
1758
1759cell_matrix(i) += temp.imag();
1760@endcode
1761
1762<a name="step_81-Postprocessing"></a><h4> Postprocessing </h4>
1763
1764We will create a video demonstrating the wave in motion, which is
1765essentially an implementation of @f$e^{-i\omega t}(Re(E) + i*Im(E))@f$ as we
1766increment time. This is done by slightly changing the output function to
1767generate a series of .vtk files, which will represent out solution wave as
1768we increment time. Introduce an input variable @f$t@f$ in the output_results()
1769class as output_results(unsigned int t). Then change the class itself to
1770the following:
1771
1772@code
1773template <int dim>
1774void Maxwell<dim>::output_results(unsigned int t)
1775{
1776 std::cout << "Running step:" << t << std::endl;
1777 DataOut<2> data_out;
1778 data_out.attach_dof_handler(dof_handler);
1779 Vector<double> postprocessed;
1780 postprocessed.reinit(solution);
1781 for (unsigned int i = 0; i < dof_handler.n_dofs(); ++i)
1782 {
1783 if (i % 4 == 0)
1784 {
1785 postprocessed[i] = std::cos(2. * numbers::PI * 0.04 * t) * solution[i] -
1786 std::sin(2. * numbers::PI * 0.04 * t) * solution[i + 1];
1787 }
1788 else if (i % 4 == 2)
1789 {
1790 postprocessed[i] = std::cos(2. * numbers::PI * 0.04 * t) * solution[i] -
1791 std::sin(2. * numbers::PI * 0.04 * t) * solution[i + 1];
1792 }
1793 }
1794 data_out.add_data_vector(postprocessed, {"E_x", "E_y", "null0", "null1"});
1795 data_out.build_patches();
1796 const std::string filename =
1797 "solution-" + Utilities::int_to_string(t) + ".vtk";
1798 std::ofstream output(filename);
1799 data_out.write_vtk(output);
1800 std::cout << "Done running step:" << t << std::endl;
1801}
1802@endcode
1803
1804Finally, in the run() function, replace output_results() with
1805@code
1806for (int t = 0; t <= 100; t++)
1807 {
1808 output_results(t);
1809 }
1810@endcode
1811
1812This would generate 100 solution .vtk files, which can be opened in a group
1813on Paraview and then can be saved as an animation. We used FFMPEG to
1814generate gifs.
1815
1816<a name="step_81-PossibilitiesforExtension"></a><h3> Possibilities for Extension </h3>
1817
1818
1819The example step could be extended in a number of different directions.
1820<ul>
1821 <li>
1822 The current program uses a direct solver to solve the linear system.
1823 This is efficient for two spatial dimensions where scattering problems
1824 up to a few millions degrees of freedom can be solved. In 3D, however,
1825 the increased stencil size of the Nedelec element pose a severe
1826 limiting factor on the problem size that can be computed. As an
1827 alternative, the idea to use iterative solvers can be entertained.
1828 This, however requires specialized preconditioners. For example, just
1829 using an iterative Krylov space solver (such as SolverGMRES) on above
1830 problem will requires many thousands of iterations to converge.
1831 Unfortunately, time-harmonic Maxwell's equations lack the usual notion
1832 of local smoothing properties, which renders the usual suspects, such
1833 as a geometric multigrid (see the Multigrid class), largely useless. A
1834 possible extension would be to implement an additive Schwarz preconditioner
1835 (based on domain decomposition, see for example
1836 @cite Gopalakrishnan2003), or a sweeping preconditioner (see for
1837 example @cite Ying2012).
1838 </li>
1839 <li>
1840 Another possible extension of the current program is to introduce local
1841 mesh refinement (either based on a residual estimator, or based on the
1842 dual weighted residual method, see @ref step_14 "step-14"). This is in particular of
1843 interest to counter the increased computational cost caused by the
1844 scale separation between the SPP and the dipole.
1845 </li>
1846</ul>
1847 *
1848 *
1849<a name="step_81-PlainProg"></a>
1850<h1> The plain program</h1>
1851@include "step-81.cc"
1852*/
void add_parameter(const std::string &entry, ParameterType &parameter, const std::string &documentation="", ParameterHandler &prm_=prm, const Patterns::PatternBase &pattern= *Patterns::Tools::Convert< ParameterType >::to_pattern())
Definition point.h:113
numbers::NumberTraits< double >::real_type norm() const
const unsigned int DoFAccessor< structdim, dim, spacedim, level_dof_access >::dimension
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
void scale(const double scaling_factor, Triangulation< dim, spacedim > &triangulation)
constexpr char A
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
Definition divergence.h:471
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 > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
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)
constexpr double PI
Definition numbers.h:239
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
unsigned int material_id
Definition types.h:184
DEAL_II_HOST constexpr SymmetricTensor< 2, dim, Number > invert(const SymmetricTensor< 2, dim, Number > &)