#include #include #include import mean_field; import test_helpers; using namespace mean_field; namespace { struct SerialMappingData { explicit SerialMappingData(mfem::Mesh &mesh) : compactification_fes( &mesh, &compactification_fec ), compactification_coordinate(&compactification_fes), mapper(field_dof_test_utils::make_domain_mapper()) { compactification_coordinate = 0.0; } mfem::H1_FECollection compactification_fec{1, 3}; mfem::FiniteElementSpace compactification_fes; mfem::GridFunction compactification_coordinate; mapping::DomainMapper mapper; }; double compute_roche_surface_scale( const double rotation_fraction, const double sine_theta_squared ) { const double eta = (8.0 / 27.0) * rotation_fraction * rotation_fraction; double surface_scale = 1.0; for (int iteration = 0; iteration < 20; ++iteration) { const double residual = 1.0 / surface_scale + 0.5 * eta * surface_scale * surface_scale * sine_theta_squared - 1.0; const double derivative = -1.0 / (surface_scale * surface_scale) + eta * surface_scale * sine_theta_squared; surface_scale -= residual / derivative; } return surface_scale; } } // namespace TEST_CASE( "Centrifugal Integrator Matches Manufactured Cartesian Load", tags::rotation_integrator_unit ) { constexpr int dim = 3; constexpr double density = 1.7; constexpr double omega_value = 2.3; constexpr double tolerance = 1.0e-12; mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::H1_FECollection velocity_fec(1, dim); mfem::L2_FECollection density_fec(0, dim); mfem::H1_FECollection displacement_fec(1, dim); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; SerialMappingData mapping_data(mesh); mfem::Vector omega(dim); omega = 0.0; omega(2) = omega_value; integrators::CentrifugalForceIntegrator integrator( mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega ); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); quadrature::Policy policy(std::move(rule_set)); quadrature::RuleFactory quadrature_factory(std::move(policy)); const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; const int position_order = displacement_element->GetOrder(); quadrature_factory.configure_centrifugal( integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int velocity_dofs_count = velocity_element->GetDof(); const int density_dofs_count = density_element->GetDof(); mfem::Vector velocity_dofs(dim * velocity_dofs_count); mfem::Vector density_dofs(density_dofs_count); velocity_dofs = 0.0; density_dofs = density; mfem::Array elements(2); elements[0] = velocity_element; elements[1] = density_element; mfem::Array element_state(2); element_state[0] = &velocity_dofs; element_state[1] = &density_dofs; mfem::Vector velocity_residual; mfem::Vector density_residual; mfem::Array element_residual(2); element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); auto residual_action = [&](const int component, const int coordinate_weight) { mfem::Vector test_dofs(dim * velocity_dofs_count); mfem::Vector x_physical(dim); test_dofs = 0.0; const mfem::IntegrationRule &nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { transformation->Transform(nodes.IntPoint(i), x_physical); test_dofs(i + component * velocity_dofs_count) = coordinate_weight < 0 ? 1.0 : x_physical(coordinate_weight); } return test_dofs * velocity_residual; }; constexpr double force_scale = density * omega_value * omega_value; CHECK_THAT(residual_action(0, -1), Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance)); CHECK_THAT(residual_action(1, -1), Catch::Matchers::WithinAbs(-0.5 * force_scale, tolerance)); CHECK_THAT(residual_action(2, -1), Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(residual_action(0, 0), Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance)); CHECK_THAT(residual_action(1, 1), Catch::Matchers::WithinAbs(-force_scale / 3.0, tolerance)); } TEST_CASE( "Centrifugal Integrator Jacobian Matches Residual Linearization", tags::rotation_integrator_unit ) { constexpr int dim = 3; constexpr double step = 1.0e-6; constexpr double finite_difference_tolerance = 1.0e-9; constexpr double exact_tolerance = 1.0e-12; mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::H1_FECollection velocity_fec(1, dim); mfem::L2_FECollection density_fec(1, dim); mfem::H1_FECollection displacement_fec(1, dim); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; SerialMappingData mapping_data(mesh); mfem::Vector omega(dim); omega(0) = 0.7; omega(1) = -1.1; omega(2) = 1.6; integrators::CentrifugalForceIntegrator integrator( mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega ); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); quadrature::Policy policy(std::move(rule_set)); quadrature::RuleFactory quadrature_factory(std::move(policy)); const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; const int position_order = displacement_element->GetOrder(); quadrature_factory.configure_centrifugal( integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int velocity_size = dim * velocity_element->GetDof(); const int density_size = density_element->GetDof(); mfem::Vector velocity_dofs(velocity_size); mfem::Vector density_dofs(density_size); mfem::Vector velocity_direction(velocity_size); mfem::Vector density_direction(density_size); for (int i = 0; i < velocity_size; ++i) { velocity_dofs(i) = 0.03 * static_cast(i + 1); velocity_direction(i) = (i % 2 == 0 ? 0.04 : -0.02) * static_cast(i + 1); } for (int i = 0; i < density_size; ++i) { density_dofs(i) = 1.0 + 0.08 * static_cast(i + 1); density_direction(i) = (i % 2 == 0 ? 0.05 : -0.03) * static_cast(i + 1); } mfem::Array elements(2); elements[0] = velocity_element; elements[1] = density_element; auto assemble_velocity_residual = [&](const mfem::Vector &velocity, const mfem::Vector &density) { mfem::Array element_state(2); element_state[0] = &velocity; element_state[1] = &density; mfem::Vector velocity_residual; mfem::Vector density_residual; mfem::Array element_residual(2); element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); return velocity_residual; }; mfem::Array element_state(2); element_state[0] = &velocity_dofs; element_state[1] = &density_dofs; mfem::DenseMatrix dv_dv(velocity_size, velocity_size); mfem::DenseMatrix dv_drho(velocity_size, density_size); mfem::DenseMatrix drho_dv(density_size, velocity_size); mfem::DenseMatrix drho_drho(density_size, density_size); mfem::Array2D element_jacobian(2, 2); element_jacobian(0, 0) = &dv_dv; element_jacobian(0, 1) = &dv_drho; element_jacobian(1, 0) = &drho_dv; element_jacobian(1, 1) = &drho_drho; integrator.AssembleElementGrad(elements, *transformation, element_state, element_jacobian); mfem::Vector velocity_plus(velocity_dofs); mfem::Vector velocity_minus(velocity_dofs); mfem::Vector density_plus(density_dofs); mfem::Vector density_minus(density_dofs); velocity_plus.Add(step, velocity_direction); velocity_minus.Add(-step, velocity_direction); density_plus.Add(step, density_direction); density_minus.Add(-step, density_direction); mfem::Vector residual_plus = assemble_velocity_residual(velocity_plus, density_plus); mfem::Vector residual_minus = assemble_velocity_residual(velocity_minus, density_minus); mfem::Vector finite_difference(residual_plus); finite_difference -= residual_minus; finite_difference /= 2.0 * step; mfem::Vector jacobian_action(velocity_size); mfem::Vector velocity_block_action(velocity_size); mfem::Vector density_block_action(velocity_size); dv_dv.Mult(velocity_direction, velocity_block_action); dv_drho.Mult(density_direction, density_block_action); add(velocity_block_action, density_block_action, jacobian_action); mfem::Vector finite_difference_error(jacobian_action); finite_difference_error -= finite_difference; const double finite_difference_scale = std::max(1.0, finite_difference.Norml2()); const double relative_finite_difference_error = finite_difference_error.Norml2() / finite_difference_scale; CHECK_THAT(relative_finite_difference_error, Catch::Matchers::WithinAbs(0.0, finite_difference_tolerance)); CHECK_THAT(velocity_block_action.Norml2(), Catch::Matchers::WithinAbs(0.0, exact_tolerance)); mfem::Vector density_direction_residual = assemble_velocity_residual(velocity_dofs, density_direction); mfem::Vector density_linearity_error(density_block_action); density_linearity_error -= density_direction_residual; CHECK_THAT(density_linearity_error.Norml2(), Catch::Matchers::WithinAbs(0.0, exact_tolerance)); double inactive_block_maximum = 0.0; for (int i = 0; i < drho_dv.Height(); ++i) { for (int j = 0; j < drho_dv.Width(); ++j) { inactive_block_maximum = std::max(inactive_block_maximum, std::abs(drho_dv(i, j))); } } for (int i = 0; i < drho_drho.Height(); ++i) { for (int j = 0; j < drho_drho.Width(); ++j) { inactive_block_maximum = std::max(inactive_block_maximum, std::abs(drho_drho(i, j))); } } CHECK_THAT(inactive_block_maximum, Catch::Matchers::WithinAbs(0.0, exact_tolerance)); } TEST_CASE( "Centrifugal Integrator Preserves Rotation Identities", tags::rotation_integrator_unit ) { constexpr int dim = 3; constexpr double density = 1.4; constexpr double omega_scale = 2.3; constexpr double tolerance = 1.0e-12; mfem::Mesh mesh = mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON, 1.0, 1.0, 1.0); mfem::H1_FECollection velocity_fec(1, dim); mfem::L2_FECollection density_fec(0, dim); mfem::H1_FECollection displacement_fec(1, dim); mfem::FiniteElementSpace velocity_fes(&mesh, &velocity_fec); mfem::FiniteElementSpace density_fes(&mesh, &density_fec); mfem::FiniteElementSpace displacement_fes(&mesh, &displacement_fec, dim, mfem::Ordering::byVDIM); mfem::GridFunction displacement(&displacement_fes); displacement = 0.0; SerialMappingData mapping_data(mesh); mfem::Vector omega(dim); omega(0) = 0.7; omega(1) = -1.1; omega(2) = 1.6; integrators::CentrifugalForceIntegrator integrator( mapping_data.mapper, displacement, mapping_data.compactification_coordinate, omega ); const mfem::FiniteElement *velocity_element = velocity_fes.GetFE(0); const mfem::FiniteElement *density_element = density_fes.GetFE(0); const mfem::FiniteElement *displacement_element = displacement_fes.GetFE(0); mfem::ElementTransformation *transformation = mesh.GetElementTransformation(0); quadrature::RuleSet rule_set = quadrature::make_rule_set(quadrature::Mode::production); quadrature::Policy policy(std::move(rule_set)); quadrature::RuleFactory quadrature_factory(std::move(policy)); const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; const int position_order = displacement_element->GetOrder(); quadrature_factory.configure_centrifugal( integrator, quadrature::QuadratureRole::discretization, *density_element, *velocity_element, *transformation, position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int velocity_dofs_count = velocity_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; const int density_size = density_element->GetDof(); mfem::Vector velocity_dofs(velocity_size); mfem::Vector density_dofs(density_size); velocity_dofs = 0.0; density_dofs = density; mfem::Array elements(2); elements[0] = velocity_element; elements[1] = density_element; auto assemble_velocity_residual = [&](const mfem::Vector &rotation) { integrator.SetOmega(rotation); mfem::Array element_state(2); element_state[0] = &velocity_dofs; element_state[1] = &density_dofs; mfem::Vector velocity_residual; mfem::Vector density_residual; mfem::Array element_residual(2); element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); return velocity_residual; }; const mfem::Vector baseline_residual = assemble_velocity_residual(omega); mfem::Vector zero_omega(dim); zero_omega = 0.0; const mfem::Vector zero_residual = assemble_velocity_residual(zero_omega); CHECK_THAT(zero_residual.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); mfem::Vector negative_omega(omega); negative_omega *= -1.0; mfem::Vector sign_error = assemble_velocity_residual(negative_omega); sign_error -= baseline_residual; CHECK_THAT(sign_error.Norml2(), Catch::Matchers::WithinAbs(0.0, tolerance)); mfem::Vector scaled_omega(omega); scaled_omega *= omega_scale; mfem::Vector expected_scaled_residual(baseline_residual); expected_scaled_residual *= omega_scale * omega_scale; mfem::Vector scaling_error = assemble_velocity_residual(scaled_omega); scaling_error -= expected_scaled_residual; const double scaling_error_relative = scaling_error.Norml2() / std::max(1.0, expected_scaled_residual.Norml2()); CHECK_THAT(scaling_error_relative, Catch::Matchers::WithinAbs(0.0, tolerance)); mfem::Vector axis_test_dofs(velocity_size); mfem::Vector torque_test_dofs(velocity_size); mfem::Vector x_physical(dim); mfem::Vector azimuthal_direction(dim); axis_test_dofs = 0.0; torque_test_dofs = 0.0; const mfem::IntegrationRule &nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { transformation->Transform(nodes.IntPoint(i), x_physical); azimuthal_direction(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); azimuthal_direction(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); azimuthal_direction(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); for (int c = 0; c < dim; ++c) { axis_test_dofs(i + c * velocity_dofs_count) = omega(c); torque_test_dofs(i + c * velocity_dofs_count) = azimuthal_direction(c); } } const double axial_force = axis_test_dofs * baseline_residual; const double axial_torque = torque_test_dofs * baseline_residual; CHECK_THAT(axial_force, Catch::Matchers::WithinAbs(0.0, tolerance)); CHECK_THAT(axial_torque, Catch::Matchers::WithinAbs(0.0, tolerance)); } TEST_CASE( "Centrifugal Integrator Matches Rotational Virial On Roche Mappings", tags::rotation_integrator_integration ) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); constexpr int dim = 3; constexpr double concentration = 4.0; constexpr double assembly_tolerance = 1.0e-7; constexpr double position_tolerance = 1.0e-6; const double radius = utils::RADIUS; auto reference_density = [radius](const mfem::Vector &x) { const double normalized_radius_squared = (x * x) / (radius * radius); if (normalized_radius_squared >= 1.0) { return 0.0; } const double denominator = 1.0 + concentration * normalized_radius_squared; return (1.0 - normalized_radius_squared) / (denominator * denominator); }; mfem::FunctionCoefficient density_coefficient(reference_density); mfem::ParGridFunction density(f.densityFes.get()); density.ProjectCoefficient(density_coefficient); mfem::ParGridFunction displacement(f.displacementFes.get()); const mfem::FiniteElement &representative_velocity_element = *f.displacementFes->GetTypicalFE(); const mfem::FiniteElement &representative_density_element = *f.densityFes->GetTypicalFE(); mfem::ElementTransformation &representative_transformation = *f.mesh->GetElementTransformation(0); const int position_order = f.displacementFes->GetMaxElementOrder(); for (constexpr std::array rotation_fractions = {0.0001, 0.1, 0.25, 0.50, 0.70, 0.85, 0.95, 0.99}; const double rotation_fraction : rotation_fractions) { CAPTURE(rotation_fraction); auto rotation_displacement = [radius, rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(dim); const double radius_squared = x * x; if (radius_squared <= 1.0e-28) { displacement_value = 0.0; return; } const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1); const double sine_theta_squared = cylindrical_radius_squared / radius_squared; const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared); const double radial_weight = std::min(radius_squared / (radius * radius), 1.0); const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0); for (int d = 0; d < dim; ++d) { displacement_value(d) = (mapped_scale - 1.0) * x(d); } }; mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); displacement.ProjectCoefficient(displacement_coefficient); *f.displacement = displacement; mapping::GridFunctionMappingEvaluator mapping_evaluator( *f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate ); mfem::Vector omega(dim); omega = 0.0; omega(2) = rotation_fraction; integrators::CentrifugalForceIntegrator integrator( *f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate, omega ); const quadrature::MappingKind mapping_kind = quadrature::MappingKind::general; f.quadratureFactory->configure_centrifugal( integrator, quadrature::QuadratureRole::discretization, representative_density_element, representative_velocity_element, representative_transformation, position_order, utils::DOMAINS::STELLAR, mapping_kind ); const int reference_order = 2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16; double local_residual_action = 0.0; double local_discrete_reference_action = 0.0; double local_continuous_reference_action = 0.0; double local_minimum_map_determinant = std::numeric_limits::infinity(); double local_maximum_map_determinant = std::numeric_limits::lowest(); for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) { if (f.mesh->GetAttribute(elem_id) == 3) { continue; } const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id); const mfem::FiniteElement *density_element = f.densityFes->GetFE(elem_id); mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); const int velocity_dofs_count = velocity_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; const int density_size = density_element->GetDof(); mfem::Array density_dof_indices; mfem::Vector density_dofs; f.densityFes->GetElementDofs(elem_id, density_dof_indices); density.GetSubVector(density_dof_indices, density_dofs); mfem::Vector velocity_dofs(velocity_size); velocity_dofs = 0.0; mfem::Array elements(2); elements[0] = velocity_element; elements[1] = density_element; mfem::Array element_state(2); element_state[0] = &velocity_dofs; element_state[1] = &density_dofs; mfem::Vector velocity_residual(velocity_size); mfem::Vector density_residual(density_size); velocity_residual = 0.0; density_residual = 0.0; mfem::Array element_residual(2); element_residual[0] = &velocity_residual; element_residual[1] = &density_residual; integrator.AssembleElementVector(elements, *transformation, element_state, element_residual); mfem::Vector position_test_dofs(velocity_size); mfem::Vector x_physical(dim); position_test_dofs = 0.0; const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical); for (int d = 0; d < dim; ++d) { position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); } } local_residual_action += position_test_dofs * velocity_residual; mfem::Vector velocity_shape(velocity_dofs_count); mfem::Vector position_test_value(dim); mfem::Vector omega_cross_position(dim); mfem::Vector centrifugal_acceleration(dim); const mfem::IntegrationRule &reference_rule = mfem::IntRules.Get(transformation->GetGeometryType(), reference_order); for (int q = 0; q < reference_rule.GetNPoints(); ++q) { const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); const mapping::VolumeQuadratureContext context = mapping_evaluator.GetQuadratureContext(*transformation, integration_point); const double signed_map_determinant = context.detJ; local_minimum_map_determinant = std::min(local_minimum_map_determinant, signed_map_determinant); local_maximum_map_determinant = std::max(local_maximum_map_determinant, signed_map_determinant); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical); velocity_element->CalcShape(integration_point, velocity_shape); position_test_value = 0.0; for (int i = 0; i < velocity_dofs_count; ++i) { for (int d = 0; d < dim; ++d) { position_test_value(d) += position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i); } } omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); centrifugal_acceleration(0) = omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1); centrifugal_acceleration(1) = omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2); centrifugal_acceleration(2) = omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0); const double density_value = density.GetValue(elem_id, integration_point); local_discrete_reference_action += density_value * (position_test_value * centrifugal_acceleration) * context.weight; local_continuous_reference_action += density_value * (x_physical * centrifugal_acceleration) * context.weight; } } double global_residual_action = 0.0; double global_discrete_reference_action = 0.0; double global_continuous_reference_action = 0.0; double global_minimum_map_determinant = 0.0; double global_maximum_map_determinant = 0.0; MPI_Comm communicator = f.mesh->GetComm(); MPI_Allreduce(&local_residual_action, &global_residual_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( &local_discrete_reference_action, &global_discrete_reference_action, 1, MPI_DOUBLE, MPI_SUM, communicator ); MPI_Allreduce( &local_continuous_reference_action, &global_continuous_reference_action, 1, MPI_DOUBLE, MPI_SUM, communicator ); MPI_Allreduce( &local_minimum_map_determinant, &global_minimum_map_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); MPI_Allreduce( &local_maximum_map_determinant, &global_maximum_map_determinant, 1, MPI_DOUBLE, MPI_MAX, communicator ); const double relative_assembly_error = std::abs(global_residual_action - global_discrete_reference_action) / std::abs(global_discrete_reference_action); const double relative_position_error = std::abs(global_discrete_reference_action - global_continuous_reference_action) / std::abs(global_continuous_reference_action); const double equatorial_scale = compute_roche_surface_scale(rotation_fraction, 1.0); INFO("Rotation fraction = " << rotation_fraction); INFO("Roche equatorial scale = " << equatorial_scale); INFO("Minimum mapping determinant = " << global_minimum_map_determinant); INFO("Maximum mapping determinant = " << global_maximum_map_determinant); INFO("Assembled centrifugal virial = " << global_residual_action); INFO("Discrete reference virial = " << global_discrete_reference_action); INFO("Continuous reference virial = " << global_continuous_reference_action); INFO("Relative assembly error = " << relative_assembly_error); INFO("Relative position representation error = " << relative_position_error); REQUIRE(equatorial_scale > 1.0); REQUIRE(global_minimum_map_determinant > 0.0); CHECK_THAT(relative_assembly_error, Catch::Matchers::WithinAbs(0.0, assembly_tolerance)); CHECK_THAT(relative_position_error, Catch::Matchers::WithinAbs(0.0, position_tolerance)); } *f.displacement = 0.0; } TEST_CASE( "Centrifugal Virial Position Representation Is Consistent At The " "Registered Order", tags::rotation_integrator_integration ) { constexpr int dim = 3; constexpr double concentration = 4.0; constexpr std::array velocity_orders = {field::Displacement::Vector::familyOrder}; constexpr std::array rotation_fractions = {0.70, 0.99}; std::array, rotation_fractions.size()> position_errors{}; std::array, rotation_fractions.size()> minimum_determinants{}; for (std::size_t order_index = 0; order_index < velocity_orders.size(); ++order_index) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, 0); constexpr double radius = utils::RADIUS; auto reference_density = [radius](const mfem::Vector &x) { const double normalized_radius_squared = (x * x) / (radius * radius); if (normalized_radius_squared >= 1.0) { return 0.0; } const double denominator = 1.0 + concentration * normalized_radius_squared; return (1.0 - normalized_radius_squared) / (denominator * denominator); }; mfem::FunctionCoefficient density_coefficient(reference_density); mfem::ParGridFunction density(f.densityFes.get()); density.ProjectCoefficient(density_coefficient); mfem::ParGridFunction displacement(f.displacementFes.get()); for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { const double rotation_fraction = rotation_fractions[rotation_index]; auto rotation_displacement = [radius, rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(dim); const double radius_squared = x * x; if (radius_squared <= 1.0e-28) { displacement_value = 0.0; return; } const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1); const double sine_theta_squared = cylindrical_radius_squared / radius_squared; const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared); const double radial_weight = std::min(radius_squared / (radius * radius), 1.0); const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0); for (int d = 0; d < dim; ++d) { displacement_value(d) = (mapped_scale - 1.0) * x(d); } }; mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); displacement.ProjectCoefficient(displacement_coefficient); *f.displacement = displacement; mapping::GridFunctionMappingEvaluator mapping_evaluator( *f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate ); mfem::Vector omega(dim); omega = 0.0; omega(2) = rotation_fraction; const int reference_order = 2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16; double local_discrete_action = 0.0; double local_continuous_action = 0.0; double local_minimum_determinant = std::numeric_limits::infinity(); for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) { if (f.mesh->GetAttribute(elem_id) == 3) { continue; } const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id); mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); const int velocity_dofs_count = velocity_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; mfem::Vector position_test_dofs(velocity_size); mfem::Vector x_physical(dim); position_test_dofs = 0.0; const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical); for (int d = 0; d < dim; ++d) { position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); } } mfem::Vector velocity_shape(velocity_dofs_count); mfem::Vector position_test_value(dim); mfem::Vector omega_cross_position(dim); mfem::Vector centrifugal_acceleration(dim); const mfem::IntegrationRule &reference_rule = mfem::IntRules.Get(transformation->GetGeometryType(), reference_order); for (int q = 0; q < reference_rule.GetNPoints(); ++q) { const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); const mapping::VolumeQuadratureContext context = mapping_evaluator.GetQuadratureContext(*transformation, integration_point); const double signed_map_determinant = context.detJ; local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical); velocity_element->CalcShape(integration_point, velocity_shape); position_test_value = 0.0; for (int i = 0; i < velocity_dofs_count; ++i) { for (int d = 0; d < dim; ++d) { position_test_value(d) += position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i); } } omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); centrifugal_acceleration(0) = omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1); centrifugal_acceleration(1) = omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2); centrifugal_acceleration(2) = omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0); const double density_value = density.GetValue(elem_id, integration_point); local_discrete_action += density_value * (position_test_value * centrifugal_acceleration) * context.weight; local_continuous_action += density_value * (x_physical * centrifugal_acceleration) * context.weight; } } double global_discrete_action = 0.0; double global_continuous_action = 0.0; double global_minimum_determinant = 0.0; MPI_Comm communicator = f.mesh->GetComm(); MPI_Allreduce(&local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce(&local_continuous_action, &global_continuous_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( &local_minimum_determinant, &global_minimum_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); position_errors[rotation_index][order_index] = std::abs(global_discrete_action - global_continuous_action) / std::abs(global_continuous_action); minimum_determinants[rotation_index][order_index] = global_minimum_determinant; } *f.displacement = 0.0; } for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { constexpr double consistency_tolerance = 1.0e-1; const double registered_order_error = position_errors[rotation_index][0]; CAPTURE(rotation_fractions[rotation_index]); INFO("Registered displacement family order = " << velocity_orders.front()); INFO("Position error = " << registered_order_error); INFO("Minimum determinant = " << minimum_determinants[rotation_index][0]); REQUIRE(minimum_determinants[rotation_index][0] > 0.0); REQUIRE(std::isfinite(registered_order_error)); CHECK(registered_order_error < consistency_tolerance); } } TEST_CASE( "Centrifugal Virial Position Representation Converges Under H Refinement", tags::rotation_integrator_convergence ) { constexpr int dim = 3; constexpr double concentration = 4.0; constexpr double minimum_rate = 1.5; constexpr double finest_level_tolerance = 1.0e-5; constexpr std::array refinement_levels = {0, 1, 2}; constexpr std::array rotation_fractions = {0.70, 0.85}; std::array, rotation_fractions.size()> position_errors{}; std::array, rotation_fractions.size()> minimum_determinants{}; for (std::size_t refinement_index = 0; refinement_index < refinement_levels.size(); ++refinement_index) { auto args = test_utils::setup_args(); fem::FEM f = fem::setup_fem(args.mesh_file, args, refinement_levels[refinement_index]); const double radius = utils::RADIUS; auto reference_density = [radius](const mfem::Vector &x) { const double normalized_radius_squared = (x * x) / (radius * radius); if (normalized_radius_squared >= 1.0) { return 0.0; } const double denominator = 1.0 + concentration * normalized_radius_squared; return (1.0 - normalized_radius_squared) / (denominator * denominator); }; mfem::FunctionCoefficient density_coefficient(reference_density); mfem::ParGridFunction density(f.densityFes.get()); density.ProjectCoefficient(density_coefficient); mfem::ParGridFunction displacement(f.displacementFes.get()); for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { const double rotation_fraction = rotation_fractions[rotation_index]; auto rotation_displacement = [radius, rotation_fraction](const mfem::Vector &x, mfem::Vector &displacement_value) { displacement_value.SetSize(dim); const double radius_squared = x * x; if (radius_squared <= 1.0e-28) { displacement_value = 0.0; return; } const double cylindrical_radius_squared = x(0) * x(0) + x(1) * x(1); const double sine_theta_squared = cylindrical_radius_squared / radius_squared; const double surface_scale = compute_roche_surface_scale(rotation_fraction, sine_theta_squared); const double radial_weight = std::min(radius_squared / (radius * radius), 1.0); const double mapped_scale = 1.0 + radial_weight * (surface_scale - 1.0); for (int d = 0; d < dim; ++d) { displacement_value(d) = (mapped_scale - 1.0) * x(d); } }; mfem::VectorFunctionCoefficient displacement_coefficient(dim, rotation_displacement); displacement.ProjectCoefficient(displacement_coefficient); *f.displacement = displacement; mapping::GridFunctionMappingEvaluator mapping_evaluator( *f.domainMapperStateless, *f.displacement, *f.compactificationCoordinate ); mfem::Vector omega(dim); omega = 0.0; omega(2) = rotation_fraction; const int reference_order = 2 * std::max(f.displacementFes->GetMaxElementOrder(), f.densityFes->GetMaxElementOrder()) + 16; double local_discrete_action = 0.0; double local_continuous_action = 0.0; double local_minimum_determinant = std::numeric_limits::infinity(); for (int elem_id = 0; elem_id < f.mesh->GetNE(); ++elem_id) { if (f.mesh->GetAttribute(elem_id) == 3) { continue; } const mfem::FiniteElement *velocity_element = f.displacementFes->GetFE(elem_id); mfem::ElementTransformation *transformation = f.mesh->GetElementTransformation(elem_id); const int velocity_dofs_count = velocity_element->GetDof(); const int velocity_size = dim * velocity_dofs_count; mfem::Vector position_test_dofs(velocity_size); mfem::Vector x_physical(dim); position_test_dofs = 0.0; const mfem::IntegrationRule &velocity_nodes = velocity_element->GetNodes(); for (int i = 0; i < velocity_dofs_count; ++i) { const mfem::IntegrationPoint &node = velocity_nodes.IntPoint(i); transformation->SetIntPoint(&node); mapping_evaluator.GetPhysicalPoint(*transformation, node, x_physical); for (int d = 0; d < dim; ++d) { position_test_dofs(i + d * velocity_dofs_count) = x_physical(d); } } mfem::Vector velocity_shape(velocity_dofs_count); mfem::Vector position_test_value(dim); mfem::Vector omega_cross_position(dim); mfem::Vector centrifugal_acceleration(dim); const mfem::IntegrationRule &reference_rule = mfem::IntRules.Get(transformation->GetGeometryType(), reference_order); for (int q = 0; q < reference_rule.GetNPoints(); ++q) { const mfem::IntegrationPoint &integration_point = reference_rule.IntPoint(q); transformation->SetIntPoint(&integration_point); const mapping::VolumeQuadratureContext context = mapping_evaluator.GetQuadratureContext(*transformation, integration_point); const double signed_map_determinant = context.detJ; local_minimum_determinant = std::min(local_minimum_determinant, signed_map_determinant); mapping_evaluator.GetPhysicalPoint(*transformation, integration_point, x_physical); velocity_element->CalcShape(integration_point, velocity_shape); position_test_value = 0.0; for (int i = 0; i < velocity_dofs_count; ++i) { for (int d = 0; d < dim; ++d) { position_test_value(d) += position_test_dofs(i + d * velocity_dofs_count) * velocity_shape(i); } } omega_cross_position(0) = omega(1) * x_physical(2) - omega(2) * x_physical(1); omega_cross_position(1) = omega(2) * x_physical(0) - omega(0) * x_physical(2); omega_cross_position(2) = omega(0) * x_physical(1) - omega(1) * x_physical(0); centrifugal_acceleration(0) = omega(1) * omega_cross_position(2) - omega(2) * omega_cross_position(1); centrifugal_acceleration(1) = omega(2) * omega_cross_position(0) - omega(0) * omega_cross_position(2); centrifugal_acceleration(2) = omega(0) * omega_cross_position(1) - omega(1) * omega_cross_position(0); const double density_value = density.GetValue(elem_id, integration_point); local_discrete_action += density_value * (position_test_value * centrifugal_acceleration) * context.weight; local_continuous_action += density_value * (x_physical * centrifugal_acceleration) * context.weight; } } double global_discrete_action = 0.0; double global_continuous_action = 0.0; double global_minimum_determinant = 0.0; MPI_Comm communicator = f.mesh->GetComm(); MPI_Allreduce(&local_discrete_action, &global_discrete_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce(&local_continuous_action, &global_continuous_action, 1, MPI_DOUBLE, MPI_SUM, communicator); MPI_Allreduce( &local_minimum_determinant, &global_minimum_determinant, 1, MPI_DOUBLE, MPI_MIN, communicator ); position_errors[rotation_index][refinement_index] = std::abs(global_discrete_action - global_continuous_action) / std::abs(global_continuous_action); minimum_determinants[rotation_index][refinement_index] = global_minimum_determinant; } *f.displacement = 0.0; } for (std::size_t rotation_index = 0; rotation_index < rotation_fractions.size(); ++rotation_index) { const double error_h = position_errors[rotation_index][0]; const double error_h2 = position_errors[rotation_index][1]; const double error_h4 = position_errors[rotation_index][2]; REQUIRE(error_h > 0.0); REQUIRE(error_h2 > 0.0); REQUIRE(error_h4 > 0.0); const double rate_h_h2 = std::log(error_h / error_h2) / std::log(2.0); const double rate_h2_h4 = std::log(error_h2 / error_h4) / std::log(2.0); CAPTURE(rotation_fractions[rotation_index]); INFO("Level 0 position error = " << error_h); INFO("Level 1 position error = " << error_h2); INFO("Level 2 position error = " << error_h4); INFO("Level 0 to 1 reduction = " << error_h / error_h2); INFO("Level 1 to 2 reduction = " << error_h2 / error_h4); INFO("Observed level 0 to 1 rate = " << rate_h_h2); INFO("Observed level 1 to 2 rate = " << rate_h2_h4); INFO("Level 0 minimum determinant = " << minimum_determinants[rotation_index][0]); INFO("Level 1 minimum determinant = " << minimum_determinants[rotation_index][1]); INFO("Level 2 minimum determinant = " << minimum_determinants[rotation_index][2]); REQUIRE(minimum_determinants[rotation_index][0] > 0.0); REQUIRE(minimum_determinants[rotation_index][1] > 0.0); REQUIRE(minimum_determinants[rotation_index][2] > 0.0); CHECK(error_h2 < error_h); CHECK(error_h4 < error_h2); CHECK(rate_h_h2 > minimum_rate); CHECK(rate_h2_h4 > minimum_rate); CHECK_THAT(error_h4, Catch::Matchers::WithinAbs(0.0, finest_level_tolerance)); } }