1896 *
std::pair<unsigned int, double>
1943 * @
ref step_18
"step-18",
deal.II
's native quadrature point data manager is employed
1947 * CellDataStorage<typename Triangulation<dim>::cell_iterator,
1948 * PointHistory<dim>>
1949 * quadrature_point_history;
1953 * A description of the finite-element system including the displacement
1954 * polynomial degree, the degree-of-freedom handler, number of DoFs per
1955 * cell and the extractor objects used to retrieve information from the
1959 * const unsigned int degree;
1960 * const FESystem<dim> fe;
1961 * DoFHandler<dim> dof_handler;
1962 * const unsigned int dofs_per_cell;
1963 * const FEValuesExtractors::Vector u_fe;
1964 * const FEValuesExtractors::Scalar p_fe;
1965 * const FEValuesExtractors::Scalar J_fe;
1969 * Description of how the block-system is arranged. There are 3 blocks,
1970 * the first contains a vector DOF @f$\mathbf{u}@f$ while the other two
1971 * describe scalar DOFs, @f$\widetilde{p}@f$ and @f$\widetilde{J}@f$.
1974 * static const unsigned int n_blocks = 3;
1975 * static const unsigned int n_components = dim + 2;
1976 * static const unsigned int first_u_component = 0;
1977 * static const unsigned int p_component = dim;
1978 * static const unsigned int J_component = dim + 1;
1987 * std::vector<types::global_dof_index> dofs_per_block;
1988 * std::vector<types::global_dof_index> element_indices_u;
1989 * std::vector<types::global_dof_index> element_indices_p;
1990 * std::vector<types::global_dof_index> element_indices_J;
1994 * Rules for Gauss-quadrature on both the cell and faces. The number of
1995 * quadrature points on both cells and faces is recorded.
1998 * const QGauss<dim> qf_cell;
1999 * const QGauss<dim - 1> qf_face;
2000 * const unsigned int n_q_points;
2001 * const unsigned int n_q_points_f;
2005 * Objects that store the converged solution and right-hand side vectors,
2006 * as well as the tangent matrix. There is an AffineConstraints object used
2007 * to keep track of constraints. We make use of a sparsity pattern
2008 * designed for a block system.
2011 * AffineConstraints<double> constraints;
2012 * BlockSparsityPattern sparsity_pattern;
2013 * BlockSparseMatrix<double> tangent_matrix;
2014 * BlockVector<double> system_rhs;
2015 * BlockVector<double> solution_n;
2019 * Then define a number of variables to store norms and update norms and
2020 * normalization factors.
2039 * void normalize(const Errors &rhs)
2041 * if (rhs.norm != 0.0)
2051 * double norm, u, p, J;
2054 * Errors error_residual, error_residual_0, error_residual_norm, error_update,
2055 * error_update_0, error_update_norm;
2059 * Methods to calculate error measures
2062 * void get_error_residual(Errors &error_residual);
2064 * void get_error_update(const BlockVector<double> &newton_update,
2065 * Errors & error_update);
2067 * std::pair<double, double> get_error_dilation() const;
2071 * Compute the volume in the spatial configuration
2074 * double compute_vol_current() const;
2078 * Print information to screen in a pleasing way...
2081 * static void print_conv_header();
2083 * void print_conv_footer();
2089 * <a name="ImplementationofthecodeSolidcodeclass"></a>
2090 * <h3>Implementation of the <code>Solid</code> class</h3>
2095 * <a name="Publicinterface"></a>
2096 * <h4>Public interface</h4>
2100 * We initialize the Solid class using data extracted from the parameter file.
2103 * template <int dim>
2104 * Solid<dim>::Solid(const std::string &input_file)
2105 * : parameters(input_file)
2106 * , vol_reference(0.)
2107 * , triangulation(Triangulation<dim>::maximum_smoothing)
2108 * , time(parameters.end_time, parameters.delta_t)
2109 * , timer(std::cout, TimerOutput::summary, TimerOutput::wall_times)
2110 * , degree(parameters.poly_degree)
2114 * The Finite Element System is composed of dim continuous displacement
2115 * DOFs, and discontinuous pressure and dilatation DOFs. In an attempt to
2116 * satisfy the Babuska-Brezzi or LBB stability conditions (see Hughes
2117 * (2000)), we set up a @f$Q_n \times DGP_{n-1} \times DGP_{n-1}@f$
2118 * system. @f$Q_2 \times DGP_1 \times DGP_1@f$ elements satisfy this
2119 * condition, while @f$Q_1 \times DGP_0 \times DGP_0@f$ elements do
2120 * not. However, it has been shown that the latter demonstrate good
2121 * convergence characteristics nonetheless.
2124 * fe(FE_Q<dim>(parameters.poly_degree),
2125 * dim, // displacement
2126 * FE_DGP<dim>(parameters.poly_degree - 1),
2128 * FE_DGP<dim>(parameters.poly_degree - 1),
2130 * , dof_handler(triangulation)
2131 * , dofs_per_cell(fe.n_dofs_per_cell())
2132 * , u_fe(first_u_component)
2133 * , p_fe(p_component)
2134 * , J_fe(J_component)
2135 * , dofs_per_block(n_blocks)
2136 * , qf_cell(parameters.quad_order)
2137 * , qf_face(parameters.quad_order)
2138 * , n_q_points(qf_cell.size())
2139 * , n_q_points_f(qf_face.size())
2141 * Assert(dim == 2 || dim == 3,
2142 * ExcMessage("This problem only works in 2 or 3 space dimensions."));
2143 * determine_component_extractors();
2149 * In solving the quasi-static problem, the time becomes a loading parameter,
2150 * i.e. we increasing the loading linearly with time, making the two concepts
2151 * interchangeable. We choose to increment time linearly using a constant time
2156 * We start the function with preprocessing, setting the initial dilatation
2157 * values, and then output the initial grid before starting the simulation
2158 * proper with the first time (and loading)
2163 * Care must be taken (or at least some thought given) when imposing the
2164 * constraint @f$\widetilde{J}=1@f$ on the initial solution field. The constraint
2165 * corresponds to the determinant of the deformation gradient in the
2166 * undeformed configuration, which is the identity tensor. We use
2167 * FE_DGP bases to interpolate the dilatation field, thus we can't
2169 * coefficients
of a
truncated Legendre polynomial.
2183 *
template <
int dim>
2190 *
constraints.close();
2212 *
while (time.current() < time.
end())
2241 * <a name=
"Privateinterface"></a>
2247 * <a name=
"Threadingbuildingblocksstructures"></a>
2258 * threads
module for more information).
2262 * Firstly we deal with the tangent matrix and right-hand side assembly
2263 * structures. The PerTaskData object stores local contributions to the global
2267 * template <int dim>
2268 * struct Solid<dim>::PerTaskData_ASM
2270 * FullMatrix<double> cell_matrix;
2272 *
std::vector<types::global_dof_index> local_dof_indices;
2277 *
, local_dof_indices(dofs_per_cell)
2296 *
template <
int dim>
2302 *
std::vector<std::vector<double>>
Nx;
2303 *
std::vector<std::vector<Tensor<2, dim>>>
grad_Nx;
2304 *
std::vector<std::vector<SymmetricTensor<2, dim>>>
symm_grad_Nx;
2322 *
: fe_values(
rhs.fe_values.get_fe(),
2323 *
rhs.fe_values.get_quadrature(),
2324 *
rhs.fe_values.get_update_flags())
2325 *
, fe_face_values(
rhs.fe_face_values.get_fe(),
2326 *
rhs.fe_face_values.get_quadrature(),
2327 *
rhs.fe_face_values.get_update_flags())
2335 *
const unsigned int n_q_points =
Nx.
size();
2336 *
const unsigned int n_dofs_per_cell =
Nx[0].
size();
2344 *
for (
unsigned int k = 0;
k < n_dofs_per_cell; ++
k)
2371 *
template <
int dim>
2375 *
std::vector<types::global_dof_index> local_dof_indices;
2388 *
const unsigned int n_u,
2389 *
const unsigned int n_p,
2390 *
const unsigned int n_J)
2392 *
, local_dof_indices(dofs_per_cell)
2393 *
,
k_orig(dofs_per_cell, dofs_per_cell)
2417 *
template <
int dim>
2444 *
template <
int dim>
2455 * vector
so that we don't have to copy this large data structure. We then
2456 * define a number of vectors to extract the solution values and gradients at
2457 * the quadrature points.
2460 * template <int dim>
2461 * struct Solid<dim>::ScratchData_UQPH
2463 * const BlockVector<double> &solution_total;
2465 * std::vector<Tensor<2, dim>> solution_grads_u_total;
2466 * std::vector<double> solution_values_p_total;
2467 * std::vector<double> solution_values_J_total;
2469 * FEValues<dim> fe_values;
2471 * ScratchData_UQPH(const FiniteElement<dim> & fe_cell,
2472 * const QGauss<dim> & qf_cell,
2473 * const UpdateFlags uf_cell,
2474 * const BlockVector<double> &solution_total)
2475 * : solution_total(solution_total)
2476 * , solution_grads_u_total(qf_cell.size())
2477 * , solution_values_p_total(qf_cell.size())
2478 * , solution_values_J_total(qf_cell.size())
2479 * , fe_values(fe_cell, qf_cell, uf_cell)
2482 * ScratchData_UQPH(const ScratchData_UQPH &rhs)
2483 * : solution_total(rhs.solution_total)
2484 * , solution_grads_u_total(rhs.solution_grads_u_total)
2485 * , solution_values_p_total(rhs.solution_values_p_total)
2486 * , solution_values_J_total(rhs.solution_values_J_total)
2487 * , fe_values(rhs.fe_values.get_fe(),
2488 * rhs.fe_values.get_quadrature(),
2489 * rhs.fe_values.get_update_flags())
2494 * const unsigned int n_q_points = solution_grads_u_total.size();
2495 * for (unsigned int q = 0; q < n_q_points; ++q)
2497 * solution_grads_u_total[q] = 0.0;
2498 * solution_values_p_total[q] = 0.0;
2499 * solution_values_J_total[q] = 0.0;
2508 * <a name="Solidmake_grid"></a>
2509 * <h4>Solid::make_grid</h4>
2513 * On to the first of the private member functions. Here we create the
2514 * triangulation of the domain, for which we choose the scaled cube with each
2515 * face given a boundary ID number. The grid must be refined at least once
2516 * for the indentation problem.
2520 * We then determine the volume of the reference configuration and print it
2524 * template <int dim>
2525 * void Solid<dim>::make_grid()
2527 * GridGenerator::hyper_rectangle(
2529 * (dim == 3 ? Point<dim>(0.0, 0.0, 0.0) : Point<dim>(0.0, 0.0)),
2530 * (dim == 3 ? Point<dim>(1.0, 1.0, 1.0) : Point<dim>(1.0, 1.0)),
2532 * GridTools::scale(parameters.scale, triangulation);
2533 * triangulation.refine_global(std::max(1U, parameters.global_refinement));
2535 * vol_reference = GridTools::volume(triangulation);
2536 * std::cout << "Grid:\n\t Reference volume: " << vol_reference << std::endl;
2540 * Since we wish to apply a Neumann BC to a patch on the top surface, we
2541 * must find the cell faces in this part of the domain and mark them with
2542 * a distinct boundary ID number. The faces we are looking for are on the
2543 * +y surface and will get boundary ID 6 (zero through five are already
2544 * used when creating the six faces of the cube domain):
2547 * for (const auto &cell : triangulation.active_cell_iterators())
2548 * for (const auto &face : cell->face_iterators())
2550 * if (face->at_boundary() == true &&
2551 * face->center()[1] == 1.0 * parameters.scale)
2555 * if (face->center()[0] < 0.5 * parameters.scale &&
2556 * face->center()[2] < 0.5 * parameters.scale)
2557 * face->set_boundary_id(6);
2561 * if (face->center()[0] < 0.5 * parameters.scale)
2562 * face->set_boundary_id(6);
2572 * <a name="Solidsystem_setup"></a>
2573 * <h4>Solid::system_setup</h4>
2577 * Next we describe how the FE system is setup. We first determine the number
2578 * of components per block. Since the displacement is a vector component, the
2579 * first dim components belong to it, while the next two describe scalar
2580 * pressure and dilatation DOFs.
2583 * template <int dim>
2584 * void Solid<dim>::system_setup()
2586 * timer.enter_subsection("Setup system");
2588 * std::vector<unsigned int> block_component(n_components,
2589 * u_dof); // Displacement
2590 * block_component[p_component] = p_dof; // Pressure
2591 * block_component[J_component] = J_dof; // Dilatation
2595 * The DOF handler is then initialized and we renumber the grid in an
2596 * efficient manner. We also record the number of DOFs per block.
2599 * dof_handler.distribute_dofs(fe);
2600 * DoFRenumbering::Cuthill_McKee(dof_handler);
2601 * DoFRenumbering::component_wise(dof_handler, block_component);
2604 * DoFTools::count_dofs_per_fe_block(dof_handler, block_component);
2606 * std::cout << "Triangulation:"
2607 * << "\n\t Number of active cells: "
2608 * << triangulation.n_active_cells()
2609 * << "\n\t Number of degrees of freedom: " << dof_handler.n_dofs()
2614 * Setup the sparsity pattern and tangent matrix
2617 * tangent_matrix.clear();
2619 * BlockDynamicSparsityPattern dsp(dofs_per_block, dofs_per_block);
2623 * The global system matrix initially has the following structure
2625 * \underbrace{\begin{bmatrix}
2626 * \mathsf{\mathbf{K}}_{uu} & \mathsf{\mathbf{K}}_{u\widetilde{p}} &
2628 * \\ \mathsf{\mathbf{K}}_{\widetilde{p}u} & \mathbf{0} &
2629 * \mathsf{\mathbf{K}}_{\widetilde{p}\widetilde{J}}
2630 * \\ \mathbf{0} & \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{p}} &
2631 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{J}}
2632 * \end{bmatrix}}_{\mathsf{\mathbf{K}}(\mathbf{\Xi}_{\textrm{i}})}
2633 * \underbrace{\begin{bmatrix}
2635 * \\ d \widetilde{\mathsf{\mathbf{p}}}
2636 * \\ d \widetilde{\mathsf{\mathbf{J}}}
2637 * \end{bmatrix}}_{d \mathbf{\Xi}}
2639 * \underbrace{\begin{bmatrix}
2640 * \mathsf{\mathbf{F}}_{u}(\mathbf{u}_{\textrm{i}})
2641 * \\ \mathsf{\mathbf{F}}_{\widetilde{p}}(\widetilde{p}_{\textrm{i}})
2642 * \\ \mathsf{\mathbf{F}}_{\widetilde{J}}(\widetilde{J}_{\textrm{i}})
2643 * \end{bmatrix}}_{ \mathsf{\mathbf{F}}(\mathbf{\Xi}_{\textrm{i}}) } \, .
2645 * We optimize the sparsity pattern to reflect this structure
2646 * and prevent unnecessary data creation for the right-diagonal
2650 * Table<2, DoFTools::Coupling> coupling(n_components, n_components);
2651 * for (unsigned int ii = 0; ii < n_components; ++ii)
2652 * for (unsigned int jj = 0; jj < n_components; ++jj)
2653 * if (((ii < p_component) && (jj == J_component)) ||
2654 * ((ii == J_component) && (jj < p_component)) ||
2655 * ((ii == p_component) && (jj == p_component)))
2656 * coupling[ii][jj] = DoFTools::none;
2658 * coupling[ii][jj] = DoFTools::always;
2659 * DoFTools::make_sparsity_pattern(
2660 * dof_handler, coupling, dsp, constraints, false);
2661 * sparsity_pattern.copy_from(dsp);
2664 * tangent_matrix.reinit(sparsity_pattern);
2668 * We then set up storage vectors
2671 * system_rhs.reinit(dofs_per_block);
2672 * solution_n.reinit(dofs_per_block);
2676 * ...and finally set up the quadrature
2682 * timer.leave_subsection();
2689 * <a name="Soliddetermine_component_extractors"></a>
2690 * <h4>Solid::determine_component_extractors</h4>
2691 * Next we compute some information from the FE system that describes which
2692 * local element DOFs are attached to which block component. This is used
2693 * later to extract sub-blocks from the global matrix.
2697 * In essence, all we need is for the FESystem object to indicate to which
2698 * block component a DOF on the reference cell is attached to. Currently, the
2699 * interpolation fields are setup such that 0 indicates a displacement DOF, 1
2700 * a pressure DOF and 2 a dilatation DOF.
2703 * template <int dim>
2704 * void Solid<dim>::determine_component_extractors()
2706 * element_indices_u.clear();
2707 * element_indices_p.clear();
2708 * element_indices_J.clear();
2710 * for (unsigned int k = 0; k < fe.n_dofs_per_cell(); ++k)
2712 * const unsigned int k_group = fe.system_to_base_index(k).first.first;
2713 * if (k_group == u_dof)
2714 * element_indices_u.push_back(k);
2715 * else if (k_group == p_dof)
2716 * element_indices_p.push_back(k);
2717 * else if (k_group == J_dof)
2718 * element_indices_J.push_back(k);
2721 * Assert(k_group <= J_dof, ExcInternalError());
2729 * <a name="Solidsetup_qph"></a>
2730 * <h4>Solid::setup_qph</h4>
2731 * The method used to store quadrature information is already described in
2732 * @ref step_18 "step-18". Here we implement a similar setup for a SMP machine.
2736 * Firstly the actual QPH data objects are created. This must be done only
2737 * once the grid is refined to its finest level.
2740 * template <int dim>
2741 * void Solid<dim>::setup_qph()
2743 * std::cout << " Setting up quadrature point data..." << std::endl;
2745 * quadrature_point_history.initialize(triangulation.begin_active(),
2746 * triangulation.end(),
2751 * Next we set up the initial quadrature point data.
2752 * Note that when the quadrature point data is retrieved,
2753 * it is returned as a vector of smart pointers.
2756 * for (const auto &cell : triangulation.active_cell_iterators())
2758 * const std::vector<std::shared_ptr<PointHistory<dim>>> lqph =
2759 * quadrature_point_history.get_data(cell);
2760 * Assert(lqph.size() == n_q_points, ExcInternalError());
2762 * for (unsigned int q_point = 0; q_point < n_q_points; ++q_point)
2763 * lqph[q_point]->setup_lqp(parameters);
2770 * <a name="Solidupdate_qph_incremental"></a>
2771 * <h4>Solid::update_qph_incremental</h4>
2772 * As the update of QP information occurs frequently and involves a number of
2773 * expensive operations, we define a multithreaded approach to distributing
2774 * the task across a number of CPU cores.
2778 * To start this, we first we need to obtain the total solution as it stands
2779 * at this Newton increment and then create the initial copy of the scratch
2780 * and copy data objects:
2783 * template <int dim>
2785 * Solid<dim>::update_qph_incremental(const BlockVector<double> &solution_delta)
2787 * timer.enter_subsection("Update QPH data");
2788 * std::cout << " UQPH " << std::flush;
2790 * const BlockVector<double> solution_total(
2791 * get_total_solution(solution_delta));
2793 * const UpdateFlags uf_UQPH(update_values | update_gradients);
2794 * PerTaskData_UQPH per_task_data_UQPH;
2795 * ScratchData_UQPH scratch_data_UQPH(fe, qf_cell, uf_UQPH, solution_total);
2799 * We then pass them and the one-cell update function to the WorkStream to
2803 * WorkStream::run(dof_handler.active_cell_iterators(),
2805 * &Solid::update_qph_incremental_one_cell,
2806 * &Solid::copy_local_to_global_UQPH,
2807 * scratch_data_UQPH,
2808 * per_task_data_UQPH);
2810 * timer.leave_subsection();
2816 * Now we describe how we extract data from the solution vector and pass it
2817 * along to each QP storage object for processing.
2820 * template <int dim>
2821 * void Solid<dim>::update_qph_incremental_one_cell(
2822 * const typename DoFHandler<dim>::active_cell_iterator &cell,
2823 * ScratchData_UQPH & scratch,
2824 * PerTaskData_UQPH & /*data*/)
2826 * const std::vector<std::shared_ptr<PointHistory<dim>>> lqph =
2827 * quadrature_point_history.get_data(cell);
2828 * Assert(lqph.size() == n_q_points, ExcInternalError());
2830 * Assert(scratch.solution_grads_u_total.size() == n_q_points,
2831 * ExcInternalError());
2832 * Assert(scratch.solution_values_p_total.size() == n_q_points,
2833 * ExcInternalError());
2834 * Assert(scratch.solution_values_J_total.size() == n_q_points,
2835 * ExcInternalError());
2841 * We first need to find the values and gradients at quadrature points
2842 * inside the current cell and then we update each local QP using the
2843 * displacement gradient and total pressure and dilatation solution
2847 * scratch.fe_values.reinit(cell);
2848 * scratch.fe_values[u_fe].get_function_gradients(
2849 * scratch.solution_total, scratch.solution_grads_u_total);
2850 * scratch.fe_values[p_fe].get_function_values(
2851 * scratch.solution_total, scratch.solution_values_p_total);
2852 * scratch.fe_values[J_fe].get_function_values(
2853 * scratch.solution_total, scratch.solution_values_J_total);
2855 * for (const unsigned int q_point :
2856 * scratch.fe_values.quadrature_point_indices())
2857 * lqph[q_point]->update_values(scratch.solution_grads_u_total[q_point],
2858 * scratch.solution_values_p_total[q_point],
2859 * scratch.solution_values_J_total[q_point]);
2866 * <a name="Solidsolve_nonlinear_timestep"></a>
2867 * <h4>Solid::solve_nonlinear_timestep</h4>
2871 * The next function is the driver method for the Newton-Raphson scheme. At
2872 * its top we create a new vector to store the current Newton update step,
2873 * reset the error storage objects and print solver header.
2876 * template <int dim>
2877 * void Solid<dim>::solve_nonlinear_timestep(BlockVector<double> &solution_delta)
2879 * std::cout << std::endl
2880 * << "Timestep " << time.get_timestep() << " @ " << time.current()
2881 * << 's
' << std::endl;
2883 * BlockVector<double> newton_update(dofs_per_block);
2885 * error_residual.reset();
2886 * error_residual_0.reset();
2887 * error_residual_norm.reset();
2888 * error_update.reset();
2889 * error_update_0.reset();
2890 * error_update_norm.reset();
2892 * print_conv_header();
2896 * We now perform a number of Newton iterations to iteratively solve the
2897 * nonlinear problem. Since the problem is fully nonlinear and we are
2898 * using a full Newton method, the data stored in the tangent matrix and
2899 * right-hand side vector is not reusable and must be cleared at each
2900 * Newton step. We then initially build the linear system and
2901 * check for convergence (and store this value in the first iteration).
2902 * The unconstrained DOFs of the rhs vector hold the out-of-balance
2903 * forces, and collectively determine whether or not the equilibrium
2904 * solution has been attained.
2908 * Although for this particular problem we could potentially construct the
2909 * RHS vector before assembling the system matrix, for the sake of
2910 * extensibility we choose not to do so. The benefit to assembling the RHS
2911 * vector and system matrix separately is that the latter is an expensive
2912 * operation and we can potentially avoid an extra assembly process by not
2913 * assembling the tangent matrix when convergence is attained. However, this
2914 * makes parallelizing the code using MPI more difficult. Furthermore, when
2915 * extending the problem to the transient case additional contributions to
2916 * the RHS may result from the time discretization and application of
2917 * constraints for the velocity and acceleration fields.
2920 * unsigned int newton_iteration = 0;
2921 * for (; newton_iteration < parameters.max_iterations_NR; ++newton_iteration)
2923 * std::cout << ' ' << std::setw(2) << newton_iteration << ' '
2928 * We construct the linear system, but hold off on solving it
2929 * (a step that should be significantly more expensive than assembly):
2932 * make_constraints(newton_iteration);
2933 * assemble_system();
2937 * We can now determine the normalized residual error and check for
2938 * solution convergence:
2941 * get_error_residual(error_residual);
2942 * if (newton_iteration == 0)
2943 * error_residual_0 = error_residual;
2945 * error_residual_norm = error_residual;
2946 * error_residual_norm.normalize(error_residual_0);
2948 * if (newton_iteration > 0 && error_update_norm.u <= parameters.tol_u &&
2949 * error_residual_norm.u <= parameters.tol_f)
2951 * std::cout << " CONVERGED! " << std::endl;
2952 * print_conv_footer();
2959 * If we have decided that we want to continue with the iteration, we
2960 * solve the linearized system:
2963 * const std::pair<unsigned int, double> lin_solver_output =
2964 * solve_linear_system(newton_update);
2968 * We can now determine the normalized Newton update error:
2971 * get_error_update(newton_update, error_update);
2972 * if (newton_iteration == 0)
2973 * error_update_0 = error_update;
2975 * error_update_norm = error_update;
2976 * error_update_norm.normalize(error_update_0);
2980 * Lastly, since we implicitly accept the solution step we can perform
2981 * the actual update of the solution increment for the current time
2982 * step, update all quadrature point information pertaining to
2983 * this new displacement and stress state and continue iterating:
2986 * solution_delta += newton_update;
2987 * update_qph_incremental(solution_delta);
2989 * std::cout << " | " << std::fixed << std::setprecision(3) << std::setw(7)
2990 * << std::scientific << lin_solver_output.first << " "
2991 * << lin_solver_output.second << " "
2992 * << error_residual_norm.norm << " " << error_residual_norm.u
2993 * << " " << error_residual_norm.p << " "
2994 * << error_residual_norm.J << " " << error_update_norm.norm
2995 * << " " << error_update_norm.u << " " << error_update_norm.p
2996 * << " " << error_update_norm.J << " " << std::endl;
3001 * At the end, if it turns out that we have in fact done more iterations
3002 * than the parameter file allowed, we raise an exception that can be
3003 * caught in the main() function. The call <code>AssertThrow(condition,
3004 * exc_object)</code> is in essence equivalent to <code>if (!cond) throw
3005 * exc_object;</code> but the former form fills certain fields in the
3006 * exception object that identify the location (filename and line number)
3007 * where the exception was raised to make it simpler to identify where the
3011 * AssertThrow(newton_iteration < parameters.max_iterations_NR,
3012 * ExcMessage("No convergence in nonlinear solver!"));
3019 * <a name="Solidprint_conv_headerandSolidprint_conv_footer"></a>
3020 * <h4>Solid::print_conv_header and Solid::print_conv_footer</h4>
3024 * This program prints out data in a nice table that is updated
3025 * on a per-iteration basis. The next two functions set up the table
3026 * header and footer:
3029 * template <int dim>
3030 * void Solid<dim>::print_conv_header()
3032 * static const unsigned int l_width = 150;
3034 * for (unsigned int i = 0; i < l_width; ++i)
3036 * std::cout << std::endl;
3038 * std::cout << " SOLVER STEP "
3039 * << " | LIN_IT LIN_RES RES_NORM "
3040 * << " RES_U RES_P RES_J NU_NORM "
3041 * << " NU_U NU_P NU_J " << std::endl;
3043 * for (unsigned int i = 0; i < l_width; ++i)
3045 * std::cout << std::endl;
3050 * template <int dim>
3051 * void Solid<dim>::print_conv_footer()
3053 * static const unsigned int l_width = 150;
3055 * for (unsigned int i = 0; i < l_width; ++i)
3057 * std::cout << std::endl;
3059 * const std::pair<double, double> error_dil = get_error_dilation();
3061 * std::cout << "Relative errors:" << std::endl
3062 * << "Displacement:\t" << error_update.u / error_update_0.u
3064 * << "Force: \t\t" << error_residual.u / error_residual_0.u
3066 * << "Dilatation:\t" << error_dil.first << std::endl
3067 * << "v / V_0:\t" << error_dil.second * vol_reference << " / "
3068 * << vol_reference << " = " << error_dil.second << std::endl;
3075 * <a name="Solidget_error_dilation"></a>
3076 * <h4>Solid::get_error_dilation</h4>
3080 * Calculate the volume of the domain in the spatial configuration
3083 * template <int dim>
3084 * double Solid<dim>::compute_vol_current() const
3086 * double vol_current = 0.0;
3088 * FEValues<dim> fe_values(fe, qf_cell, update_JxW_values);
3090 * for (const auto &cell : triangulation.active_cell_iterators())
3092 * fe_values.reinit(cell);
3096 * In contrast to that which was previously called for,
3097 * in this instance the quadrature point data is specifically
3098 * non-modifiable since we will only be accessing data.
3099 * We ensure that the right get_data function is called by
3100 * marking this update function as constant.
3103 * const std::vector<std::shared_ptr<const PointHistory<dim>>> lqph =
3104 * quadrature_point_history.get_data(cell);
3105 * Assert(lqph.size() == n_q_points, ExcInternalError());
3107 * for (const unsigned int q_point : fe_values.quadrature_point_indices())
3109 * const double det_F_qp = lqph[q_point]->get_det_F();
3110 * const double JxW = fe_values.JxW(q_point);
3112 * vol_current += det_F_qp * JxW;
3115 * Assert(vol_current > 0.0, ExcInternalError());
3116 * return vol_current;
3121 * Calculate how well the dilatation @f$\widetilde{J}@f$ agrees with @f$J
3122 * \dealcoloneq \textrm{det}\ \mathbf{F}@f$ from the @f$L^2@f$ error @f$ \bigl[
3123 * \int_{\Omega_0} {[ J - \widetilde{J}]}^{2}\textrm{d}V \bigr]^{1/2}@f$.
3124 * We also return the ratio of the current volume of the
3125 * domain to the reference volume. This is of interest for incompressible
3126 * media where we want to check how well the isochoric constraint has been
3130 * template <int dim>
3131 * std::pair<double, double> Solid<dim>::get_error_dilation() const
3133 * double dil_L2_error = 0.0;
3135 * FEValues<dim> fe_values(fe, qf_cell, update_JxW_values);
3137 * for (const auto &cell : triangulation.active_cell_iterators())
3139 * fe_values.reinit(cell);
3141 * const std::vector<std::shared_ptr<const PointHistory<dim>>> lqph =
3142 * quadrature_point_history.get_data(cell);
3143 * Assert(lqph.size() == n_q_points, ExcInternalError());
3145 * for (const unsigned int q_point : fe_values.quadrature_point_indices())
3147 * const double det_F_qp = lqph[q_point]->get_det_F();
3148 * const double J_tilde_qp = lqph[q_point]->get_J_tilde();
3149 * const double the_error_qp_squared =
3150 * std::pow((det_F_qp - J_tilde_qp), 2);
3151 * const double JxW = fe_values.JxW(q_point);
3153 * dil_L2_error += the_error_qp_squared * JxW;
3157 * return std::make_pair(std::sqrt(dil_L2_error),
3158 * compute_vol_current() / vol_reference);
3165 * <a name="Solidget_error_residual"></a>
3166 * <h4>Solid::get_error_residual</h4>
3170 * Determine the true residual error for the problem. That is, determine the
3171 * error in the residual for the unconstrained degrees of freedom. Note that
3172 * to do so, we need to ignore constrained DOFs by setting the residual in
3173 * these vector components to zero.
3176 * template <int dim>
3177 * void Solid<dim>::get_error_residual(Errors &error_residual)
3179 * BlockVector<double> error_res(dofs_per_block);
3181 * for (unsigned int i = 0; i < dof_handler.n_dofs(); ++i)
3182 * if (!constraints.is_constrained(i))
3183 * error_res(i) = system_rhs(i);
3185 * error_residual.norm = error_res.l2_norm();
3186 * error_residual.u = error_res.block(u_dof).l2_norm();
3187 * error_residual.p = error_res.block(p_dof).l2_norm();
3188 * error_residual.J = error_res.block(J_dof).l2_norm();
3195 * <a name="Solidget_error_update"></a>
3196 * <h4>Solid::get_error_update</h4>
3200 * Determine the true Newton update error for the problem
3203 * template <int dim>
3204 * void Solid<dim>::get_error_update(const BlockVector<double> &newton_update,
3205 * Errors & error_update)
3207 * BlockVector<double> error_ud(dofs_per_block);
3208 * for (unsigned int i = 0; i < dof_handler.n_dofs(); ++i)
3209 * if (!constraints.is_constrained(i))
3210 * error_ud(i) = newton_update(i);
3212 * error_update.norm = error_ud.l2_norm();
3213 * error_update.u = error_ud.block(u_dof).l2_norm();
3214 * error_update.p = error_ud.block(p_dof).l2_norm();
3215 * error_update.J = error_ud.block(J_dof).l2_norm();
3223 * <a name="Solidget_total_solution"></a>
3224 * <h4>Solid::get_total_solution</h4>
3228 * This function provides the total solution, which is valid at any Newton
3229 * step. This is required as, to reduce computational error, the total
3230 * solution is only updated at the end of the timestep.
3233 * template <int dim>
3234 * BlockVector<double> Solid<dim>::get_total_solution(
3235 * const BlockVector<double> &solution_delta) const
3237 * BlockVector<double> solution_total(solution_n);
3238 * solution_total += solution_delta;
3239 * return solution_total;
3246 * <a name="Solidassemble_system"></a>
3247 * <h4>Solid::assemble_system</h4>
3251 * Since we use TBB for assembly, we simply setup a copy of the
3252 * data structures required for the process and pass them, along
3253 * with the assembly functions to the WorkStream object for processing. Note
3254 * that we must ensure that the matrix and RHS vector are reset before any
3255 * assembly operations can occur. Furthermore, since we are describing a
3256 * problem with Neumann BCs, we will need the face normals and so must specify
3257 * this in the face update flags.
3260 * template <int dim>
3261 * void Solid<dim>::assemble_system()
3263 * timer.enter_subsection("Assemble system");
3264 * std::cout << " ASM_SYS " << std::flush;
3266 * tangent_matrix = 0.0;
3269 * const UpdateFlags uf_cell(update_values | update_gradients |
3270 * update_JxW_values);
3271 * const UpdateFlags uf_face(update_values | update_normal_vectors |
3272 * update_JxW_values);
3274 * PerTaskData_ASM per_task_data(dofs_per_cell);
3275 * ScratchData_ASM scratch_data(fe, qf_cell, uf_cell, qf_face, uf_face);
3279 * The syntax used here to pass data to the WorkStream class
3280 * is discussed in @ref step_13 "step-13".
3284 * dof_handler.active_cell_iterators(),
3285 * [this](const typename DoFHandler<dim>::active_cell_iterator &cell,
3286 * ScratchData_ASM & scratch,
3287 * PerTaskData_ASM & data) {
3288 * this->assemble_system_one_cell(cell, scratch, data);
3290 * [this](const PerTaskData_ASM &data) {
3291 * this->constraints.distribute_local_to_global(data.cell_matrix,
3293 * data.local_dof_indices,
3300 * timer.leave_subsection();
3305 * Of course, we still have to define how we assemble the tangent matrix
3306 * contribution for a single cell. We first need to reset and initialize some
3307 * of the scratch data structures and retrieve some basic information
3308 * regarding the DOF numbering on this cell. We can precalculate the cell
3309 * shape function values and gradients. Note that the shape function gradients
3310 * are defined with regard to the current configuration. That is
3311 * @f$\textrm{grad}\ \boldsymbol{\varphi} = \textrm{Grad}\ \boldsymbol{\varphi}
3312 * \ \mathbf{F}^{-1}@f$.
3315 * template <int dim>
3316 * void Solid<dim>::assemble_system_one_cell(
3317 * const typename DoFHandler<dim>::active_cell_iterator &cell,
3318 * ScratchData_ASM & scratch,
3319 * PerTaskData_ASM & data) const
3323 * scratch.fe_values.reinit(cell);
3324 * cell->get_dof_indices(data.local_dof_indices);
3326 * const std::vector<std::shared_ptr<const PointHistory<dim>>> lqph =
3327 * quadrature_point_history.get_data(cell);
3328 * Assert(lqph.size() == n_q_points, ExcInternalError());
3330 * for (const unsigned int q_point :
3331 * scratch.fe_values.quadrature_point_indices())
3333 * const Tensor<2, dim> F_inv = lqph[q_point]->get_F_inv();
3334 * for (const unsigned int k : scratch.fe_values.dof_indices())
3336 * const unsigned int k_group = fe.system_to_base_index(k).first.first;
3338 * if (k_group == u_dof)
3340 * scratch.grad_Nx[q_point][k] =
3341 * scratch.fe_values[u_fe].gradient(k, q_point) * F_inv;
3342 * scratch.symm_grad_Nx[q_point][k] =
3343 * symmetrize(scratch.grad_Nx[q_point][k]);
3345 * else if (k_group == p_dof)
3346 * scratch.Nx[q_point][k] =
3347 * scratch.fe_values[p_fe].value(k, q_point);
3348 * else if (k_group == J_dof)
3349 * scratch.Nx[q_point][k] =
3350 * scratch.fe_values[J_fe].value(k, q_point);
3352 * Assert(k_group <= J_dof, ExcInternalError());
3358 * Now we build the local cell @ref GlossStiffnessMatrix "stiffness matrix" and RHS vector. Since the
3359 * global and local system matrices are symmetric, we can exploit this
3360 * property by building only the lower half of the local matrix and copying
3361 * the values to the upper half. So we only assemble half of the
3362 * @f$\mathsf{\mathbf{k}}_{uu}@f$, @f$\mathsf{\mathbf{k}}_{\widetilde{p}
3363 * \widetilde{p}} = \mathbf{0}@f$, @f$\mathsf{\mathbf{k}}_{\widetilde{J}
3364 * \widetilde{J}}@f$ blocks, while the whole
3365 * @f$\mathsf{\mathbf{k}}_{\widetilde{p} \widetilde{J}}@f$,
3366 * @f$\mathsf{\mathbf{k}}_{u \widetilde{J}} = \mathbf{0}@f$,
3367 * @f$\mathsf{\mathbf{k}}_{u \widetilde{p}}@f$ blocks are built.
3371 * In doing so, we first extract some configuration dependent variables
3372 * from our quadrature history objects for the current quadrature point.
3375 * for (const unsigned int q_point :
3376 * scratch.fe_values.quadrature_point_indices())
3378 * const SymmetricTensor<2, dim> tau = lqph[q_point]->get_tau();
3379 * const Tensor<2, dim> tau_ns = lqph[q_point]->get_tau();
3380 * const SymmetricTensor<4, dim> Jc = lqph[q_point]->get_Jc();
3381 * const double det_F = lqph[q_point]->get_det_F();
3382 * const double p_tilde = lqph[q_point]->get_p_tilde();
3383 * const double J_tilde = lqph[q_point]->get_J_tilde();
3384 * const double dPsi_vol_dJ = lqph[q_point]->get_dPsi_vol_dJ();
3385 * const double d2Psi_vol_dJ2 = lqph[q_point]->get_d2Psi_vol_dJ2();
3386 * const SymmetricTensor<2, dim> &I =
3387 * Physics::Elasticity::StandardTensors<dim>::I;
3391 * These two tensors store some precomputed data. Their use will
3392 * explained shortly.
3395 * SymmetricTensor<2, dim> symm_grad_Nx_i_x_Jc;
3396 * Tensor<1, dim> grad_Nx_i_comp_i_x_tau;
3400 * Next we define some aliases to make the assembly process easier to
3404 * const std::vector<double> & N = scratch.Nx[q_point];
3405 * const std::vector<SymmetricTensor<2, dim>> &symm_grad_Nx =
3406 * scratch.symm_grad_Nx[q_point];
3407 * const std::vector<Tensor<2, dim>> &grad_Nx = scratch.grad_Nx[q_point];
3408 * const double JxW = scratch.fe_values.JxW(q_point);
3410 * for (const unsigned int i : scratch.fe_values.dof_indices())
3412 * const unsigned int component_i =
3413 * fe.system_to_component_index(i).first;
3414 * const unsigned int i_group = fe.system_to_base_index(i).first.first;
3418 * We first compute the contributions
3419 * from the internal forces. Note, by
3420 * definition of the rhs as the negative
3421 * of the residual, these contributions
3425 * if (i_group == u_dof)
3426 * data.cell_rhs(i) -= (symm_grad_Nx[i] * tau) * JxW;
3427 * else if (i_group == p_dof)
3428 * data.cell_rhs(i) -= N[i] * (det_F - J_tilde) * JxW;
3429 * else if (i_group == J_dof)
3430 * data.cell_rhs(i) -= N[i] * (dPsi_vol_dJ - p_tilde) * JxW;
3432 * Assert(i_group <= J_dof, ExcInternalError());
3436 * Before we go into the inner loop, we have one final chance to
3440 *
We won't be excessive here, but will rather focus on expensive
3441 * operations, namely those involving the rank-4 material stiffness
3442 * tensor and the rank-2 stress tensor.
3446 * What we may observe is that both of these tensors are contracted
3447 * with shape function gradients indexed on the "i" DoF. This
3448 * implies that this particular operation remains constant as we
3449 * loop over the "j" DoF. For that reason, we can extract this from
3450 * the inner loop and save the many operations that, for each
3451 * quadrature point and DoF index "i" and repeated over index "j"
3452 * are required to double contract a rank-2 symmetric tensor with a
3453 * rank-4 symmetric tensor, and a rank-1 tensor with a rank-2
3458 * At the loss of some readability, this small change will reduce
3459 * the assembly time of the symmetrized system by about half when
3460 * using the simulation default parameters, and becomes more
3461 * significant as the h-refinement level increases.
3464 * if (i_group == u_dof)
3466 * symm_grad_Nx_i_x_Jc = symm_grad_Nx[i] * Jc;
3467 * grad_Nx_i_comp_i_x_tau = grad_Nx[i][component_i] * tau_ns;
3475 *
for (
const unsigned int j :
3476 *
scratch.fe_values.dof_indices_ending_at(i))
3479 *
fe.system_to_component_index(
j).
first;
3480 *
const unsigned int j_group =
3481 *
fe.system_to_base_index(
j).first.first;
3507 *
data.cell_matrix(i,
j) +=
3518 *
data.cell_matrix(i,
j) += N[i] *
det_F *
3529 *
data.cell_matrix(i,
j) -= N[i] * N[
j] * JxW;
3534 *
ExcInternalError());
3546 *
for (
const auto &face : cell->face_iterators())
3549 *
scratch.fe_face_values.
reinit(cell, face);
3552 *
scratch.fe_face_values.quadrature_point_indices())
3555 *
scratch.fe_face_values.normal_vector(
f_q_point);
3576 *
static const double p0 =
3577 *
-4.0 / (parameters.scale * parameters.scale);
3578 *
const double time_ramp = (time.current() / time.
end());
3582 *
for (
const unsigned int i : scratch.fe_values.dof_indices())
3590 *
fe.system_to_component_index(i).first;
3592 *
scratch.fe_face_values.shape_value(i,
f_q_point);
3593 *
const double JxW = scratch.fe_face_values.JxW(
f_q_point);
3607 *
for (
const unsigned int i : scratch.fe_values.dof_indices())
3609 *
scratch.fe_values.dof_indices_starting_at(i + 1))
3618 * <a name=
"Solidmake_constraints"></a>
3623 * build non-homogeneous constraints in the zeroth iteration (that is, when
3624 * `apply_dirichlet_bc == true` in the code block that follows) and build
3625 * only the corresponding homogeneous constraints in the following step. While
3626 * the current example has only homogeneous constraints, previous experiences
3627 * have shown that a common error is forgetting to add the extra condition
3628 * when refactoring the code to specific uses. This could lead to errors that
3629 * are hard to debug. In this spirit, we choose to make the code more verbose
3630 * in terms of what operations are performed at each Newton step.
3633 * template <int dim>
3634 * void Solid<dim>::make_constraints(const int it_nr)
3638 * Since we (a) are dealing with an iterative Newton method, (b) are using
3639 * an incremental formulation for the displacement, and (c) apply the
3640 * constraints to the incremental displacement field, any non-homogeneous
3641 * constraints on the displacement update should only be specified at the
3642 * zeroth iteration. No subsequent contributions are to be made since the
3643 * constraints will be exactly satisfied after that iteration.
3646 * const bool apply_dirichlet_bc = (it_nr == 0);
3650 * Furthermore, after the first Newton iteration within a timestep, the
3651 * constraints remain the same and we do not need to modify or rebuild them
3652 * so long as we do not clear the @p constraints object.
3657 * std::cout << " --- " << std::flush;
3661 * std::cout << " CST " << std::flush;
3663 * if (apply_dirichlet_bc)
3667 * At the zeroth Newton iteration we wish to apply the full set of
3668 * non-homogeneous and homogeneous constraints that represent the
3669 * boundary conditions on the displacement increment. Since in general
3670 * the constraints may be different at each time step, we need to clear
3671 * the constraints matrix and completely rebuild it. An example case
3672 * would be if a surface is accelerating; in such a scenario the change
3673 * in displacement is non-constant between each time step.
3676 * constraints.clear();
3680 * The boundary conditions for the indentation problem in 3d are as
3681 * follows: On the -x, -y and -z faces (IDs 0,2,4) we set up a symmetry
3682 * condition to allow only planar movement while the +x and +z faces
3683 * (IDs 1,5) are traction free. In this contrived problem, part of the
3684 * +y face (ID 3) is set to have no motion in the x- and z-component.
3685 * Finally, as described earlier, the other part of the +y face has an
3686 * the applied pressure but is also constrained in the x- and
3691 * In the following, we will have to tell the function interpolation
3692 * boundary values which components of the solution vector should be
3809 *
if (constraints.has_inhomogeneities())
3817 * back
to the main @p constraints
object.
3821 *
for (
unsigned int dof = 0; dof != dof_handler.n_dofs(); ++dof)
3825 *
constraints.clear();
3830 *
constraints.close();
3836 * <a name=
"Solidassemble_sc"></a>
3866 *
from each element
's contributions. These contributions are then added to
3867 * the global stiffness matrix. Given this description, the following two
3868 * functions should be clear:
3871 * template <int dim>
3872 * void Solid<dim>::assemble_sc()
3874 * timer.enter_subsection("Perform static condensation");
3875 * std::cout << " ASM_SC " << std::flush;
3877 * PerTaskData_SC per_task_data(dofs_per_cell,
3878 * element_indices_u.size(),
3879 * element_indices_p.size(),
3880 * element_indices_J.size());
3881 * ScratchData_SC scratch_data;
3883 * WorkStream::run(dof_handler.active_cell_iterators(),
3885 * &Solid::assemble_sc_one_cell,
3886 * &Solid::copy_local_to_global_sc,
3890 * timer.leave_subsection();
3894 * template <int dim>
3895 * void Solid<dim>::copy_local_to_global_sc(const PerTaskData_SC &data)
3897 * for (unsigned int i = 0; i < dofs_per_cell; ++i)
3898 * for (unsigned int j = 0; j < dofs_per_cell; ++j)
3899 * tangent_matrix.add(data.local_dof_indices[i],
3900 * data.local_dof_indices[j],
3901 * data.cell_matrix(i, j));
3907 * Now we describe the static condensation process. As per usual, we must
3908 * first find out which global numbers the degrees of freedom on this cell
3909 * have and reset some data structures:
3912 * template <int dim>
3913 * void Solid<dim>::assemble_sc_one_cell(
3914 * const typename DoFHandler<dim>::active_cell_iterator &cell,
3915 * ScratchData_SC & scratch,
3916 * PerTaskData_SC & data)
3920 * cell->get_dof_indices(data.local_dof_indices);
3924 * We now extract the contribution of the dofs associated with the current
3925 * cell to the global stiffness matrix. The discontinuous nature of the
3926 * @f$\widetilde{p}@f$ and @f$\widetilde{J}@f$ interpolations mean that their is
3927 * no coupling of the local contributions at the global level. This is not
3928 * the case with the @f$\mathbf{u}@f$ dof. In other words,
3929 * @f$\mathsf{\mathbf{k}}_{\widetilde{J} \widetilde{p}}@f$,
3930 * @f$\mathsf{\mathbf{k}}_{\widetilde{p} \widetilde{p}}@f$ and
3931 * @f$\mathsf{\mathbf{k}}_{\widetilde{J} \widetilde{p}}@f$, when extracted
3932 * from the global stiffness matrix are the element contributions. This
3933 * is not the case for @f$\mathsf{\mathbf{k}}_{uu}@f$.
3937 * Note: A lower-case symbol is used to denote element stiffness matrices.
3941 * Currently the matrix corresponding to
3942 * the dof associated with the current element
3943 * (denoted somewhat loosely as @f$\mathsf{\mathbf{k}}@f$)
3947 * \mathsf{\mathbf{k}}_{uu} & \mathsf{\mathbf{k}}_{u\widetilde{p}}
3949 * \\ \mathsf{\mathbf{k}}_{\widetilde{p}u} & \mathbf{0} &
3950 * \mathsf{\mathbf{k}}_{\widetilde{p}\widetilde{J}}
3951 * \\ \mathbf{0} & \mathsf{\mathbf{k}}_{\widetilde{J}\widetilde{p}} &
3952 * \mathsf{\mathbf{k}}_{\widetilde{J}\widetilde{J}} \end{bmatrix}
3957 * We now need to modify it such that it appear as
3960 * \mathsf{\mathbf{k}}_{\textrm{con}} &
3961 * \mathsf{\mathbf{k}}_{u\widetilde{p}} & \mathbf{0}
3962 * \\ \mathsf{\mathbf{k}}_{\widetilde{p}u} & \mathbf{0} &
3963 * \mathsf{\mathbf{k}}_{\widetilde{p}\widetilde{J}}^{-1}
3964 * \\ \mathbf{0} & \mathsf{\mathbf{k}}_{\widetilde{J}\widetilde{p}} &
3965 * \mathsf{\mathbf{k}}_{\widetilde{J}\widetilde{J}} \end{bmatrix}
3967 * with @f$\mathsf{\mathbf{k}}_{\textrm{con}} = \bigl[
3968 * \mathsf{\mathbf{k}}_{uu} +\overline{\overline{\mathsf{\mathbf{k}}}}~
3969 * \bigr]@f$ where @f$ \overline{\overline{\mathsf{\mathbf{k}}}}
3970 * \dealcoloneq \mathsf{\mathbf{k}}_{u\widetilde{p}}
3971 * \overline{\mathsf{\mathbf{k}}} \mathsf{\mathbf{k}}_{\widetilde{p}u}
3975 * \overline{\mathsf{\mathbf{k}}} =
3976 * \mathsf{\mathbf{k}}_{\widetilde{J}\widetilde{p}}^{-1}
3977 * \mathsf{\mathbf{k}}_{\widetilde{J}\widetilde{J}}
3978 * \mathsf{\mathbf{k}}_{\widetilde{p}\widetilde{J}}^{-1}
3983 * At this point, we need to take note of
3984 * the fact that global data already exists
3985 * in the @f$\mathsf{\mathbf{K}}_{uu}@f$,
3986 * @f$\mathsf{\mathbf{K}}_{\widetilde{p} \widetilde{J}}@f$
3988 * @f$\mathsf{\mathbf{K}}_{\widetilde{J} \widetilde{p}}@f$
3989 * sub-blocks. So if we are to modify them, we must account for the data
3990 * that is already there (i.e. simply add to it or remove it if
3991 * necessary). Since the copy_local_to_global operation is a "+="
3992 * operation, we need to take this into account
3996 * For the @f$\mathsf{\mathbf{K}}_{uu}@f$ block in particular, this means that
3997 * contributions have been added from the surrounding cells, so we need to
3998 * be careful when we manipulate this block. We can't
just erase
the
4008 *
Since we don't have access to @f$\mathsf{\mathbf{k}}_{uu}@f$,
4009 * but we know its contribution is added to
4010 * the global @f$\mathsf{\mathbf{K}}_{uu}@f$ matrix, we just want
4011 * to add the element wise
4012 * static-condensation @f$\overline{\overline{\mathsf{\mathbf{k}}}}@f$.
4016 * - @f$\mathsf{\mathbf{k}}^{-1}_{\widetilde{p} \widetilde{J}}@f$:
4017 * Similarly, @f$\mathsf{\mathbf{k}}_{\widetilde{p}
4018 * \widetilde{J}}@f$ exists in
4019 * the subblock. Since the copy
4020 * operation is a += operation, we
4021 * need to subtract the existing
4022 * @f$\mathsf{\mathbf{k}}_{\widetilde{p} \widetilde{J}}@f$
4023 * submatrix in addition to
4024 * "adding" that which we wish to
4029 * - @f$\mathsf{\mathbf{k}}^{-1}_{\widetilde{J} \widetilde{p}}@f$:
4030 * Since the global matrix
4031 * is symmetric, this block is the
4032 * same as the one above and we
4034 * @f$\mathsf{\mathbf{k}}^{-1}_{\widetilde{p} \widetilde{J}}@f$
4035 * as a substitute for this one.
4039 * We first extract element data from the
4040 * system matrix. So first we get the
4041 * entire subblock for the cell, then
4042 * extract @f$\mathsf{\mathbf{k}}@f$
4043 * for the dofs associated with
4044 * the current element
4047 * data.k_orig.extract_submatrix_from(tangent_matrix,
4048 * data.local_dof_indices,
4049 * data.local_dof_indices);
4052 * and next the local matrices for
4053 * @f$\mathsf{\mathbf{k}}_{ \widetilde{p} u}@f$
4054 * @f$\mathsf{\mathbf{k}}_{ \widetilde{p} \widetilde{J}}@f$
4056 * @f$\mathsf{\mathbf{k}}_{ \widetilde{J} \widetilde{J}}@f$:
4059 * data.k_pu.extract_submatrix_from(data.k_orig,
4060 * element_indices_p,
4061 * element_indices_u);
4062 * data.k_pJ.extract_submatrix_from(data.k_orig,
4063 * element_indices_p,
4064 * element_indices_J);
4065 * data.k_JJ.extract_submatrix_from(data.k_orig,
4066 * element_indices_J,
4067 * element_indices_J);
4071 * To get the inverse of @f$\mathsf{\mathbf{k}}_{\widetilde{p}
4072 * \widetilde{J}}@f$, we invert it directly. This operation is relatively
4073 * inexpensive since @f$\mathsf{\mathbf{k}}_{\widetilde{p} \widetilde{J}}@f$
4074 * since block-diagonal.
4077 * data.k_pJ_inv.invert(data.k_pJ);
4081 * Now we can make condensation terms to
4082 * add to the @f$\mathsf{\mathbf{k}}_{uu}@f$
4083 * block and put them in
4084 * the cell local matrix
4086 * \mathsf{\mathbf{A}}
4088 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{p} \widetilde{J}}
4089 * \mathsf{\mathbf{k}}_{\widetilde{p} u}
4093 * data.k_pJ_inv.mmult(data.A, data.k_pu);
4097 * \mathsf{\mathbf{B}}
4099 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{J} \widetilde{J}}
4100 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{p} \widetilde{J}}
4101 * \mathsf{\mathbf{k}}_{\widetilde{p} u}
4105 * data.k_JJ.mmult(data.B, data.A);
4109 * \mathsf{\mathbf{C}}
4111 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{J} \widetilde{p}}
4112 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{J} \widetilde{J}}
4113 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{p} \widetilde{J}}
4114 * \mathsf{\mathbf{k}}_{\widetilde{p} u}
4118 * data.k_pJ_inv.Tmmult(data.C, data.B);
4122 * \overline{\overline{\mathsf{\mathbf{k}}}}
4124 * \mathsf{\mathbf{k}}_{u \widetilde{p}}
4125 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{J} \widetilde{p}}
4126 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{J} \widetilde{J}}
4127 * \mathsf{\mathbf{k}}^{-1}_{\widetilde{p} \widetilde{J}}
4128 * \mathsf{\mathbf{k}}_{\widetilde{p} u}
4132 * data.k_pu.Tmmult(data.k_bbar, data.C);
4133 * data.k_bbar.scatter_matrix_to(element_indices_u,
4134 * element_indices_u,
4135 * data.cell_matrix);
4140 * @f$\mathsf{\mathbf{k}}^{-1}_{ \widetilde{p} \widetilde{J}}@f$
4142 * @f$\mathsf{\mathbf{k}}_{ \widetilde{p} \widetilde{J}}@f$
4143 * block for post-processing. Note again
4144 * that we need to remove the
4145 * contribution that already exists there.
4148 * data.k_pJ_inv.add(-1.0, data.k_pJ);
4149 * data.k_pJ_inv.scatter_matrix_to(element_indices_p,
4150 * element_indices_J,
4151 * data.cell_matrix);
4157 * <a name="Solidsolve_linear_system"></a>
4158 * <h4>Solid::solve_linear_system</h4>
4159 * We now have all of the necessary components to use one of two possible
4160 * methods to solve the linearised system. The first is to perform static
4161 * condensation on an element level, which requires some alterations
4162 * to the tangent matrix and RHS vector. Alternatively, the full block
4163 * system can be solved by performing condensation on a global level.
4164 * Below we implement both approaches.
4167 * template <int dim>
4168 * std::pair<unsigned int, double>
4169 * Solid<dim>::solve_linear_system(BlockVector<double> &newton_update)
4171 * unsigned int lin_it = 0;
4172 * double lin_res = 0.0;
4174 * if (parameters.use_static_condensation == true)
4178 * Firstly, here is the approach using the (permanent) augmentation of
4179 * the tangent matrix. For the following, recall that
4181 * \mathsf{\mathbf{K}}_{\textrm{store}}
4184 * \mathsf{\mathbf{K}}_{\textrm{con}} &
4185 * \mathsf{\mathbf{K}}_{u\widetilde{p}} & \mathbf{0}
4186 * \\ \mathsf{\mathbf{K}}_{\widetilde{p}u} & \mathbf{0} &
4187 * \mathsf{\mathbf{K}}_{\widetilde{p}\widetilde{J}}^{-1}
4189 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{p}} &
4190 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{J}} \end{bmatrix} \, .
4194 * d \widetilde{\mathsf{\mathbf{p}}}
4196 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{p}}^{-1}
4198 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4200 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{J}}
4201 * d \widetilde{\mathsf{\mathbf{J}}} \bigr]
4202 * \\ d \widetilde{\mathsf{\mathbf{J}}}
4204 * \mathsf{\mathbf{K}}_{\widetilde{p}\widetilde{J}}^{-1}
4206 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4207 * - \mathsf{\mathbf{K}}_{\widetilde{p}u} d
4208 * \mathsf{\mathbf{u}} \bigr]
4209 * \\ \Rightarrow d \widetilde{\mathsf{\mathbf{p}}}
4210 * &= \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{p}}^{-1}
4211 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4213 * \underbrace{\bigl[\mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{p}}^{-1}
4214 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{J}}
4215 * \mathsf{\mathbf{K}}_{\widetilde{p}\widetilde{J}}^{-1}\bigr]}_{\overline{\mathsf{\mathbf{K}}}}\bigl[
4216 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4217 * - \mathsf{\mathbf{K}}_{\widetilde{p}u} d
4218 * \mathsf{\mathbf{u}} \bigr]
4222 * \underbrace{\bigl[ \mathsf{\mathbf{K}}_{uu} +
4223 * \overline{\overline{\mathsf{\mathbf{K}}}}~ \bigr]
4224 * }_{\mathsf{\mathbf{K}}_{\textrm{con}}} d
4225 * \mathsf{\mathbf{u}}
4229 * \mathsf{\mathbf{F}}_{u}
4230 * - \mathsf{\mathbf{K}}_{u\widetilde{p}} \bigl[
4231 * \mathsf{\mathbf{K}}_{\widetilde{J}\widetilde{p}}^{-1}
4232 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4234 * \overline{\mathsf{\mathbf{K}}}\mathsf{\mathbf{F}}_{\widetilde{p}}
4236 * \Bigr]}_{\mathsf{\mathbf{F}}_{\textrm{con}}}
4240 * \overline{\overline{\mathsf{\mathbf{K}}}} \dealcoloneq
4241 * \mathsf{\mathbf{K}}_{u\widetilde{p}}
4242 * \overline{\mathsf{\mathbf{K}}}
4243 * \mathsf{\mathbf{K}}_{\widetilde{p}u} \, .
4248 * At the top, we allocate two temporary vectors to help with the
4249 * static condensation, and variables to store the number of
4250 * linear solver iterations and the (hopefully converged) residual.
4253 * BlockVector<double> A(dofs_per_block);
4254 * BlockVector<double> B(dofs_per_block);
4259 * In the first step of this function, we solve for the incremental
4260 * displacement @f$d\mathbf{u}@f$. To this end, we perform static
4261 * condensation to make
4262 * @f$\mathsf{\mathbf{K}}_{\textrm{con}}
4263 * = \bigl[ \mathsf{\mathbf{K}}_{uu} +
4264 * \overline{\overline{\mathsf{\mathbf{K}}}}~ \bigr]@f$
4266 * @f$\mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}@f$
4267 * in the original @f$\mathsf{\mathbf{K}}_{\widetilde{p} \widetilde{J}}@f$
4268 * block. That is, we make @f$\mathsf{\mathbf{K}}_{\textrm{store}}@f$.
4277 * \mathsf{\mathbf{A}}_{\widetilde{J}}
4279 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}
4280 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4284 * tangent_matrix.block(p_dof, J_dof)
4285 * .vmult(A.block(J_dof), system_rhs.block(p_dof));
4289 * \mathsf{\mathbf{B}}_{\widetilde{J}}
4291 * \mathsf{\mathbf{K}}_{\widetilde{J} \widetilde{J}}
4292 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}
4293 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4297 * tangent_matrix.block(J_dof, J_dof)
4298 * .vmult(B.block(J_dof), A.block(J_dof));
4302 * \mathsf{\mathbf{A}}_{\widetilde{J}}
4304 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4306 * \mathsf{\mathbf{K}}_{\widetilde{J} \widetilde{J}}
4307 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}
4308 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4312 * A.block(J_dof) = system_rhs.block(J_dof);
4313 * A.block(J_dof) -= B.block(J_dof);
4317 * \mathsf{\mathbf{A}}_{\widetilde{J}}
4319 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{J} \widetilde{p}}
4321 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4323 * \mathsf{\mathbf{K}}_{\widetilde{J} \widetilde{J}}
4324 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}
4325 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4330 * tangent_matrix.block(p_dof, J_dof)
4331 * .Tvmult(A.block(p_dof), A.block(J_dof));
4335 * \mathsf{\mathbf{A}}_{u}
4337 * \mathsf{\mathbf{K}}_{u \widetilde{p}}
4338 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{J} \widetilde{p}}
4340 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4342 * \mathsf{\mathbf{K}}_{\widetilde{J} \widetilde{J}}
4343 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}
4344 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4349 * tangent_matrix.block(u_dof, p_dof)
4350 * .vmult(A.block(u_dof), A.block(p_dof));
4354 * \mathsf{\mathbf{F}}_{\text{con}}
4356 * \mathsf{\mathbf{F}}_{u}
4358 * \mathsf{\mathbf{K}}_{u \widetilde{p}}
4359 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{J} \widetilde{p}}
4361 * \mathsf{\mathbf{F}}_{\widetilde{J}}
4363 * \mathsf{\mathbf{K}}_{\widetilde{J} \widetilde{J}}
4364 * \mathsf{\mathbf{K}}^{-1}_{\widetilde{p} \widetilde{J}}
4365 * \mathsf{\mathbf{F}}_{\widetilde{p}}
4370 * system_rhs.block(u_dof) -= A.block(u_dof);
4372 * timer.enter_subsection("Linear solver");
4373 * std::cout << " SLV " << std::flush;
4374 * if (parameters.type_lin == "CG")
4376 * const auto solver_its = static_cast<unsigned int>(
4377 * tangent_matrix.block(u_dof, u_dof).m() *
4378 * parameters.max_iterations_lin);
4379 * const double tol_sol =
4380 * parameters.tol_lin * system_rhs.block(u_dof).l2_norm();
4382 * SolverControl solver_control(solver_its, tol_sol);
4384 * GrowingVectorMemory<Vector<double>> GVM;
4385 * SolverCG<Vector<double>> solver_CG(solver_control, GVM);
4396 *
preconditioner(parameters.preconditioner_type,
4397 *
parameters.preconditioner_relaxation);
4405 *
lin_it = solver_control.last_step();
4406 *
lin_res = solver_control.last_value();
4408 *
else if (parameters.type_lin ==
"Direct")
4426 *
Assert(
false, ExcMessage(
"Linear solver type not implemented"));
4428 *
timer.leave_subsection();
4439 *
timer.enter_subsection(
"Linear solver postprocessing");
4440 *
std::cout <<
" PP " << std::flush;
4478 *
A.block(
p_dof) *= -1.0;
4554 *
A.block(
J_dof) *= -1.0;
4596 *
timer.leave_subsection();
4600 *
std::cout <<
" ------ " << std::flush;
4602 *
timer.enter_subsection(
"Linear solver");
4603 *
std::cout <<
" SLV " << std::flush;
4605 *
if (parameters.type_lin ==
"CG")
4627 * blocks in
the RHS vector
4667 * diagonal), a Jacobi preconditioner
is suitable.
4676 *
parameters.max_iterations_lin),
4678 *
parameters.tol_lin);
4722 *
parameters.preconditioner_relaxation);
4727 *
parameters.max_iterations_lin),
4729 *
parameters.tol_lin);
4750 *
timer.leave_subsection();
4758 *
timer.enter_subsection(
"Linear solver postprocessing");
4759 *
std::cout <<
" PP " << std::flush;
4767 *
else if (parameters.type_lin ==
"Direct")
4786 *
std::cout <<
" -- " << std::flush;
4789 *
Assert(
false, ExcMessage(
"Linear solver type not implemented"));
4791 *
timer.leave_subsection();
4808 * <a name=
"Solidoutput_results"></a>
4824 *
template <
int dim>
4828 *
std::vector<DataComponentInterpretation::DataComponentInterpretation>
4829 *
data_component_interpretation(
4831 *
data_component_interpretation.push_back(
4833 *
data_component_interpretation.push_back(
4836 *
std::vector<std::string>
solution_name(dim,
"displacement");
4845 *
data_out.attach_dof_handler(dof_handler);
4849 *
data_component_interpretation);
4864 *
for (
unsigned int i = 0; i <
soln.
size(); ++i)
4867 *
data_out.build_patches(
q_mapping, degree);
4869 *
std::ofstream output(
"solution-" + std::to_string(dim) +
"d-" +
4870 *
std::to_string(time.get_timestep()) +
".vtu");
4871 *
data_out.write_vtu(output);
4880 * <a name=
"Mainfunction"></a>
4888 *
using namespace Step44;
4892 *
const unsigned int dim = 3;
4896 *
catch (std::exception &exc)
4898 *
std::cerr << std::endl
4900 *
<<
"----------------------------------------------------"
4902 *
std::cerr <<
"Exception on processing: " << std::endl
4903 *
<< exc.what() << std::endl
4904 *
<<
"Aborting!" << std::endl
4905 *
<<
"----------------------------------------------------"
4912 *
std::cerr << std::endl
4914 *
<<
"----------------------------------------------------"
4916 *
std::cerr <<
"Unknown exception!" << std::endl
4917 *
<<
"Aborting!" << std::endl
4918 *
<<
"----------------------------------------------------"
4968 Reference volume: 1e-09
4988v /
V_0: 1.000e-09 / 1.000e-09 = 1.000e+00
5007v /
V_0: 1.000e-09 / 1.000e-09 = 1.000e+00
5023+---------------------------------------------+------------+------------+
5027+---------------------------------+-----------+------------+------------+
5030|
Linear solver | 43 | 9.248e+02s | 9.37e+01% |
5035+---------------------------------+-----------+------------+------------+
5067 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Q1-P0_gr_1_p_ratio_80-displacement.png" alt=
"">
5073 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Q1-P0_gr_1_p_ratio_80-pressure.png" alt=
"">
5079 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Q1-P0_gr_1_p_ratio_80-dilatation.png" alt=
"">
5108 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Q2-P1_gr_3_p_ratio_80-displacement.png" alt=
"">
5114 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Q2-P1_gr_3_p_ratio_80-pressure.png" alt=
"">
5120 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Q2-P1_gr_3_p_ratio_80-dilatation.png" alt=
"">
5135In terms of run-time, the @f$Q_2-DGPM_1-DGPM_1@f$ formulation tends to be more computationally expensive
5136than the @f$Q_1-DGPM_0-DGPM_0@f$ for a similar number of degrees-of-freedom
5137(produced by adding an extra grid refinement level for the lower-order interpolation).
5138This is shown in the graph below for a batch of tests run consecutively on a single 4-core (8-thread) machine.
5139The increase in computational time for the higher-order method is likely due to
5140the increased band-width required for the higher-order elements.
5141As previously mentioned, the use of a better solver and preconditioner may mitigate the
5142expense of using a higher-order formulation.
5143It was observed that for the given problem using the multithreaded Jacobi preconditioner can reduce the
5144computational runtime by up to 72% (for the worst case being a higher-order formulation with a large number
5145of degrees-of-freedom) in comparison to the single-thread SSOR preconditioner.
5153 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.Normalised_runtime.png" alt=
"">
5171 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.2d-gr_2.png" alt=
"">
5177 <
img src=
"https://www.dealii.org/images/steps/developer/step-44.2d-gr_5.png" alt=
"">
5185<a name=
"extensions"></a>
5186<a name=
"Possibilitiesforextensions"></a><
h3>Possibilities
for extensions</
h3>
5237<a name=
"PlainProg"></a>
value_type * data() const noexcept
__global__ void set(Number *val, const Number s, const size_type N)
#define Assert(cond, exc)
static ::ExceptionBase & ExcInternalError()
LinearOperator< Range, Domain, Payload > linear_operator(const OperatorExemplar &, const Matrix &)
LinearOperator< Domain, Range, Payload > transpose_operator(const LinearOperator< Range, Domain, Payload > &op)
LinearOperator< Range_2, Domain_2, Payload > schur_complement(const LinearOperator< Domain_1, Range_1, Payload > &A_inv, const LinearOperator< Range_1, Domain_2, Payload > &B, const LinearOperator< Range_2, Domain_1, Payload > &C, const LinearOperator< Range_2, Domain_2, Payload > &D)
LinearOperator< Domain, Range, Payload > inverse_operator(const LinearOperator< Range, Domain, Payload > &op, Solver &solver, const Preconditioner &preconditioner)
void loop(ITERATOR begin, std_cxx20::type_identity_t< ITERATOR > end, DOFINFO &dinfo, INFOBOX &info, const std::function< void(DOFINFO &, typename INFOBOX::CellInfo &)> &cell_worker, const std::function< void(DOFINFO &, typename INFOBOX::CellInfo &)> &boundary_worker, const std::function< void(DOFINFO &, DOFINFO &, typename INFOBOX::CellInfo &, typename INFOBOX::CellInfo &)> &face_worker, ASSEMBLER &assembler, const LoopControl &lctrl=LoopControl())
Task< RT > new_task(const std::function< RT()> &function)
std::vector< value_type > split(const typename ::Triangulation< dim, spacedim >::cell_iterator &parent, const value_type parent_value)
CGAL::Exact_predicates_exact_constructions_kernel_with_sqrt K
@ component_is_part_of_vector
Expression sign(const Expression &x)
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
@ diagonal
Matrix is diagonal.
@ general
No special properties.
void cell_matrix(FullMatrix< double > &M, const FEValuesBase< dim > &fe, const FEValuesBase< dim > &fetest, const ArrayView< const std::vector< double > > &velocity, const double factor=1.)
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > C(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
Tensor< 2, dim, Number > F(const Tensor< 2, dim, Number > &Grad_u)
VectorType::value_type * end(VectorType &V)
bool check(const ConstraintKinds kind_in, const unsigned int dim)
DEAL_II_HOST constexpr TableIndices< 2 > merge(const TableIndices< 2 > &previous_indices, const unsigned int new_index, const unsigned int position)
void copy(const T *begin, const T *end, U *dest)
int(&) functions(const void *v1, const void *v2)
void assemble(const MeshWorker::DoFInfoBox< dim, DOFINFO > &dinfo, A *assembler)
void reinit(MatrixBlock< MatrixType > &v, const BlockSparsityPattern &p)
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation