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-19.h
Go to the documentation of this file.
1dim)
764 *   , next_unused_particle_id(0)
765 *   , n_recently_lost_particles(0)
766 *   , n_total_lost_particles(0)
767 *   , n_particles_lost_through_anode(0)
768 *   , time(0, 1e-4)
769 *   {
770 *   particle_handler.signals.particle_lost.connect(
771 *   [this](const typename Particles::ParticleIterator<dim> &particle,
772 *   const typename Triangulation<dim>::active_cell_iterator &cell) {
773 *   this->track_lost_particle(particle, cell);
774 *   });
775 *   }
776 *  
777 *  
778 *  
779 * @endcode
780 *
781 *
782 * <a name="step_19-ThecodeCathodeRaySimulatormake_gridcodefunction"></a>
783 * <h4>The <code>CathodeRaySimulator::make_grid</code> function</h4>
784 *
785
786 *
787 * The next function is then responsible for generating the mesh on which
788 * we want to solve. Recall how the domain looks like:
789 * <p align="center">
790 * <img
791 * src="https://www.dealii.org/images/steps/developer/step-19.geometry.png"
792 * alt="The geometry used in this program"
793 * width="600">
794 * </p>
795 * We subdivide this geometry into a mesh of @f$4\times 2@f$ cells that looks
796 * like this:
797 * <div class=CodeFragmentInTutorialComment>
798 * @code
799 * *---*---*---*---*
800 * \ | | | |
801 * *--*---*---*---*
802 * / | | | |
803 * *---*---*---*---*
804 * @endcode
805 * </div>
806 * The way this is done is by first defining where the @f$15=5\times 3@f$
807 * vertices are located -- here, we say that they are on integer points
808 * with the middle one on the left side moved to the right by a value of
809 * `delta=0.5`.
810 *
811
812 *
813 * In the following, we then have to say which vertices together form
814 * the 8 cells. The following code is then entirely equivalent to what
815 * we also do in @ref step_14 "step-14":
816 *
817 * @code
818 *   template <int dim>
819 *   void CathodeRaySimulator<dim>::make_grid()
820 *   {
821 *   static_assert(dim == 2,
822 *   "This function is currently only implemented for 2d.");
823 *  
824 *   const double delta = 0.5;
825 *   const unsigned int nx = 5;
826 *   const unsigned int ny = 3;
827 *  
828 *   const std::vector<Point<dim>> vertices
829 *   = {{0, 0},
830 *   {1, 0},
831 *   {2, 0},
832 *   {3, 0},
833 *   {4, 0},
834 *   {delta, 1},
835 *   {1, 1},
836 *   {2, 1},
837 *   {3, 1},
838 *   {4, 1},
839 *   {0, 2},
840 *   {1, 2},
841 *   {2, 2},
842 *   {3, 2},
843 *   {4, 2}};
844 *   AssertDimension(vertices.size(), nx * ny);
845 *  
846 *   const std::vector<unsigned int> cell_vertices[(nx - 1) * (ny - 1)] = {
847 *   {0, 1, nx + 0, nx + 1},
848 *   {1, 2, nx + 1, nx + 2},
849 *   {2, 3, nx + 2, nx + 3},
850 *   {3, 4, nx + 3, nx + 4},
851 *  
852 *   {5, nx + 1, 2 * nx + 0, 2 * nx + 1},
853 *   {nx + 1, nx + 2, 2 * nx + 1, 2 * nx + 2},
854 *   {nx + 2, nx + 3, 2 * nx + 2, 2 * nx + 3},
855 *   {nx + 3, nx + 4, 2 * nx + 3, 2 * nx + 4}};
856 *  
857 * @endcode
858 *
859 * With these arrays out of the way, we can move to slightly higher
860 * higher-level data structures. We create a vector of CellData
861 * objects that store for each cell to be created the vertices in
862 * question as well as the @ref GlossMaterialId "material id" (which
863 * we will here simply set to zero since we don't use it in the program).
864 *
865
866 *
867 * This information is then handed to the
868 * Triangulation::create_triangulation() function, and the mesh is twice
869 * globally refined. As discussed in the corresponding place in @ref step_14 "step-14",
870 * the inputs to Triangulation::create_triangulation() need to be
871 * consistently oriented, which a function in namespace GridTools
872 * does for us.
873 *
874 * @code
875 *   std::vector<CellData<dim>> cells((nx - 1) * (ny - 1), CellData<dim>());
876 *   for (unsigned int i = 0; i < cells.size(); ++i)
877 *   {
878 *   cells[i].vertices = cell_vertices[i];
879 *   cells[i].material_id = 0;
880 *   }
881 *  
882 *   GridTools::consistently_order_cells(cells);
883 *   triangulation.create_triangulation(
884 *   vertices,
885 *   cells,
886 *   SubCellData()); // No boundary information
887 *  
888 *   triangulation.refine_global(2);
889 *  
890 * @endcode
891 *
892 * The remaining part of the function loops over all cells and their faces,
893 * and if a face is at the boundary determines which boundary indicator
894 * should be applied to it. The various conditions should make sense if
895 * you compare the code with the picture of the geometry above.
896 *
897
898 *
899 * Once done with this step, we refine the mesh once more globally.
900 *
901 * @code
902 *   for (auto &cell : triangulation.active_cell_iterators())
903 *   for (auto &face : cell->face_iterators())
904 *   if (face->at_boundary())
905 *   {
906 *   if ((face->center()[0] > 0) && (face->center()[0] < 0.5) &&
907 *   (face->center()[1] > 0) && (face->center()[1] < 2))
908 *   face->set_boundary_id(BoundaryIds::cathode);
909 *   else if ((face->center()[0] > 0) && (face->center()[0] < 2))
910 *   face->set_boundary_id(BoundaryIds::focus_element);
911 *   else if ((face->center()[0] > 4 - 1e-12) &&
912 *   ((face->center()[1] > 1.5) || (face->center()[1] < 0.5)))
913 *   face->set_boundary_id(BoundaryIds::anode);
914 *   else
915 *   face->set_boundary_id(BoundaryIds::open);
916 *   }
917 *  
918 *   triangulation.refine_global(1);
919 *   }
920 *  
921 *  
922 * @endcode
923 *
924 *
925 * <a name="step_19-ThecodeCathodeRaySimulatorsetup_systemcodefunction"></a>
926 * <h4>The <code>CathodeRaySimulator::setup_system</code> function</h4>
927 *
928
929 *
930 * The next function in this program deals with setting up the various
931 * objects related to solving the partial differential equations. It is
932 * in essence a copy of the corresponding function in @ref step_6 "step-6" and requires
933 * no further discussion.
934 *
935 * @code
936 *   template <int dim>
937 *   void CathodeRaySimulator<dim>::setup_system()
938 *   {
939 *   dof_handler.distribute_dofs(fe);
940 *  
941 *   solution.reinit(dof_handler.n_dofs());
942 *   system_rhs.reinit(dof_handler.n_dofs());
943 *  
944 *   constraints.clear();
945 *   DoFTools::make_hanging_node_constraints(dof_handler, constraints);
946 *  
947 *   VectorTools::interpolate_boundary_values(dof_handler,
948 *   BoundaryIds::cathode,
949 *   Functions::ConstantFunction<dim>(
950 *   -Constants::V0),
951 *   constraints);
952 *   VectorTools::interpolate_boundary_values(dof_handler,
953 *   BoundaryIds::focus_element,
954 *   Functions::ConstantFunction<dim>(
955 *   -Constants::V0),
956 *   constraints);
957 *   VectorTools::interpolate_boundary_values(dof_handler,
958 *   BoundaryIds::anode,
959 *   Functions::ConstantFunction<dim>(
960 *   +Constants::V0),
961 *   constraints);
962 *   constraints.close();
963 *  
964 *   DynamicSparsityPattern dsp(dof_handler.n_dofs());
965 *   DoFTools::make_sparsity_pattern(dof_handler,
966 *   dsp,
967 *   constraints,
968 *   /*keep_constrained_dofs = */ false);
969 *   sparsity_pattern.copy_from(dsp);
970 *  
971 *   system_matrix.reinit(sparsity_pattern);
972 *   }
973 *  
974 *  
975 * @endcode
976 *
977 *
978 * <a name="step_19-ThecodeCathodeRaySimulatorassemble_systemcodefunction"></a>
979 * <h4>The <code>CathodeRaySimulator::assemble_system</code> function</h4>
980 *
981
982 *
983 * The function that computes
984 * the matrix entries is again in essence a copy of the
985 * corresponding function in @ref step_6 "step-6":
986 *
987 * @code
988 *   template <int dim>
989 *   void CathodeRaySimulator<dim>::assemble_system()
990 *   {
991 *   system_matrix = 0;
992 *   system_rhs = 0;
993 *  
994 *   const QGauss<dim> quadrature_formula(fe.degree + 1);
995 *  
996 *   FEValues<dim> fe_values(fe,
997 *   quadrature_formula,
998 *   update_values | update_gradients |
999 *   update_quadrature_points | update_JxW_values);
1000 *  
1001 *   const unsigned int dofs_per_cell = fe.dofs_per_cell;
1002 *  
1003 *   FullMatrix<double> cell_matrix(dofs_per_cell, dofs_per_cell);
1004 *   Vector<double> cell_rhs(dofs_per_cell);
1005 *  
1006 *   std::vector<types::global_dof_index> local_dof_indices(dofs_per_cell);
1007 *  
1008 *   for (const auto &cell : dof_handler.active_cell_iterators())
1009 *   {
1010 *   cell_matrix = 0;
1011 *   cell_rhs = 0;
1012 *  
1013 *   fe_values.reinit(cell);
1014 *  
1015 *   for (const unsigned int q_index : fe_values.quadrature_point_indices())
1016 *   for (const unsigned int i : fe_values.dof_indices())
1017 *   {
1018 *   for (const unsigned int j : fe_values.dof_indices())
1019 *   cell_matrix(i, j) +=
1020 *   (fe_values.shape_grad(i, q_index) * // grad phi_i(x_q)
1021 *   fe_values.shape_grad(j, q_index) * // grad phi_j(x_q)
1022 *   fe_values.JxW(q_index)); // dx
1023 *   }
1024 *  
1025 * @endcode
1026 *
1027 * The only interesting part of this function is how it forms the right
1028 * hand side of the linear system. Recall that the right hand side
1029 * of the PDE is
1030 * @f[
1031 * \sum_p (N e)\delta(\mathbf x-\mathbf x_p),
1032 * @f]
1033 * where we have used @f$p@f$ to index the particles here to avoid
1034 * confusion with the shape function @f$\varphi_i@f$; @f$\mathbf x_p@f$
1035 * is the position of the @f$p@f$th particle.
1036 *
1037
1038 *
1039 * When multiplied by a test function @f$\varphi_i@f$ and integrated over
1040 * the domain results in a right hand side vector
1041 * @f{align*}{
1042 * F_i &= \int_\Omega \varphi_i (\mathbf x)\left[
1043 * \sum_p (N e)\delta(\mathbf x-\mathbf x_p) \right] dx
1044 * \\ &= \sum_p (N e) \varphi_i(\mathbf x_p).
1045 * @f}
1046 * Note that the final line no longer contains an integral, and
1047 * consequently also no occurrence of @f$dx@f$ which would require the
1048 * appearance of the `JxW` symbol in our code.
1049 *
1050
1051 *
1052 * For a given cell @f$K@f$, this cell's contribution to the right hand
1053 * side is then
1054 * @f{align*}{
1055 * F_i^K &= \sum_{p, \mathbf x_p\in K} (N e) \varphi_i(\mathbf x_p),
1056 * @f}
1057 * i.e., we only have to worry about those particles that are actually
1058 * located on the current cell @f$K@f$.
1059 *
1060
1061 *
1062 * In practice, what we do here is the following: If there are any
1063 * particles on the current cell, then we first obtain an iterator range
1064 * pointing to the first particle of that cell as well as the particle
1065 * past the last one on this cell (or the end iterator) -- i.e., a
1066 * half-open range as is common for C++ functions. Knowing now the list
1067 * of particles, we query their reference locations (with respect to
1068 * the reference cell), evaluate the shape functions in these reference
1069 * locations, and compute the force according to the formula above
1070 * (without any FEValues::JxW).
1071 *
1072
1073 *
1074 * @note It is worth pointing out that calling the
1076 * Particles::ParticleHandler::n_particles_in_cell() functions is not
1077 * very efficient on problems with a large number of particles. But it
1078 * illustrates the easiest way to write this algorithm, and so we are
1079 * willing to incur this cost for the moment for expository purposes.
1080 * We discuss the issue in more detail in the
1081 * <a href="#extensions">"possibilities for extensions" section</a>
1082 * below, and use a better approach in @ref step_70 "step-70", for example.
1083 *
1084 * @code
1085 *   if (particle_handler.n_particles_in_cell(cell) > 0)
1086 *   for (const auto &particle : particle_handler.particles_in_cell(cell))
1087 *   {
1088 *   const Point<dim> &reference_location =
1089 *   particle.get_reference_location();
1090 *   for (const unsigned int i : fe_values.dof_indices())
1091 *   cell_rhs(i) +=
1092 *   (fe.shape_value(i, reference_location) * // phi_i(x_p)
1093 *   (-Constants::electrons_per_particle * // N
1094 *   Constants::electron_charge)); // e
1095 *   }
1096 *  
1097 * @endcode
1098 *
1099 * Finally, we can copy the contributions of this cell into
1100 * the global matrix and right hand side vector:
1101 *
1102 * @code
1103 *   cell->get_dof_indices(local_dof_indices);
1104 *   constraints.distribute_local_to_global(
1105 *   cell_matrix, cell_rhs, local_dof_indices, system_matrix, system_rhs);
1106 *   }
1107 *   }
1108 *  
1109 *  
1110 * @endcode
1111 *
1112 *
1113 * <a name="step_19-CathodeRaySimulatorsolve"></a>
1114 * <h4>CathodeRaySimulator::solve</h4>
1115 *
1116
1117 *
1118 * The function that solves the linear system is then again exactly as in
1119 * @ref step_6 "step-6":
1120 *
1121 * @code
1122 *   template <int dim>
1123 *   void CathodeRaySimulator<dim>::solve_field()
1124 *   {
1125 *   SolverControl solver_control(1000, 1e-12);
1126 *   SolverCG<Vector<double>> solver(solver_control);
1127 *  
1128 *   PreconditionSSOR<SparseMatrix<double>> preconditioner;
1129 *   preconditioner.initialize(system_matrix, 1.2);
1130 *  
1131 *   solver.solve(system_matrix, solution, system_rhs, preconditioner);
1132 *  
1133 *   constraints.distribute(solution);
1134 *   }
1135 *  
1136 *  
1137 * @endcode
1138 *
1139 *
1140 * <a name="step_19-CathodeRaySimulatorrefine_grid"></a>
1141 * <h4>CathodeRaySimulator::refine_grid</h4>
1142 *
1143
1144 *
1145 * The final field-related function is the one that refines the grid. We will
1146 * call it a number of times in the first time step to obtain a mesh that
1147 * is well-adapted to the structure of the solution and, in particular,
1148 * resolves the various singularities in the solution that are due to
1149 * re-entrant corners and places where the boundary condition type
1150 * changes. You might want to refer to @ref step_6 "step-6" again for more details:
1151 *
1152 * @code
1153 *   template <int dim>
1154 *   void CathodeRaySimulator<dim>::refine_grid()
1155 *   {
1156 *   Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
1157 *  
1158 *   KellyErrorEstimator<dim>::estimate(dof_handler,
1159 *   QGauss<dim - 1>(fe.degree + 1),
1160 *   {},
1161 *   solution,
1162 *   estimated_error_per_cell);
1163 *  
1164 *   GridRefinement::refine_and_coarsen_fixed_number(triangulation,
1165 *   estimated_error_per_cell,
1166 *   0.1,
1167 *   0.03);
1168 *  
1169 *   triangulation.execute_coarsening_and_refinement();
1170 *   }
1171 *  
1172 *  
1173 * @endcode
1174 *
1175 *
1176 * <a name="step_19-CathodeRaySimulatorcreate_particles"></a>
1177 * <h4>CathodeRaySimulator::create_particles</h4>
1178 *
1179
1180 *
1181 * Let us now turn to the functions that deal with particles. The first one
1182 * is about the creation of particles. As mentioned in the introduction,
1183 * we want to create a particle at points of the cathode if the electric
1184 * field @f$\mathbf E=\nabla V@f$ exceeds a certain threshold, i.e., if
1185 * @f$|\mathbf E| \ge E_\text{threshold}@f$, and if furthermore the electric field
1186 * points into the domain (i.e., if @f$\mathbf E \cdot \mathbf n < 0@f$). As is
1187 * common in the finite element method, we evaluate fields (and their
1188 * derivatives) at specific evaluation points; typically, these are
1189 * "quadrature points", and so we create a "quadrature formula" that we will
1190 * use to designate the points at which we want to evaluate the solution.
1191 * Here, we will simply take QMidpoint implying that we will only check the
1192 * threshold condition at the midpoints of faces. We then use this to
1193 * initialize an object of type FEFaceValues to evaluate the solution at these
1194 * points.
1195 *
1196
1197 *
1198 * All of this will then be used in a loop over all cells, their faces, and
1199 * specifically those faces that are at the boundary and, moreover, the
1200 * cathode part of the boundary.
1201 *
1202 * @code
1203 *   template <int dim>
1204 *   void CathodeRaySimulator<dim>::create_particles()
1205 *   {
1206 *   FEFaceValues<dim> fe_face_values(fe,
1207 *   QMidpoint<dim - 1>(),
1208 *   update_quadrature_points |
1209 *   update_gradients |
1210 *   update_normal_vectors);
1211 *  
1212 *   std::vector<Tensor<1, dim>> solution_gradients(
1213 *   fe_face_values.n_quadrature_points);
1214 *  
1215 *   for (const auto &cell : dof_handler.active_cell_iterators())
1216 *   for (const auto &face : cell->face_iterators())
1217 *   if (face->at_boundary() &&
1218 *   (face->boundary_id() == BoundaryIds::cathode))
1219 *   {
1220 *   fe_face_values.reinit(cell, face);
1221 *  
1222 * @endcode
1223 *
1224 * So we have found a face on the cathode. Next, we let the
1225 * FEFaceValues object compute the gradient of the solution at each
1226 * "quadrature" point, and extract the electric field vector from
1227 * the gradient in the form of a Tensor variable through the methods
1228 * discussed in the
1229 * @ref vector_valued "vector-valued problems" documentation topic.
1230 *
1231 * @code
1232 *   const FEValuesExtractors::Scalar electric_potential(0);
1233 *   fe_face_values[electric_potential].get_function_gradients(
1234 *   solution, solution_gradients);
1235 *   for (const unsigned int q_point :
1236 *   fe_face_values.quadrature_point_indices())
1237 *   {
1238 *   const Tensor<1, dim> E = solution_gradients[q_point];
1239 *  
1240 * @endcode
1241 *
1242 * Electrons can only escape the cathode if the electric field
1243 * strength exceeds a threshold and,
1244 * crucially, if the electric field points *into* the domain.
1245 * Once we have that checked, we create a new
1246 * Particles::Particle object at this location and insert it
1247 * into the Particles::ParticleHandler object with a unique ID.
1248 *
1249
1250 *
1251 * The only thing that may be not obvious here is that we also
1252 * associate with this particle the location in the reference
1253 * coordinates of the cell we are currently on. This is done
1254 * because we will in downstream functions compute quantities
1255 * such as the electric field at the location of the particle
1256 * (e.g., to compute the forces that act on it when updating its
1257 * position in each time step). Evaluating a finite element
1258 * field at arbitrary coordinates is quite an expensive
1259 * operation because shape functions are really only defined on
1260 * the reference cell, and so when asking for the electric field
1261 * at an arbitrary point requires us first to determine what the
1262 * reference coordinates of that point are. To avoid having to
1263 * do this over and over, we determine these coordinates once
1264 * and for all and then store these reference coordinates
1265 * directly with the particle.
1266 *
1267 * @code
1268 *   if ((E * fe_face_values.normal_vector(q_point) < 0) &&
1269 *   (E.norm() > Constants::E_threshold))
1270 *   {
1271 *   const Point<dim> &location =
1272 *   fe_face_values.quadrature_point(q_point);
1273 *  
1274 *   Particles::Particle<dim> new_particle;
1275 *   new_particle.set_location(location);
1276 *   new_particle.set_reference_location(
1277 *   mapping.transform_real_to_unit_cell(cell, location));
1278 *   new_particle.set_id(next_unused_particle_id);
1279 *   particle_handler.insert_particle(new_particle, cell);
1280 *  
1281 *   ++next_unused_particle_id;
1282 *   }
1283 *   }
1284 *   }
1285 *  
1286 * @endcode
1287 *
1288 * At the end of all of these insertions, we let the `particle_handler`
1289 * update some internal statistics about the particles it stores.
1290 *
1291 * @code
1292 *   particle_handler.update_cached_numbers();
1293 *   }
1294 *  
1295 *  
1296 * @endcode
1297 *
1298 *
1299 * <a name="step_19-CathodeRaySimulatormove_particles"></a>
1300 * <h4>CathodeRaySimulator::move_particles</h4>
1301 *
1302
1303 *
1304 * The second particle-related function is the one that moves the particles
1305 * in each time step. To do this, we have to loop over all cells, the
1306 * particles in each cell, and evaluate the electric field at each of the
1307 * particles' positions.
1308 *
1309
1310 *
1311 * The approach used here is conceptually the same used in the
1312 * `assemble_system()` function: We loop over all cells, find the particles
1313 * located there (with the same caveat about the inefficiency of the algorithm
1314 * used here to find these particles), and use FEPointEvaluation object to
1315 * evaluate the gradient at these positions:
1316 *
1317 * @code
1318 *   template <int dim>
1319 *   void CathodeRaySimulator<dim>::move_particles()
1320 *   {
1321 *   const double dt = time.get_next_step_size();
1322 *  
1323 *   Vector<double> solution_values(fe.n_dofs_per_cell());
1324 *   FEPointEvaluation<1, dim> evaluator(mapping, fe, update_gradients);
1325 *  
1326 *   for (const auto &cell : dof_handler.active_cell_iterators())
1327 *   if (particle_handler.n_particles_in_cell(cell) > 0)
1328 *   {
1329 *   const typename Particles::ParticleHandler<
1330 *   dim>::particle_iterator_range particles_in_cell =
1331 *   particle_handler.particles_in_cell(cell);
1332 *  
1333 *   std::vector<Point<dim>> particle_positions;
1334 *   for (const auto &particle : particles_in_cell)
1335 *   particle_positions.push_back(particle.get_reference_location());
1336 *  
1337 *   cell->get_dof_values(solution, solution_values);
1338 *  
1339 * @endcode
1340 *
1341 * Then we can ask the FEPointEvaluation object for the gradients of
1342 * the solution (i.e., the electric field @f$\mathbf E@f$) at these
1343 * locations and loop over the individual particles:
1344 *
1345 * @code
1346 *   evaluator.reinit(cell, particle_positions);
1347 *   evaluator.evaluate(make_array_view(solution_values),
1348 *   EvaluationFlags::gradients);
1349 *  
1350 *   {
1351 *   typename Particles::ParticleHandler<dim>::particle_iterator
1352 *   particle = particles_in_cell.begin();
1353 *   for (unsigned int particle_index = 0;
1354 *   particle != particles_in_cell.end();
1355 *   ++particle, ++particle_index)
1356 *   {
1357 *   const Tensor<1, dim> &E =
1358 *   evaluator.get_gradient(particle_index);
1359 *  
1360 * @endcode
1361 *
1362 * Having now obtained the electric field at the location of one
1363 * of the particles, we use this to update first the velocity
1364 * and then the position. To do so, let us first get the old
1365 * velocity out of the properties stored with the particle,
1366 * compute the acceleration, update the velocity, and store this
1367 * new velocity again in the properties of the particle. Recall
1368 * that this corresponds to the first of the following set of
1369 * update equations discussed in the introduction:
1370 * @f{align*}{
1371 * \frac{{\mathbf v}_i^{(n)}
1372 * -{\mathbf v}_i^{(n-1)}}{\Delta t}
1373 * &= \frac{e\nabla V^{(n)}}{m}
1374 * \\ \frac{{\mathbf x}_i^{(n)}-{\mathbf x}_i^{(n-1)}}
1375 * {\Delta t} &= {\mathbf v}_i^{(n)}.
1376 * @f}
1377 *
1378 * @code
1379 *   const Tensor<1, dim> old_velocity(particle->get_properties());
1380 *  
1381 *   const Tensor<1, dim> acceleration =
1382 *   Constants::electron_charge / Constants::electron_mass * E;
1383 *  
1384 *   const Tensor<1, dim> new_velocity =
1385 *   old_velocity + acceleration * dt;
1386 *  
1387 *   particle->set_properties(new_velocity);
1388 *  
1389 * @endcode
1390 *
1391 * With the new velocity, we can then also update the location
1392 * of the particle and tell the particle about it.
1393 *
1394 * @code
1395 *   const Point<dim> new_location =
1396 *   particle->get_location() + dt * new_velocity;
1397 *   particle->set_location(new_location);
1398 *   }
1399 *   }
1400 *   }
1401 *  
1402 * @endcode
1403 *
1404 * Having updated the locations and properties (i.e., velocities) of all
1405 * particles, we need to make sure that the `particle_handler` again knows
1406 * which cells they are in, and what their locations in the coordinate
1407 * system of the reference cell are. The following function does that. (It
1408 * also makes sure that, in parallel computations, particles are moved from
1409 * one processor to another processor if a particle moves from the subdomain
1410 * owned by the former to the subdomain owned by the latter.)
1411 *
1412 * @code
1413 *   particle_handler.sort_particles_into_subdomains_and_cells();
1414 *   }
1415 *  
1416 *  
1417 * @endcode
1418 *
1419 *
1420 * <a name="step_19-CathodeRaySimulatortrack_lost_particle"></a>
1421 * <h4>CathodeRaySimulator::track_lost_particle</h4>
1422 *
1423
1424 *
1425 * The final particle-related function is the one that is called whenever a
1426 * particle is lost from the simulation. This typically happens if it leaves
1427 * the domain. If that happens, this function is called both the cell (which
1428 * we can ask for its new location) and the cell it was previously on. The
1429 * function then keeps track of updating the number of particles lost in this
1430 * time step, the total number of lost particles, and then estimates whether
1431 * the particle left through the hole in the middle of the anode. We do so by
1432 * first checking whether the cell it was in last had an @f$x@f$ coordinate to the
1433 * left of the right boundary (located at @f$x=4@f$) and the particle now has a
1434 * position to the right of the right boundary. If that is so, we compute a
1435 * direction vector of its motion that is normalized so that the @f$x@f$ component
1436 * of the direction vector is equal to @f$1@f$. With this direction vector, we can
1437 * compute where it would have intersected the line @f$x=4@f$. If this intersect
1438 * is between @f$0.5@f$ and @f$1.5@f$, then we claim that the particle left through
1439 * the hole and increment a counter.
1440 *
1441 * @code
1442 *   template <int dim>
1443 *   void CathodeRaySimulator<dim>::track_lost_particle(
1444 *   const typename Particles::ParticleIterator<dim> &particle,
1445 *   const typename Triangulation<dim>::active_cell_iterator &cell)
1446 *   {
1447 *   ++n_recently_lost_particles;
1448 *   ++n_total_lost_particles;
1449 *  
1450 *   const Point<dim> current_location = particle->get_location();
1451 *   const Point<dim> approximate_previous_location = cell->center();
1452 *  
1453 *   if ((approximate_previous_location[0] < 4) && (current_location[0] > 4))
1454 *   {
1455 *   const Tensor<1, dim> direction =
1456 *   (current_location - approximate_previous_location) /
1457 *   (current_location[0] - approximate_previous_location[0]);
1458 *  
1459 *   const double right_boundary_intercept =
1460 *   approximate_previous_location[1] +
1461 *   (4 - approximate_previous_location[0]) * direction[1];
1462 *   if ((right_boundary_intercept > 0.5) &&
1463 *   (right_boundary_intercept < 1.5))
1464 *   ++n_particles_lost_through_anode;
1465 *   }
1466 *   }
1467 *  
1468 *  
1469 *  
1470 * @endcode
1471 *
1472 *
1473 * <a name="step_19-CathodeRaySimulatorupdate_timestep_size"></a>
1474 * <h4>CathodeRaySimulator::update_timestep_size</h4>
1475 *
1476
1477 *
1478 * As discussed at length in the introduction, we need to respect a time step
1479 * condition whereby particles can not move further than one cell in one time
1480 * step. To ensure that this is the case, we again first compute the maximal
1481 * speed of all particles on each cell, and divide the cell size by that
1482 * speed. We then compute the next time step size as the minimum of this
1483 * quantity over all cells, using the safety factor discussed in the
1484 * introduction, and set this as the desired time step size using the
1485 * DiscreteTime::set_desired_time_step_size() function.
1486 *
1487 * @code
1488 *   template <int dim>
1489 *   void CathodeRaySimulator<dim>::update_timestep_size()
1490 *   {
1491 *   if (time.get_step_number() > 0)
1492 *   {
1493 *   double min_cell_size_over_velocity = std::numeric_limits<double>::max();
1494 *  
1495 *   for (const auto &cell : dof_handler.active_cell_iterators())
1496 *   if (particle_handler.n_particles_in_cell(cell) > 0)
1497 *   {
1498 *   const double cell_size = cell->minimum_vertex_distance();
1499 *  
1500 *   double max_particle_velocity(0.0);
1501 *  
1502 *   for (const auto &particle :
1503 *   particle_handler.particles_in_cell(cell))
1504 *   {
1505 *   const Tensor<1, dim> velocity(particle.get_properties());
1506 *   max_particle_velocity =
1507 *   std::max(max_particle_velocity, velocity.norm());
1508 *   }
1509 *  
1510 *   if (max_particle_velocity > 0)
1511 *   min_cell_size_over_velocity =
1512 *   std::min(min_cell_size_over_velocity,
1513 *   cell_size / max_particle_velocity);
1514 *   }
1515 *  
1516 *   constexpr double c_safety = 0.5;
1517 *   time.set_desired_next_step_size(c_safety * 0.5 *
1518 *   min_cell_size_over_velocity);
1519 *   }
1520 * @endcode
1521 *
1522 * As mentioned in the introduction, we have to treat the very first
1523 * time step differently since there, particles are not available yet or
1524 * do not yet have the information associated that we need for the
1525 * computation of a reasonable step length. The formulas below follow the
1526 * discussion in the introduction.
1527 *
1528 * @code
1529 *   else
1530 *   {
1531 *   const QTrapezoid<dim> vertex_quadrature;
1532 *   FEValues<dim> fe_values(fe, vertex_quadrature, update_gradients);
1533 *  
1534 *   std::vector<Tensor<1, dim>> field_gradients(vertex_quadrature.size());
1535 *  
1536 *   double min_timestep = std::numeric_limits<double>::max();
1537 *  
1538 *   for (const auto &cell : dof_handler.active_cell_iterators())
1539 *   if (particle_handler.n_particles_in_cell(cell) > 0)
1540 *   {
1541 *   const double cell_size = cell->minimum_vertex_distance();
1542 *  
1543 *   fe_values.reinit(cell);
1544 *   fe_values.get_function_gradients(solution, field_gradients);
1545 *  
1546 *   double max_E = 0;
1547 *   for (const auto q_point : fe_values.quadrature_point_indices())
1548 *   max_E = std::max(max_E, field_gradients[q_point].norm());
1549 *  
1550 *   if (max_E > 0)
1551 *   min_timestep =
1552 *   std::min(min_timestep,
1553 *   std::sqrt(0.5 * cell_size *
1554 *   Constants::electron_mass /
1555 *   Constants::electron_charge / max_E));
1556 *   }
1557 *  
1558 *   time.set_desired_next_step_size(min_timestep);
1559 *   }
1560 *   }
1561 *  
1562 *  
1563 *  
1564 * @endcode
1565 *
1566 *
1567 * <a name="step_19-ThecodeCathodeRaySimulatoroutput_resultscodefunction"></a>
1568 * <h4>The <code>CathodeRaySimulator::output_results()</code> function</h4>
1569 *
1570
1571 *
1572 * The final function implementing pieces of the overall algorithm is the one
1573 * that generates graphical output. In the current context, we want to output
1574 * both the electric potential field as well as the particle locations and
1575 * velocities. But we also want to output the electric field, i.e., the
1576 * gradient of the solution.
1577 *
1578
1579 *
1580 * deal.II has a general way how one can compute derived quantities from
1581 * the solution and output those as well. Here, this is the electric
1582 * field, but it could also be some other quantity -- say, the norm of the
1583 * electric field, or in fact anything else one could want to compute from
1584 * the solution @f$V_h(\mathbf x)@f$ or its derivatives. This general solution
1585 * uses the DataPostprocessor class and, in cases like the one here where we
1586 * want to output a quantity that represents a vector field, the
1587 * DataPostprocessorVector class.
1588 *
1589
1590 *
1591 * Rather than try and explain how this class works, let us simply refer to
1592 * the documentation of the DataPostprocessorVector class that has essentially
1593 * this case as a well-documented example.
1594 *
1595 * @code
1596 *   template <int dim>
1597 *   class ElectricFieldPostprocessor : public DataPostprocessorVector<dim>
1598 *   {
1599 *   public:
1600 *   ElectricFieldPostprocessor()
1601 *   : DataPostprocessorVector<dim>("electric_field", update_gradients)
1602 *   {}
1603 *  
1604 *   virtual void evaluate_scalar_field(
1605 *   const DataPostprocessorInputs::Scalar<dim> &input_data,
1606 *   std::vector<Vector<double>> &computed_quantities) const override
1607 *   {
1608 *   AssertDimension(input_data.solution_gradients.size(),
1609 *   computed_quantities.size());
1610 *  
1611 *   for (unsigned int p = 0; p < input_data.solution_gradients.size(); ++p)
1612 *   {
1613 *   AssertDimension(computed_quantities[p].size(), dim);
1614 *   for (unsigned int d = 0; d < dim; ++d)
1615 *   computed_quantities[p][d] = input_data.solution_gradients[p][d];
1616 *   }
1617 *   }
1618 *   };
1619 *  
1620 *  
1621 *  
1622 * @endcode
1623 *
1624 * With this, the `output_results()` function becomes relatively
1625 * straightforward: We use the DataOut class as we have in almost
1626 * every one of the previous tutorial programs to output the
1627 * solution (the "electric potential") and we use the postprocessor
1628 * defined above to also output its gradient (the "electric
1629 * field"). This all is then written into a file in VTU format after
1630 * also associating the current time and time step number with this
1631 * file, and providing physical units for the two fields to be
1632 * output.
1633 *
1634 * @code
1635 *   template <int dim>
1636 *   void CathodeRaySimulator<dim>::output_results() const
1637 *   {
1638 *   {
1639 *   ElectricFieldPostprocessor<dim> electric_field;
1640 *   DataOut<dim> data_out;
1641 *   data_out.attach_dof_handler(dof_handler);
1642 *   data_out.add_data_vector(solution, "electric_potential");
1643 *   data_out.add_data_vector(solution, electric_field);
1644 *   data_out.build_patches();
1645 *  
1646 *   DataOutBase::VtkFlags output_flags;
1647 *   output_flags.time = time.get_current_time();
1648 *   output_flags.cycle = time.get_step_number();
1649 *   output_flags.physical_units["electric_potential"] = "V";
1650 *   output_flags.physical_units["electric_field"] = "V/m";
1651 *  
1652 *   data_out.set_flags(output_flags);
1653 *  
1654 *   std::ofstream output("solution-" +
1655 *   Utilities::int_to_string(time.get_step_number(), 4) +
1656 *   ".vtu");
1657 *   data_out.write_vtu(output);
1658 *   }
1659 *  
1660 * @endcode
1661 *
1662 * Output the particle positions and properties is not more complicated. The
1663 * Particles::DataOut class plays the role of the DataOut class for
1664 * particles, and all we have to do is tell that class where to take
1665 * particles from and how to interpret the `dim` components of the
1666 * properties -- namely, as a single vector indicating the velocity, rather
1667 * than as `dim` scalar properties. The rest is then the same as above:
1668 *
1669 * @code
1670 *   {
1671 *   Particles::DataOut<dim> particle_out;
1672 *   particle_out.build_patches(
1673 *   particle_handler,
1674 *   std::vector<std::string>(dim, "velocity"),
1675 *   std::vector<DataComponentInterpretation::DataComponentInterpretation>(
1676 *   dim, DataComponentInterpretation::component_is_part_of_vector));
1677 *  
1678 *   DataOutBase::VtkFlags output_flags;
1679 *   output_flags.time = time.get_current_time();
1680 *   output_flags.cycle = time.get_step_number();
1681 *   output_flags.physical_units["velocity"] = "m/s";
1682 *  
1683 *   particle_out.set_flags(output_flags);
1684 *  
1685 *   std::ofstream output("particles-" +
1686 *   Utilities::int_to_string(time.get_step_number(), 4) +
1687 *   ".vtu");
1688 *   particle_out.write_vtu(output);
1689 *   }
1690 *   }
1691 *  
1692 *  
1693 * @endcode
1694 *
1695 *
1696 * <a name="step_19-CathodeRaySimulatorrun"></a>
1697 * <h4>CathodeRaySimulator::run</h4>
1698 *
1699
1700 *
1701 * The last member function of the principal class of this program is then the
1702 * driver. At the top, it refines the mesh a number of times by solving the
1703 * problem (with no particles yet created) on a sequence of finer and finer
1704 * meshes.
1705 *
1706 * @code
1707 *   template <int dim>
1708 *   void CathodeRaySimulator<dim>::run()
1709 *   {
1710 *   make_grid();
1711 *  
1712 *   const unsigned int n_pre_refinement_cycles = 3;
1713 *   for (unsigned int refinement_cycle = 0;
1714 *   refinement_cycle < n_pre_refinement_cycles;
1715 *   ++refinement_cycle)
1716 *   {
1717 *   setup_system();
1718 *   assemble_system();
1719 *   solve_field();
1720 *   refine_grid();
1721 *   }
1722 *  
1723 *  
1724 * @endcode
1725 *
1726 * Now do the loop over time. The sequence of steps follows closely the
1727 * outline of the algorithm discussed in the introduction. As discussed in
1728 * great detail in the documentation of the DiscreteTime class, while we
1729 * move the field and particle information forward by one time step, the
1730 * time stored in the `time` variable is not consistent with where (some of)
1731 * these quantities are (in the diction of DiscreteTime, this is the "update
1732 * stage"). The call to `time.advance_time()` makes everything consistent
1733 * again by setting the `time` variable to the time at which the field and
1734 * particles already are, and once we are in this "consistent stage", we can
1735 * generate graphical output and write information about the current state
1736 * of the simulation to screen.
1737 *
1738 * @code
1739 *   setup_system();
1740 *   do
1741 *   {
1742 *   std::cout << "Timestep " << time.get_step_number() + 1 << std::endl;
1743 *   std::cout << " Field degrees of freedom: "
1744 *   << dof_handler.n_dofs() << std::endl;
1745 *  
1746 *   assemble_system();
1747 *   solve_field();
1748 *  
1749 *   create_particles();
1750 *   std::cout << " Total number of particles in simulation: "
1751 *   << particle_handler.n_global_particles() << std::endl;
1752 *  
1753 *   n_recently_lost_particles = 0;
1754 *   update_timestep_size();
1755 *   move_particles();
1756 *  
1757 *   time.advance_time();
1758 *  
1759 *   output_results();
1760 *  
1761 *   std::cout << " Number of particles lost this time step: "
1762 *   << n_recently_lost_particles << std::endl;
1763 *   if (n_total_lost_particles > 0)
1764 *   std::cout << " Fraction of particles lost through anode: "
1765 *   << 1. * n_particles_lost_through_anode /
1766 *   n_total_lost_particles
1767 *   << std::endl;
1768 *  
1769 *   std::cout << std::endl
1770 *   << " Now at t=" << time.get_current_time()
1771 *   << ", dt=" << time.get_previous_step_size() << '.'
1772 *   << std::endl
1773 *   << std::endl;
1774 *   }
1775 *   while (time.is_at_end() == false);
1776 *   }
1777 *   } // namespace Step19
1778 *  
1779 *  
1780 *  
1781 * @endcode
1782 *
1783 *
1784 * <a name="step_19-Thecodemaincodefunction"></a>
1785 * <h3>The <code>main</code> function</h3>
1786 *
1787
1788 *
1789 * The final function of the program is then again the `main()` function. It is
1790 * unchanged in all tutorial programs since @ref step_6 "step-6" and so there is nothing new
1791 * to discuss:
1792 *
1793 * @code
1794 *   int main()
1795 *   {
1796 *   try
1797 *   {
1798 *   Step19::CathodeRaySimulator<2> cathode_ray_simulator_2d;
1799 *   cathode_ray_simulator_2d.run();
1800 *   }
1801 *   catch (std::exception &exc)
1802 *   {
1803 *   std::cerr << std::endl
1804 *   << std::endl
1805 *   << "----------------------------------------------------"
1806 *   << std::endl;
1807 *   std::cerr << "Exception on processing: " << std::endl
1808 *   << exc.what() << std::endl
1809 *   << "Aborting!" << std::endl
1810 *   << "----------------------------------------------------"
1811 *   << std::endl;
1812 *  
1813 *   return 1;
1814 *   }
1815 *   catch (...)
1816 *   {
1817 *   std::cerr << std::endl
1818 *   << std::endl
1819 *   << "----------------------------------------------------"
1820 *   << std::endl;
1821 *   std::cerr << "Unknown exception!" << std::endl
1822 *   << "Aborting!" << std::endl
1823 *   << "----------------------------------------------------"
1824 *   << std::endl;
1825 *   return 1;
1826 *   }
1827 *   return 0;
1828 *   }
1829 * @endcode
1830<a name="step_19-Results"></a><h1>Results</h1>
1831
1832
1833When this program is run, it produces output that looks as follows:
1834```
1835Timestep 1
1836 Field degrees of freedom: 4989
1837 Total number of particles in simulation: 20
1838 Number of particles lost this time step: 0
1839
1840 Now at t=2.12647e-07, dt=2.12647e-07.
1841
1842Timestep 2
1843 Field degrees of freedom: 4989
1844 Total number of particles in simulation: 40
1845 Number of particles lost this time step: 0
1846
1847 Now at t=4.14362e-07, dt=2.01715e-07.
1848
1849Timestep 3
1850 Field degrees of freedom: 4989
1851 Total number of particles in simulation: 60
1852 Number of particles lost this time step: 0
1853
1854 Now at t=5.23066e-07, dt=1.08704e-07.
1855
1856Timestep 4
1857 Field degrees of freedom: 4989
1858 Total number of particles in simulation: 80
1859 Number of particles lost this time step: 0
1860
1861 Now at t=6.08431e-07, dt=8.53649e-08.
1862
1863
1864...
1865
1866
1867Timestep 1000
1868 Field degrees of freedom: 4989
1869 Total number of particles in simulation: 394
1870 Number of particles lost this time step: 2
1871 Fraction of particles lost through anode: 0.823188
1872
1873 Now at t=3.83822e-05, dt=4.18519e-08.
1874
1875Timestep 1001
1876 Field degrees of freedom: 4989
1877 Total number of particles in simulation: 392
1878 Number of particles lost this time step: 0
1879 Fraction of particles lost through anode: 0.823188
1880
1881 Now at t=3.84244e-05, dt=4.22178e-08.
1882
1883
1884...
1885
1886
1887Timestep 2478
1888 Field degrees of freedom: 4989
1889 Total number of particles in simulation: 364
1890 Number of particles lost this time step: 0
1891 Fraction of particles lost through anode: 0.867745
1892
1893 Now at t=9.99509e-05, dt=4.20455e-08.
1894
1895Timestep 2479
1896 Field degrees of freedom: 4989
1897 Total number of particles in simulation: 364
1898 Number of particles lost this time step: 2
1899 Fraction of particles lost through anode: 0.867871
1900
1901 Now at t=9.99929e-05, dt=4.20105e-08.
1902
1903Timestep 2480
1904 Field degrees of freedom: 4989
1905 Total number of particles in simulation: 362
1906 Number of particles lost this time step: 2
1907 Fraction of particles lost through anode: 0.867047
1908
1909 Now at t=0.0001, dt=7.11401e-09.
1910```
1911
1912Picking a random few time steps, we can visualize the solution in the
1913form of streamlines for the electric field and dots for the electrons
1914(the pictures were created with an earlier version of the program and
1915are no longer exact representations of the solution as currently
1916computed -- but they are conceptually still correct):
1917<div class="twocolumn" style="width: 80%">
1918 <div>
1919 <img src="https://www.dealii.org/images/steps/developer/step-19.solution.0000.png"
1920 alt="The solution at time step 0 (t=0 seconds)."
1921 width="500">
1922 <br>
1923 Solution at time step 0 (t=0 seconds).
1924 <br>
1925 </div>
1926 <div>
1927 <img src="https://www.dealii.org/images/steps/developer/step-19.solution.1400.png"
1928 alt="The solution at time step 1400 (t=0.000068 seconds)."
1929 width="500">
1930 <br>
1931 Solution at time step 1400 (t=0.000068 seconds).
1932 <br>
1933 </div>
1934 <div>
1935 <img src="https://www.dealii.org/images/steps/developer/step-19.solution.0700.png"
1936 alt="The solution at time step 700 (t=0.000035 seconds)."
1937 width="500">
1938 <br>
1939 Solution at time step 700 (t=0.000035 seconds).
1940 <br>
1941 </div>
1942 <div>
1943 <img src="https://www.dealii.org/images/steps/developer/step-19.solution.2092.png"
1944 alt="The solution at time step 2092 (t=0.0001 seconds)."
1945 width="500">
1946 <br>
1947 Solution at time step 2092 (t=0.0001 seconds).
1948 <br>
1949 </div>
1950</div>
1951
1952That said, a more appropriate way to visualize the results of this
1953program are by creating a video that shows how these electrons move, and how
1954the electric field changes in response to their motion (again showing
1955results of an earlier version of the program):
1956
1957@htmlonly
1958<p align="center">
1959 <iframe width="560" height="315" src="https://www.youtube.com/embed/HwUtE7xuteE"
1960 frameborder="0"
1961 allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"
1962 allowfullscreen></iframe>
1963 </p>
1964@endhtmlonly
1965
1966What you can see here is how the "focus element" of the boundary with its negative
1967voltage repels the electrons and makes sure that they do not just fly away
1968perpendicular from the cathode (as they do in the initial part of their
1969trajectories). It also shows how the electric field lines
1970move around over time, in response to the charges flying by -- in other words,
1971the feedback the particles have on the electric field that itself drives the
1972motion of the electrons.
1973
1974The movie suggests that electrons move in "bunches" or "bursts". One element of
1975this appearance is an artifact of how the movie was created: Every frame of the
1976movie corresponds to one time step, but the time step length varies. More specifically,
1977the fastest particle moving through the smallest cell determines the length of the
1978time step (see the discussion in the introduction), and consequently time steps
1979are small whenever a (fast) particle moves through the small cells at the right
1980edge of the domain; time steps are longer again once the particle has left
1981the domain. This slowing-accelerating effect can easily be visualized by plotting
1982the time step length shown in the screen output.
1983
1984The second part of this is real, however: The simulation creates a large group
1985of particles in the beginning, and fewer after about the 300th time step. This
1986is probably because of the negative charge of the particles in the simulation:
1987They reduce the magnitude of the electric field at the (also negatively charged
1988electrode) and consequently reduce the number of points on the cathode at which
1989the magnitude exceeds the threshold necessary to draw an electron out of the
1990electrode.
1991
1992
1993<a name="step-19-extensions"></a>
1994<a name="step_19-Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
1995
1996
1997
1998<a name="step_19-Morestatisticsaboutelectrons"></a><h4> More statistics about electrons </h4>
1999
2000
2001At the end of the day, we are rarely interested in the *solution* of an equation,
2002but in numbers that can be *extracted* from it -- in other words, we want to
2003*postprocess* the solution (see the results section of @ref step_4 "step-4" for an extensive
2004discussion of this concept). Here, what one would likely be most interested in
2005assessing are some statistics about the particles.
2006
2007The program already computes the fraction of the electrons that leave the
2008domain through the hole in the anode. But there are other quantities one might be
2009interested in. For example, the average velocity of these particles. It would
2010not be very difficult to obtain each particle's velocity from its properties,
2011in the same way as we do in the `move_particles()` function, and compute
2012statistics from it.
2013
2014
2015<a name="step_19-Abettersynchronizedvisualization"></a><h4> A better-synchronized visualization </h4>
2016
2017
2018As discussed above, there is a varying time difference between different frames
2019of the video because we create output for every time step. A better way to
2020create movies would be to generate a new output file in fixed time intervals,
2021regardless of how many time steps lie between each such point.
2022
2023
2024<a name="step_19-Abettertimeintegrator"></a><h4> A better time integrator </h4>
2025
2026
2027The problem we are considering in this program is a coupled, multiphysics
2028problem. But the way we solve it is by first computing the (electric) potential
2029field and then updating first the particle velocity and finally the
2030particle locations. This is what is called an
2031"operator-splitting method", a concept we will investigate in more detail
2032in @ref step_58 "step-58".
2033
2034While it is awkward to think of a way to solve this problem that does not involve
2035splitting the problem into a PDE piece and a particles piece, one *can*
2036(and probably should!) think of a better way to update the particle
2037locations. Specifically, the equations we use to update the particle location
2038are
2039@f{align*}{
2040 \frac{{\mathbf v}_i^{(n)}-{\mathbf v}_i^{(n-1)}}{\Delta t} &= \frac{e\nabla V^{(n)}}{m}
2041 \\
2042 \frac{{\mathbf x}_i^{(n)}-{\mathbf x}_i^{(n-1)}}{\Delta t} &= {\mathbf v}_i^{(n)}.
2043@f}
2044This corresponds to a Lie splitting where we first update one variable
2045(the velocity) and then, using the already-updated value of the one variable
2046to update the other (the position). It is well understood that the Lie
2047splitting incurs a first order error accuracy in the time step (i.e.,
2048the error introduced by the splitting is @f${\cal O}(\Delta t)@f$) that we know we should
2049avoid because we can do better. Independently, of course, we incur an error
2050of the same rate a second time because we use an Euler-like scheme for each of
2051the two updates (we replace the time derivative by a simple finite difference
2052quotient containing the old and new value, and we evaluate the right hand side
2053only at the end time of the interval, at @f$t_n@f$), and a better scheme would also
2054use a better way to do this kind of update.
2055
2056Better strategies would replace the Lie splitting by something like a
2057[Strang splitting](https://en.wikipedia.org/wiki/Strang_splitting), and the
2058update of the particle position and velocity by a scheme such
2059as the
2060[leapfrog scheme](https://en.wikipedia.org/wiki/Leapfrog_integration)
2061or more generally
2062[symplectic integrators](https://en.wikipedia.org/wiki/Symplectic_integrator)
2063such as the
2064[Verlet scheme](https://en.wikipedia.org/wiki/Verlet_integration).
2065
2066
2067<a name="step_19-Parallelization"></a><h4> Parallelization </h4>
2068
2069
2070In release mode, the program runs in about 3.5 minutes on one of the author's
2071laptops at the time of writing this. That's acceptable. But what if we wanted
2072to make the simulation three-dimensional? If we wanted to not use a maximum
2073of around 100 particles at any given time (as happens with the parameters
2074used here) but 100,000? If we needed a substantially finer mesh?
2075
2076In those cases, one would want to run the program not just on a single processor,
2077but in fact on as many as one has available. This requires parallelization
2078of both the PDE solution as well as over particles. In practice, while there
2079are substantial challenges to making this efficient and scale well, these
2080challenges are all addressed in deal.II itself, and have been demonstrated
2081on simulations running on 10,000 or more cores. For example, @ref step_40 "step-40" shows
2082how to parallelize the finite element part, and @ref step_70 "step-70" shows how one can
2083then also parallelize the particles part.
2084 *
2085 *
2086<a name="step_19-PlainProg"></a>
2087<h1> The plain program</h1>
2088@include "step-19.cc"
2089*/
double JxW(const unsigned int q_point) const
particle_iterator_range particles_in_cell(const typename Triangulation< dim, spacedim >::active_cell_iterator &cell)
Point< 2 > second
Definition grid_out.cc:4624
Point< 2 > first
Definition grid_out.cc:4623
unsigned int level
Definition grid_out.cc:4626
#define AssertDimension(dim1, dim2)
std::vector< index_type > data
Definition mpi.cc:735
const Event initial
Definition event.cc:64
CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt K
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
VectorType::value_type * end(VectorType &V)
T reduce(const T &local_value, const MPI_Comm comm, const std::function< T(const T &, const T &)> &combiner, const unsigned int root_process=0)
int(&) functions(const void *v1, const void *v2)