/* SPDX-FileCopyrightText: Copyright (c) 2022 - 2026, the adamantine authors. * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception */ #define BOOST_TEST_MODULE MechanicalOperator #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "main.cc" namespace utf = boost::unit_test; template class AffineDisplacement : public dealii::Function { public: explicit AffineDisplacement(unsigned int component) : dealii::Function(dim), _component(component) { } double value(dealii::Point const &point, unsigned int component = 0) const override { return component == _component ? point[0] : 0.; } private: unsigned int _component; }; template void right_hand_side(const std::vector> &points, std::vector> &values) { dealii::Point point_1, point_2; point_1(0) = 0.5; point_2(0) = -0.5; for (unsigned int point_n = 0; point_n < points.size(); ++point_n) { if (((points[point_n] - point_1).norm_square() < 0.2 * 0.2) || ((points[point_n] - point_2).norm_square() < 0.2 * 0.2)) values[point_n][0] = 1.0; else values[point_n][0] = 0.0; if (points[point_n].norm_square() < 0.2 * 0.2) values[point_n][1] = 1.0; else values[point_n][1] = 0.0; } } BOOST_AUTO_TEST_CASE(elastostatic, *utf::tolerance(1e-12)) { MPI_Comm communicator = MPI_COMM_WORLD; int constexpr dim = 3; // Create the Geometry boost::property_tree::ptree geometry_database; geometry_database.put("import_mesh", false); geometry_database.put("length", 6); geometry_database.put("length_divisions", 3); geometry_database.put("height", 6); geometry_database.put("height_divisions", 3); geometry_database.put("width", 6); geometry_database.put("width_divisions", 3); boost::optional units_optional_database; adamantine::Geometry geometry(communicator, geometry_database, units_optional_database); auto const &triangulation = geometry.get_triangulation(); for (auto cell : triangulation.cell_iterators()) { cell->set_material_id(0); cell->set_user_index( static_cast(adamantine::SolidLiquidPowder::State::solid)); } // Create the MaterialProperty boost::property_tree::ptree material_database; material_database.put("property_format", "polynomial"); material_database.put("n_materials", 1); double const lame_first = 2.; double const lame_second = 3.; material_database.put("material_0.solid.density", 1.); material_database.put("material_0.solid.lame_first_parameter", lame_first); material_database.put("material_0.solid.lame_second_parameter", lame_second); adamantine::MaterialProperty material_properties(communicator, triangulation, material_database); // Create the DoFHandler dealii::hp::FECollection fe_collection; fe_collection.push_back(dealii::FESystem(dealii::FE_Q(2) ^ dim)); fe_collection.push_back( dealii::FESystem(dealii::FE_Nothing() ^ dim)); dealii::DoFHandler dof_handler(geometry.get_triangulation()); dof_handler.distribute_dofs(fe_collection); dealii::AffineConstraints affine_constraints; dealii::DoFTools::make_hanging_node_constraints(dof_handler, affine_constraints); dealii::VectorTools::interpolate_boundary_values( dof_handler, 0, dealii::Functions::ZeroFunction(dim), affine_constraints); affine_constraints.close(); dealii::hp::QCollection q_collection; q_collection.push_back(dealii::QGauss(3)); q_collection.push_back(dealii::QGauss(1)); std::vector empty_vector; adamantine::MechanicalOperator mechanical_operator(communicator, material_properties, empty_vector); std::vector>> body_forces; auto gravity_force = std::make_shared>( material_properties); body_forces.push_back(gravity_force); mechanical_operator.reinit(dof_handler, affine_constraints, q_collection, body_forces); // deal.II reference implementation dealii::SparsityPattern sparsity_pattern; dealii::SparseMatrix system_matrix; { dealii::DoFHandler ref_dof_handler(geometry.get_triangulation()); dealii::FESystem fe(dealii::FE_Q(2), dim); ref_dof_handler.distribute_dofs(fe); dealii::Vector system_rhs(ref_dof_handler.n_dofs()); dealii::AffineConstraints constraints; dealii::DoFTools::make_hanging_node_constraints(ref_dof_handler, constraints); dealii::VectorTools::interpolate_boundary_values( ref_dof_handler, 0, dealii::Functions::ZeroFunction(dim), constraints); constraints.close(); dealii::DynamicSparsityPattern dsp(ref_dof_handler.n_dofs(), ref_dof_handler.n_dofs()); dealii::DoFTools::make_sparsity_pattern(ref_dof_handler, dsp, constraints, /*keep_constrained_dofs = */ false); sparsity_pattern.copy_from(dsp); system_matrix.reinit(sparsity_pattern); dealii::QGauss quadrature_formula(3); dealii::FEValues fe_values( fe, quadrature_formula, dealii::update_values | dealii::update_gradients | dealii::update_quadrature_points | dealii::update_JxW_values); unsigned int const dofs_per_cell = fe.n_dofs_per_cell(); unsigned int const n_q_points = quadrature_formula.size(); dealii::FullMatrix cell_matrix(dofs_per_cell, dofs_per_cell); dealii::Vector cell_rhs(dofs_per_cell); std::vector local_dof_indices( dofs_per_cell); std::vector lambda_values(n_q_points); std::vector mu_values(n_q_points); dealii::Functions::ConstantFunction lambda(lame_first), mu(lame_second); std::vector> rhs_values(n_q_points); for (const auto &cell : ref_dof_handler.active_cell_iterators()) { cell_matrix = 0; cell_rhs = 0; fe_values.reinit(cell); lambda.value_list(fe_values.get_quadrature_points(), lambda_values); mu.value_list(fe_values.get_quadrature_points(), mu_values); right_hand_side(fe_values.get_quadrature_points(), rhs_values); for (const unsigned int i : fe_values.dof_indices()) { const unsigned int component_i = fe.system_to_component_index(i).first; for (const unsigned int j : fe_values.dof_indices()) { const unsigned int component_j = fe.system_to_component_index(j).first; for (const unsigned int 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_values[q_point]) + (fe_values.shape_grad(i, q_point)[component_j] * fe_values.shape_grad(j, q_point)[component_i] * mu_values[q_point]) + ((component_i == component_j) ? (fe_values.shape_grad(i, q_point) * fe_values.shape_grad(j, q_point) * mu_values[q_point]) : 0)) * fe_values.JxW(q_point); } } } for (const unsigned int i : fe_values.dof_indices()) { const unsigned int component_i = fe.system_to_component_index(i).first; for (const unsigned int q_point : fe_values.quadrature_point_indices()) cell_rhs(i) += fe_values.shape_value(i, q_point) * rhs_values[q_point][component_i] * fe_values.JxW(q_point); } cell->get_dof_indices(local_dof_indices); constraints.distribute_local_to_global(cell_matrix, local_dof_indices, system_matrix); } } // Compare the results unsigned int const n_dofs = system_matrix.m(); dealii::LA::distributed::Vector src( n_dofs); dealii::LA::distributed::Vector dst_1( n_dofs); dealii::LA::distributed::Vector dst_2( n_dofs); dealii::IndexSet index_set = dealii::complete_index_set(n_dofs); #if DEAL_II_VERSION_GTE(9, 7, 0) && defined(DEAL_II_TRILINOS_WITH_TPETRA) dealii::LinearAlgebra::TpetraWrappers::Vector src_device(index_set, MPI_COMM_SELF); dealii::LinearAlgebra::TpetraWrappers::Vector dst_device(index_set, MPI_COMM_SELF); #else dealii::TrilinosWrappers::MPI::Vector src_device(index_set, MPI_COMM_SELF); dealii::TrilinosWrappers::MPI::Vector dst_device(index_set, MPI_COMM_SELF); #endif dealii::LinearAlgebra::ReadWriteVector rw_vector(index_set); for (unsigned int i = 0; i < n_dofs; ++i) { src = 0.; src[i] = 1; rw_vector.import_elements(src, dealii::VectorOperation::insert); src_device.import_elements(rw_vector, dealii::VectorOperation::insert); mechanical_operator.system_matrix().vmult(dst_device, src_device); rw_vector.import_elements(dst_device, dealii::VectorOperation::insert); dst_1.import_elements(rw_vector, dealii::VectorOperation::insert); system_matrix.vmult(dst_2, src); for (unsigned int j = 0; j < n_dofs; ++j) BOOST_TEST(dst_1[j] - dst_2[j] == 0.); } // Linear displacement: u = (x, 0, 0) gives // a(u,u) = (lambda + 2 mu) |Omega|. dealii::VectorTools::interpolate(dof_handler, AffineDisplacement(0), src); affine_constraints.distribute(src); rw_vector.import_elements(src, dealii::VectorOperation::insert); src_device.import_elements(rw_vector, dealii::VectorOperation::insert); mechanical_operator.system_matrix().vmult(dst_device, src_device); rw_vector.import_elements(dst_device, dealii::VectorOperation::insert); dst_1.import_elements(rw_vector, dealii::VectorOperation::insert); double constexpr volume = 6. * 6. * 6.; double const elastic_energy = src * dst_1; BOOST_TEST(elastic_energy == (lame_first + 2. * lame_second) * volume); // The virtual field v = (0, 0, x) gives the exact gravity work // integral_Omega rho g . v = -rho g |Omega| L / 2. dealii::VectorTools::interpolate(dof_handler, AffineDisplacement(2), src); affine_constraints.distribute(src); double const gravity_work = src * mechanical_operator.rhs(); BOOST_TEST(gravity_work == -9.80665 * volume * 6. / 2.); } BOOST_AUTO_TEST_CASE(thermoelastic, *utf::tolerance(1e-12)) { MPI_Comm communicator = MPI_COMM_WORLD; int constexpr dim = 3; // Create the Geometry boost::property_tree::ptree geometry_database; geometry_database.put("import_mesh", false); geometry_database.put("length", 6); geometry_database.put("length_divisions", 3); geometry_database.put("height", 6); geometry_database.put("height_divisions", 3); geometry_database.put("width", 6); geometry_database.put("width_divisions", 3); boost::optional units_optional_database; adamantine::Geometry geometry(communicator, geometry_database, units_optional_database); auto const &triangulation = geometry.get_triangulation(); for (auto cell : triangulation.cell_iterators()) { cell->set_material_id(0); cell->set_user_index( static_cast(adamantine::SolidLiquidPowder::State::solid)); } // Create the MaterialProperty boost::property_tree::ptree material_database; material_database.put("property_format", "polynomial"); material_database.put("n_materials", 1); double const lame_first = 2.; double const lame_second = 3.; double const thermal_expansion = 0.25; material_database.put("material_0.solid.lame_first_parameter", lame_first); material_database.put("material_0.solid.lame_second_parameter", lame_second); material_database.put("material_0.solid.thermal_expansion_coef", thermal_expansion); adamantine::MaterialProperty material_properties(communicator, triangulation, material_database); // Create the thermal DoFHandler dealii::hp::FECollection thermal_fe_collection; thermal_fe_collection.push_back(dealii::FE_Q(3)); thermal_fe_collection.push_back(dealii::FE_Nothing()); dealii::DoFHandler thermal_dof_handler(geometry.get_triangulation()); thermal_dof_handler.distribute_dofs(thermal_fe_collection); dealii::AffineConstraints thermal_affine_constraints; thermal_affine_constraints.close(); dealii::hp::QCollection<1> thermal_q_collection; thermal_q_collection.push_back(dealii::QGauss<1>(4)); thermal_q_collection.push_back(dealii::QGauss<1>(1)); // Create the mechanical DoFHandler dealii::hp::FECollection mechanical_fe_collection; mechanical_fe_collection.push_back( dealii::FESystem(dealii::FE_Q(2) ^ dim)); mechanical_fe_collection.push_back( dealii::FESystem(dealii::FE_Nothing() ^ dim)); dealii::DoFHandler mechanical_dof_handler(geometry.get_triangulation()); mechanical_dof_handler.distribute_dofs(mechanical_fe_collection); dealii::AffineConstraints mechanical_affine_constraints; dealii::DoFTools::make_hanging_node_constraints( mechanical_dof_handler, mechanical_affine_constraints); dealii::VectorTools::interpolate_boundary_values( mechanical_dof_handler, 0, dealii::Functions::ZeroFunction(dim), mechanical_affine_constraints); mechanical_affine_constraints.close(); dealii::hp::QCollection mechanical_q_collection; mechanical_q_collection.push_back(dealii::QGauss(3)); mechanical_q_collection.push_back(dealii::QGauss(1)); // Create the temperature vector dealii::LinearAlgebra::distributed::Vector temperature( thermal_dof_handler.locally_owned_dofs(), communicator); temperature = 1.; // Create the MechanicalOperator std::vector reference_temperatures = {2.0, 0.0}; adamantine::MechanicalOperator mechanical_operator(communicator, material_properties, reference_temperatures); std::vector has_melted(triangulation.n_active_cells(), false); mechanical_operator.update_temperature(thermal_dof_handler, temperature, has_melted); std::vector>> body_forces; auto gravity_force = std::make_shared>( material_properties); body_forces.push_back(gravity_force); mechanical_operator.reinit(mechanical_dof_handler, mechanical_affine_constraints, mechanical_q_collection, body_forces); dealii::LA::distributed::Vector virtual_displacement(mechanical_dof_handler.locally_owned_dofs(), communicator); dealii::VectorTools::interpolate( mechanical_dof_handler, AffineDisplacement(0), virtual_displacement); mechanical_affine_constraints.distribute(virtual_displacement); // The thermoelastic weak form gives // l(v) = integral_Omega div(v) beta (T-T_ref), with // beta = (3 lambda + 2 mu) alpha. For v = (x,0,0), div(v) = 1. double constexpr volume = 6. * 6. * 6.; double const beta = (3. * lame_first + 2. * lame_second) * thermal_expansion; double virtual_work = virtual_displacement * mechanical_operator.rhs(); BOOST_TEST(virtual_work == beta * volume); // Once a cell has melted, its reference temperature changes from the initial // substrate temperature to the material-specific reference temperature. std::fill(has_melted.begin(), has_melted.end(), true); mechanical_operator.update_temperature(thermal_dof_handler, temperature, has_melted); mechanical_operator.assemble_rhs({}); virtual_work = virtual_displacement * mechanical_operator.rhs(); BOOST_TEST(virtual_work == -beta * volume); }