/* SPDX-FileCopyrightText: Copyright (c) 2022 - 2025, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if DEAL_II_VERSION_GTE(9, 7, 0) && defined(DEAL_II_TRILINOS_WITH_TPETRA) #include #endif #include #include #include #include #ifdef ADAMANTINE_WITH_CALIPER #include #endif namespace adamantine { namespace { template struct ScratchDataRightHandSide { ScratchDataRightHandSide( dealii::hp::FEValues const &displacement_hp_fe_values, dealii::hp::FEValues const &temperature_hp_fe_values) : displacement_hp_fe_values( displacement_hp_fe_values.get_mapping_collection(), displacement_hp_fe_values.get_fe_collection(), displacement_hp_fe_values.get_quadrature_collection(), displacement_hp_fe_values.get_update_flags()), temperature_hp_fe_values( temperature_hp_fe_values.get_mapping_collection(), temperature_hp_fe_values.get_fe_collection(), temperature_hp_fe_values.get_quadrature_collection(), temperature_hp_fe_values.get_update_flags()) { } ScratchDataRightHandSide(ScratchDataRightHandSide const &scratch_data) : displacement_hp_fe_values( scratch_data.displacement_hp_fe_values.get_mapping_collection(), scratch_data.displacement_hp_fe_values.get_fe_collection(), scratch_data.displacement_hp_fe_values.get_quadrature_collection(), scratch_data.displacement_hp_fe_values.get_update_flags()), temperature_hp_fe_values( scratch_data.temperature_hp_fe_values.get_mapping_collection(), scratch_data.temperature_hp_fe_values.get_fe_collection(), scratch_data.temperature_hp_fe_values.get_quadrature_collection(), scratch_data.temperature_hp_fe_values.get_update_flags()) { } dealii::hp::FEValues displacement_hp_fe_values; dealii::hp::FEValues temperature_hp_fe_values; }; struct CopyDataRightHandSide { CopyDataRightHandSide(unsigned int dofs_per_cell) { cell_rhs.reinit(dofs_per_cell); local_dof_indices.resize(dofs_per_cell); } dealii::Vector cell_rhs; std::vector local_dof_indices; template void reinit(const Iterator &cell) { cell_rhs = 0.; cell->get_dof_indices(local_dof_indices); } }; template struct ScratchDataMatrix { ScratchDataMatrix(dealii::hp::FEValues const &displacement_hp_fe_values) : displacement_hp_fe_values( displacement_hp_fe_values.get_mapping_collection(), displacement_hp_fe_values.get_fe_collection(), displacement_hp_fe_values.get_quadrature_collection(), displacement_hp_fe_values.get_update_flags()) { } ScratchDataMatrix(ScratchDataMatrix const &scratch_data) : displacement_hp_fe_values( scratch_data.displacement_hp_fe_values.get_mapping_collection(), scratch_data.displacement_hp_fe_values.get_fe_collection(), scratch_data.displacement_hp_fe_values.get_quadrature_collection(), scratch_data.displacement_hp_fe_values.get_update_flags()) { } dealii::hp::FEValues displacement_hp_fe_values; }; struct CopyDataMatrix { CopyDataMatrix(unsigned int dofs_per_cell) { cell_matrix.reinit(dofs_per_cell, dofs_per_cell); local_dof_indices.resize(dofs_per_cell); } dealii::FullMatrix cell_matrix; std::vector local_dof_indices; template void reinit(const Iterator &cell) { cell_matrix = 0.; cell->get_dof_indices(local_dof_indices); } }; } // namespace template MechanicalOperator:: MechanicalOperator( MPI_Comm const &communicator, MaterialProperty &material_properties, std::vector const &reference_temperatures) : _communicator(communicator), _reference_temperatures(reference_temperatures), _material_properties(material_properties) { } template void MechanicalOperator:: reinit(dealii::DoFHandler const &dof_handler, dealii::AffineConstraints const &affine_constraints, dealii::hp::QCollection const &q_collection, std::vector>> const &body_forces) { _dof_handler = &dof_handler; _affine_constraints = &affine_constraints; _q_collection = &q_collection; assemble_matrix(); assemble_rhs(body_forces); } template void MechanicalOperator:: update_temperature( dealii::DoFHandler const &thermal_dof_handler, dealii::LA::distributed::Vector const &temperature, std::vector const &has_melted) { _thermal_dof_handler = &thermal_dof_handler; _temperature = temperature; _has_melted = has_melted; } template void MechanicalOperator::assemble_matrix() { #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_BEGIN("assemble mechanical matrix"); #endif // Create the sparsity pattern. Since we use a Trilinos matrix we don't need // the sparsity pattern to outlive the sparse matrix. auto locally_owned_dofs = _dof_handler->locally_owned_dofs(); auto locally_relevant_dofs = dealii::DoFTools::extract_locally_relevant_dofs(*_dof_handler); dealii::DynamicSparsityPattern dsp(locally_relevant_dofs); dealii::DoFTools::make_sparsity_pattern(*_dof_handler, dsp, *_affine_constraints, false); dealii::SparsityTools::distribute_sparsity_pattern( dsp, locally_owned_dofs, _communicator, locally_relevant_dofs); _system_matrix.reinit(locally_owned_dofs, dsp, _communicator); dealii::hp::FEValues displacement_hp_fe_values( _dof_handler->get_fe_collection(), *_q_collection, dealii::update_values | dealii::update_gradients | dealii::update_JxW_values); unsigned int const dofs_per_cell = _dof_handler->get_fe_collection().max_dofs_per_cell(); // Loop over the locally owned cells that are not FE_Nothing and assemble the // sparse matrix and the right-hand-side auto cell_worker = [&](const typename dealii::DoFHandler::active_cell_iterator &cell, ScratchDataMatrix &scratch_data, CopyDataMatrix ©_data) { dealii::hp::FEValues &displacement_hp_fe_values = scratch_data.displacement_hp_fe_values; displacement_hp_fe_values.reinit(cell); auto const &fe_values = displacement_hp_fe_values.get_present_fe_values(); auto const &fe = fe_values.get_fe(); // Assemble the local martrix copy_data.reinit(cell); dealii::FullMatrix &cell_matrix = copy_data.cell_matrix; double const lambda = this->_material_properties.get_mechanical_property( cell, StateProperty::lame_first_parameter); double const mu = this->_material_properties.get_mechanical_property( cell, StateProperty::lame_second_parameter); for (auto const i : fe_values.dof_indices()) { auto const component_i = fe.system_to_component_index(i).first; for (auto const j : fe_values.dof_indices()) { auto const component_j = fe.system_to_component_index(j).first; for (auto const q_point : fe_values.quadrature_point_indices()) { cell_matrix(i, j) += ((fe_values.shape_grad(i, q_point)[component_i] * fe_values.shape_grad(j, q_point)[component_j] * lambda) + (fe_values.shape_grad(i, q_point)[component_j] * fe_values.shape_grad(j, q_point)[component_i] * mu) + ((component_i == component_j) ? mu * fe_values.shape_grad(i, q_point) * fe_values.shape_grad(j, q_point) : 0.)) * fe_values.JxW(q_point); } } } }; auto copier = [&](const CopyDataMatrix &cd) { _affine_constraints->distribute_local_to_global( cd.cell_matrix, cd.local_dof_indices, _system_matrix); }; dealii::IteratorFilters::ActiveFEIndexEqualTo filter( 0, /* locally owned */ true); dealii::FilteredIterator< typename dealii::DoFHandler::active_cell_iterator> begin(filter, _dof_handler->begin_active()); dealii::FilteredIterator< typename dealii::DoFHandler::active_cell_iterator> end(filter, _dof_handler->end()); ScratchDataMatrix scratch_data(displacement_hp_fe_values); dealii::MeshWorker::mesh_loop(begin, end, cell_worker, copier, scratch_data, CopyDataMatrix(dofs_per_cell), dealii::MeshWorker::assemble_own_cells); _system_matrix.compress(dealii::VectorOperation::add); #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_END("assemble mechanical matrix"); #endif #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_BEGIN("initialize mechanical matrix preconditioner"); #endif // Constant modes reduce cg iterations by 40%. // FIXME: With Tpetra, we run into a level 0 scratch memory issue on Frontier. #if DEAL_II_VERSION_GTE(9, 7, 0) && defined(DEAL_II_TRILINOS_WITH_TPETRA) _preconditioner.clear(); _preconditioner.initialize(_system_matrix); #else // Provide the constant displacement modes to AMG for the elasticity problem. dealii::FEValuesExtractors::Vector const displacement_components(0); auto const component_mask = _dof_handler->get_fe_collection().component_mask( displacement_components); std::vector> constant_modes; dealii::DoFTools::extract_constant_modes( *_dof_handler, component_mask, constant_modes); typename TrilinosPreconditionerType::AdditionalData amg_data; amg_data.constant_modes = constant_modes; _preconditioner.clear(); _preconditioner.initialize(_system_matrix, amg_data); #endif #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_END("initialize mechanical matrix preconditioner"); #endif } template void MechanicalOperator:: assemble_rhs( std::vector>> const &body_forces) { #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_BEGIN("assemble mechanical rhs"); #endif auto locally_owned_dofs = _dof_handler->locally_owned_dofs(); auto locally_relevant_dofs = dealii::DoFTools::extract_locally_relevant_dofs(*_dof_handler); unsigned int const dofs_per_cell = _dof_handler->get_fe_collection().max_dofs_per_cell(); dealii::hp::FEValues displacement_hp_fe_values( _dof_handler->get_fe_collection(), *_q_collection, dealii::update_values | dealii::update_gradients | dealii::update_JxW_values); dealii::LA::distributed::Vector assembled_rhs(locally_owned_dofs, locally_relevant_dofs, _communicator); std::vector local_dof_indices(dofs_per_cell); // If the list of reference temperatures is non-empty, we solve the // thermo-elastic problem. if (_reference_temperatures.size() > 0) { // _has_melted is using its own indices. The indices are computed using the // locally owned cells of the thermal DoFHandler that have an FE_Index // equal to zero. We need to translate these indices to be used with the // mechanical DoFHandler. This is simplified by the fact that both the // thermal and the mechanical simulation use the same Triangulation and have // the same cells locally owned. auto &triangulation = _dof_handler->get_triangulation(); unsigned int const n_local_active_cells = triangulation.n_active_cells(); std::vector cell_indices(n_local_active_cells); unsigned int cell_index = 0; for (auto const &tria_cell : triangulation.active_cell_iterators() | dealii::IteratorFilters::LocallyOwnedCell()) { dealii::TriaIterator> temperature_cell(&triangulation, tria_cell->level(), tria_cell->index(), _thermal_dof_handler); if (temperature_cell->active_fe_index() == 0) { dealii::TriaIterator> displacement_cell(&triangulation, tria_cell->level(), tria_cell->index(), _dof_handler); if (displacement_cell->active_fe_index() == 0) { cell_indices[displacement_cell->active_cell_index()] = cell_index; } ++cell_index; } } _temperature.update_ghost_values(); std::vector temperature_local_dof_indices( _thermal_dof_handler->get_fe_collection().max_dofs_per_cell()); double const initial_temperature = _reference_temperatures.back(); auto cell_worker = [&](const typename dealii::DoFHandler::active_cell_iterator &cell, ScratchDataRightHandSide &scratch_data, CopyDataRightHandSide ©_data) { // Get the temperature cell associated to the mechanical cell dealii::TriaIterator> temperature_cell(&triangulation, cell->level(), cell->index(), _thermal_dof_handler); // Get the appropriate reference temperature for the cell. If the cell // is not in the unmelted substrate, the reference temperature depends // on the material. double reference_temperature = _has_melted[cell_indices[cell->active_cell_index()]] ? _reference_temperatures[temperature_cell->material_id()] : initial_temperature; dealii::hp::FEValues &displacement_hp_fe_values = scratch_data.displacement_hp_fe_values; displacement_hp_fe_values.reinit(cell); auto const &fe_values = displacement_hp_fe_values.get_present_fe_values(); double const alpha = this->_material_properties.get_mechanical_property( cell, StateProperty::thermal_expansion_coef); double const lambda = this->_material_properties.get_mechanical_property( cell, StateProperty::lame_first_parameter); double const mu = this->_material_properties.get_mechanical_property( cell, StateProperty::lame_second_parameter); dealii::hp::FEValues &temperature_hp_fe_values = scratch_data.temperature_hp_fe_values; temperature_hp_fe_values.reinit(temperature_cell); auto &temperature_fe_values = temperature_hp_fe_values.get_present_fe_values(); temperature_cell->get_dof_indices(temperature_local_dof_indices); copy_data.reinit(cell); dealii::Vector &cell_rhs = copy_data.cell_rhs; dealii::FEValuesExtractors::Vector const displacements(0); for (auto const q_point : fe_values.quadrature_point_indices()) { double delta_T = -reference_temperature; for (unsigned int j = 0; j < temperature_fe_values.dofs_per_cell; ++j) { delta_T += temperature_fe_values.shape_value(j, q_point) * _temperature(temperature_local_dof_indices[j]); } auto const B = (3. * lambda + 2 * mu) * alpha * delta_T; for (auto const i : fe_values.dof_indices()) { cell_rhs(i) += dealii::trace(fe_values[displacements].gradient(i, q_point)) * B * fe_values.JxW(q_point); } } }; auto copier = [&](const CopyDataRightHandSide &cd) { _affine_constraints->distribute_local_to_global( cd.cell_rhs, cd.local_dof_indices, assembled_rhs); }; dealii::IteratorFilters::ActiveFEIndexEqualTo filter( 0, /* locally owned */ true); dealii::FilteredIterator< typename dealii::DoFHandler::active_cell_iterator> begin(filter, _dof_handler->begin_active()); dealii::FilteredIterator< typename dealii::DoFHandler::active_cell_iterator> end(filter, _dof_handler->end()); dealii::hp::FEValues temperature_hp_fe_values( _thermal_dof_handler->get_fe_collection(), *_q_collection, dealii::update_values); ScratchDataRightHandSide scratch_data(displacement_hp_fe_values, temperature_hp_fe_values); dealii::MeshWorker::mesh_loop(begin, end, cell_worker, copier, scratch_data, CopyDataRightHandSide(dofs_per_cell), dealii::MeshWorker::assemble_own_cells); } // Add gravitational body force if present if (body_forces.size()) { dealii::Vector cell_rhs(dofs_per_cell); for (auto const &cell : _dof_handler->active_cell_iterators() | dealii::IteratorFilters::ActiveFEIndexEqualTo( 0, /* locally owned */ true)) { cell_rhs = 0.; displacement_hp_fe_values.reinit(cell); auto const &fe_values = displacement_hp_fe_values.get_present_fe_values(); dealii::Tensor<1, dim, double> body_force; for (auto &force : body_forces) { body_force += force->eval(cell); } dealii::FEValuesExtractors::Vector const displacements(0); for (auto const i : fe_values.dof_indices()) { for (auto const q_point : fe_values.quadrature_point_indices()) cell_rhs(i) += body_force * fe_values[displacements].value(i, q_point) * fe_values.JxW(q_point); } cell->get_dof_indices(local_dof_indices); _affine_constraints->distribute_local_to_global( cell_rhs, local_dof_indices, assembled_rhs); } } assembled_rhs.compress(dealii::VectorOperation::add); // When solving the system, we don't want ghost entries _system_rhs.reinit(_dof_handler->locally_owned_dofs(), _communicator); _system_rhs = assembled_rhs; #ifdef ADAMANTINE_WITH_CALIPER CALI_MARK_END("assemble mechanical rhs"); #endif } } // namespace adamantine INSTANTIATE_DIM_NMAT_PORDER_MATERIALSTATES_HOST(MechanicalOperator) INSTANTIATE_DIM_NMAT_PORDER_MATERIALSTATES_DEVICE(MechanicalOperator)