module; #include "mfem.hpp" #include #include #include #include #include module mean_field; import :mapping.coefficients; import :analysis.integral; namespace { double centrifugal_potential(const mfem::Vector &phys_x, const double omega) { const double s2 = std::pow(phys_x(0), 2) + std::pow(phys_x(1), 2); return -0.5 * s2 * std::pow(omega, 2); } } namespace mean_field::physics { GravitySolution grav_potential( fem::FEM &f, const utils::Args &args, const mfem::GridFunction &rho, const bool phi_warm ) { mfem::Array outer_bdr_marker(f.mesh->bdr_attributes.Max()); outer_bdr_marker = 0; outer_bdr_marker[1] = 1; mfem::ParLinearForm g_rhs(f.RT_fes.get()); // ReSharper disable once CppTooWideScope std::unique_ptr boundary_potential_coeff; if (!f.has_mapping()) { // We only need to explicitly add a boundary integrator if a mapping is not being used. In the case where the outer domain has been compactified the φ=0 boundary condition is the natural condition and MFEM automatically handles this auto boundary_potential = [&f](const mfem::Vector& x_physical) { return l2_multipole_potential(f, utils::MASS, x_physical); }; boundary_potential_coeff = std::make_unique(boundary_potential); auto boundary_integrator = std::make_unique(*boundary_potential_coeff); const mfem::FiniteElement& boundary_element = *f.RT_fes->GetTypicalTraceElement(); f.quadrature_factory->configure_gravity_boundary(*boundary_integrator, quadrature::QuadratureRole::discretization, boundary_element, utils::DOMAINS::VACUUM, quadrature::MappingKind::none); g_rhs.AddBoundaryIntegrator(boundary_integrator.release(), outer_bdr_marker); } g_rhs.Assemble(); mfem::GridFunctionCoefficient rho_coeff(&rho); mfem::ConstantCoefficient G4pi(4.0 * M_PI * utils::G); mfem::ProductCoefficient source_coeff(G4pi, rho_coeff); mfem::ParLinearForm f_rhs(f.L2_fes.get()); std::unique_ptr mapped_source_coeff; mfem::Coefficient* active_source_coeff = &source_coeff; quadrature::MappingKind source_mapping_kind = quadrature::MappingKind::none; if (f.has_mapping()) { mapped_source_coeff = std::make_unique(*f.mapping, source_coeff); active_source_coeff = mapped_source_coeff.get(); source_mapping_kind = quadrature::MappingKind::general; } auto source_integrator = std::make_unique(*active_source_coeff); const mfem::FiniteElement& source_test_element = *f.L2_fes->GetTypicalFE(); const mfem::ElementTransformation& source_transformation = *f.mesh->GetElementTransformation(0); const int source_coefficient_order = f.L2_fes->GetMaxElementOrder(); f.quadrature_factory->configure_gravity_source(*source_integrator, quadrature::QuadratureRole::discretization, source_test_element, source_transformation, source_coefficient_order, utils::DOMAINS::STELLAR, source_mapping_kind); f_rhs.AddDomainIntegrator(source_integrator.release(), f.gravity_context.stellar_mask); f_rhs.Assemble(); mfem::BlockVector RHS(f.gravity_block_true_offsets); RHS.GetBlock(0) = *g_rhs.ParallelAssemble(); RHS.GetBlock(1) = *f_rhs.ParallelAssemble(); mfem::BlockVector X(f.gravity_block_true_offsets); X = 0.0; f.gravity_context.minres->SetOperator(*f.gravity_context.block_A); f.gravity_context.minres->Mult(RHS, X); GravitySolution solution(f); solution.gradPhi.SetFromTrueDofs(X.GetBlock(0)); solution.phi.SetFromTrueDofs(X.GetBlock(1)); return solution; } mfem::GridFunction get_potential( fem::FEM &fem, const utils::Args &args, const mfem::GridFunction &rho, const bool warm ) { auto phi = grav_potential(fem, args, rho, warm); if (args.r.enabled) { auto rot = [&fem, &args](const mfem::Vector &x) { mfem::Vector rel_x = x; rel_x -= fem.com; return centrifugal_potential(rel_x, args.r.omega); }; std::unique_ptr centrifugal_coeff; if (fem.has_mapping()) { centrifugal_coeff = std::make_unique(*fem.mapping, rot); } else { centrifugal_coeff = std::make_unique(rot); } mfem::GridFunction centrifugal_gf(fem.H1_fes.get()); centrifugal_gf.ProjectCoefficient(*centrifugal_coeff); phi.phi += centrifugal_gf; } return phi.phi; } mfem::DenseMatrix compute_quadrupole_moment_tensor( const fem::FEM &fem, const mfem::GridFunction &rho, const mfem::Vector &com ) { const int dim = fem.mesh->Dimension(); mfem::DenseMatrix local_Q(dim, dim); local_Q = 0.0; for (int i = 0; i < fem.H1_fes->GetNE(); ++i) { if (fem.mesh->GetAttribute(i) == 3) continue; mfem::ElementTransformation *trans = fem.mesh->GetElementTransformation(i); const mfem::IntegrationRule &ir = *fem.int_rule; for (int j = 0; j < ir.GetNPoints(); ++j) { const mfem::IntegrationPoint &ip = ir.IntPoint(j); trans->SetIntPoint(&ip); double weight = trans->Weight() * ip.weight; if (fem.has_mapping()) { weight *= fem.mapping->ComputeDetJ(*trans, ip); } const double rho_val = rho.GetValue(i, ip); mfem::Vector phys_point(dim); if (fem.has_mapping()) { fem.mapping->GetPhysicalPoint(*trans, ip, phys_point); } else { trans->Transform(ip, phys_point); } mfem::Vector x_prime(dim); double r_sq = 0.0; for (int d = 0; d < dim; ++d) { x_prime(d) = phys_point(d) - com(d); r_sq += x_prime(d) * x_prime(d); } for (int m = 0; m < dim; ++m) { for (int n = 0; n < dim; ++n) { const double delta = (m == n) ? 1.0 : 0.0; const double contrib = 3.0 * x_prime(m) * x_prime(n) - delta * r_sq; local_Q(m, n) += rho_val * contrib * weight; } } } } mfem::DenseMatrix global_Q(dim, dim); MPI_Allreduce(local_Q.GetData(), global_Q.GetData(), dim * dim, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm()); return global_Q; } double l2_multipole_potential( const fem::FEM &fem, const double total_mass, const mfem::Vector &phys_x ) { const double r = phys_x.Norml2(); if (r < 1e-12) return 0.0; const int dim = fem.mesh->Dimension(); mfem::Vector n(phys_x); n /= r; double l2_mult_factor = 0.0; for (int i = 0; i < dim; ++i) { for (int j = 0; j < dim; ++j) { l2_mult_factor += fem.Q(i, j) * n(i) * n(j); } } const double l2_contrib = -(utils::G / (2.0 * std::pow(r, 3))) * l2_mult_factor; const double l0_contrib = -utils::G * total_mass / r; // l1 contribution is zero for a system centered on its COM return l0_contrib + l2_contrib; } void update_stiffness_matrix(fem::FEM &f) { mfem::Array empty_tdofs; // ========================================== // 1. Partially Assemble the High-Order Mass Block // ========================================== f.gravity_context.m_form = std::make_unique(f.RT_fes.get()); f.gravity_context.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); std::unique_ptr hdiv_mass_integrator; if (f.has_mapping()) { f.gravity_context.mapped_hdiv_mass_coeff = std::make_unique(*f.mapping, f.mesh->Dimension()); hdiv_mass_integrator = std::make_unique(*f.gravity_context.mapped_hdiv_mass_coeff); } else { f.gravity_context.mapped_hdiv_mass_coeff.reset(); hdiv_mass_integrator = std::make_unique(); } const mfem::FiniteElement& hdiv_element = *f.RT_fes->GetTypicalFE(); const mfem::ElementTransformation& hdiv_transformation = *f.mesh->GetElementTransformation(0); const quadrature::MappingKind mapping_kind = f.has_mapping() ? quadrature::MappingKind::general : quadrature::MappingKind::none; f.quadrature_factory->configure_gravity_hdiv_mass(*hdiv_mass_integrator, quadrature::QuadratureRole::discretization, hdiv_element, hdiv_transformation, utils::DOMAINS::ALL, mapping_kind); f.gravity_context.m_form->AddDomainIntegrator(hdiv_mass_integrator.release()); f.gravity_context.m_form->Assemble(); // ========================================== // 2. Partially Assemble the High-Order Divergence Block // ========================================== f.gravity_context.b_form = std::make_unique(f.RT_fes.get(), f.L2_fes.get()); f.gravity_context.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL); auto divergence_discretization_integrator = std::make_unique(); const mfem::FiniteElement& divergence_discretization_test_element = *f.L2_fes->GetTypicalFE(); f.quadrature_factory->configure_gravity_divergence(*divergence_discretization_integrator, quadrature::QuadratureRole::discretization, hdiv_element, divergence_discretization_test_element, hdiv_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none); f.gravity_context.b_form->AddDomainIntegrator(divergence_discretization_integrator.release()); f.gravity_context.b_form->Assemble(); // ========================================== // 3. Assemble Global Block Operator // ========================================== f.gravity_context.BT = std::make_unique(f.gravity_context.b_form.get()); f.gravity_context.block_A = std::make_unique(f.gravity_block_true_offsets); f.gravity_context.block_A->SetBlock(0, 0, f.gravity_context.m_form.get()); f.gravity_context.block_A->SetBlock(0, 1, f.gravity_context.BT.get()); f.gravity_context.block_A->SetBlock(1, 0, f.gravity_context.b_form.get()); // ========================================== // 4. Construct a mapped Schur preconditioner // ========================================== mfem::Vector mass_diagonal(f.RT_fes->GetTrueVSize()); f.gravity_context.m_form->AssembleDiagonal(mass_diagonal); mfem::Vector inverse_mass_diagonal(mass_diagonal); for (int i = 0; i < inverse_mass_diagonal.Size(); ++i) { MFEM_VERIFY(std::isfinite(inverse_mass_diagonal(i)) && inverse_mass_diagonal(i) > 0.0, "Mapped RT mass matrix has a non-positive or non-finite diagonal entry."); inverse_mass_diagonal(i) = 1.0 / inverse_mass_diagonal(i); } mfem::ParMixedBilinearForm b_preconditioner(f.RT_fes.get(), f.L2_fes.get()); auto divergence_preconditioner_integrator = std::make_unique(); const mfem::FiniteElement& divergence_trial_element = *f.RT_fes->GetTypicalFE(); const mfem::FiniteElement& divergence_test_element = *f.L2_fes->GetTypicalFE(); const mfem::ElementTransformation& divergence_transformation = *f.mesh->GetElementTransformation(0); f.quadrature_factory->configure_gravity_divergence(*divergence_preconditioner_integrator, quadrature::QuadratureRole::preconditioner, divergence_trial_element, divergence_test_element, divergence_transformation, utils::DOMAINS::ALL, quadrature::MappingKind::none); b_preconditioner.AddDomainIntegrator(divergence_preconditioner_integrator.release()); b_preconditioner.Assemble(); b_preconditioner.Finalize(); std::unique_ptr b_matrix(b_preconditioner.ParallelAssemble()); std::unique_ptr inverse_mass_b_transpose(b_matrix->Transpose()); inverse_mass_b_transpose->ScaleRows(inverse_mass_diagonal); f.gravity_context.Schur.reset(mfem::ParMult(b_matrix.get(), inverse_mass_b_transpose.get())); // ========================================== // 5. Wire Up the preconditioners // ========================================== f.gravity_context.prec_M = std::make_unique(mass_diagonal, empty_tdofs); f.gravity_context.prec_Phi->SetOperator(*f.gravity_context.Schur); f.gravity_context.block_prec->SetDiagonalBlock(0, f.gravity_context.prec_M.get()); f.gravity_context.block_prec->SetDiagonalBlock(1, f.gravity_context.prec_Phi.get()); } }