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