1510 *
constexpr double kappa = 1
e-6;
1518 *
constexpr double R0 = 6371000. - 2890000.;
1519 *
constexpr double R1 = 6371000. - 35000.;
1521 *
constexpr double T0 = 4000 + 273;
1522 *
constexpr double T1 = 700 + 273;
1541 *
template <
int dim>
1544 *
const double r = p.norm();
1545 *
return -(1.245e-6 * r + 7.714e13 / r / r) * p / r;
1550 *
template <
int dim>
1559 *
const unsigned int component = 0)
const override;
1561 *
virtual void vector_value(
const Point<dim> &p,
1567 *
template <
int dim>
1569 *
const unsigned int)
const
1571 *
const double r = p.norm();
1572 *
const double h =
R1 -
R0;
1574 *
const double s = (r -
R0) / h;
1577 *
const double phi = std::atan2(p(0), p(1));
1584 *
template <
int dim>
1589 *
for (
unsigned int c = 0; c < this->n_components; ++c)
1612 * so we convert to years using the factor defined here:
1615 * const double year_in_seconds = 60 * 60 * 24 * 365.2425;
1617 * } // namespace EquationData
1624 * <a name="PreconditioningtheStokessystem"></a>
1625 * <h3>Preconditioning the Stokes system</h3>
1629 * This namespace implements the preconditioner. As discussed in the
1630 * introduction, this preconditioner differs in a number of key portions
1631 * from the one used in @ref step_31 "step-31". Specifically, it is a right preconditioner,
1632 * implementing the matrix
1634 * \left(\begin{array}{cc}A^{-1} & B^T
1636 * \end{array}\right)
1638 * where the two inverse matrix operations
1639 * are approximated by linear solvers or, if the right flag is given to the
1640 * constructor of this class, by a single AMG V-cycle for the velocity
1641 * block. The three code blocks of the <code>vmult</code> function implement
1642 * the multiplications with the three blocks of this preconditioner matrix
1643 * and should be self explanatory if you have read through @ref step_31 "step-31" or the
1644 * discussion of composing solvers in @ref step_20 "step-20".
1647 * namespace LinearSolvers
1649 * template <class PreconditionerTypeA, class PreconditionerTypeMp>
1650 * class BlockSchurPreconditioner : public Subscriptor
1653 * BlockSchurPreconditioner(const TrilinosWrappers::BlockSparseMatrix &S,
1654 * const TrilinosWrappers::BlockSparseMatrix &Spre,
1655 * const PreconditionerTypeMp &Mppreconditioner,
1656 * const PreconditionerTypeA & Apreconditioner,
1657 * const bool do_solve_A)
1658 * : stokes_matrix(&S)
1659 * , stokes_preconditioner_matrix(&Spre)
1660 * , mp_preconditioner(Mppreconditioner)
1661 * , a_preconditioner(Apreconditioner)
1662 * , do_solve_A(do_solve_A)
1665 * void vmult(TrilinosWrappers::MPI::BlockVector & dst,
1666 * const TrilinosWrappers::MPI::BlockVector &src) const
1668 * TrilinosWrappers::MPI::Vector utmp(src.block(0));
1671 * SolverControl solver_control(5000, 1e-6 * src.block(1).l2_norm());
1673 * SolverCG<TrilinosWrappers::MPI::Vector> solver(solver_control);
1675 * solver.solve(stokes_preconditioner_matrix->block(1, 1),
1678 * mp_preconditioner);
1680 * dst.block(1) *= -1.0;
1684 * stokes_matrix->block(0, 1).vmult(utmp, dst.block(1));
1686 * utmp.add(src.block(0));
1689 * if (do_solve_A == true)
1691 * SolverControl solver_control(5000, utmp.l2_norm() * 1e-2);
1692 * TrilinosWrappers::SolverCG solver(solver_control);
1693 * solver.solve(stokes_matrix->block(0, 0),
1696 * a_preconditioner);
1699 * a_preconditioner.vmult(dst.block(0), utmp);
1703 * const SmartPointer<const TrilinosWrappers::BlockSparseMatrix>
1705 * const SmartPointer<const TrilinosWrappers::BlockSparseMatrix>
1706 * stokes_preconditioner_matrix;
1707 * const PreconditionerTypeMp &mp_preconditioner;
1708 * const PreconditionerTypeA & a_preconditioner;
1709 * const bool do_solve_A;
1711 * } // namespace LinearSolvers
1718 * <a name="Definitionofassemblydatastructures"></a>
1719 * <h3>Definition of assembly data structures</h3>
1723 * As described in the introduction, we will use the WorkStream mechanism
1724 * discussed in the @ref threads module to parallelize operations among the
1725 * processors of a single machine. The WorkStream class requires that data
1726 * is passed around in two kinds of data structures, one for scratch data
1727 * and one to pass data from the assembly function to the function that
1728 * copies local contributions into global objects.
1732 * The following namespace (and the two sub-namespaces) contains a
1733 * collection of data structures that serve this purpose, one pair for each
1734 * of the four operations discussed in the introduction that we will want to
1735 * parallelize. Each assembly routine gets two sets of data: a Scratch array
1736 * that collects all the classes and arrays that are used for the
1737 * calculation of the cell contribution, and a CopyData array that keeps
1738 * local matrices and vectors which will be written into the global
1739 * matrix. Whereas CopyData is a container for the final data that is
1740 * written into the global matrices and vector (and, thus, absolutely
1741 * necessary), the Scratch arrays are merely there for performance reasons
1742 * — it would be much more expensive to set up a FEValues object on
1743 * each cell, than creating it only once and updating some derivative data.
1747 * @ref step_31 "step-31" had four assembly routines: One for the preconditioner matrix of
1748 * the Stokes system, one for the Stokes matrix and right hand side, one for
1749 * the temperature matrices and one for the right hand side of the
1750 * temperature equation. We here organize the scratch arrays and CopyData
1751 * objects for each of those four assembly components using a
1752 * <code>struct</code> environment (since we consider these as temporary
1753 * objects we pass around, rather than classes that implement functionality
1754 * of their own, though this is a more subjective point of view to
1755 * distinguish between <code>struct</code>s and <code>class</code>es).
1759 * Regarding the Scratch objects, each struct is equipped with a constructor
1760 * that creates an @ref FEValues object using the @ref FiniteElement,
1761 * Quadrature, @ref Mapping (which describes the interpolation of curved
1762 * boundaries), and @ref UpdateFlags instances. Moreover, we manually
1763 * implement a copy constructor (since the FEValues class is not copyable by
1764 * itself), and provide some additional vector fields that are used to hold
1765 * intermediate data during the computation of local contributions.
1769 * Let us start with the scratch arrays and, specifically, the one used for
1770 * assembly of the Stokes preconditioner:
1773 * namespace Assembly
1777 * template <int dim>
1778 * struct StokesPreconditioner
1780 * StokesPreconditioner(const FiniteElement<dim> &stokes_fe,
1781 * const Quadrature<dim> & stokes_quadrature,
1782 * const Mapping<dim> & mapping,
1783 * const UpdateFlags update_flags);
1785 * StokesPreconditioner(const StokesPreconditioner &data);
1788 * FEValues<dim> stokes_fe_values;
1790 * std::vector<Tensor<2, dim>> grad_phi_u;
1791 * std::vector<double> phi_p;
1794 * template <int dim>
1795 * StokesPreconditioner<dim>::StokesPreconditioner(
1796 * const FiniteElement<dim> &stokes_fe,
1797 * const Quadrature<dim> & stokes_quadrature,
1798 * const Mapping<dim> & mapping,
1799 * const UpdateFlags update_flags)
1800 * : stokes_fe_values(mapping, stokes_fe, stokes_quadrature, update_flags)
1801 * , grad_phi_u(stokes_fe.n_dofs_per_cell())
1802 * , phi_p(stokes_fe.n_dofs_per_cell())
1807 * template <int dim>
1808 * StokesPreconditioner<dim>::StokesPreconditioner(
1809 * const StokesPreconditioner &scratch)
1810 * : stokes_fe_values(scratch.stokes_fe_values.get_mapping(),
1811 * scratch.stokes_fe_values.get_fe(),
1812 * scratch.stokes_fe_values.get_quadrature(),
1813 * scratch.stokes_fe_values.get_update_flags())
1814 * , grad_phi_u(scratch.grad_phi_u)
1815 * , phi_p(scratch.phi_p)
1822 * The next one is the scratch object used for the assembly of the full
1823 * Stokes system. Observe that we derive the StokesSystem scratch class
1824 * from the StokesPreconditioner class above. We do this because all the
1825 * objects that are necessary for the assembly of the preconditioner are
1826 * also needed for the actual matrix system and right hand side, plus
1827 * some extra data. This makes the program more compact. Note also that
1828 * the assembly of the Stokes system and the temperature right hand side
1829 * further down requires data from temperature and velocity,
1830 * respectively, so we actually need two FEValues objects for those two
1834 * template <int dim>
1835 * struct StokesSystem : public StokesPreconditioner<dim>
1837 * StokesSystem(const FiniteElement<dim> &stokes_fe,
1838 * const Mapping<dim> & mapping,
1839 * const Quadrature<dim> & stokes_quadrature,
1840 * const UpdateFlags stokes_update_flags,
1841 * const FiniteElement<dim> &temperature_fe,
1842 * const UpdateFlags temperature_update_flags);
1844 * StokesSystem(const StokesSystem<dim> &data);
1847 * FEValues<dim> temperature_fe_values;
1849 * std::vector<Tensor<1, dim>> phi_u;
1850 * std::vector<SymmetricTensor<2, dim>> grads_phi_u;
1851 * std::vector<double> div_phi_u;
1853 * std::vector<double> old_temperature_values;
1857 * template <int dim>
1858 * StokesSystem<dim>::StokesSystem(
1859 * const FiniteElement<dim> &stokes_fe,
1860 * const Mapping<dim> & mapping,
1861 * const Quadrature<dim> & stokes_quadrature,
1862 * const UpdateFlags stokes_update_flags,
1863 * const FiniteElement<dim> &temperature_fe,
1864 * const UpdateFlags temperature_update_flags)
1865 * : StokesPreconditioner<dim>(stokes_fe,
1866 * stokes_quadrature,
1868 * stokes_update_flags)
1869 * , temperature_fe_values(mapping,
1871 * stokes_quadrature,
1872 * temperature_update_flags)
1873 * , phi_u(stokes_fe.n_dofs_per_cell())
1874 * , grads_phi_u(stokes_fe.n_dofs_per_cell())
1875 * , div_phi_u(stokes_fe.n_dofs_per_cell())
1876 * , old_temperature_values(stokes_quadrature.size())
1880 * template <int dim>
1881 * StokesSystem<dim>::StokesSystem(const StokesSystem<dim> &scratch)
1882 * : StokesPreconditioner<dim>(scratch)
1883 * , temperature_fe_values(
1884 * scratch.temperature_fe_values.get_mapping(),
1885 * scratch.temperature_fe_values.get_fe(),
1886 * scratch.temperature_fe_values.get_quadrature(),
1887 * scratch.temperature_fe_values.get_update_flags())
1888 * , phi_u(scratch.phi_u)
1889 * , grads_phi_u(scratch.grads_phi_u)
1890 * , div_phi_u(scratch.div_phi_u)
1891 * , old_temperature_values(scratch.old_temperature_values)
1897 * After defining the objects used in the assembly of the Stokes system,
1898 * we do the same for the assembly of the matrices necessary for the
1899 * temperature system. The general structure is very similar:
1902 * template <int dim>
1903 * struct TemperatureMatrix
1905 * TemperatureMatrix(const FiniteElement<dim> &temperature_fe,
1906 * const Mapping<dim> & mapping,
1907 * const Quadrature<dim> & temperature_quadrature);
1909 * TemperatureMatrix(const TemperatureMatrix &data);
1912 * FEValues<dim> temperature_fe_values;
1914 * std::vector<double> phi_T;
1915 * std::vector<Tensor<1, dim>> grad_phi_T;
1919 * template <int dim>
1920 * TemperatureMatrix<dim>::TemperatureMatrix(
1921 * const FiniteElement<dim> &temperature_fe,
1922 * const Mapping<dim> & mapping,
1923 * const Quadrature<dim> & temperature_quadrature)
1924 * : temperature_fe_values(mapping,
1926 * temperature_quadrature,
1927 * update_values | update_gradients |
1928 * update_JxW_values)
1929 * , phi_T(temperature_fe.n_dofs_per_cell())
1930 * , grad_phi_T(temperature_fe.n_dofs_per_cell())
1934 * template <int dim>
1935 * TemperatureMatrix<dim>::TemperatureMatrix(
1936 * const TemperatureMatrix &scratch)
1937 * : temperature_fe_values(
1938 * scratch.temperature_fe_values.get_mapping(),
1939 * scratch.temperature_fe_values.get_fe(),
1940 * scratch.temperature_fe_values.get_quadrature(),
1941 * scratch.temperature_fe_values.get_update_flags())
1942 * , phi_T(scratch.phi_T)
1943 * , grad_phi_T(scratch.grad_phi_T)
1949 * The final scratch object is used in the assembly of the right hand
1950 * side of the temperature system. This object is significantly larger
1951 * than the ones above because a lot more quantities enter the
1952 * computation of the right hand side of the temperature equation. In
1953 * particular, the temperature values and gradients of the previous two
1954 * time steps need to be evaluated at the quadrature points, as well as
1955 * the velocities and the strain rates (i.e. the symmetric gradients of
1956 * the velocity) that enter the right hand side as friction heating
1957 * terms. Despite the number of terms, the following should be rather
1961 * template <int dim>
1962 * struct TemperatureRHS
1964 * TemperatureRHS(const FiniteElement<dim> &temperature_fe,
1965 * const FiniteElement<dim> &stokes_fe,
1966 * const Mapping<dim> & mapping,
1967 * const Quadrature<dim> & quadrature);
1969 * TemperatureRHS(const TemperatureRHS &data);
1972 * FEValues<dim> temperature_fe_values;
1973 * FEValues<dim> stokes_fe_values;
1975 * std::vector<double> phi_T;
1976 * std::vector<Tensor<1, dim>> grad_phi_T;
1978 * std::vector<Tensor<1, dim>> old_velocity_values;
1979 * std::vector<Tensor<1, dim>> old_old_velocity_values;
1981 * std::vector<SymmetricTensor<2, dim>> old_strain_rates;
1982 * std::vector<SymmetricTensor<2, dim>> old_old_strain_rates;
1984 * std::vector<double> old_temperature_values;
1985 * std::vector<double> old_old_temperature_values;
1986 * std::vector<Tensor<1, dim>> old_temperature_grads;
1987 * std::vector<Tensor<1, dim>> old_old_temperature_grads;
1988 * std::vector<double> old_temperature_laplacians;
1989 * std::vector<double> old_old_temperature_laplacians;
1993 * template <int dim>
1994 * TemperatureRHS<dim>::TemperatureRHS(
1995 * const FiniteElement<dim> &temperature_fe,
1996 * const FiniteElement<dim> &stokes_fe,
1997 * const Mapping<dim> & mapping,
1998 * const Quadrature<dim> & quadrature)
1999 * : temperature_fe_values(mapping,
2002 * update_values | update_gradients |
2003 * update_hessians | update_quadrature_points |
2004 * update_JxW_values)
2005 * , stokes_fe_values(mapping,
2008 * update_values | update_gradients)
2009 * , phi_T(temperature_fe.n_dofs_per_cell())
2010 * , grad_phi_T(temperature_fe.n_dofs_per_cell())
2013 * old_velocity_values(quadrature.size())
2014 * , old_old_velocity_values(quadrature.size())
2015 * , old_strain_rates(quadrature.size())
2016 * , old_old_strain_rates(quadrature.size())
2019 * old_temperature_values(quadrature.size())
2020 * , old_old_temperature_values(quadrature.size())
2021 * , old_temperature_grads(quadrature.size())
2022 * , old_old_temperature_grads(quadrature.size())
2023 * , old_temperature_laplacians(quadrature.size())
2024 * , old_old_temperature_laplacians(quadrature.size())
2028 * template <int dim>
2029 * TemperatureRHS<dim>::TemperatureRHS(const TemperatureRHS &scratch)
2030 * : temperature_fe_values(
2031 * scratch.temperature_fe_values.get_mapping(),
2032 * scratch.temperature_fe_values.get_fe(),
2033 * scratch.temperature_fe_values.get_quadrature(),
2034 * scratch.temperature_fe_values.get_update_flags())
2035 * , stokes_fe_values(scratch.stokes_fe_values.get_mapping(),
2036 * scratch.stokes_fe_values.get_fe(),
2037 * scratch.stokes_fe_values.get_quadrature(),
2038 * scratch.stokes_fe_values.get_update_flags())
2039 * , phi_T(scratch.phi_T)
2040 * , grad_phi_T(scratch.grad_phi_T)
2043 * old_velocity_values(scratch.old_velocity_values)
2044 * , old_old_velocity_values(scratch.old_old_velocity_values)
2045 * , old_strain_rates(scratch.old_strain_rates)
2046 * , old_old_strain_rates(scratch.old_old_strain_rates)
2049 * old_temperature_values(scratch.old_temperature_values)
2050 * , old_old_temperature_values(scratch.old_old_temperature_values)
2051 * , old_temperature_grads(scratch.old_temperature_grads)
2052 * , old_old_temperature_grads(scratch.old_old_temperature_grads)
2053 * , old_temperature_laplacians(scratch.old_temperature_laplacians)
2054 * , old_old_temperature_laplacians(scratch.old_old_temperature_laplacians)
2056 * } // namespace Scratch
2061 * The CopyData objects are even simpler than the Scratch objects as all
2062 * they have to do is to store the results of local computations until
2063 * they can be copied into the global matrix or vector objects. These
2064 * structures therefore only need to provide a constructor, a copy
2065 * operation, and some arrays for local matrix, local vectors and the
2066 * relation between local and global degrees of freedom (a.k.a.
2067 * <code>local_dof_indices</code>). Again, we have one such structure for
2068 * each of the four operations we will parallelize using the WorkStream
2072 * namespace CopyData
2074 * template <int dim>
2075 * struct StokesPreconditioner
2077 * StokesPreconditioner(const FiniteElement<dim> &stokes_fe);
2078 * StokesPreconditioner(const StokesPreconditioner &data);
2079 * StokesPreconditioner &operator=(const StokesPreconditioner &) = default;
2081 * FullMatrix<double> local_matrix;
2082 * std::vector<types::global_dof_index> local_dof_indices;
2085 * template <int dim>
2086 * StokesPreconditioner<dim>::StokesPreconditioner(
2087 * const FiniteElement<dim> &stokes_fe)
2088 * : local_matrix(stokes_fe.n_dofs_per_cell(), stokes_fe.n_dofs_per_cell())
2089 * , local_dof_indices(stokes_fe.n_dofs_per_cell())
2092 * template <int dim>
2093 * StokesPreconditioner<dim>::StokesPreconditioner(
2094 * const StokesPreconditioner &data)
2095 * : local_matrix(data.local_matrix)
2096 * , local_dof_indices(data.local_dof_indices)
2101 * template <int dim>
2102 * struct StokesSystem : public StokesPreconditioner<dim>
2104 * StokesSystem(const FiniteElement<dim> &stokes_fe);
2106 * Vector<double> local_rhs;
2109 * template <int dim>
2110 * StokesSystem<dim>::StokesSystem(const FiniteElement<dim> &stokes_fe)
2111 * : StokesPreconditioner<dim>(stokes_fe)
2112 * , local_rhs(stokes_fe.n_dofs_per_cell())
2117 * template <int dim>
2118 * struct TemperatureMatrix
2120 * TemperatureMatrix(const FiniteElement<dim> &temperature_fe);
2122 * FullMatrix<double> local_mass_matrix;
2123 * FullMatrix<double> local_stiffness_matrix;
2124 * std::vector<types::global_dof_index> local_dof_indices;
2127 * template <int dim>
2128 * TemperatureMatrix<dim>::TemperatureMatrix(
2129 * const FiniteElement<dim> &temperature_fe)
2130 * : local_mass_matrix(temperature_fe.n_dofs_per_cell(),
2131 * temperature_fe.n_dofs_per_cell())
2132 * , local_stiffness_matrix(temperature_fe.n_dofs_per_cell(),
2133 * temperature_fe.n_dofs_per_cell())
2134 * , local_dof_indices(temperature_fe.n_dofs_per_cell())
2139 * template <int dim>
2140 * struct TemperatureRHS
2142 * TemperatureRHS(const FiniteElement<dim> &temperature_fe);
2144 * Vector<double> local_rhs;
2145 * std::vector<types::global_dof_index> local_dof_indices;
2146 * FullMatrix<double> matrix_for_bc;
2149 * template <int dim>
2150 * TemperatureRHS<dim>::TemperatureRHS(
2151 * const FiniteElement<dim> &temperature_fe)
2152 * : local_rhs(temperature_fe.n_dofs_per_cell())
2153 * , local_dof_indices(temperature_fe.n_dofs_per_cell())
2154 * , matrix_for_bc(temperature_fe.n_dofs_per_cell(),
2155 * temperature_fe.n_dofs_per_cell())
2157 * } // namespace CopyData
2158 * } // namespace Assembly
2165 * <a name="ThecodeBoussinesqFlowProblemcodeclasstemplate"></a>
2166 * <h3>The <code>BoussinesqFlowProblem</code> class template</h3>
2170 * This is the declaration of the main class. It is very similar to @ref step_31 "step-31"
2171 * but there are a number differences we will comment on below.
2175 * The top of the class is essentially the same as in @ref step_31 "step-31", listing the
2176 * public methods and a set of private functions that do the heavy
2177 * lifting. Compared to @ref step_31 "step-31" there are only two additions to this
2178 * section: the function <code>get_cfl_number()</code> that computes the
2179 * maximum CFL number over all cells which we then compute the global time
2180 * step from, and the function <code>get_entropy_variation()</code> that is
2181 * used in the computation of the entropy stabilization. It is akin to the
2182 * <code>get_extrapolated_temperature_range()</code> we have used in @ref step_31 "step-31"
2183 * for this purpose, but works on the entropy instead of the temperature
2187 * template <int dim>
2188 * class BoussinesqFlowProblem
2191 * struct Parameters;
2192 * BoussinesqFlowProblem(Parameters ¶meters);
2196 * void setup_dofs();
2197 * void assemble_stokes_preconditioner();
2198 * void build_stokes_preconditioner();
2199 * void assemble_stokes_system();
2200 * void assemble_temperature_matrix();
2201 * void assemble_temperature_system(const double maximal_velocity);
2202 * double get_maximal_velocity() const;
2203 * double get_cfl_number() const;
2204 * double get_entropy_variation(const double average_temperature) const;
2205 * std::pair<double, double> get_extrapolated_temperature_range() const;
2207 * void output_results();
2208 * void refine_mesh(const unsigned int max_grid_level);
2210 * double compute_viscosity(
2211 * const std::vector<double> & old_temperature,
2212 * const std::vector<double> & old_old_temperature,
2213 * const std::vector<Tensor<1, dim>> &old_temperature_grads,
2214 * const std::vector<Tensor<1, dim>> &old_old_temperature_grads,
2215 * const std::vector<double> & old_temperature_laplacians,
2216 * const std::vector<double> & old_old_temperature_laplacians,
2217 * const std::vector<Tensor<1, dim>> &old_velocity_values,
2218 * const std::vector<Tensor<1, dim>> &old_old_velocity_values,
2219 * const std::vector<SymmetricTensor<2, dim>> &old_strain_rates,
2220 * const std::vector<SymmetricTensor<2, dim>> &old_old_strain_rates,
2221 * const double global_u_infty,
2222 * const double global_T_variation,
2223 * const double average_temperature,
2224 * const double global_entropy_variation,
2225 * const double cell_diameter) const;
2230 * The first significant new component is the definition of a struct for
2231 * the parameters according to the discussion in the introduction. This
2232 * structure is initialized by reading from a parameter file during
2233 * construction of this object.
2238 * Parameters(const std::string ¶meter_filename);
2240 * static void declare_parameters(ParameterHandler &prm);
2241 * void parse_parameters(ParameterHandler &prm);
2245 * unsigned int initial_global_refinement;
2246 * unsigned int initial_adaptive_refinement;
2248 * bool generate_graphical_output;
2249 * unsigned int graphical_output_interval;
2251 * unsigned int adaptive_refinement_interval;
2253 * double stabilization_alpha;
2254 * double stabilization_c_R;
2255 * double stabilization_beta;
2257 * unsigned int stokes_velocity_degree;
2258 * bool use_locally_conservative_discretization;
2260 * unsigned int temperature_degree;
2264 * Parameters ¶meters;
2268 * The <code>pcout</code> (for <i>%parallel <code>std::cout</code></i>)
2269 * object is used to simplify writing output: each MPI process can use
2270 * this to generate output as usual, but since each of these processes
2271 * will (hopefully) produce the same output it will just be replicated
2272 * many times over; with the ConditionalOStream class, only the output
2273 * generated by one MPI process will actually be printed to screen,
2274 * whereas the output by all the other threads will simply be forgotten.
2277 * ConditionalOStream pcout;
2281 * The following member variables will then again be similar to those in
2282 * @ref step_31 "step-31" (and to other tutorial programs). As mentioned in the
2283 * introduction, we fully distribute computations, so we will have to use
2284 * the parallel::distributed::Triangulation class (see @ref step_40 "step-40") but the
2285 * remainder of these variables is rather standard with two exceptions:
2289 * - The <code>mapping</code> variable is used to denote a higher-order
2290 * polynomial mapping. As mentioned in the introduction, we use this
2291 * mapping when forming integrals through quadrature for all cells.
2295 * - In a bit of naming confusion, you will notice below that some of the
2296 * variables from namespace TrilinosWrappers are taken from namespace
2297 * TrilinosWrappers::MPI (such as the right hand side vectors) whereas
2298 * others are not (such as the various matrices). This is due to legacy
2299 * reasons. We will frequently have to query velocities
2300 * and temperatures at arbitrary quadrature points; consequently, rather
2301 * than importing ghost information of a vector whenever we need access
2302 * to degrees of freedom that are relevant locally but owned by another
2303 * processor, we solve linear systems in %parallel but then immediately
2304 * initialize a vector including ghost entries of the solution for further
2305 * processing. The various <code>*_solution</code> vectors are therefore
2306 * filled immediately after solving their respective linear system in
2307 * %parallel and will always contain values for all
2308 * @ref GlossLocallyRelevantDof "locally relevant degrees of freedom";
2309 * the fully distributed vectors that we obtain from the solution process
2310 * and that only ever contain the
2311 * @ref GlossLocallyOwnedDof "locally owned degrees of freedom" are
2312 * destroyed immediately after the solution process and after we have
2313 * copied the relevant values into the member variable vectors.
2316 * parallel::distributed::Triangulation<dim> triangulation;
2317 * double global_Omega_diameter;
2319 * const MappingQ<dim> mapping;
2321 * const FESystem<dim> stokes_fe;
2322 * DoFHandler<dim> stokes_dof_handler;
2323 * AffineConstraints<double> stokes_constraints;
2325 * TrilinosWrappers::BlockSparseMatrix stokes_matrix;
2326 * TrilinosWrappers::BlockSparseMatrix stokes_preconditioner_matrix;
2328 * TrilinosWrappers::MPI::BlockVector stokes_solution;
2329 * TrilinosWrappers::MPI::BlockVector old_stokes_solution;
2330 * TrilinosWrappers::MPI::BlockVector stokes_rhs;
2333 * FE_Q<dim> temperature_fe;
2334 * DoFHandler<dim> temperature_dof_handler;
2335 * AffineConstraints<double> temperature_constraints;
2337 * TrilinosWrappers::SparseMatrix temperature_mass_matrix;
2338 * TrilinosWrappers::SparseMatrix temperature_stiffness_matrix;
2339 * TrilinosWrappers::SparseMatrix temperature_matrix;
2341 * TrilinosWrappers::MPI::Vector temperature_solution;
2342 * TrilinosWrappers::MPI::Vector old_temperature_solution;
2343 * TrilinosWrappers::MPI::Vector old_old_temperature_solution;
2344 * TrilinosWrappers::MPI::Vector temperature_rhs;
2348 * double old_time_step;
2349 * unsigned int timestep_number;
2351 * std::shared_ptr<TrilinosWrappers::PreconditionAMG> Amg_preconditioner;
2352 * std::shared_ptr<TrilinosWrappers::PreconditionJacobi> Mp_preconditioner;
2353 * std::shared_ptr<TrilinosWrappers::PreconditionJacobi> T_preconditioner;
2355 * bool rebuild_stokes_matrix;
2356 * bool rebuild_stokes_preconditioner;
2357 * bool rebuild_temperature_matrices;
2358 * bool rebuild_temperature_preconditioner;
2362 * The next member variable, <code>computing_timer</code> is used to
2363 * conveniently account for compute time spent in certain "sections" of
2364 * the code that are repeatedly entered. For example, we will enter (and
2365 * leave) sections for Stokes matrix assembly and would like to accumulate
2366 * the run time spent in this section over all time steps. Every so many
2367 * time steps as well as at the end of the program (through the destructor
2368 * of the TimerOutput class) we will then produce a nice summary of the
2369 * times spent in the different sections into which we categorize the
2370 * run-time of this program.
2373 * TimerOutput computing_timer;
2377 * After these member variables we have a number of auxiliary functions
2378 * that have been broken out of the ones listed above. Specifically, there
2379 * are first three functions that we call from <code>setup_dofs</code> and
2380 * then the ones that do the assembling of linear systems:
2383 * void setup_stokes_matrix(
2384 * const std::vector<IndexSet> &stokes_partitioning,
2385 * const std::vector<IndexSet> &stokes_relevant_partitioning);
2386 * void setup_stokes_preconditioner(
2387 * const std::vector<IndexSet> &stokes_partitioning,
2388 * const std::vector<IndexSet> &stokes_relevant_partitioning);
2389 * void setup_temperature_matrices(
2390 * const IndexSet &temperature_partitioning,
2391 * const IndexSet &temperature_relevant_partitioning);
2396 * Following the @ref MTWorkStream "task-based parallelization" paradigm,
2397 * we split all the assembly routines into two parts: a first part that
2398 * can do all the calculations on a certain cell without taking care of
2399 * other threads, and a second part (which is writing the local data into
2400 * the global matrices and vectors) which can be entered by only one
2401 * thread at a time. In order to implement that, we provide functions for
2402 * each of those two steps for all the four assembly routines that we use
2403 * in this program. The following eight functions do exactly this:
2406 * void local_assemble_stokes_preconditioner(
2407 * const typename DoFHandler<dim>::active_cell_iterator &cell,
2408 * Assembly::Scratch::StokesPreconditioner<dim> & scratch,
2409 * Assembly::CopyData::StokesPreconditioner<dim> & data);
2411 * void copy_local_to_global_stokes_preconditioner(
2412 * const Assembly::CopyData::StokesPreconditioner<dim> &data);
2415 * void local_assemble_stokes_system(
2416 * const typename DoFHandler<dim>::active_cell_iterator &cell,
2417 * Assembly::Scratch::StokesSystem<dim> & scratch,
2418 * Assembly::CopyData::StokesSystem<dim> & data);
2420 * void copy_local_to_global_stokes_system(
2421 * const Assembly::CopyData::StokesSystem<dim> &data);
2424 * void local_assemble_temperature_matrix(
2425 * const typename DoFHandler<dim>::active_cell_iterator &cell,
2426 * Assembly::Scratch::TemperatureMatrix<dim> & scratch,
2427 * Assembly::CopyData::TemperatureMatrix<dim> & data);
2429 * void copy_local_to_global_temperature_matrix(
2430 * const Assembly::CopyData::TemperatureMatrix<dim> &data);
2434 * void local_assemble_temperature_rhs(
2435 * const std::pair<double, double> global_T_range,
2436 * const double global_max_velocity,
2437 * const double global_entropy_variation,
2438 * const typename DoFHandler<dim>::active_cell_iterator &cell,
2439 * Assembly::Scratch::TemperatureRHS<dim> & scratch,
2440 * Assembly::CopyData::TemperatureRHS<dim> & data);
2442 * void copy_local_to_global_temperature_rhs(
2443 * const Assembly::CopyData::TemperatureRHS<dim> &data);
2447 * Finally, we forward declare a member class that we will define later on
2448 * and that will be used to compute a number of quantities from our
2460 * <a name=
"BoussinesqFlowProblemclassimplementation"></a>
2466 * <a name=
"BoussinesqFlowProblemParameters"></a>
2492 *
template <
int dim>
2522 *
"> not found. Creating a template file of the same name."));
2526 *
parse_parameters(prm);
2538 *
template <
int dim>
2542 *
prm.declare_entry(
"End time",
2545 *
"The end time of the simulation in years.");
2546 *
prm.declare_entry(
"Initial global refinement",
2549 *
"The number of global refinement steps performed on "
2550 *
"the initial coarse mesh, before the problem is first "
2552 *
prm.declare_entry(
"Initial adaptive refinement",
2555 *
"The number of adaptive refinement steps performed after "
2556 *
"initial global refinement.");
2557 *
prm.declare_entry(
"Time steps between mesh refinement",
2560 *
"The number of time steps after which the mesh is to be "
2561 *
"adapted based on computed error indicators.");
2562 *
prm.declare_entry(
"Generate graphical output",
2565 *
"Whether graphical output is to be generated or not. "
2566 *
"You may not want to get graphical output if the number "
2567 *
"of processors is large.");
2568 *
prm.declare_entry(
"Time steps between graphical output",
2571 *
"The number of time steps between each generation of "
2572 *
"graphical output files.");
2574 *
prm.enter_subsection(
"Stabilization parameters");
2576 *
prm.declare_entry(
"alpha",
2579 *
"The exponent in the entropy viscosity stabilization.");
2580 *
prm.declare_entry(
"c_R",
2583 *
"The c_R factor in the entropy viscosity "
2584 *
"stabilization.");
2585 *
prm.declare_entry(
"beta",
2588 *
"The beta factor in the artificial viscosity "
2589 *
"stabilization. An appropriate value for 2d is 0.052 "
2590 *
"and 0.078 for 3d.");
2592 *
prm.leave_subsection();
2594 *
prm.enter_subsection(
"Discretization");
2596 *
prm.declare_entry(
2597 *
"Stokes velocity polynomial degree",
2600 *
"The polynomial degree to use for the velocity variables "
2601 *
"in the Stokes system.");
2602 *
prm.declare_entry(
2603 *
"Temperature polynomial degree",
2606 *
"The polynomial degree to use for the temperature variable.");
2607 *
prm.declare_entry(
2608 *
"Use locally conservative discretization",
2611 *
"Whether to use a Stokes discretization that is locally "
2612 *
"conservative at the expense of a larger number of degrees "
2613 *
"of freedom, or to go with a cheaper discretization "
2614 *
"that does not locally conserve mass (although it is "
2615 *
"globally conservative.");
2617 *
prm.leave_subsection();
2630 *
template <
int dim>
2634 *
end_time = prm.get_double(
"End time");
2637 *
prm.get_integer(
"Initial adaptive refinement");
2640 *
prm.get_integer(
"Time steps between mesh refinement");
2644 *
prm.get_integer(
"Time steps between graphical output");
2646 *
prm.enter_subsection(
"Stabilization parameters");
2652 *
prm.leave_subsection();
2654 *
prm.enter_subsection(
"Discretization");
2657 *
prm.get_integer(
"Stokes velocity polynomial degree");
2660 *
prm.get_bool(
"Use locally conservative discretization");
2662 *
prm.leave_subsection();
2670 * <a name=
"BoussinesqFlowProblemBoussinesqFlowProblem"></a>
2702 *
template <
int dim>
2722 *
(parameters.use_locally_conservative_discretization ?
2724 *
FE_DGP<dim>(parameters.stokes_velocity_degree - 1)) :
2757 * <a name=
"TheBoussinesqFlowProblemhelperfunctions"></a>
2760 * <a name=
"BoussinesqFlowProblemget_maximal_velocity"></a>
2778 * <
code>cell-@>is_locally_owned()</
code>.
2790 *
call,
but it's even simpler to use the respective function in namespace
2791 * Utilities::MPI using the MPI communicator object since that will do the
2792 * right thing even if we work without MPI and on a single machine only. The
2793 * call to <code>Utilities::MPI::max</code> needs two arguments, namely the
2794 * local maximum (input) and the MPI communicator, which is MPI_COMM_WORLD
2798 * template <int dim>
2799 * double BoussinesqFlowProblem<dim>::get_maximal_velocity() const
2801 * const QIterated<dim> quadrature_formula(QTrapezoid<1>(),
2802 * parameters.stokes_velocity_degree);
2803 * const unsigned int n_q_points = quadrature_formula.size();
2805 * FEValues<dim> fe_values(mapping,
2807 * quadrature_formula,
2809 * std::vector<Tensor<1, dim>> velocity_values(n_q_points);
2811 * const FEValuesExtractors::Vector velocities(0);
2813 * double max_local_velocity = 0;
2815 * for (const auto &cell : stokes_dof_handler.active_cell_iterators())
2816 * if (cell->is_locally_owned())
2818 * fe_values.reinit(cell);
2819 * fe_values[velocities].get_function_values(stokes_solution,
2822 * for (unsigned int q = 0; q < n_q_points; ++q)
2823 * max_local_velocity =
2824 * std::max(max_local_velocity, velocity_values[q].norm());
2827 * return Utilities::MPI::max(max_local_velocity, MPI_COMM_WORLD);
2834 * <a name="BoussinesqFlowProblemget_cfl_number"></a>
2835 * <h5>BoussinesqFlowProblem::get_cfl_number</h5>
2839 * The next function does something similar, but we now compute the CFL
2840 * number, i.e., maximal velocity on a cell divided by the cell
2841 * diameter. This number is necessary to determine the time step size, as we
2842 * use a semi-explicit time stepping scheme for the temperature equation
2843 * (see @ref step_31 "step-31" for a discussion). We compute it in the same way as above:
2844 * Compute the local maximum over all locally owned cells, then exchange it
2845 * via MPI to find the global maximum.
2848 * template <int dim>
2849 * double BoussinesqFlowProblem<dim>::get_cfl_number() const
2851 * const QIterated<dim> quadrature_formula(QTrapezoid<1>(),
2852 * parameters.stokes_velocity_degree);
2853 * const unsigned int n_q_points = quadrature_formula.size();
2855 * FEValues<dim> fe_values(mapping,
2857 * quadrature_formula,
2859 * std::vector<Tensor<1, dim>> velocity_values(n_q_points);
2861 * const FEValuesExtractors::Vector velocities(0);
2863 * double max_local_cfl = 0;
2865 * for (const auto &cell : stokes_dof_handler.active_cell_iterators())
2866 * if (cell->is_locally_owned())
2868 * fe_values.reinit(cell);
2869 * fe_values[velocities].get_function_values(stokes_solution,
2872 * double max_local_velocity = 1e-10;
2873 * for (unsigned int q = 0; q < n_q_points; ++q)
2874 * max_local_velocity =
2875 * std::max(max_local_velocity, velocity_values[q].norm());
2877 * std::max(max_local_cfl, max_local_velocity / cell->diameter());
2880 * return Utilities::MPI::max(max_local_cfl, MPI_COMM_WORLD);
2887 * <a name="BoussinesqFlowProblemget_entropy_variation"></a>
2888 * <h5>BoussinesqFlowProblem::get_entropy_variation</h5>
2892 * Next comes the computation of the global entropy variation
2893 * @f$\|E(T)-\bar{E}(T)\|_\infty@f$ where the entropy @f$E@f$ is defined as
2894 * discussed in the introduction. This is needed for the evaluation of the
2895 * stabilization in the temperature equation as explained in the
2896 * introduction. The entropy variation is actually only needed if we use
2897 * @f$\alpha=2@f$ as a power in the residual computation. The infinity norm is
2898 * computed by the maxima over quadrature points, as usual in discrete
2903 * In order to compute this quantity, we first have to find the
2904 * space-average @f$\bar{E}(T)@f$ and then evaluate the maximum. However, that
2905 * means that we would need to perform two loops. We can avoid the overhead
2906 * by noting that @f$\|E(T)-\bar{E}(T)\|_\infty =
2907 * \max\big(E_{\textrm{max}}(T)-\bar{E}(T),
2908 * \bar{E}(T)-E_{\textrm{min}}(T)\big)@f$, i.e., the maximum out of the
2909 * deviation from the average entropy in positive and negative
2910 * directions. The four quantities we need for the latter formula (maximum
2911 * entropy, minimum entropy, average entropy, area) can all be evaluated in
2912 * the same loop over all cells, so we choose this simpler variant.
2915 * template <int dim>
2916 * double BoussinesqFlowProblem<dim>::get_entropy_variation(
2917 * const double average_temperature) const
2919 * if (parameters.stabilization_alpha != 2)
2922 * const QGauss<dim> quadrature_formula(parameters.temperature_degree + 1);
2923 * const unsigned int n_q_points = quadrature_formula.size();
2925 * FEValues<dim> fe_values(temperature_fe,
2926 * quadrature_formula,
2927 * update_values | update_JxW_values);
2928 * std::vector<double> old_temperature_values(n_q_points);
2929 * std::vector<double> old_old_temperature_values(n_q_points);
2933 * In the two functions above we computed the maximum of numbers that were
2934 * all non-negative, so we knew that zero was certainly a lower bound. On
2935 * the other hand, here we need to find the maximum deviation from the
2936 * average value, i.e., we will need to know the maximal and minimal
2957 *
if (cell->is_locally_owned())
2959 *
fe_values.
reinit(cell);
2964 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
2973 *
area += fe_values.JxW(
q);
3017 * <a name=
"BoussinesqFlowProblemget_extrapolated_temperature_range"></a>
3036 *
template <
int dim>
3037 *
std::pair<double, double>
3041 *
parameters.temperature_degree);
3057 *
if (cell->is_locally_owned())
3059 *
fe_values.
reinit(cell);
3065 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
3082 *
if (cell->is_locally_owned())
3084 *
fe_values.
reinit(cell);
3088 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
3111 * <a name=
"BoussinesqFlowProblemcompute_viscosity"></a>
3121 *
template <
int dim>
3137 *
const double cell_diameter)
const
3140 *
return 5
e-3 * cell_diameter;
3147 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
3156 *
const double dT_dt =
3165 *
const double gamma =
3171 *
if (parameters.stabilization_alpha == 2)
3179 *
(parameters.stabilization_beta *
max_velocity * cell_diameter);
3187 *
if (parameters.stabilization_alpha == 2)
3189 *
(parameters.stabilization_c_R * cell_diameter * cell_diameter *
3193 *
(parameters.stabilization_c_R * cell_diameter *
3206 * <a name=
"TheBoussinesqFlowProblemsetupfunctions"></a>
3263 * structures for accumulating off-processor data on the fly are not thread
3264 * safe. With the initialization presented here, there is no such problem
3265 * and one could safely introduce graph coloring for this algorithm.
3269 * The only other change we need to make is to tell the
3270 * DoFTools::make_sparsity_pattern() function that it is only supposed to
3271 * work on a subset of cells, namely the ones whose
3272 * <code>subdomain_id</code> equals the number of the current processor, and
3273 * to ignore all other cells.
3277 * This strategy is replicated across all three of the following functions.
3281 * Note that Trilinos matrices store the information contained in the
3282 * sparsity patterns, so we can safely release the <code>sp</code> variable
3283 * once the matrix has been given the sparsity structure.
3286 * template <int dim>
3287 * void BoussinesqFlowProblem<dim>::setup_stokes_matrix(
3288 * const std::vector<IndexSet> &stokes_partitioning,
3289 * const std::vector<IndexSet> &stokes_relevant_partitioning)
3291 * stokes_matrix.clear();
3293 * TrilinosWrappers::BlockSparsityPattern sp(stokes_partitioning,
3294 * stokes_partitioning,
3295 * stokes_relevant_partitioning,
3298 * Table<2, DoFTools::Coupling> coupling(dim + 1, dim + 1);
3299 * for (unsigned int c = 0; c < dim + 1; ++c)
3300 * for (unsigned int d = 0; d < dim + 1; ++d)
3301 * if (!((c == dim) && (d == dim)))
3302 * coupling[c][d] = DoFTools::always;
3304 * coupling[c][d] = DoFTools::none;
3306 * DoFTools::make_sparsity_pattern(stokes_dof_handler,
3309 * stokes_constraints,
3311 * Utilities::MPI::this_mpi_process(
3315 * stokes_matrix.reinit(sp);
3320 * template <int dim>
3321 * void BoussinesqFlowProblem<dim>::setup_stokes_preconditioner(
3322 * const std::vector<IndexSet> &stokes_partitioning,
3323 * const std::vector<IndexSet> &stokes_relevant_partitioning)
3325 * Amg_preconditioner.reset();
3326 * Mp_preconditioner.reset();
3328 * stokes_preconditioner_matrix.clear();
3330 * TrilinosWrappers::BlockSparsityPattern sp(stokes_partitioning,
3331 * stokes_partitioning,
3332 * stokes_relevant_partitioning,
3335 * Table<2, DoFTools::Coupling> coupling(dim + 1, dim + 1);
3336 * for (unsigned int c = 0; c < dim + 1; ++c)
3337 * for (unsigned int d = 0; d < dim + 1; ++d)
3339 * coupling[c][d] = DoFTools::always;
3341 * coupling[c][d] = DoFTools::none;
3343 * DoFTools::make_sparsity_pattern(stokes_dof_handler,
3346 * stokes_constraints,
3348 * Utilities::MPI::this_mpi_process(
3352 * stokes_preconditioner_matrix.reinit(sp);
3356 * template <int dim>
3357 * void BoussinesqFlowProblem<dim>::setup_temperature_matrices(
3358 * const IndexSet &temperature_partitioner,
3359 * const IndexSet &temperature_relevant_partitioner)
3361 * T_preconditioner.reset();
3362 * temperature_mass_matrix.clear();
3363 * temperature_stiffness_matrix.clear();
3364 * temperature_matrix.clear();
3366 * TrilinosWrappers::SparsityPattern sp(temperature_partitioner,
3367 * temperature_partitioner,
3368 * temperature_relevant_partitioner,
3370 * DoFTools::make_sparsity_pattern(temperature_dof_handler,
3372 * temperature_constraints,
3374 * Utilities::MPI::this_mpi_process(
3378 * temperature_matrix.reinit(sp);
3379 * temperature_mass_matrix.reinit(sp);
3380 * temperature_stiffness_matrix.reinit(sp);
3387 * The remainder of the setup function (after splitting out the three
3388 * functions above) mostly has to deal with the things we need to do for
3389 * parallelization across processors. Because setting all of this up is a
3390 * significant compute time expense of the program, we put everything we do
3391 * here into a timer group so that we can get summary information about the
3392 * fraction of time spent in this part of the program at its end.
3396 * At the top as usual we enumerate degrees of freedom and sort them by
3397 * component/block, followed by writing their numbers to the screen from
3398 * processor zero. The DoFHandler::distributed_dofs() function, when applied
3399 * to a parallel::distributed::Triangulation object, sorts degrees of
3400 * freedom in such a way that all degrees of freedom associated with
3401 * subdomain zero come before all those associated with subdomain one,
3402 * etc. For the Stokes part, this entails, however, that velocities and
3403 * pressures become intermixed, but this is trivially solved by sorting
3404 * again by blocks; it is worth noting that this latter operation leaves the
3405 * relative ordering of all velocities and pressures alone, i.e. within the
3406 * velocity block we will still have all those associated with subdomain
3407 * zero before all velocities associated with subdomain one, etc. This is
3408 * important since we store each of the blocks of this matrix distributed
3409 * across all processors and want this to be done in such a way that each
3410 * processor stores that part of the matrix that is roughly equal to the
3411 * degrees of freedom located on those cells that it will actually work on.
3415 * When printing the numbers of degrees of freedom, note that these numbers
3416 * are going to be large if we use many processors. Consequently, we let the
3417 * stream put a comma separator in between every three digits. The state of
3418 * the stream, using the locale, is saved from before to after this
3419 * operation. While slightly opaque, the code works because the default
3420 * locale (which we get using the constructor call
3421 * <code>std::locale("")</code>) implies printing numbers with a comma
3422 * separator for every third digit (i.e., thousands, millions, billions).
3426 * In this function as well as many below, we measure how much time
3427 * we spend here and collect that in a section called "Setup dof
3428 * systems" across function invocations. This is done using an
3429 * TimerOutput::Scope object that gets a timer going in the section
3430 * with above name of the `computing_timer` object upon construction
3431 * of the local variable; the timer is stopped again when the
3432 * destructor of the `timing_section` variable is called. This, of
3433 * course, happens either at the end of the function, or if we leave
3434 * the function through a `return` statement or when an exception is
3435 * thrown somewhere -- in other words, whenever we leave this
3436 * function in any way. The use of such "scope" objects therefore
3437 * makes sure that we do not have to manually add code that tells
3438 * the timer to stop at every location where this function may be
3442 * template <int dim>
3443 * void BoussinesqFlowProblem<dim>::setup_dofs()
3445 * TimerOutput::Scope timing_section(computing_timer, "Setup dof systems");
3447 * stokes_dof_handler.distribute_dofs(stokes_fe);
3449 * std::vector<unsigned int> stokes_sub_blocks(dim + 1, 0);
3450 * stokes_sub_blocks[dim] = 1;
3451 * DoFRenumbering::component_wise(stokes_dof_handler, stokes_sub_blocks);
3453 * temperature_dof_handler.distribute_dofs(temperature_fe);
3455 * const std::vector<types::global_dof_index> stokes_dofs_per_block =
3456 * DoFTools::count_dofs_per_fe_block(stokes_dof_handler, stokes_sub_blocks);
3458 * const types::global_dof_index n_u = stokes_dofs_per_block[0],
3459 * n_p = stokes_dofs_per_block[1],
3460 * n_T = temperature_dof_handler.n_dofs();
3462 * std::locale s = pcout.get_stream().getloc();
3463 * pcout.get_stream().imbue(std::locale(""));
3464 * pcout << "Number of active cells: " << triangulation.n_global_active_cells()
3465 * << " (on " << triangulation.n_levels() << " levels)" << std::endl
3466 * << "Number of degrees of freedom: " << n_u + n_p + n_T << " (" << n_u
3467 * << '+
' << n_p << '+
' << n_T << ')
' << std::endl
3469 * pcout.get_stream().imbue(s);
3474 * After this, we have to set up the various partitioners (of type
3475 * <code>IndexSet</code>, see the introduction) that describe which parts
3476 * of each matrix or vector will be stored where, then call the functions
3477 * that actually set up the matrices, and at the end also resize the
3478 * various vectors we keep around in this program.
3481 * std::vector<IndexSet> stokes_partitioning, stokes_relevant_partitioning;
3482 * IndexSet temperature_partitioning(n_T),
3483 * temperature_relevant_partitioning(n_T);
3484 * IndexSet stokes_relevant_set;
3486 * IndexSet stokes_index_set = stokes_dof_handler.locally_owned_dofs();
3487 * stokes_partitioning.push_back(stokes_index_set.get_view(0, n_u));
3488 * stokes_partitioning.push_back(stokes_index_set.get_view(n_u, n_u + n_p));
3490 * stokes_relevant_set =
3491 * DoFTools::extract_locally_relevant_dofs(stokes_dof_handler);
3492 * stokes_relevant_partitioning.push_back(
3493 * stokes_relevant_set.get_view(0, n_u));
3494 * stokes_relevant_partitioning.push_back(
3495 * stokes_relevant_set.get_view(n_u, n_u + n_p));
3497 * temperature_partitioning = temperature_dof_handler.locally_owned_dofs();
3498 * temperature_relevant_partitioning =
3499 * DoFTools::extract_locally_relevant_dofs(temperature_dof_handler);
3504 * Following this, we can compute constraints for the solution vectors,
3505 * including hanging node constraints and homogeneous and inhomogeneous
3506 * boundary values for the Stokes and temperature fields. Note that as for
3507 * everything else, the constraint objects can not hold <i>all</i>
3508 * constraints on every processor. Rather, each processor needs to store
3509 * only those that are actually necessary for correctness given that it
3510 * only assembles linear systems on cells it owns. As discussed in the
3511 * @ref distributed_paper "this paper", the set of constraints we need to
3512 * know about is exactly the set of constraints on all locally relevant
3513 * degrees of freedom, so this is what we use to initialize the constraint
3518 * stokes_constraints.clear();
3519 * stokes_constraints.reinit(stokes_relevant_set);
3521 * DoFTools::make_hanging_node_constraints(stokes_dof_handler,
3522 * stokes_constraints);
3524 * const FEValuesExtractors::Vector velocity_components(0);
3525 * VectorTools::interpolate_boundary_values(
3526 * stokes_dof_handler,
3528 * Functions::ZeroFunction<dim>(dim + 1),
3529 * stokes_constraints,
3530 * stokes_fe.component_mask(velocity_components));
3532 * std::set<types::boundary_id> no_normal_flux_boundaries;
3533 * no_normal_flux_boundaries.insert(1);
3534 * VectorTools::compute_no_normal_flux_constraints(stokes_dof_handler,
3536 * no_normal_flux_boundaries,
3537 * stokes_constraints,
3539 * stokes_constraints.close();
3542 * temperature_constraints.clear();
3543 * temperature_constraints.reinit(temperature_relevant_partitioning);
3545 * DoFTools::make_hanging_node_constraints(temperature_dof_handler,
3546 * temperature_constraints);
3547 * VectorTools::interpolate_boundary_values(
3548 * temperature_dof_handler,
3550 * EquationData::TemperatureInitialValues<dim>(),
3551 * temperature_constraints);
3552 * VectorTools::interpolate_boundary_values(
3553 * temperature_dof_handler,
3555 * EquationData::TemperatureInitialValues<dim>(),
3556 * temperature_constraints);
3557 * temperature_constraints.close();
3562 * All this done, we can then initialize the various matrix and vector
3563 * objects to their proper sizes. At the end, we also record that all
3564 * matrices and preconditioners have to be re-computed at the beginning of
3565 * the next time step. Note how we initialize the vectors for the Stokes
3566 * and temperature right hand sides: These are writable vectors (last
3567 * boolean argument set to @p true) that have the correct one-to-one
3568 * partitioning of locally owned elements but are still given the relevant
3569 * partitioning for means of figuring out the vector entries that are
3570 * going to be set right away. As for matrices, this allows for writing
3571 * local contributions into the vector with multiple threads (always
3572 * assuming that the same vector entry is not accessed by multiple threads
3573 * at the same time). The other vectors only allow for read access of
3574 * individual elements, including ghosts, but are not suitable for
3578 * setup_stokes_matrix(stokes_partitioning, stokes_relevant_partitioning);
3579 * setup_stokes_preconditioner(stokes_partitioning,
3580 * stokes_relevant_partitioning);
3581 * setup_temperature_matrices(temperature_partitioning,
3582 * temperature_relevant_partitioning);
3584 * stokes_rhs.reinit(stokes_partitioning,
3585 * stokes_relevant_partitioning,
3588 * stokes_solution.reinit(stokes_relevant_partitioning, MPI_COMM_WORLD);
3589 * old_stokes_solution.reinit(stokes_solution);
3591 * temperature_rhs.reinit(temperature_partitioning,
3592 * temperature_relevant_partitioning,
3595 * temperature_solution.reinit(temperature_relevant_partitioning,
3597 * old_temperature_solution.reinit(temperature_solution);
3598 * old_old_temperature_solution.reinit(temperature_solution);
3600 * rebuild_stokes_matrix = true;
3601 * rebuild_stokes_preconditioner = true;
3602 * rebuild_temperature_matrices = true;
3603 * rebuild_temperature_preconditioner = true;
3611 * <a name="TheBoussinesqFlowProblemassemblyfunctions"></a>
3612 * <h4>The BoussinesqFlowProblem assembly functions</h4>
3616 * Following the discussion in the introduction and in the @ref threads
3617 * module, we split the assembly functions into different parts:
3621 * <ul> <li> The local calculations of matrices and right hand sides, given
3622 * a certain cell as input (these functions are named
3623 * <code>local_assemble_*</code> below). The resulting function is, in other
3624 * words, essentially the body of the loop over all cells in @ref step_31 "step-31". Note,
3625 * however, that these functions store the result from the local
3626 * calculations in variables of classes from the CopyData namespace.
3630 * <li>These objects are then given to the second step which writes the
3631 * local data into the global data structures (these functions are named
3632 * <code>copy_local_to_global_*</code> below). These functions are pretty
3637 * <li>These two subfunctions are then used in the respective assembly
3638 * routine (called <code>assemble_*</code> below), where a WorkStream object
3639 * is set up and runs over all the cells that belong to the processor's
3645 * <a name=
"Stokespreconditionerassembly"></a>
3667 *
const unsigned int n_q_points =
3668 *
scratch.stokes_fe_values.n_quadrature_points;
3673 *
scratch.stokes_fe_values.
reinit(cell);
3674 *
cell->get_dof_indices(
data.local_dof_indices);
3676 *
data.local_matrix = 0;
3678 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
3680 *
for (
unsigned int k = 0;
k < dofs_per_cell; ++
k)
3682 *
scratch.grad_phi_u[
k] =
3684 *
scratch.phi_p[
k] = scratch.stokes_fe_values[
pressure].value(
k,
q);
3687 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
3688 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
3689 *
data.local_matrix(i,
j) +=
3691 *
scalar_product(scratch.grad_phi_u[i], scratch.grad_phi_u[
j]) +
3694 *
(scratch.phi_p[i] * scratch.phi_p[
j])) *
3695 *
scratch.stokes_fe_values.JxW(
q);
3701 *
template <
int dim>
3703 *
const Assembly::CopyData::StokesPreconditioner<dim> &
data)
3706 *
data.local_dof_indices,
3774 *
using CellFilter =
3779 *
Assembly::Scratch::StokesPreconditioner<dim> & scratch,
3780 *
Assembly::CopyData::StokesPreconditioner<dim> & data) {
3785 *
[
this](
const Assembly::CopyData::StokesPreconditioner<dim> &data) {
3795 *
Assembly::Scratch::StokesPreconditioner<dim>(
3800 *
Assembly::CopyData::StokesPreconditioner<dim>(
stokes_fe));
3816 *
template <
int dim>
3823 *
" Build Stokes preconditioner");
3824 *
pcout <<
" Rebuilding Stokes preconditioner..." << std::flush;
3828 *
std::vector<std::vector<bool>> constant_modes;
3836 *
std::make_shared<TrilinosWrappers::PreconditionJacobi>();
3840 *
Amg_data.constant_modes = constant_modes;
3842 *
Amg_data.higher_order_elements =
true;
3844 *
Amg_data.aggregation_threshold = 0.02;
3852 *
pcout << std::endl;
3859 * <a name=
"Stokessystemassembly"></a>
3876 *
template <
int dim>
3879 *
Assembly::Scratch::StokesSystem<dim> & scratch,
3880 *
Assembly::CopyData::StokesSystem<dim> &
data)
3882 *
const unsigned int dofs_per_cell =
3883 *
scratch.stokes_fe_values.get_fe().n_dofs_per_cell();
3884 *
const unsigned int n_q_points =
3885 *
scratch.stokes_fe_values.n_quadrature_points;
3890 *
scratch.stokes_fe_values.
reinit(cell);
3897 *
data.local_matrix = 0;
3898 *
data.local_rhs = 0;
3900 *
scratch.temperature_fe_values.get_function_values(
3903 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
3907 *
for (
unsigned int k = 0;
k < dofs_per_cell; ++
k)
3909 *
scratch.phi_u[
k] = scratch.stokes_fe_values[
velocities].value(
k,
q);
3912 *
scratch.grads_phi_u[
k] =
3913 *
scratch.stokes_fe_values[
velocities].symmetric_gradient(
k,
q);
3914 *
scratch.div_phi_u[
k] =
3915 *
scratch.stokes_fe_values[
velocities].divergence(
k,
q);
3916 *
scratch.phi_p[
k] =
3917 *
scratch.stokes_fe_values[
pressure].value(
k,
q);
3922 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
3923 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
3924 *
data.local_matrix(i,
j) +=
3926 *
(scratch.grads_phi_u[i] * scratch.grads_phi_u[
j]) -
3928 *
scratch.phi_p[
j]) -
3930 *
scratch.div_phi_u[
j])) *
3931 *
scratch.stokes_fe_values.JxW(
q);
3934 *
scratch.stokes_fe_values.quadrature_point(
q));
3936 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
3938 *
gravity * scratch.phi_u[i]) *
3939 *
scratch.stokes_fe_values.JxW(
q);
3942 *
cell->get_dof_indices(
data.local_dof_indices);
3947 *
template <
int dim>
3949 *
const Assembly::CopyData::StokesSystem<dim> &
data)
3954 *
data.local_dof_indices,
3959 *
data.local_dof_indices,
3965 *
template <
int dim>
3969 *
" Assemble Stokes system");
3978 *
using CellFilter =
3986 *
Assembly::Scratch::StokesSystem<dim> & scratch,
3987 *
Assembly::CopyData::StokesSystem<dim> &
data) {
3988 * this->local_assemble_stokes_system(cell, scratch, data);
3990 *
[
this](
const Assembly::CopyData::StokesSystem<dim> &
data) {
3991 * this->copy_local_to_global_stokes_system(data);
3993 *
Assembly::Scratch::StokesSystem<dim>(
4009 *
pcout << std::endl;
4016 * <a name=
"Temperaturematrixassembly"></a>
4033 *
template <
int dim>
4036 *
Assembly::Scratch::TemperatureMatrix<dim> & scratch,
4037 *
Assembly::CopyData::TemperatureMatrix<dim> &
data)
4039 *
const unsigned int dofs_per_cell =
4040 *
scratch.temperature_fe_values.get_fe().n_dofs_per_cell();
4041 *
const unsigned int n_q_points =
4042 *
scratch.temperature_fe_values.n_quadrature_points;
4044 *
scratch.temperature_fe_values.
reinit(cell);
4045 *
cell->get_dof_indices(
data.local_dof_indices);
4047 *
data.local_mass_matrix = 0;
4048 *
data.local_stiffness_matrix = 0;
4050 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
4052 *
for (
unsigned int k = 0;
k < dofs_per_cell; ++
k)
4054 *
scratch.grad_phi_T[
k] =
4055 *
scratch.temperature_fe_values.shape_grad(
k,
q);
4056 *
scratch.phi_T[
k] = scratch.temperature_fe_values.shape_value(
k,
q);
4059 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
4060 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
4062 *
data.local_mass_matrix(i,
j) +=
4063 *
(scratch.phi_T[i] * scratch.phi_T[
j] *
4064 *
scratch.temperature_fe_values.JxW(
q));
4065 *
data.local_stiffness_matrix(i,
j) +=
4067 *
scratch.grad_phi_T[
j] * scratch.temperature_fe_values.JxW(
q));
4074 *
template <
int dim>
4076 *
const Assembly::CopyData::TemperatureMatrix<dim> &
data)
4079 *
data.local_dof_indices,
4082 *
data.local_stiffness_matrix,
4083 *
data.local_dof_indices,
4088 *
template <
int dim>
4095 *
" Assemble temperature matrices");
4101 *
using CellFilter =
4110 *
Assembly::Scratch::TemperatureMatrix<dim> & scratch,
4111 *
Assembly::CopyData::TemperatureMatrix<dim> &
data) {
4112 * this->local_assemble_temperature_matrix(cell, scratch, data);
4114 *
[
this](
const Assembly::CopyData::TemperatureMatrix<dim> &
data) {
4115 * this->copy_local_to_global_temperature_matrix(data);
4133 * <a name=
"Temperaturerighthandsideassembly"></a>
4164 *
const unsigned int dofs_per_cell =
4165 *
scratch.temperature_fe_values.get_fe().n_dofs_per_cell();
4166 *
const unsigned int n_q_points =
4167 *
scratch.temperature_fe_values.n_quadrature_points;
4171 *
data.local_rhs = 0;
4172 *
data.matrix_for_bc = 0;
4173 *
cell->get_dof_indices(
data.local_dof_indices);
4175 *
scratch.temperature_fe_values.
reinit(cell);
4181 *
scratch.temperature_fe_values.get_function_values(
4183 *
scratch.temperature_fe_values.get_function_values(
4186 *
scratch.temperature_fe_values.get_function_gradients(
4188 *
scratch.temperature_fe_values.get_function_gradients(
4191 *
scratch.temperature_fe_values.get_function_laplacians(
4193 *
scratch.temperature_fe_values.get_function_laplacians(
4196 *
scratch.stokes_fe_values[
velocities].get_function_values(
4198 *
scratch.stokes_fe_values[
velocities].get_function_values(
4200 *
scratch.stokes_fe_values[
velocities].get_function_symmetric_gradients(
4202 *
scratch.stokes_fe_values[
velocities].get_function_symmetric_gradients(
4207 *
scratch.old_old_temperature_values,
4208 *
scratch.old_temperature_grads,
4209 *
scratch.old_old_temperature_grads,
4210 *
scratch.old_temperature_laplacians,
4211 *
scratch.old_old_temperature_laplacians,
4212 *
scratch.old_velocity_values,
4213 *
scratch.old_old_velocity_values,
4214 *
scratch.old_strain_rates,
4215 *
scratch.old_old_strain_rates,
4220 *
cell->diameter());
4222 *
for (
unsigned int q = 0;
q < n_q_points; ++
q)
4224 *
for (
unsigned int k = 0;
k < dofs_per_cell; ++
k)
4226 *
scratch.phi_T[
k] = scratch.temperature_fe_values.shape_value(
k,
q);
4227 *
scratch.grad_phi_T[
k] =
4228 *
scratch.temperature_fe_values.shape_grad(
k,
q);
4234 *
(scratch.old_temperature_values[
q] *
4240 *
const double ext_T =
4243 *
scratch.old_old_temperature_values[
q] *
4250 *
scratch.old_old_temperature_grads[
q] *
time_step /
4266 *
const double gamma =
4272 *
for (
unsigned int i = 0; i < dofs_per_cell; ++i)
4274 *
data.local_rhs(i) +=
4278 *
time_step * gamma * scratch.phi_T[i]) *
4279 *
scratch.temperature_fe_values.JxW(
q);
4282 *
data.local_dof_indices[i]))
4284 *
for (
unsigned int j = 0;
j < dofs_per_cell; ++
j)
4285 *
data.matrix_for_bc(
j, i) +=
4286 *
(scratch.phi_T[i] * scratch.phi_T[
j] *
4299 *
template <
int dim>
4301 *
const Assembly::CopyData::TemperatureRHS<dim> &
data)
4304 *
data.local_dof_indices,
4306 *
data.matrix_for_bc);
4332 *
template <
int dim>
4354 *
std::make_shared<TrilinosWrappers::PreconditionJacobi>();
4384 *
using CellFilter =
4390 *
Assembly::Scratch::TemperatureRHS<dim> & scratch,
4391 *
Assembly::CopyData::TemperatureRHS<dim> &
data) {
4400 *
auto copier = [
this](
const Assembly::CopyData::TemperatureRHS<dim> &
data) {
4410 *
Assembly::Scratch::TemperatureRHS<dim>(
4422 * <a name=
"BoussinesqFlowProblemsolve"></a>
4423 * <
h4>BoussinesqFlowProblem::solve</
h4>
4472 * <code>solve()</code> functions. This is certainly not what we want to
4473 * happen here. Rather, we want to switch to the strong solver and continue
4474 * the solution process with whatever vector we got so far. Hence, we catch
4475 * the exception with the C++ try/catch mechanism. We then simply go through
4476 * the same solver sequence again in the <code>catch</code> clause, this
4477 * time passing the @p true flag to the preconditioner for the strong
4478 * solver, signaling an approximate CG solve.
4481 * template <int dim>
4482 * void BoussinesqFlowProblem<dim>::solve()
4485 * TimerOutput::Scope timer_section(computing_timer,
4486 * " Solve Stokes system");
4488 * pcout << " Solving Stokes system... " << std::flush;
4490 * TrilinosWrappers::MPI::BlockVector distributed_stokes_solution(
4492 * distributed_stokes_solution = stokes_solution;
4494 * distributed_stokes_solution.block(1) /= EquationData::pressure_scaling;
4496 * const unsigned int
4497 * start = (distributed_stokes_solution.block(0).size() +
4498 * distributed_stokes_solution.block(1).local_range().first),
4499 * end = (distributed_stokes_solution.block(0).size() +
4500 * distributed_stokes_solution.block(1).local_range().second);
4501 * for (unsigned int i = start; i < end; ++i)
4502 * if (stokes_constraints.is_constrained(i))
4503 * distributed_stokes_solution(i) = 0;
4506 * PrimitiveVectorMemory<TrilinosWrappers::MPI::BlockVector> mem;
4508 * unsigned int n_iterations = 0;
4509 * const double solver_tolerance = 1e-8 * stokes_rhs.l2_norm();
4510 * SolverControl solver_control(30, solver_tolerance);
4514 * const LinearSolvers::BlockSchurPreconditioner<
4515 * TrilinosWrappers::PreconditionAMG,
4516 * TrilinosWrappers::PreconditionJacobi>
4517 * preconditioner(stokes_matrix,
4518 * stokes_preconditioner_matrix,
4519 * *Mp_preconditioner,
4520 * *Amg_preconditioner,
4523 * SolverFGMRES<TrilinosWrappers::MPI::BlockVector> solver(
4526 * SolverFGMRES<TrilinosWrappers::MPI::BlockVector>::AdditionalData(
4528 * solver.solve(stokes_matrix,
4529 * distributed_stokes_solution,
4533 * n_iterations = solver_control.last_step();
4536 * catch (SolverControl::NoConvergence &)
4538 * const LinearSolvers::BlockSchurPreconditioner<
4539 * TrilinosWrappers::PreconditionAMG,
4540 * TrilinosWrappers::PreconditionJacobi>
4541 * preconditioner(stokes_matrix,
4542 * stokes_preconditioner_matrix,
4543 * *Mp_preconditioner,
4544 * *Amg_preconditioner,
4547 * SolverControl solver_control_refined(stokes_matrix.m(),
4548 * solver_tolerance);
4549 * SolverFGMRES<TrilinosWrappers::MPI::BlockVector> solver(
4550 * solver_control_refined,
4552 * SolverFGMRES<TrilinosWrappers::MPI::BlockVector>::AdditionalData(
4554 * solver.solve(stokes_matrix,
4555 * distributed_stokes_solution,
4560 * (solver_control.last_step() + solver_control_refined.last_step());
4564 * stokes_constraints.distribute(distributed_stokes_solution);
4566 * distributed_stokes_solution.block(1) *= EquationData::pressure_scaling;
4568 * stokes_solution = distributed_stokes_solution;
4569 * pcout << n_iterations << " iterations." << std::endl;
4601 *
" Assemble temperature rhs");
4605 *
const double scaling = (dim == 3 ? 0.25 : 1.0);
4610 *
pcout <<
" Maximal velocity: "
4612 *
<<
" cm/year" << std::endl;
4615 *
<<
" years" << std::endl;
4623 *
" Solve temperature system");
4641 *
pcout <<
" " << solver_control.last_step()
4642 *
<<
" CG iterations for temperature" << std::endl;
4644 *
double temperature[2] = {std::numeric_limits<double>::max(),
4645 *
-std::numeric_limits<double>::max()};
4648 *
for (
unsigned int i =
4674 * <a name=
"BoussinesqFlowProblemoutput_results"></a>
4692 *
template <
int dim>
4699 *
virtual void evaluate_vector_field(
4703 *
virtual std::vector<std::string> get_names()
const override;
4705 *
virtual std::vector<
4707 *
get_data_component_interpretation()
const override;
4709 *
virtual UpdateFlags get_needed_update_flags()
const override;
4717 *
template <
int dim>
4719 *
const unsigned int partition,
4736 *
template <
int dim>
4737 *
std::vector<std::string>
4750 *
template <
int dim>
4751 *
std::vector<DataComponentInterpretation::DataComponentInterpretation>
4755 *
std::vector<DataComponentInterpretation::DataComponentInterpretation>
4768 *
template <
int dim>
4800 *
ExcInternalError());
4802 *
ExcInternalError());
4807 *
for (
unsigned int d = 0;
d < dim; ++
d)
4819 *
for (
unsigned int d = 0;
d < dim; ++
d)
4835 *
way we're going to achieve this recombination is to create a joint
4836 * DoFHandler that collects both components, the Stokes solution and the
4837 * temperature solution. This can be nicely done by combining the finite
4838 * elements from the two systems to form one FESystem, and let this
4839 * collective system define a new DoFHandler object. To be sure that
4840 * everything was done correctly, we perform a sanity check that ensures
4841 * that we got all the dofs from both Stokes and temperature even in the
4842 * combined system. We then combine the data vectors. Unfortunately, there
4843 * is no straight-forward relation that tells us how to sort Stokes and
4844 * temperature vector into the joint vector. The way we can get around this
4845 * trouble is to rely on the information collected in the FESystem. For each
4846 * dof on a cell, the joint finite element knows to which equation component
4847 * (velocity component, pressure, or temperature) it belongs – that's
the
4853 * Stokes dof or a temperature dof, which is contained in
4854 * joint_fe.system_to_base_index(i).first.first. Eventually, the dof_indices
4855 * data structures on either of the three systems tell us how the relation
4856 * between global vector and local dofs looks like on the present cell,
4857 * which concludes this tedious work. We make sure that each processor only
4858 * works on the subdomain it owns locally (and not on ghost or artificial
4859 * cells) when building the joint solution vector. The same will then have
4860 * to be done in DataOut::build_patches(), but that function does so
4865 * What we end up with is a set of patches that we can write using the
4866 * functions in DataOutBase in a variety of output formats. Here, we then
4867 * have to pay attention that what each processor writes is really only its
4868 * own part of the domain, i.e. we will want to write each processor's
4880 *
template <
int dim>
4891 *
ExcInternalError());
4918 *
for (
unsigned int i = 0; i <
joint_fe.n_dofs_per_cell(); ++i)
4919 *
if (
joint_fe.system_to_base_index(i).first.first == 0)
4923 *
ExcInternalError());
4932 *
ExcInternalError());
4935 *
ExcInternalError());
4939 *
[
joint_fe.system_to_base_index(i).second]);
4961 *
data_out.build_patches();
4963 *
static int out_index = 0;
4964 *
data_out.write_vtu_with_pvtu_record(
4975 * <a name=
"BoussinesqFlowProblemrefine_mesh"></a>
4980 *
This function
isn't really new either. Since the <code>setup_dofs</code>
4981 * function that we call in the middle has its own timer section, we split
4982 * timing this function into two sections. It will also allow us to easily
4983 * identify which of the two is more expensive.
4987 * One thing of note, however, is that we only want to compute error
4988 * indicators on the locally owned subdomain. In order to achieve this, we
4989 * pass one additional argument to the KellyErrorEstimator::estimate
4990 * function. Note that the vector for error estimates is resized to the
4991 * number of active cells present on the current process, which is less than
4992 * the total number of active cells on all processors (but more than the
4993 * number of locally owned active cells); each processor only has a few
4994 * coarse cells around the locally owned ones, as also explained in @ref step_40 "step-40".
4998 * The local error estimates are then handed to a %parallel version of
4999 * GridRefinement (in namespace parallel::distributed::GridRefinement, see
5000 * also @ref step_40 "step-40") which looks at the errors and finds the cells that need
5001 * refinement by comparing the error values across processors. As in
5002 * @ref step_31 "step-31", we want to limit the maximum grid level. So in case some cells
5003 * have been marked that are already at the finest level, we simply clear
5007 * template <int dim>
5009 * BoussinesqFlowProblem<dim>::refine_mesh(const unsigned int max_grid_level)
5011 * parallel::distributed::SolutionTransfer<dim, TrilinosWrappers::MPI::Vector>
5012 * temperature_trans(temperature_dof_handler);
5013 * parallel::distributed::SolutionTransfer<dim,
5014 * TrilinosWrappers::MPI::BlockVector>
5015 * stokes_trans(stokes_dof_handler);
5018 * TimerOutput::Scope timer_section(computing_timer,
5019 * "Refine mesh structure, part 1");
5021 * Vector<float> estimated_error_per_cell(triangulation.n_active_cells());
5023 * KellyErrorEstimator<dim>::estimate(
5024 * temperature_dof_handler,
5025 * QGauss<dim - 1>(parameters.temperature_degree + 1),
5026 * std::map<types::boundary_id, const Function<dim> *>(),
5027 * temperature_solution,
5028 * estimated_error_per_cell,
5032 * triangulation.locally_owned_subdomain());
5034 * parallel::distributed::GridRefinement::refine_and_coarsen_fixed_fraction(
5035 * triangulation, estimated_error_per_cell, 0.3, 0.1);
5037 * if (triangulation.n_levels() > max_grid_level)
5038 * for (typename Triangulation<dim>::active_cell_iterator cell =
5039 * triangulation.begin_active(max_grid_level);
5040 * cell != triangulation.end();
5042 * cell->clear_refine_flag();
5046 * With all flags marked as necessary, we can then tell the
5047 * parallel::distributed::SolutionTransfer objects to get ready to
5048 * transfer data from one mesh to the next, which they will do when
5050 * Triangulation as part of the @p execute_coarsening_and_refinement() call.
5051 * The syntax is similar to the non-%parallel solution transfer (with the
5052 * exception that here a pointer to the vector entries is enough). The
5053 * remainder of the function further down below is then concerned with
5054 * setting up the data structures again after mesh refinement and
5055 * restoring the solution vectors on the new mesh.
5058 * std::vector<const TrilinosWrappers::MPI::Vector *> x_temperature(2);
5059 * x_temperature[0] = &temperature_solution;
5060 * x_temperature[1] = &old_temperature_solution;
5061 * std::vector<const TrilinosWrappers::MPI::BlockVector *> x_stokes(2);
5062 * x_stokes[0] = &stokes_solution;
5063 * x_stokes[1] = &old_stokes_solution;
5065 * triangulation.prepare_coarsening_and_refinement();
5067 * temperature_trans.prepare_for_coarsening_and_refinement(x_temperature);
5068 * stokes_trans.prepare_for_coarsening_and_refinement(x_stokes);
5070 * triangulation.execute_coarsening_and_refinement();
5076 * TimerOutput::Scope timer_section(computing_timer,
5077 * "Refine mesh structure, part 2");
5080 * TrilinosWrappers::MPI::Vector distributed_temp1(temperature_rhs);
5081 * TrilinosWrappers::MPI::Vector distributed_temp2(temperature_rhs);
5083 * std::vector<TrilinosWrappers::MPI::Vector *> tmp(2);
5084 * tmp[0] = &(distributed_temp1);
5085 * tmp[1] = &(distributed_temp2);
5086 * temperature_trans.interpolate(tmp);
5090 * enforce constraints to make the interpolated solution conforming on
5094 * temperature_constraints.distribute(distributed_temp1);
5095 * temperature_constraints.distribute(distributed_temp2);
5097 * temperature_solution = distributed_temp1;
5098 * old_temperature_solution = distributed_temp2;
5102 * TrilinosWrappers::MPI::BlockVector distributed_stokes(stokes_rhs);
5103 * TrilinosWrappers::MPI::BlockVector old_distributed_stokes(stokes_rhs);
5105 * std::vector<TrilinosWrappers::MPI::BlockVector *> stokes_tmp(2);
5106 * stokes_tmp[0] = &(distributed_stokes);
5107 * stokes_tmp[1] = &(old_distributed_stokes);
5109 * stokes_trans.interpolate(stokes_tmp);
5113 * enforce constraints to make the interpolated solution conforming on
5117 * stokes_constraints.distribute(distributed_stokes);
5118 * stokes_constraints.distribute(old_distributed_stokes);
5120 * stokes_solution = distributed_stokes;
5121 * old_stokes_solution = old_distributed_stokes;
5131 * <a name="BoussinesqFlowProblemrun"></a>
5132 * <h4>BoussinesqFlowProblem::run</h4>
5136 * This is the final and controlling function in this class. It, in fact,
5137 * runs the entire rest of the program and is, once more, very similar to
5138 * @ref step_31 "step-31". The only substantial difference is that we use a different mesh
5139 * now (a GridGenerator::hyper_shell instead of a simple cube geometry).
5142 * template <int dim>
5143 * void BoussinesqFlowProblem<dim>::run()
5145 * GridGenerator::hyper_shell(triangulation,
5149 * (dim == 3) ? 96 : 12,
5152 * global_Omega_diameter = GridTools::diameter(triangulation);
5154 * triangulation.refine_global(parameters.initial_global_refinement);
5158 * unsigned int pre_refinement_step = 0;
5160 * start_time_iteration:
5163 * TrilinosWrappers::MPI::Vector solution(
5164 * temperature_dof_handler.locally_owned_dofs());
5167 * VectorTools::project supports parallel vector classes with most
5184 *
the previous time step
's temperature field. That said, nothing good can
5185 * come from not initializing the other vectors as well (especially since
5219 *
pcout << std::endl;
5224 *
refine_mesh(parameters.initial_global_refinement +
5225 *
parameters.initial_adaptive_refinement);
5232 *
refine_mesh(parameters.initial_global_refinement +
5233 *
parameters.initial_adaptive_refinement);
5235 *
if ((parameters.generate_graphical_output ==
true) &&
5268 *
into distributed vectors
for now:
5307 *
if ((parameters.generate_graphical_output ==
true) &&
5308 *
!((
timestep_number - 1) % parameters.graphical_output_interval == 0))
5318 * <a name=
"Thecodemaincodefunction"></a>
5341 *
using namespace Step32;
5342 *
using namespace dealii;
5353 *
const int dim = 2;
5358 *
catch (std::exception &exc)
5360 *
std::cerr << std::endl
5362 *
<<
"----------------------------------------------------"
5364 *
std::cerr <<
"Exception on processing: " << std::endl
5365 *
<< exc.what() << std::endl
5366 *
<<
"Aborting!" << std::endl
5367 *
<<
"----------------------------------------------------"
5374 *
std::cerr << std::endl
5376 *
<<
"----------------------------------------------------"
5378 *
std::cerr <<
"Unknown exception!" << std::endl
5379 *
<<
"Aborting!" << std::endl
5380 *
<<
"----------------------------------------------------"
5411<table
align=
"center" class=
"doxtable">
5414 <
img src=
"https://www.dealii.org/images/steps/developer/step-32.3d.cube.0.png" alt=
"">
5419 <
img src=
"https://www.dealii.org/images/steps/developer/step-32.3d.cube.1.png" alt=
"">
5447\$ mpirun -np 16 ./step-32
5448Number of active cells: 12,288 (on 6 levels)
5449Number of degrees of freedom: 186,624 (99,840+36,864+49,920)
5451Timestep 0: t=0 years
5453 Rebuilding Stokes preconditioner...
5454 Solving Stokes system... 41 iterations.
5455 Maximal velocity: 60.4935 cm/year
5456 Time step: 18166.9 years
5457 17 CG iterations for temperature
5458 Temperature range: 973 4273.16
5460Number of active cells: 15,921 (on 7 levels)
5461Number of degrees of freedom: 252,723 (136,640+47,763+68,320)
5463Timestep 0: t=0 years
5465 Rebuilding Stokes preconditioner...
5466 Solving Stokes system... 50 iterations.
5467 Maximal velocity: 60.3223 cm/year
5468 Time step: 10557.6 years
5469 19 CG iterations for temperature
5470 Temperature range: 973 4273.16
5472Number of active cells: 19,926 (on 8 levels)
5473Number of degrees of freedom: 321,246 (174,312+59,778+87,156)
5475Timestep 0: t=0 years
5477 Rebuilding Stokes preconditioner...
5478 Solving Stokes system... 50 iterations.
5479 Maximal velocity: 57.8396 cm/year
5480 Time step: 5453.78 years
5481 18 CG iterations for temperature
5482 Temperature range: 973 4273.16
5484Timestep 1: t=5453.78 years
5486 Solving Stokes system... 49 iterations.
5487 Maximal velocity: 59.0231 cm/year
5488 Time step: 5345.86 years
5489 18 CG iterations for temperature
5490 Temperature range: 973 4273.16
5492Timestep 2: t=10799.6 years
5494 Solving Stokes system... 24 iterations.
5495 Maximal velocity: 60.2139 cm/year
5496 Time step: 5241.51 years
5497 17 CG iterations for temperature
5498 Temperature range: 973 4273.16
5502Timestep 100: t=272151 years
5504 Solving Stokes system... 21 iterations.
5505 Maximal velocity: 161.546 cm/year
5506 Time step: 1672.96 years
5507 17 CG iterations for temperature
5508 Temperature range: 973 4282.57
5510Number of active cells: 56,085 (on 8 levels)
5511Number of degrees of freedom: 903,408 (490,102+168,255+245,051)
5515+---------------------------------------------+------------+------------+
5516| Total wallclock time elapsed since start | 115s | |
5518| Section | no. calls | wall time | % of total |
5519+---------------------------------+-----------+------------+------------+
5520| Assemble Stokes system | 103 | 2.82s | 2.5% |
5521| Assemble temperature matrices | 12 | 0.452s | 0.39% |
5522| Assemble temperature rhs | 103 | 11.5s | 10% |
5523| Build Stokes preconditioner | 12 | 2.09s | 1.8% |
5524| Solve Stokes system | 103 | 90.4s | 79% |
5525| Solve temperature system | 103 | 1.53s | 1.3% |
5526| Postprocessing | 3 | 0.532s | 0.46% |
5527| Refine mesh structure, part 1 | 12 | 0.93s | 0.81% |
5528| Refine mesh structure, part 2 | 12 | 0.384s | 0.33% |
5529| Setup dof systems | 13 | 2.96s | 2.6% |
5530+---------------------------------+-----------+------------+------------+
5534+---------------------------------------------+------------+------------+
5535| Total wallclock time elapsed since start | 9.14e+04s | |
5537| Section | no. calls | wall time | % of total |
5538+---------------------------------+-----------+------------+------------+
5539| Assemble Stokes system | 47045 | 2.05e+03s | 2.2% |
5540| Assemble temperature matrices | 4707 | 310s | 0.34% |
5541| Assemble temperature rhs | 47045 | 8.7e+03s | 9.5% |
5542| Build Stokes preconditioner | 4707 | 1.48e+03s | 1.6% |
5543| Solve Stokes system | 47045 | 7.34e+04s | 80% |
5544| Solve temperature system | 47045 | 1.46e+03s | 1.6% |
5545| Postprocessing | 1883 | 222s | 0.24% |
5546| Refine mesh structure, part 1 | 4706 | 641s | 0.7% |
5547| Refine mesh structure, part 2 | 4706 | 259s | 0.28% |
5548| Setup dof systems | 4707 | 1.86e+03s | 2% |
5549+---------------------------------+-----------+------------+------------+
5553The simulation terminates when the time reaches the 1 billion years
5554selected in the input file. You can extrapolate from this how long a
5555simulation would take for a different final time (the time step size
5556ultimately settles on somewhere around 20,000 years, so computing for
5557two billion years will take 100,000 time steps, give or take 20%). As
5558can be seen here, we spend most of the compute time in assembling
5559linear systems and — above all — in solving Stokes
5563To demonstrate the output we show the output from every 1250th time step here:
5567 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-000.png" alt="">
5570 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-050.png" alt="">
5573 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-100.png" alt="">
5578 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-150.png" alt="">
5581 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-200.png" alt="">
5584 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-250.png" alt="">
5589 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-300.png" alt="">
5592 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-350.png" alt="">
5595 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-400.png" alt="">
5600 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-450.png" alt="">
5603 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-500.png" alt="">
5606 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-550.png" alt="">
5611 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-time-600.png" alt="">
5614 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-cells.png" alt="">
5617 <img src="https://www.dealii.org/images/steps/developer/step-32-2d-partition.png" alt="">
5622The last two images show the grid as well as the partitioning of the mesh for
5623the same computation with 16 subdomains and 16 processors. The full dynamics of
5624this simulation are really only visible by looking at an animation, for example
5626href="https://www.dealii.org/images/steps/developer/step-32-2d-temperature.webm">shown
5627on this site</a>. This image is well worth watching due to its artistic quality
5628and entrancing depiction of the evolution of the magma plumes.
5645<
img src=
"https://www.dealii.org/images/steps/developer/step-32.2d.t_vs_vmax.png" alt=
"">
5665developed independently of deal.II and that already incorporates some
5666of the extensions discussed below. The following two pictures show
5667isocontours of the temperature and the partition of the domain (along
5668with the mesh) onto 512 processors:
5671<img src="https://www.dealii.org/images/steps/developer/step-32.3d-sphere.solution.png" alt="">
5673<img src="https://www.dealii.org/images/steps/developer/step-32.3d-sphere.partition.png" alt="">
5677<a name="extensions"></a>
5678<a name="Possibilitiesforextensions"></a><h3>Possibilities for extensions</h3>
5681There are many directions in which this program could be extended. As
5682mentioned at the end of the introduction, most of these are under active
5683development in the <i>ASPECT</i> (short for <i>Advanced %Solver for Problems
5694 closer to the earth core should be hotter than closer to the
5695 surface. The reason is that the energy equation we have used does
5696 not include a term that describes adiabatic cooling and heating:
5697 rock, like gas, heats up as you compress it. Consequently, material
5698 that rises up cools adiabatically, and cold material that sinks down
5699 heats adiabatically. The correct temperature equation would
5700 therefore look somewhat like this:
5704 \nabla \cdot \kappa \nabla T &=& \gamma + \tau\frac{Dp}{Dt},
5706 or, expanding the advected derivative @f$\frac{D}{Dt} =
5707 \frac{\partial}{\partial t} + \mathbf u \cdot \nabla@f$:
5709 \frac{\partial T}{\partial t}
5711 {\mathbf u} \cdot \nabla T
5713 \nabla \cdot \kappa \nabla T &=& \gamma +
5714 \tau\left\{\frac{\partial
5715 p}{\partial t} + \mathbf u \cdot \nabla p \right\}.
5717 In other words, as pressure increases in a rock volume
5718 (@f$\frac{Dp}{Dt}>0@f$) we get an additional heat source, and vice
5721 The time derivative of the pressure is a bit awkward to
5722 implement. If necessary, one could approximate using the fact
5723 outlined in the introduction that the pressure can be decomposed
5724 into a dynamic component due to temperature differences and the
5725 resulting flow, and a static component that results solely from the
5726 static pressure of the overlying rock. Since the latter is much
5727 bigger, one may approximate @f$p\approx p_{\text{static}}=-\rho_{\text{ref}}
5728 [1+\beta T_{\text{ref}}] \varphi@f$, and consequently
5729 @f$\frac{Dp}{Dt} \approx \left\{- \mathbf u \cdot \nabla \rho_{\text{ref}}
5730 [1+\beta T_{\text{ref}}]\varphi\right\} = \rho_{\text{ref}}
5731 [1+\beta T_{\text{ref}}] \mathbf u \cdot \mathbf g@f$.
5732 In other words, if the fluid is moving in the direction of gravity
5733 (downward) it will be compressed and because in that case @f$\mathbf u
5734 \cdot \mathbf g > 0@f$ we get a positive heat source. Conversely, the
5735 fluid will cool down if it moves against the direction of gravity.
5737<li> <b>Compressibility:</b>
5738 As already hinted at in the temperature model above,
5739 mantle rocks are not incompressible. Rather, given the enormous pressures in
5740 the earth mantle (at the core-mantle boundary, the pressure is approximately
5741 140 GPa, equivalent to 1,400,000 times atmospheric pressure), rock actually
5742 does compress to something around 1.5 times the density it would have
5743 at surface pressure. Modeling this presents any number of
5744 difficulties. Primarily, the mass conservation equation is no longer
5745 @f$\textrm{div}\;\mathbf u=0@f$ but should read
5746 @f$\textrm{div}(\rho\mathbf u)=0@f$ where the density @f$\rho@f$ is now no longer
5747 spatially constant but depends on temperature and pressure. A consequence is
5748 that the model is now no longer linear; a linearized version of the Stokes
5749 equation is also no longer symmetric requiring us to rethink preconditioners
5750 and, possibly, even the discretization. We won't
go into detail
here as to
5815<a name=
"PlainProg"></a>
value_type * data() const noexcept
void reinit(value_type *starting_element, const std::size_t n_elements)
virtual void build_patches(const unsigned int n_subdivisions=0)
virtual void evaluate_vector_field(const DataPostprocessorInputs::Vector< dim > &input_data, std::vector< Vector< double > > &computed_quantities) const
active_cell_iterator begin_active(const unsigned int level=0) const
std::pair< std::pair< unsigned int, unsigned int >, unsigned int > system_to_base_index(const unsigned int index) const
__global__ void set(Number *val, const Number s, const size_type N)
__global__ void sadd(const Number s, Number *val, const Number a, const Number *V_val, const size_type N)
#define Assert(cond, exc)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
typename ActiveSelector::active_cell_iterator active_cell_iterator
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())
@ update_values
Shape function values.
@ update_JxW_values
Transformed quadrature weights.
@ update_gradients
Shape function gradients.
@ update_quadrature_points
Transformed quadrature points.
std::vector< value_type > split(const typename ::Triangulation< dim, spacedim >::cell_iterator &parent, const value_type parent_value)
void apply(const Kokkos::TeamPolicy< MemorySpace::Default::kokkos_space::execution_space >::member_type &team_member, const Kokkos::View< Number *, MemorySpace::Default::kokkos_space > shape_data, const ViewTypeIn in, ViewTypeOut out)
DataComponentInterpretation
@ component_is_part_of_vector
void approximate(SynchronousIterators< std::tuple< typename DoFHandler< dim, spacedim >::active_cell_iterator, Vector< float >::iterator > > const &cell, const Mapping< dim, spacedim > &mapping, const DoFHandler< dim, spacedim > &dof_handler, const InputVector &solution, const unsigned int component)
Expression sign(const Expression &x)
void refine(Triangulation< dim, spacedim > &tria, const Vector< Number > &criteria, const double threshold, const unsigned int max_to_mark=numbers::invalid_unsigned_int)
@ valid
Iterator points to a valid object.
@ matrix
Contents is actually a matrix.
@ symmetric
Matrix is symmetric.
double norm(const FEValuesBase< dim > &fe, const ArrayView< const std::vector< Tensor< 1, dim > > > &Du)
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > b(const Tensor< 2, dim, Number > &F)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
constexpr ReturnType< rank, T >::value_type & extract(T &t, const ArrayType &indices)
void call(const std::function< RT()> &function, internal::return_value< RT > &ret_val)
std::vector< unsigned int > serial(const std::vector< unsigned int > &targets, const std::function< RequestType(const unsigned int)> &create_request, const std::function< AnswerType(const unsigned int, const RequestType &)> &answer_request, const std::function< void(const unsigned int, const AnswerType &)> &process_answer, const MPI_Comm comm)
T sum(const T &t, const MPI_Comm mpi_communicator)
T max(const T &t, const MPI_Comm mpi_communicator)
unsigned int this_mpi_process(const MPI_Comm mpi_communicator)
std::string compress(const std::string &input)
void run(const Iterator &begin, const std_cxx20::type_identity_t< Iterator > &end, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length, const unsigned int chunk_size)
void run(const std::vector< std::vector< Iterator > > &colored_iterators, Worker worker, Copier copier, const ScratchData &sample_scratch_data, const CopyData &sample_copy_data, const unsigned int queue_length=2 *MultithreadInfo::n_threads(), const unsigned int chunk_size=8)
void abort(const ExceptionBase &exc) noexcept
bool check(const ConstraintKinds kind_in, const unsigned int dim)
long double gamma(const unsigned int n)
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)
static constexpr double PI
static const unsigned int invalid_unsigned_int
::VectorizedArray< Number, width > min(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > max(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > abs(const ::VectorizedArray< Number, width > &)
unsigned int subdomain_id
const ::parallel::distributed::Triangulation< dim, spacedim > * triangulation
DEAL_II_HOST constexpr SymmetricTensor< 2, dim, Number > symmetrize(const Tensor< 2, dim, Number > &t)