feat(mean_field): added initial implementation
note this implementation lacks many tests
This commit is contained in:
182
libmeanfield/impl/analysis/integral.cpp
Normal file
182
libmeanfield/impl/analysis/integral.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.coefficients;
|
||||
|
||||
namespace mean_field::analysis {
|
||||
double domain_integrate_grid_function(const fem::FEM &fem, const mfem::GridFunction &gf, utils::DOMAINS domain, mapping::COORDINATE_SPACE coord_space) {
|
||||
mfem::LinearForm lf(fem.H1_fes.get());
|
||||
mfem::GridFunctionCoefficient gf_c(&gf);
|
||||
double local_integral;
|
||||
mfem::Array<int> elem_markers;
|
||||
populate_element_mask(fem.mesh.get(), domain, elem_markers);
|
||||
|
||||
if (fem.has_mapping() && coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
|
||||
mapping::MappedScalarCoefficient mapped_gf_c(*fem.mapping, gf_c);
|
||||
|
||||
// ReSharper disable once CppDFAMemoryLeak // Disabled because MFEM takes ownership so memory is not leaked
|
||||
auto *lf_integrator = new mfem::DomainLFIntegrator(mapped_gf_c);
|
||||
lf_integrator->SetIntRule(fem.int_rule.get());
|
||||
lf.AddDomainIntegrator(lf_integrator, elem_markers);
|
||||
lf.Assemble();
|
||||
|
||||
|
||||
local_integral = lf.Sum();
|
||||
} else {
|
||||
if (coord_space == mapping::COORDINATE_SPACE::PHYSICAL) {
|
||||
MFEM_ABORT(
|
||||
"Physical evaluation mode requested but no mapping provided. Check domain bounds and mapping setup.");
|
||||
}
|
||||
lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(gf_c), elem_markers);
|
||||
lf.Assemble();
|
||||
local_integral = lf.Sum();
|
||||
}
|
||||
|
||||
double global_integral = 0.0;
|
||||
MPI_Allreduce(&local_integral, &global_integral, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
|
||||
return global_integral;
|
||||
}
|
||||
|
||||
mfem::Vector get_com(const fem::FEM &fem, const mfem::GridFunction &rho) {
|
||||
const int dim = fem.mesh->Dimension();
|
||||
mfem::Vector local_com(dim);
|
||||
local_com = 0.0;
|
||||
double local_mass = 0.0;
|
||||
|
||||
for (int i = 0; i < fem.H1_fes->GetNE(); ++i) {
|
||||
if (fem.mesh->GetAttribute(i) == 3) continue;
|
||||
mfem::ElementTransformation *trans = fem.H1_fes->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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
const double mass_term = rho_val * weight;
|
||||
local_mass += mass_term;
|
||||
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
local_com(d) += phys_point(d) * mass_term;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double global_mass = 0.0;
|
||||
mfem::Vector global_com(dim);
|
||||
MPI_Comm comm = fem.H1_fes->GetComm();
|
||||
|
||||
MPI_Allreduce(&local_mass, &global_mass, 1, MPI_DOUBLE, MPI_SUM, comm);
|
||||
|
||||
MPI_Allreduce(local_com.GetData(), global_com.GetData(), dim, MPI_DOUBLE, MPI_SUM, comm);
|
||||
|
||||
if (global_mass > 1e-18) {
|
||||
global_com /= global_mass;
|
||||
} else {
|
||||
global_com = 0.0;
|
||||
}
|
||||
|
||||
return global_com;
|
||||
}
|
||||
|
||||
void conserve_mass(const fem::FEM &fem, mfem::GridFunction &rho, const double target_mass) {
|
||||
if (const double current_mass = domain_integrate_grid_function(fem, rho, utils::DOMAINS::STELLAR); current_mass > 1e-15)
|
||||
rho *= (target_mass / current_mass);
|
||||
}
|
||||
|
||||
double get_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho) {
|
||||
auto s2_func = [](const mfem::Vector &x) {
|
||||
return std::pow(x(0), 2) + std::pow(x(1), 2);
|
||||
};
|
||||
|
||||
std::unique_ptr<mfem::Coefficient> s2_coeff;
|
||||
if (fem.has_mapping()) {
|
||||
s2_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, s2_func);
|
||||
} else {
|
||||
s2_coeff = std::make_unique<mfem::FunctionCoefficient>(s2_func);
|
||||
}
|
||||
|
||||
mfem::GridFunctionCoefficient rho_coeff(&rho);
|
||||
mfem::ProductCoefficient I_integrand(rho_coeff, *s2_coeff);
|
||||
|
||||
mfem::LinearForm I_lf(fem.H1_fes.get());
|
||||
|
||||
double I = 0.0;
|
||||
// TODO: Need to filter here to just the stellar domain and also update the IntRule
|
||||
if (fem.has_mapping()) {
|
||||
mapping::MappedScalarCoefficient mapped_integrand(*fem.mapping, I_integrand);
|
||||
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(mapped_integrand));
|
||||
I_lf.Assemble();
|
||||
I = I_lf.Sum();
|
||||
} else {
|
||||
I_lf.AddDomainIntegrator(new mfem::DomainLFIntegrator(I_integrand));
|
||||
I_lf.Assemble();
|
||||
I = I_lf.Sum();
|
||||
}
|
||||
|
||||
return I;
|
||||
}
|
||||
|
||||
double get_mesh_volume(
|
||||
const fem::FEM& fem,
|
||||
const mapping::COORDINATE_SPACE coordinate_space,
|
||||
const utils::DOMAINS domain
|
||||
) {
|
||||
mfem::ParMesh &mesh = *fem.mesh;
|
||||
const mapping::DomainMapper &map = *fem.mapping;
|
||||
const mfem::IntegrationRule &ir = *fem.int_rule;
|
||||
|
||||
const bool physical =
|
||||
(coordinate_space == mapping::COORDINATE_SPACE::PHYSICAL);
|
||||
|
||||
double local_volume = 0.0;
|
||||
|
||||
for (int e = 0; e < mesh.GetNE(); ++e) {
|
||||
const int attr = mesh.GetAttribute(e);
|
||||
switch (domain) {
|
||||
case utils::DOMAINS::ALL:
|
||||
break;
|
||||
case utils::DOMAINS::STELLAR:
|
||||
if (attr == 3) continue;
|
||||
break;
|
||||
case utils::DOMAINS::VACUUM:
|
||||
if (attr != 3) continue;
|
||||
break;
|
||||
default:
|
||||
MFEM_ABORT("Unsupported domain type for volume computation.");
|
||||
}
|
||||
mfem::ElementTransformation *T = mesh.GetElementTransformation(e);
|
||||
|
||||
for (int q = 0; q < ir.GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint &ip = ir.IntPoint(q);
|
||||
T->SetIntPoint(&ip);
|
||||
|
||||
double dV = ip.weight * T->Weight();
|
||||
|
||||
if (physical) {
|
||||
dV *= std::fabs(map.ComputeDetJ(*T, ip));
|
||||
}
|
||||
|
||||
local_volume += dV;
|
||||
}
|
||||
}
|
||||
|
||||
double global_volume = 0.0;
|
||||
MPI_Allreduce(&local_volume, &global_volume, 1, MPI_DOUBLE, MPI_SUM,
|
||||
mesh.GetComm());
|
||||
return global_volume;
|
||||
}
|
||||
}
|
||||
190
libmeanfield/impl/fem.cpp
Normal file
190
libmeanfield/impl/fem.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
module;
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <mfem.hpp>
|
||||
#include <stroid/stroid.h>
|
||||
|
||||
module mean_field;
|
||||
import :boundary.contexts;
|
||||
import :mapping.coefficients;
|
||||
import :utils.misc;
|
||||
import :utils.user;
|
||||
|
||||
|
||||
namespace mean_field::fem {
|
||||
FEM setup_fem(const std::string &filename, const utils::Args &args, const int extra_refine) {
|
||||
FEM fem;
|
||||
|
||||
//==================================================================
|
||||
// Section 1: Mesh and FE Space Setup
|
||||
//==================================================================
|
||||
fem.smesh = stroid::IO::LoadStroidMesh(filename).value();
|
||||
if (extra_refine > 0) {
|
||||
stroid::refinement::UniformRefinement(fem.smesh, extra_refine);
|
||||
}
|
||||
|
||||
fem.mesh = std::make_unique<mfem::ParMesh>(MPI_COMM_WORLD, *fem.smesh.mesh);
|
||||
fem.mesh->EnsureNodes();
|
||||
|
||||
const int geom_order = utils::get_mesh_order(*fem.mesh);
|
||||
const int dim = fem.mesh->Dimension();
|
||||
|
||||
const int v_order = 2;
|
||||
const int rho_order = 2;
|
||||
const int p = rho_order ;
|
||||
|
||||
const int cb_type = mfem::BasisType::GaussLobatto;
|
||||
const int ob_type = mfem::BasisType::IntegratedGLL;
|
||||
|
||||
fem.RT_fec = std::make_unique<mfem::RT_FECollection>(p, dim, cb_type, ob_type);
|
||||
fem.RT_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.RT_fec.get());
|
||||
|
||||
fem.H1_fec = std::make_unique<mfem::H1_FECollection>(v_order, dim);
|
||||
fem.L2_fec = std::make_unique<mfem::L2_FECollection>(rho_order, dim);
|
||||
|
||||
// Gravity (Scalar H1) and Velocity (Vector H1)
|
||||
fem.H1_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.H1_fec.get());
|
||||
fem.Vec_H1_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.H1_fec.get(), dim,
|
||||
mfem::Ordering::byNODES);
|
||||
|
||||
// Density & Pressure (Scalar Discontinuous L2)
|
||||
fem.L2_fes = std::make_unique<mfem::ParFiniteElementSpace>(fem.mesh.get(), fem.L2_fec.get());
|
||||
|
||||
//==================================================================
|
||||
// Section 2: Domain Mapping
|
||||
//==================================================================
|
||||
auto [r_star_ref, r_inf_ref] = utils::discover_bounds(fem.mesh.get(), 3)
|
||||
.or_else([](const boundary::BoundsError &err)-> std::expected<boundary::Bounds, boundary::BoundsError> {
|
||||
throw std::runtime_error("Unable to determine vacuum domain reference boundary...");
|
||||
}).value();
|
||||
|
||||
fem.mapping = std::make_unique<mapping::DomainMapper>(r_star_ref, r_inf_ref);
|
||||
|
||||
//==================================================================
|
||||
// Section 3: Multi-physics Block-offsets
|
||||
//==================================================================
|
||||
fem.block_true_offsets.SetSize(3);
|
||||
fem.block_true_offsets[0] = 0;
|
||||
fem.block_true_offsets[1] = fem.Vec_H1_fes->GetTrueVSize();
|
||||
fem.block_true_offsets[2] = fem.block_true_offsets[1] + fem.L2_fes->GetTrueVSize();
|
||||
|
||||
fem.gravity_block_true_offsets.SetSize(3);
|
||||
fem.gravity_block_true_offsets[0] = 0;
|
||||
fem.gravity_block_true_offsets[1] = fem.RT_fes->GetTrueVSize();
|
||||
fem.gravity_block_true_offsets[2] = fem.gravity_block_true_offsets[1] + fem.L2_fes->GetTrueVSize();
|
||||
|
||||
//==================================================================
|
||||
// Section 4: Multipole BC setup.
|
||||
//==================================================================
|
||||
fem.com.SetSize(dim);
|
||||
fem.com = 0.0;
|
||||
fem.Q.SetSize(dim, dim);
|
||||
fem.Q = 0.0;
|
||||
|
||||
//==================================================================
|
||||
// Section 5: Integration Rules
|
||||
//==================================================================
|
||||
MFEM_ASSERT(fem.mesh->GetElementGeometry(0) == mfem::Geometry::CUBE,
|
||||
"Currently only hexahedral meshes are supported");
|
||||
const int element_order = fem.H1_fes->GetMaxElementOrder();
|
||||
fem.int_order = 2 * element_order + geom_order - 2 + args.quad_boost;
|
||||
|
||||
fem.int_rule = std::make_unique<mfem::IntegrationRule>(mfem::IntRules.Get(mfem::Geometry::CUBE, fem.int_order));
|
||||
|
||||
//==================================================================
|
||||
// Section 6: Essential Boundaries & Domain Masks
|
||||
//==================================================================
|
||||
fem.ess_v_tdofs.SetSize(0);
|
||||
|
||||
populate_element_mask(fem.mesh.get(), utils::DOMAINS::STELLAR, fem.gravity_context.stellar_mask);
|
||||
|
||||
const int n_bdr_attrs = fem.mesh->bdr_attributes.Max();
|
||||
fem.boundary_context.inf_bounds.SetSize(n_bdr_attrs);
|
||||
fem.boundary_context.stellar_bounds.SetSize(n_bdr_attrs);
|
||||
|
||||
fem.boundary_context.inf_bounds = 0;
|
||||
fem.boundary_context.stellar_bounds = 0;
|
||||
|
||||
fem.boundary_context.inf_bounds[static_cast<int>(boundary::Boundaries::INF_SURFACE) - 1] = 1;
|
||||
fem.boundary_context.stellar_bounds[static_cast<int>(boundary::Boundaries::STELLAR_SURFACE) - 1] = 1;
|
||||
|
||||
//==================================================================
|
||||
// Section 7: Gravity Context Setup
|
||||
//==================================================================
|
||||
fem.gravity_context.minres = std::make_unique<mfem::MINRESSolver>(fem.mesh->GetComm());
|
||||
fem.gravity_context.minres->SetRelTol(1e-12);
|
||||
fem.gravity_context.minres->SetAbsTol(1e-12);
|
||||
fem.gravity_context.minres->SetMaxIter(1000);
|
||||
fem.gravity_context.minres->SetPrintLevel(0);
|
||||
|
||||
fem.gravity_context.prec_Phi = std::make_unique<mfem::HypreBoomerAMG>();
|
||||
fem.gravity_context.prec_Phi->SetPrintLevel(0);
|
||||
|
||||
fem.gravity_context.block_prec = std::make_unique<mfem::BlockDiagonalPreconditioner>(fem.gravity_block_true_offsets);
|
||||
|
||||
fem.gravity_context.minres->SetPreconditioner(*fem.gravity_context.block_prec);
|
||||
//=========================================================
|
||||
// Section 10: Set All vacuum elements true degrees of freedom
|
||||
//=========================================================
|
||||
{
|
||||
mfem::Array<int> vacuum_mask;
|
||||
utils::populate_element_mask(fem.mesh.get(), utils::DOMAINS::VACUUM, vacuum_mask);
|
||||
|
||||
utils::populate_domain_tdofs(fem.Vec_H1_fes.get(), vacuum_mask, fem.vacuum_tdof_v);
|
||||
utils::populate_domain_tdofs(fem.L2_fes.get(), vacuum_mask, fem.vacuum_tdof_rho);
|
||||
}
|
||||
|
||||
const quadrature::QuadratureOptions& quadrature_options = args.quadrature;
|
||||
|
||||
if (quadrature_options.validation.reject_negative_boosts && quadrature_options.global_boost < 0) {
|
||||
throw std::invalid_argument("Global quadrature boost cannot be negative.");
|
||||
}
|
||||
|
||||
quadrature::RuleSet quadrature_rule_set = quadrature::make_rule_set(quadrature_options.mode, quadrature_options.global_boost);
|
||||
|
||||
if (quadrature_options.fallback_fixed_order.has_value()) {
|
||||
if (*quadrature_options.fallback_fixed_order < 0) {
|
||||
throw std::invalid_argument("Fallback quadrature order cannot be negative.");
|
||||
}
|
||||
quadrature_rule_set.fallback.fixed_order = quadrature_options.fallback_fixed_order;
|
||||
}
|
||||
|
||||
auto apply_quadrature_options = [&quadrature_options](quadrature::RuleControl& rule_control, const quadrature::QuadratureTermOptions& term_options) {
|
||||
if (term_options.fixed_order.has_value() && *term_options.fixed_order < 0) {
|
||||
throw std::invalid_argument("Fixed quadrature order cannot be negative.");
|
||||
}
|
||||
|
||||
if (quadrature_options.validation.reject_negative_boosts && term_options.additional_boost < 0) {
|
||||
throw std::invalid_argument("Term quadrature boost cannot be negative.");
|
||||
}
|
||||
|
||||
rule_control.boost += term_options.additional_boost;
|
||||
|
||||
if (term_options.fixed_order.has_value()) {
|
||||
rule_control.fixed_order = term_options.fixed_order;
|
||||
}
|
||||
};
|
||||
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_hdiv_mass, quadrature_options.gravity_hdiv_mass);
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_divergence, quadrature_options.gravity_divergence);
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_source, quadrature_options.gravity_source);
|
||||
apply_quadrature_options(quadrature_rule_set.gravity_boundary, quadrature_options.gravity_boundary);
|
||||
apply_quadrature_options(quadrature_rule_set.density_projection, quadrature_options.density_projection);
|
||||
apply_quadrature_options(quadrature_rule_set.mass_conservation, quadrature_options.mass_conservation);
|
||||
apply_quadrature_options(quadrature_rule_set.center_of_mass, quadrature_options.center_of_mass);
|
||||
apply_quadrature_options(quadrature_rule_set.quadrupole, quadrature_options.quadrupole);
|
||||
apply_quadrature_options(quadrature_rule_set.gravitational_energy, quadrature_options.gravitational_energy);
|
||||
apply_quadrature_options(quadrature_rule_set.virial, quadrature_options.virial);
|
||||
apply_quadrature_options(quadrature_rule_set.error_norm, quadrature_options.error_norm);
|
||||
|
||||
apply_quadrature_options(quadrature_rule_set.roles.discretization, quadrature_options.roles.discretization);
|
||||
apply_quadrature_options(quadrature_rule_set.roles.preconditioner, quadrature_options.roles.preconditioner);
|
||||
apply_quadrature_options(quadrature_rule_set.roles.diagnostic, quadrature_options.roles.diagnostic);
|
||||
apply_quadrature_options(quadrature_rule_set.roles.projection, quadrature_options.roles.projection);
|
||||
|
||||
fem.quadrature_factory = std::make_unique<quadrature::RuleFactory>(quadrature::Policy(std::move(quadrature_rule_set)));
|
||||
return fem;
|
||||
}
|
||||
}
|
||||
|
||||
206
libmeanfield/impl/integrators/advection.cpp
Normal file
206
libmeanfield/impl/integrators/advection.cpp
Normal file
@@ -0,0 +1,206 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
AdvectionIntegrator::AdvectionIntegrator(const mapping::DomainMapper &map) : m_map(map) {}
|
||||
|
||||
void AdvectionIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector &r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); q++) {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_v->CalcDShape(ip, dshape_v_ref);
|
||||
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
|
||||
mfem::Vector v_val(dim);
|
||||
v_val = 0.0;
|
||||
mfem::DenseMatrix grad_v(dim, dim);
|
||||
grad_v = 0.0;
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const double v_ic = v_dofs(i + c * dof_v);
|
||||
v_val(c) += v_ic * shape_v(i);
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
grad_v(c, d) += v_ic * dshape_v_phys(i, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector adv_val(dim);
|
||||
adv_val = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
adv_val(c) += v_val(d) * grad_v(c, d);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * adv_val(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AdvectionIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
mfem::DenseMatrix *dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix *dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + 1);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); q++) {
|
||||
const mfem::IntegrationPoint &ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_v->CalcDShape(ip, dshape_v_ref);
|
||||
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
|
||||
mfem::Vector v_val(dim);
|
||||
v_val = 0.0;
|
||||
mfem::DenseMatrix grad_v(dim, dim);
|
||||
grad_v = 0.0;
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
double v_ic = v_dofs(i + c * dof_v);
|
||||
v_val(c) += v_ic * shape_v(i);
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
grad_v(c, d) += v_ic * dshape_v_phys(i, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mfem::Vector adv_val(dim);
|
||||
adv_val = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
adv_val(c) += v_val(d) * grad_v(c, d);
|
||||
}
|
||||
}
|
||||
|
||||
// Jacobian wrt. Velocity: dR_v/dv
|
||||
if (dv_dv) {
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
// Test function index
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
// Test function component
|
||||
int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_v; ++j) {
|
||||
// Trial function index
|
||||
double v_dot_grad_phi_j = 0.0;
|
||||
|
||||
for (int k = 0; k < dim; ++k) {
|
||||
v_dot_grad_phi_j += v_val(k) * dshape_v_phys(j, k);
|
||||
}
|
||||
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
// Trial function component
|
||||
int col = j + d * dof_v;
|
||||
|
||||
// \rho (\delta \vec{v} \cdot \nabla \vec{v})
|
||||
// \delta v is along direction 'd' for the cth component of advection
|
||||
double termA = shape_v(j) * grad_v(c, d);
|
||||
|
||||
// \rho(\vec{v} \cdot \nabla \delta \vec{v})
|
||||
// Only non-zero when the advected component matches the test component
|
||||
double termB = (c == d) ? v_dot_grad_phi_j : 0.0;
|
||||
|
||||
(*dv_dv)(row, col) += shape_v(i) * rho_val * (termA + termB) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Jacobian wrt. Density: dR_v / drho
|
||||
if (dv_drho) {
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
int col = j;
|
||||
|
||||
// \delta \rho * (\vec{v} \cdot \nabla \vec{v})
|
||||
double term = shape_rho(j) * adv_val(c);
|
||||
(*dv_drho)(row, col) += shape_v(i) * term * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
146
libmeanfield/impl/integrators/centrifugal.cpp
Normal file
146
libmeanfield/impl/integrators/centrifugal.cpp
Normal file
@@ -0,0 +1,146 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
CentrifugalForceIntegrator::CentrifugalForceIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const mfem::Vector& omega
|
||||
) : m_map(map), m_omega(3) {
|
||||
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
|
||||
m_omega = omega;
|
||||
}
|
||||
|
||||
void CentrifugalForceIntegrator::SetOmega(const mfem::Vector& omega) {
|
||||
MFEM_ASSERT(omega.Size() == 3, "Omega vector must be 3D");
|
||||
m_omega = omega;
|
||||
}
|
||||
|
||||
void CentrifugalForceIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
r_v = 0.0;
|
||||
r_v.SetSize(dof_v * dim);
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector x_phys(dim);
|
||||
mfem::Vector a(dim), b(dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
m_map.GetPhysicalPoint(Tr, ip, x_phys);
|
||||
|
||||
// ω x r
|
||||
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
|
||||
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
|
||||
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
|
||||
|
||||
// ω x (ω x r) [centrifugal acceleration]
|
||||
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
|
||||
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
|
||||
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * b(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CentrifugalForceIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0,0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0,1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (elmats(1, 0)) *elmats(1, 0) = 0.0;
|
||||
if (elmats(1, 1)) *elmats(1, 1) = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
if (!dv_drho) return;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector x_phys(dim);
|
||||
mfem::Vector a(dim), b(dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
m_map.GetPhysicalPoint(Tr, ip, x_phys);
|
||||
|
||||
// ω x r
|
||||
a(0) = m_omega(1) * x_phys(2) - m_omega(2) * x_phys(1);
|
||||
a(1) = m_omega(2) * x_phys(0) - m_omega(0) * x_phys(2);
|
||||
a(2) = m_omega(0) * x_phys(1) - m_omega(1) * x_phys(0);
|
||||
|
||||
// ω x (ω x r) [centrifugal acceleration]
|
||||
b(0) = m_omega(1) * a(2) - m_omega(2) * a(1);
|
||||
b(1) = m_omega(2) * a(0) - m_omega(0) * a(2);
|
||||
b(2) = m_omega(0) * a(1) - m_omega(1) * a(0);
|
||||
|
||||
// dR_dv_i_c / drho_j = φ_i * φ_j * b_c
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
(*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * b(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
152
libmeanfield/impl/integrators/coriolis.cpp
Normal file
152
libmeanfield/impl/integrators/coriolis.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
CoriolisIntegrator::CoriolisIntegrator(const mapping::DomainMapper& map, const mfem::Vector& omega)
|
||||
: m_map(map), m_omega(omega) {
|
||||
m_omega_mat.SetSize(3, 3);
|
||||
m_omega_mat = 0.0;
|
||||
m_omega_mat(0, 1) = -m_omega(2);
|
||||
m_omega_mat(0, 2) = m_omega(1);
|
||||
m_omega_mat(1, 0) = m_omega(2);
|
||||
m_omega_mat(1, 2) = -m_omega(0);
|
||||
m_omega_mat(2, 0) = -m_omega(1);
|
||||
m_omega_mat(2, 1) = m_omega(0);
|
||||
}
|
||||
|
||||
void CoriolisIntegrator::AssembleElementVector(const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
|
||||
}
|
||||
|
||||
mfem::Vector F_coriolis(dim);
|
||||
m_omega_mat.Mult(v_val, F_coriolis);
|
||||
F_coriolis *= 2.0;
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * F_coriolis(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CoriolisIntegrator::AssembleElementGrad(const mfem::Array<const mfem::FiniteElement*> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) rho_val += rho_dofs(i) * shape_rho(i);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) v_val(c) += v_dofs(i + c * dof_v) * shape_v(i);
|
||||
}
|
||||
|
||||
mfem::Vector F_coriolis(dim);
|
||||
m_omega_mat.Mult(v_val, F_coriolis);
|
||||
F_coriolis *= 2.0;
|
||||
|
||||
if (dv_dv) {
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_v; ++j) {
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
int col = j + d * dof_v;
|
||||
double coupling = m_omega_mat(c, d);
|
||||
(*dv_dv)(row, col) += shape_v(i) * shape_v(j) * 2.0 * rho_val * coupling * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (dv_drho) {
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
int col = j;
|
||||
(*dv_drho)(row, col) += shape_v(i) * shape_rho(j) * F_coriolis(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
124
libmeanfield/impl/integrators/gravity.cpp
Normal file
124
libmeanfield/impl/integrators/gravity.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
GravityForceIntegrator::GravityForceIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const mfem::GridFunction& phi
|
||||
): m_map(map), m_phi(&phi) {}
|
||||
|
||||
void GravityForceIntegrator::SetPotential(const mfem::GridFunction& phi) { m_phi = φ };
|
||||
|
||||
void GravityForceIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
}
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
m_phi->GetGradient(Tr, grad_phi_elem);
|
||||
mfem::DenseMatrix J_map(dim, dim), J_map_inv(dim, dim);
|
||||
m_map.ComputeJacobian(Tr, J_map);
|
||||
mfem::CalcInverse(J_map, J_map_inv);
|
||||
J_map_inv.MultTranspose(grad_phi_elem, grad_phi_phys);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
r_v(i + c * dof_v) += shape_v(i) * rho_val * grad_phi_phys(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GravityForceIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (dv_drho) *dv_drho = 0.0;
|
||||
|
||||
if (!dv_drho) return;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::Vector grad_phi_ref(dim), grad_phi_phys(dim), grad_phi_elem(dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
m_phi->GetGradient(Tr, grad_phi_elem);
|
||||
mfem::DenseMatrix J_map(dim, dim), J_map_inv(dim, dim);
|
||||
m_map.ComputeJacobian(Tr, J_map);
|
||||
mfem::CalcInverse(J_map, J_map_inv);
|
||||
J_map_inv.MultTranspose(grad_phi_elem, grad_phi_phys);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
(*dv_drho)(row, j) += shape_v(i) * shape_rho(j) * grad_phi_phys(c) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
417
libmeanfield/impl/integrators/mass_continuity.cpp
Normal file
417
libmeanfield/impl/integrators/mass_continuity.cpp
Normal file
@@ -0,0 +1,417 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
ContinuityVolumeIntegrator::ContinuityVolumeIntegrator(const mapping::DomainMapper& map) : m_map(map) {};
|
||||
|
||||
void ContinuityVolumeIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector v_dofs = *elfun[0];
|
||||
const mfem::Vector rho_dofs = *elfun[1];
|
||||
|
||||
void* data_rho_before = elvec[1] ? (void*)elvec[1]->GetData() : nullptr;
|
||||
int size_rho_before = elvec[1] ? elvec[1]->Size() : -1;
|
||||
|
||||
if (elvec[0]) {
|
||||
elvec[0]->SetSize(dof_v * dim);
|
||||
*elvec[0] = 0.0;
|
||||
}
|
||||
mfem::Vector& r_rho = *elvec[1];
|
||||
r_rho.SetSize(dof_rho);
|
||||
r_rho = 0.0;
|
||||
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
fe_rho->CalcDShape(ip, dshape_rho_ref);
|
||||
mfem::Mult(dshape_rho_ref, J_inv, dshape_rho_phys);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
v_val(c) += v_dofs(row) * shape_v(i);
|
||||
}
|
||||
}
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
double grad_dot_rhov = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
grad_dot_rhov += dshape_rho_phys(i, c) * rho_val * v_val(c);
|
||||
}
|
||||
r_rho(i) -= grad_dot_rhov * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContinuityVolumeIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
|
||||
const mfem::FiniteElement *fe_v = el[0];
|
||||
const mfem::FiniteElement *fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
const mfem::Vector& rho_dofs = *elfun[1];
|
||||
|
||||
mfem::DenseMatrix* drho_dv = elmats(1, 0);
|
||||
mfem::DenseMatrix* drho_drho = elmats(1, 1);
|
||||
|
||||
if (elmats(0, 0)) *elmats(0, 0) = 0.0;
|
||||
if (elmats(0, 1)) *elmats(0, 1) = 0.0;
|
||||
|
||||
if (drho_dv) *drho_dv = 0.0;
|
||||
if (drho_drho) *drho_drho = 0.0;
|
||||
|
||||
mfem::Vector shape_v(dof_v), shape_rho(dof_rho);
|
||||
mfem::DenseMatrix dshape_rho_ref(dof_rho, dim), dshape_rho_phys(dof_rho, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcShape(ip, shape_v);
|
||||
fe_rho->CalcShape(ip, shape_rho);
|
||||
fe_rho->CalcDShape(ip, dshape_rho_ref);
|
||||
mfem::Mult(dshape_rho_ref, J_inv, dshape_rho_phys);
|
||||
|
||||
mfem::Vector v_val(dim); v_val = 0.0;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
v_val(c) += v_dofs(row) * shape_v(i);
|
||||
}
|
||||
}
|
||||
|
||||
double rho_val = 0.0;
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
rho_val += rho_dofs(i) * shape_rho(i);
|
||||
}
|
||||
|
||||
if (drho_dv) {
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
for (int j = 0; j < dof_v; ++j) {
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
const int col = j + d * dof_v;
|
||||
(*drho_dv)(i, col) -= dshape_rho_phys(i, d) * rho_val * shape_v(j) * weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (drho_drho) {
|
||||
for (int i = 0; i < dof_rho; ++i) {
|
||||
double grad_psi_dot_v = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
grad_psi_dot_v += dshape_rho_phys(i, c) * v_val(c);
|
||||
}
|
||||
for (int j = 0; j < dof_rho; ++j) {
|
||||
(*drho_drho)(i, j) -= grad_psi_dot_v * shape_rho(j) * weight;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ContinuityFaceIntegrator::ContinuityFaceIntegrator(const mapping::DomainMapper& map): m_map(map) {}
|
||||
|
||||
void ContinuityFaceIntegrator::AssembleFaceVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el1,
|
||||
const mfem::Array<const mfem::FiniteElement *> &el2,
|
||||
mfem::FaceElementTransformations &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvect
|
||||
) {
|
||||
const mfem::FiniteElement *fe_v_minus = el1[0];
|
||||
const mfem::FiniteElement *fe_v_plus = el2[0];
|
||||
|
||||
const mfem::FiniteElement *fe_rho_minus = el1[1];
|
||||
const mfem::FiniteElement *fe_rho_plus = el2[1];
|
||||
|
||||
const int dof_v_minus = fe_v_minus->GetDof();
|
||||
const int dof_v_plus = fe_v_plus->GetDof();
|
||||
|
||||
const int dof_rho_minus = fe_rho_minus->GetDof();
|
||||
const int dof_rho_plus = fe_rho_plus->GetDof();
|
||||
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
if (elvect[0]) {
|
||||
elvect[0]->SetSize(dim * dof_v_minus + dim * dof_v_plus);
|
||||
*elvect[0] = 0.0;
|
||||
}
|
||||
mfem::Vector &r_rho = *elvect[1];
|
||||
r_rho.SetSize(dof_rho_minus + dof_rho_plus);
|
||||
r_rho = 0.0;
|
||||
|
||||
const int attr_minus = Tr.Elem1->Attribute;
|
||||
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
|
||||
constexpr int VACUUM_ATTR = 3;
|
||||
|
||||
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) {
|
||||
return; // No flux contribution for vacuum faces
|
||||
}
|
||||
|
||||
if (Tr.Elem2 == nullptr) {
|
||||
return; // Boundary face,
|
||||
}
|
||||
|
||||
const mfem::Vector &v_dofs = *elfun[0]; // Size: dim * dof_v_minus + dim*dof_v_plus
|
||||
const mfem::Vector &rho_dofs = *elfun[1]; // Size: dof_rho_minus + dof_rho_plus
|
||||
|
||||
// Helpers to auto offset to the correct point in the dof array
|
||||
auto rho_minus_dof = [&](const int i) {return rho_dofs(i);};
|
||||
auto rho_plus_dof = [&](const int i) {return rho_dofs(i + dof_rho_minus);};
|
||||
auto v_minus_dof = [&](const int k, const int c) {return v_dofs(k + c * dof_v_minus);};
|
||||
|
||||
const int p_v = fe_v_minus->GetOrder();
|
||||
const int p_rho = fe_rho_minus->GetOrder();
|
||||
const int int_order = 2 * std::max(p_v, p_rho) + 1;
|
||||
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
|
||||
|
||||
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& face_ip = ir->IntPoint(q);
|
||||
|
||||
Tr.SetAllIntPoints(&face_ip);
|
||||
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
|
||||
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
|
||||
|
||||
auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip);
|
||||
|
||||
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
|
||||
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
|
||||
fe_rho_plus->CalcShape(ip_plus, shape_rho_plus);
|
||||
|
||||
// v dot n
|
||||
// u_n = ∑ n_c * ∑ v_kc * φ_k
|
||||
double u_n = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
double v_c = 0.0;
|
||||
for (int k = 0; k < dof_v_minus; ++k) {
|
||||
v_c += v_minus_dof(k, c) * shape_v_minus(k);
|
||||
}
|
||||
u_n += v_c * n_unit(c);
|
||||
}
|
||||
|
||||
double rho_minus_val = 0.0;
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
rho_minus_val += shape_rho_minus(i) * rho_minus_dof(i);
|
||||
}
|
||||
|
||||
double rho_plus_val = 0.0;
|
||||
for (int i = 0; i < dof_rho_plus; ++i) {
|
||||
rho_plus_val += shape_rho_plus(i) * rho_plus_dof(i);
|
||||
}
|
||||
|
||||
// Upwind density
|
||||
// I use the convention that the flow is positive when moving from minus to plus
|
||||
const double rho_up = (u_n >= 0) ? rho_minus_val : rho_plus_val;
|
||||
|
||||
const double flux_weighted = u_n * rho_up * ds;
|
||||
|
||||
// Note the normals need to be in opposite directions for these two fluxes
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
r_rho(i) += shape_rho_minus(i) * flux_weighted;
|
||||
}
|
||||
|
||||
for (int i = 0; i < dof_rho_plus; ++i) {
|
||||
r_rho(dof_rho_minus + i) -= shape_rho_plus(i) * flux_weighted;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ContinuityFaceIntegrator::AssembleFaceGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el1,
|
||||
const mfem::Array<const mfem::FiniteElement *> &el2,
|
||||
mfem::FaceElementTransformations &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement *fe_v_minus = el1[0];
|
||||
const mfem::FiniteElement *fe_v_plus = el2[0];
|
||||
const mfem::FiniteElement *fe_rho_minus = el1[1];
|
||||
const mfem::FiniteElement *fe_rho_plus = el2[1];
|
||||
|
||||
const int dof_v_minus = fe_v_minus->GetDof();
|
||||
const int dof_v_plus = fe_v_plus->GetDof();
|
||||
const int dof_rho_minus = fe_rho_minus->GetDof();
|
||||
const int dof_rho_plus = fe_rho_plus->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const int N_v_total = dim * (dof_v_minus + dof_v_plus);
|
||||
const int N_rho_total = dof_rho_minus + dof_rho_plus;
|
||||
|
||||
auto size_and_zero_mat = [&](mfem::DenseMatrix* mat, const int r_size, const int c_size) {
|
||||
if (mat) {
|
||||
mat->SetSize(r_size, c_size);
|
||||
*mat = 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
size_and_zero_mat(elmats(0, 0), N_v_total, N_v_total);
|
||||
size_and_zero_mat(elmats(0, 1), N_v_total, N_rho_total);
|
||||
size_and_zero_mat(elmats(1, 0), N_rho_total, N_v_total);
|
||||
size_and_zero_mat(elmats(1, 1), N_rho_total, N_rho_total);
|
||||
|
||||
if (skip_face(Tr)) return;
|
||||
|
||||
mfem::DenseMatrix *drho_dv = elmats(1, 0);
|
||||
mfem::DenseMatrix *drho_drho = elmats(1, 1);
|
||||
|
||||
if (!drho_dv && !drho_drho) return;
|
||||
|
||||
const mfem::Vector &v_dofs = *elfun[0];
|
||||
const mfem::Vector &rho_dofs = *elfun[1];
|
||||
|
||||
const int int_order = 2 * std::max(fe_v_minus->GetOrder(), fe_rho_minus->GetOrder()) + 1;
|
||||
const mfem::IntegrationRule *ir = &mfem::IntRules.Get(Tr.GetGeometryType(), int_order);
|
||||
|
||||
mfem::Vector shape_v_minus(dof_v_minus), shape_rho_minus(dof_rho_minus), shape_rho_plus(dof_rho_plus);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& face_ip = ir->IntPoint(q);
|
||||
Tr.SetAllIntPoints(&face_ip);
|
||||
const mfem::IntegrationPoint &ip_minus = Tr.GetElement1IntPoint();
|
||||
const mfem::IntegrationPoint &ip_plus = Tr.GetElement2IntPoint();
|
||||
|
||||
auto [n_unit, ds, v_dot_n_scale] = m_map.GetFaceQuadratureContext(Tr, face_ip);
|
||||
|
||||
fe_v_minus->CalcShape(ip_minus, shape_v_minus);
|
||||
fe_rho_minus->CalcShape(ip_minus, shape_rho_minus);
|
||||
fe_rho_plus->CalcShape(ip_plus, shape_rho_plus);
|
||||
|
||||
const double u_n = compute_u_n(v_dofs, shape_v_minus, n_unit, dof_v_minus, dim);
|
||||
|
||||
double rho_minus_val = 0.0;
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
rho_minus_val += shape_rho_minus(i) * rho_dofs(i);
|
||||
}
|
||||
|
||||
double rho_plus_val = 0.0;
|
||||
for (int i = 0; i < dof_rho_plus; ++i) {
|
||||
rho_plus_val += shape_rho_plus(i) * rho_dofs(dof_rho_minus + i);
|
||||
}
|
||||
|
||||
const bool upwind_minus = (u_n >= 0.0);
|
||||
const double rho_up = upwind_minus ? rho_minus_val : rho_plus_val;
|
||||
|
||||
// (1, 1)
|
||||
if (drho_drho) {
|
||||
const double u_w = u_n * ds;
|
||||
|
||||
if (upwind_minus) {
|
||||
for (int ip = 0; ip < dof_rho_minus; ++ip) {
|
||||
const double col_w = u_w * shape_rho_minus(ip);
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
(*drho_drho)(i, ip) += shape_rho_minus(i) * col_w;
|
||||
}
|
||||
for (int j = 0; j < dof_rho_plus; ++j) {
|
||||
(*drho_drho)(dof_rho_minus + j, ip) -= shape_rho_plus(j) * col_w;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (int jp = 0; jp < dof_rho_plus; ++jp) {
|
||||
const double col_w = u_w * shape_rho_plus(jp);
|
||||
const int col_idx = dof_rho_minus + jp;
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
(*drho_drho)(i, col_idx) += shape_rho_minus(i) * col_w;
|
||||
}
|
||||
for (int j = 0; j < dof_rho_plus; ++j) {
|
||||
(*drho_drho)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// (1, 0)
|
||||
if (drho_dv) {
|
||||
const double rho_w = rho_up * ds;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const double n_c_rho_w = n_unit(c) * rho_w;
|
||||
for (int k = 0; k < dof_v_minus; ++k) {
|
||||
const int col_idx = k + c * dof_v_minus;
|
||||
const double col_w = n_c_rho_w * shape_v_minus(k);
|
||||
for (int i = 0; i < dof_rho_minus; ++i) {
|
||||
(*drho_dv)(i, col_idx) += shape_rho_minus(i) * col_w;
|
||||
}
|
||||
for (int j = 0; j < dof_rho_plus; ++j) {
|
||||
(*drho_dv)(dof_rho_minus + j, col_idx) -= shape_rho_plus(j) * col_w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ContinuityFaceIntegrator::skip_face(const mfem::FaceElementTransformations& Tr) {
|
||||
constexpr int VACUUM_ATTR = 3;
|
||||
const int attr_minus = Tr.Elem1->Attribute;
|
||||
const int attr_plus = (Tr.Elem2 != nullptr) ? Tr.Elem2->Attribute : -1;
|
||||
if (attr_minus == VACUUM_ATTR || attr_plus == VACUUM_ATTR) {
|
||||
return true; // No flux contribution for vacuum faces
|
||||
}
|
||||
if (Tr.Elem2 == nullptr) {
|
||||
return true; // Boundary face,
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
double ContinuityFaceIntegrator::compute_u_n(const mfem::Vector& v_dofs, const mfem::Vector& shape_v_minus, const mfem::Vector& n_unit, int dof_v_minus, int dim) {
|
||||
double u_n = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
double v_c = 0.0;
|
||||
for (int k = 0; k < dof_v_minus; ++k) {
|
||||
v_c += v_dofs(k + c * dof_v_minus) * shape_v_minus(k);
|
||||
}
|
||||
u_n += v_c * n_unit(c);
|
||||
}
|
||||
return u_n;
|
||||
}
|
||||
}
|
||||
|
||||
156
libmeanfield/impl/integrators/viscosity.cpp
Normal file
156
libmeanfield/impl/integrators/viscosity.cpp
Normal file
@@ -0,0 +1,156 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::integrators {
|
||||
ViscosityIntegrator::ViscosityIntegrator(
|
||||
const mapping::DomainMapper& map,
|
||||
const double mu,
|
||||
const int quad_boost
|
||||
) : m_map(map), m_mu(mu), m_quad_boost(quad_boost) {}
|
||||
|
||||
void ViscosityIntegrator::SetMu(const double mu) { m_mu = mu; }
|
||||
|
||||
void ViscosityIntegrator::AssembleElementVector(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array<mfem::Vector *> &elvec
|
||||
) {
|
||||
if (utils::is_vacuum(Tr, elvec)) {
|
||||
return;
|
||||
}
|
||||
|
||||
void* data_before = (void*)elvec[0]->GetData();
|
||||
int size_before = elvec[0]->Size();
|
||||
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
const mfem::Vector& v_dofs = *elfun[0];
|
||||
|
||||
mfem::Vector& r_v = *elvec[0];
|
||||
r_v.SetSize(dof_v * dim);
|
||||
r_v = 0.0;
|
||||
if (elvec[1]) {
|
||||
elvec[1]->SetSize(dof_rho);
|
||||
*elvec[1] = 0.0;
|
||||
}
|
||||
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder() + m_quad_boost);
|
||||
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
|
||||
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcDShape(ip, dshape_v_ref);
|
||||
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
|
||||
|
||||
// ∇v(c,j) = δj v_c
|
||||
mfem::DenseMatrix grad_v(dim, dim); grad_v = 0.0;
|
||||
for (int n = 0; n < dof_v; ++n) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const double vn_c = v_dofs(n + c * dof_v);
|
||||
for (int j = 0; j < dim; ++j) {
|
||||
grad_v( c, j) += vn_c * dshape_v_phys(n, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double div_v = 0.0;
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
div_v += grad_v(c, c);
|
||||
}
|
||||
|
||||
// D_cj = dj v_c + dc v_j - (2/3) δcj ∇v
|
||||
// R_v_i^c += μ * weight * ∑_j (δj φi) D_cj
|
||||
const double mu_w = m_mu * weight;
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
double acc = 0.0;
|
||||
for (int j = 0; j < dim; ++j) {
|
||||
double D_cj = grad_v(c, j) + grad_v(j, c);
|
||||
if (c == j) D_cj -= (2.0 / 3.0) * div_v;
|
||||
acc += dshape_v_phys(i, j) * D_cj;
|
||||
}
|
||||
r_v(i + c * dof_v) += mu_w * acc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ViscosityIntegrator::AssembleElementGrad(
|
||||
const mfem::Array<const mfem::FiniteElement *> &el,
|
||||
mfem::ElementTransformation &Tr,
|
||||
const mfem::Array<const mfem::Vector *> &elfun,
|
||||
const mfem::Array2D<mfem::DenseMatrix *> &elmats
|
||||
) {
|
||||
const mfem::FiniteElement* fe_v = el[0];
|
||||
const mfem::FiniteElement* fe_rho = el[1];
|
||||
|
||||
const int dof_v = fe_v->GetDof();
|
||||
const int dof_rho = fe_rho->GetDof();
|
||||
const int dim = Tr.GetSpaceDim();
|
||||
|
||||
mfem::DenseMatrix* dv_dv = elmats(0, 0);
|
||||
mfem::DenseMatrix* dv_drho = elmats(0, 1);
|
||||
|
||||
if (dv_drho) *dv_drho =0.0;
|
||||
if (dv_dv) *dv_dv = 0.0;
|
||||
if (elmats(1, 0)) *elmats(1, 0) = 0.0;
|
||||
if (elmats(1, 1)) *elmats(1, 1) = 0.0;
|
||||
if (!dv_dv) return;
|
||||
|
||||
mfem::DenseMatrix dshape_v_ref(dof_v, dim), dshape_v_phys(dof_v, dim);
|
||||
|
||||
const mfem::IntegrationRule* ir = &mfem::IntRules.Get(fe_v->GetGeomType(), 2 * fe_v->GetOrder());
|
||||
for (int q = 0; q < ir->GetNPoints(); ++q) {
|
||||
const mfem::IntegrationPoint& ip = ir->IntPoint(q);
|
||||
Tr.SetIntPoint(&ip);
|
||||
|
||||
auto [J_inv, detJ, weight] = m_map.GetQuadratureContext(Tr, ip);
|
||||
|
||||
fe_v->CalcDShape(ip, dshape_v_ref);
|
||||
mfem::Mult(dshape_v_ref, J_inv, dshape_v_phys);
|
||||
|
||||
const double mu_w = m_mu * weight;
|
||||
|
||||
for (int i = 0; i < dof_v; ++i) {
|
||||
for (int n = 0; n < dof_v; ++n) {
|
||||
double dot_grad = 0.0;
|
||||
for (int j = 0; j < dim; ++j) {
|
||||
dot_grad += dshape_v_phys(i, j) * dshape_v_phys(n, j);
|
||||
}
|
||||
|
||||
for (int c = 0; c < dim; ++c) {
|
||||
const int row = i + c * dof_v;
|
||||
for (int d = 0; d < dim; ++d) {
|
||||
const int col = n + d * dof_v;
|
||||
|
||||
double val = 0.0;
|
||||
if (c == d) val += dot_grad;
|
||||
|
||||
val += dshape_v_phys(i, d) * dshape_v_phys(n, c);
|
||||
|
||||
val -= (2.0 / 3.0) * dshape_v_phys(i, c) * dshape_v_phys(n, d);
|
||||
(*dv_dv)(row, col) += mu_w * val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
152
libmeanfield/impl/mapping/coefficients.cpp
Normal file
152
libmeanfield/impl/mapping/coefficients.cpp
Normal file
@@ -0,0 +1,152 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
import :mapping.types;
|
||||
|
||||
namespace mean_field::mapping {
|
||||
///////////////////////////////
|
||||
/// MappedScalarCoefficient ///
|
||||
//////////////////////////////
|
||||
MappedScalarCoefficient::MappedScalarCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::Coefficient &coeff,
|
||||
const COORDINATE_SPACE coord_space
|
||||
) : m_map(map),
|
||||
m_coeff(coeff),
|
||||
m_coord_space(coord_space) {};
|
||||
|
||||
double MappedScalarCoefficient::Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
T.SetIntPoint(&ip);
|
||||
double f_val = 0.0;
|
||||
|
||||
switch (m_coord_space) {
|
||||
case COORDINATE_SPACE::PHYSICAL: {
|
||||
f_val = eval_at_point(m_coeff, T, ip);
|
||||
const double detJ = m_map.ComputeDetJ(T, ip);
|
||||
return f_val * fabs(detJ);
|
||||
}
|
||||
case COORDINATE_SPACE::REFERENCE: {
|
||||
f_val = m_coeff.Eval(T, ip);
|
||||
return f_val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double MappedScalarCoefficient::eval_at_point(mfem::Coefficient &c, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
return c.Eval(T, ip);
|
||||
}
|
||||
|
||||
//////////////////////////////////
|
||||
/// MappedDiffusionCoefficient ///
|
||||
//////////////////////////////////
|
||||
|
||||
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::Coefficient &sigma,
|
||||
const int dim
|
||||
) : mfem::MatrixCoefficient(dim),
|
||||
m_map(map),
|
||||
m_scalar(&sigma),
|
||||
m_tensor(nullptr) {
|
||||
};
|
||||
|
||||
MappedDiffusionCoefficient::MappedDiffusionCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::MatrixCoefficient &sigma
|
||||
) : mfem::MatrixCoefficient(sigma.GetHeight()),
|
||||
m_map(map),
|
||||
m_scalar(nullptr),
|
||||
m_tensor(&sigma) {
|
||||
};
|
||||
|
||||
void MappedDiffusionCoefficient::Eval(mfem::DenseMatrix &K, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
const int dim = height;
|
||||
T.SetIntPoint(&ip);
|
||||
|
||||
mfem::DenseMatrix J(dim, dim), JInv(dim, dim);
|
||||
m_map.ComputeJacobian(T, J);
|
||||
const double detJ = J.Det();
|
||||
mfem::CalcInverse(J, JInv);
|
||||
|
||||
if (m_scalar) {
|
||||
const double sig_val = m_scalar->Eval(T, ip);
|
||||
mfem::MultABt(JInv, JInv, K);
|
||||
K *= sig_val * fabs(detJ);
|
||||
} else {
|
||||
mfem::DenseMatrix sig_mat(dim, dim);
|
||||
m_tensor->Eval(sig_mat, T, ip);
|
||||
|
||||
mfem::DenseMatrix temp(dim, dim);
|
||||
Mult(JInv, sig_mat, temp);
|
||||
|
||||
MultABt(temp, JInv, K);
|
||||
K *= fabs(detJ);
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////
|
||||
/// MappedVectorCoefficient ///
|
||||
///////////////////////////////
|
||||
MappedVectorCoefficient::MappedVectorCoefficient(
|
||||
const DomainMapper &map,
|
||||
mfem::VectorCoefficient &coeff
|
||||
) : mfem::VectorCoefficient(coeff.GetVDim()),
|
||||
m_map(map),
|
||||
m_coeff(coeff) {
|
||||
};
|
||||
|
||||
void MappedVectorCoefficient::Eval(mfem::Vector &V, mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
const int dim = vdim;
|
||||
T.SetIntPoint(&ip);
|
||||
|
||||
mfem::DenseMatrix JInv(dim, dim);
|
||||
m_map.ComputeInverseJacobian(T, JInv);
|
||||
double detJ = m_map.ComputeDetJ(T, ip);
|
||||
|
||||
mfem::Vector C_phys(dim);
|
||||
m_coeff.Eval(C_phys, T, ip);
|
||||
|
||||
V.SetSize(dim);
|
||||
JInv.MultTranspose(C_phys, V);
|
||||
V *= fabs(detJ);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////
|
||||
/// PhysicalPositionFunctionCoefficient ///
|
||||
///////////////////////////////////////////
|
||||
PhysicalPositionFunctionCoefficient::PhysicalPositionFunctionCoefficient(
|
||||
const DomainMapper &map,
|
||||
Func f // std::function<double(const mfem::Vector&)>
|
||||
) : m_f(std::move(f)),
|
||||
m_map(map) {};
|
||||
|
||||
double PhysicalPositionFunctionCoefficient::Eval(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) {
|
||||
T.SetIntPoint(&ip);
|
||||
mfem::Vector x;
|
||||
m_map.GetPhysicalPoint(T, ip, x);
|
||||
return m_f(x);
|
||||
}
|
||||
|
||||
MappedHDivMassCoefficient::MappedHDivMassCoefficient(const DomainMapper& map, const int dim)
|
||||
: mfem::MatrixCoefficient(dim),
|
||||
m_map(map) {}
|
||||
|
||||
void MappedHDivMassCoefficient::Eval(
|
||||
mfem::DenseMatrix& matrix,
|
||||
mfem::ElementTransformation& transformation,
|
||||
const mfem::IntegrationPoint& integration_point
|
||||
) {
|
||||
transformation.SetIntPoint(&integration_point);
|
||||
|
||||
mfem::DenseMatrix map_jacobian(height, height);
|
||||
m_map.ComputeJacobian(transformation, map_jacobian);
|
||||
|
||||
const double map_determinant = map_jacobian.Det();
|
||||
|
||||
MFEM_VERIFY(map_determinant > 0.0, "Domain mapping has a non-positive Jacobian determinant.");
|
||||
|
||||
mfem::MultAtB(map_jacobian, map_jacobian, matrix);
|
||||
matrix *= 1.0 / std::abs(map_determinant);
|
||||
}
|
||||
}
|
||||
294
libmeanfield/impl/mapping/domain_mapper.cpp
Normal file
294
libmeanfield/impl/mapping/domain_mapper.cpp
Normal file
@@ -0,0 +1,294 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::mapping {
|
||||
DomainMapper::DomainMapper(
|
||||
const double r_star_ref,
|
||||
const double r_inf_ref
|
||||
) : m_d(nullptr),
|
||||
m_r_star_ref(r_star_ref),
|
||||
m_r_inf_ref(r_inf_ref) {
|
||||
InitAllScratchSpaces();
|
||||
}
|
||||
|
||||
DomainMapper::DomainMapper(
|
||||
const mfem::GridFunction &d,
|
||||
const double r_star_ref,
|
||||
const double r_inf_ref
|
||||
) : m_d(&d),
|
||||
m_dim(d.FESpace()->GetMesh()->Dimension()),
|
||||
m_r_star_ref(r_star_ref),
|
||||
m_r_inf_ref(r_inf_ref) {
|
||||
InitAllScratchSpaces();
|
||||
}
|
||||
|
||||
bool DomainMapper::is_vacuum(const mfem::ElementTransformation &T) const {
|
||||
if (T.ElementType == mfem::ElementTransformation::ELEMENT) {
|
||||
return T.Attribute == m_vacuum_attr;
|
||||
} else if (T.ElementType == mfem::ElementTransformation::BDR_ELEMENT) {
|
||||
return T.Attribute == m_vacuum_attr - 1;
|
||||
// TODO: In a more robust code this should really be read from the stroid API to ensure that the vacuum boundary is really 1 - the vacuum material attribute
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void DomainMapper::SetDisplacement(const mfem::GridFunction &d) {
|
||||
if (m_dim != d.FESpace()->GetMesh()->Dimension()) {
|
||||
const std::string err_msg = std::format(
|
||||
"Dimension mismatch: DomainMapper is initialized for dimension {}, but provided displacement field has dimension {}.",
|
||||
m_dim, d.FESpace()->GetMesh()->Dimension());
|
||||
throw std::invalid_argument(err_msg);
|
||||
}
|
||||
m_d = &d;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
bool DomainMapper::IsIdentity() const {
|
||||
return (m_d == nullptr);
|
||||
}
|
||||
|
||||
void DomainMapper::ResetDisplacement() {
|
||||
m_d = nullptr;
|
||||
InvalidateCache();
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &J) const {
|
||||
J.SetSize(m_dim, m_dim);
|
||||
J = 0.0;
|
||||
m_J_D = 0.0;
|
||||
if (IsIdentity()) {
|
||||
for (int i = 0; i < m_dim; ++i) {
|
||||
m_J_D(i, i) = 1.0; // Identity mapping
|
||||
}
|
||||
} else {
|
||||
UpdateElementCache(T);
|
||||
m_dshape.SetSize(m_fe->GetDof(), m_dim);
|
||||
m_fe->CalcPhysDShape(T, m_dshape);
|
||||
mfem::MultAtB(m_dof_mat, m_dshape, m_J_D);
|
||||
|
||||
for (int i = 0; i < m_dim; ++i) {
|
||||
m_J_D(i, i) += 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_vacuum(T)) {
|
||||
T.Transform(T.GetIntPoint(), m_x_ref);
|
||||
|
||||
if (IsIdentity()) {
|
||||
m_x_disp = m_x_ref;
|
||||
} else {
|
||||
m_shape.SetSize(m_fe->GetDof());
|
||||
m_fe->CalcShape(T.GetIntPoint(), m_shape);
|
||||
m_dof_mat.MultTranspose(m_shape, m_d_val);
|
||||
add(m_x_ref, m_d_val, m_x_disp);
|
||||
}
|
||||
|
||||
ComputeKelvinJacobian(m_x_ref, m_x_disp, m_J_D, J);
|
||||
} else {
|
||||
J = m_J_D;
|
||||
}
|
||||
}
|
||||
|
||||
double DomainMapper::ComputeDetJ(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const {
|
||||
if (IsIdentity() && !is_vacuum(T)) return 1.0; // If no mapping, the determinant of the Jacobian is 1
|
||||
T.SetIntPoint(&ip);
|
||||
mfem::DenseMatrix J;
|
||||
ComputeJacobian(T, J);
|
||||
return J.Det();
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeMappedDiffusionTensor(mfem::ElementTransformation &T, mfem::DenseMatrix &D) const {
|
||||
ComputeJacobian(T, m_J_temp);
|
||||
const double detJ = m_J_temp.Det();
|
||||
mfem::CalcInverse(m_J_temp, m_JInv_temp);
|
||||
D.SetSize(m_dim, m_dim);
|
||||
mfem::MultABt(m_JInv_temp, m_JInv_temp, D);
|
||||
D *= fabs(detJ);
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeInverseJacobian(mfem::ElementTransformation &T, mfem::DenseMatrix &JInv) const {
|
||||
ComputeJacobian(T, m_J_temp);
|
||||
JInv.SetSize(m_dim, m_dim);
|
||||
mfem::CalcInverse(m_J_temp, JInv);
|
||||
}
|
||||
|
||||
DomainMapper::VolumeQuadratureContext DomainMapper::GetQuadratureContext(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip) const {
|
||||
const int dim = T.GetSpaceDim();
|
||||
mfem::DenseMatrix J_map(dim, dim), J_inv(dim, dim);
|
||||
ComputeJacobian(T, J_map);
|
||||
mfem::DenseMatrix J_full(dim, dim);
|
||||
mfem::Mult(J_map, T.Jacobian(), J_full);
|
||||
mfem::CalcInverse(J_full, J_inv);
|
||||
const double detJ = std::fabs(ComputeDetJ(T, ip));
|
||||
const double weight = ip.weight * T.Weight() * detJ;
|
||||
return {.J_inv = J_inv, .detJ = detJ, .weight = weight};
|
||||
}
|
||||
|
||||
DomainMapper::FaceQuadratureContext DomainMapper::GetFaceQuadratureContext(mfem::FaceElementTransformations &T, const mfem::IntegrationPoint &ip) const {
|
||||
const int dim = T.GetSpaceDim();
|
||||
T.SetAllIntPoints(&ip);
|
||||
|
||||
mfem::Vector n_raw(dim);
|
||||
mfem::CalcOrtho(T.Jacobian(), n_raw);
|
||||
|
||||
if (IsIdentity()) {
|
||||
const double n_raw_mag = n_raw.Norml2();
|
||||
mfem::Vector n_unit(dim);
|
||||
n_unit = n_raw;
|
||||
n_unit /= n_raw_mag;
|
||||
return FaceQuadratureContext{.normal=n_unit, .ds=ip.weight * n_raw_mag, .v_dot_n_scale = 1.0};
|
||||
}
|
||||
|
||||
// Nanson's Formula (https://en.wikiversity.org/wiki/Continuum_mechanics/Volume_change_and_area_change)
|
||||
// Since the displacement field lives in H1 it should be irrelevant if we pick Elem1 or Elem2
|
||||
mfem::DenseMatrix J_map(dim, dim);
|
||||
ComputeJacobian(*T.Elem1, J_map);
|
||||
const double detJ_map = J_map.Det();
|
||||
|
||||
mfem::DenseMatrix J_map_inv(dim, dim);
|
||||
mfem::CalcInverse(J_map, J_map_inv);
|
||||
|
||||
mfem::Vector n_phys(dim);
|
||||
J_map_inv.MultTranspose(n_raw, n_phys);
|
||||
n_phys *= detJ_map;
|
||||
|
||||
const double n_phys_mag = n_phys.Norml2();
|
||||
mfem::Vector n_unit(dim);
|
||||
n_unit = n_phys;
|
||||
n_unit /= n_phys_mag;
|
||||
|
||||
const double n_raw_mag = n_raw.Norml2();
|
||||
|
||||
return FaceQuadratureContext{
|
||||
.normal = n_unit,
|
||||
.ds = ip.weight * n_raw_mag,
|
||||
.v_dot_n_scale = n_phys_mag / n_raw_mag
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void DomainMapper::GetPhysicalPoint(mfem::ElementTransformation &T, const mfem::IntegrationPoint &ip, mfem::Vector &x_phys) const {
|
||||
x_phys.SetSize(m_dim);
|
||||
T.Transform(ip, m_x_ref);
|
||||
|
||||
if (IsIdentity()) {
|
||||
x_phys = m_x_ref;
|
||||
} else {
|
||||
UpdateElementCache(T);
|
||||
|
||||
m_shape.SetSize(m_fe->GetDof());
|
||||
m_fe->CalcShape(ip, m_shape);
|
||||
|
||||
m_dof_mat.MultTranspose(m_shape, m_d_val);
|
||||
add(m_x_ref, m_d_val, x_phys);
|
||||
}
|
||||
if (is_vacuum(T)) {
|
||||
ApplyKelvinMapping(m_x_ref, x_phys);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainMapper::GetVectorValue(const int i, const mfem::IntegrationPoint &ip, mfem::Vector &val) const {
|
||||
m_d->GetVectorValue(i, ip, val);
|
||||
}
|
||||
|
||||
const mfem::GridFunction *DomainMapper::GetDisplacement() const { return m_d; }
|
||||
|
||||
double DomainMapper::GetPhysInfRadius() const {
|
||||
return 1.0 - m_xi_clamp;
|
||||
}
|
||||
|
||||
size_t DomainMapper::GetCacheHits() const {
|
||||
return m_cache_hits;
|
||||
}
|
||||
|
||||
size_t DomainMapper::GetCacheMisses() const {
|
||||
return m_cache_misses;
|
||||
}
|
||||
|
||||
double DomainMapper::GetCacheHitRate() const {
|
||||
return (static_cast<double>(m_cache_hits)) / static_cast<double>(m_cache_misses + m_cache_hits);
|
||||
}
|
||||
|
||||
void DomainMapper::ResetCacheStats() const {
|
||||
m_cache_hits = 0;
|
||||
m_cache_misses = 0;
|
||||
}
|
||||
|
||||
void DomainMapper::InitAllScratchSpaces() const {
|
||||
m_J_D.SetSize(m_dim, m_dim);
|
||||
m_J_temp.SetSize(m_dim, m_dim);
|
||||
m_JInv_temp.SetSize(m_dim, m_dim);
|
||||
m_x_ref.SetSize(m_dim);
|
||||
m_x_disp.SetSize(m_dim);
|
||||
m_d_val.SetSize(m_dim);
|
||||
}
|
||||
|
||||
void DomainMapper::ApplyKelvinMapping(const mfem::Vector &x_ref, mfem::Vector &x_phys) const {
|
||||
const double r_ref = x_ref.Norml2();
|
||||
double xi = (r_ref - m_r_star_ref) / (m_r_inf_ref - m_r_star_ref);
|
||||
xi = std::clamp(xi, 0.0, m_xi_clamp);
|
||||
const double factor = m_r_star_ref / (r_ref * (1 - xi));
|
||||
x_phys *= factor;
|
||||
}
|
||||
|
||||
void DomainMapper::ComputeKelvinJacobian(const mfem::Vector &x_ref, const mfem::Vector &x_disp, const mfem::DenseMatrix &J_D,
|
||||
mfem::DenseMatrix &J) const {
|
||||
const double r_ref = x_ref.Norml2();
|
||||
const double delta_R = m_r_inf_ref - m_r_star_ref;
|
||||
|
||||
double xi = (r_ref - m_r_star_ref) / delta_R;
|
||||
xi = std::clamp(xi, 0.0, m_xi_clamp);
|
||||
|
||||
const double denom = 1.0 - xi;
|
||||
|
||||
const double k = m_r_star_ref / (r_ref * denom);
|
||||
|
||||
const double dk_dr = m_r_star_ref * ((1.0 / (delta_R * r_ref * denom * denom)) - (
|
||||
1.0 / (r_ref * r_ref * denom)));
|
||||
|
||||
J.SetSize(m_dim, m_dim);
|
||||
const double outer_factor = dk_dr / r_ref;
|
||||
|
||||
for (int i = 0; i < m_dim; ++i) {
|
||||
for (int j = 0; j < m_dim; ++j) {
|
||||
J(i, j) = outer_factor * x_disp(i) * x_ref(j) + k * J_D(i, j);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainMapper::InvalidateCache() const {
|
||||
m_cached_elem_id = -1;
|
||||
}
|
||||
|
||||
void DomainMapper::UpdateElementCache(const mfem::ElementTransformation &T) const {
|
||||
if (IsIdentity()) return;
|
||||
|
||||
if (T.ElementNo != m_cached_elem_id || T.ElementType != m_cached_elem_type) {
|
||||
m_cache_misses++;
|
||||
m_cached_elem_id = T.ElementNo;
|
||||
m_cached_elem_type = T.ElementType;
|
||||
|
||||
const mfem::FiniteElementSpace *fes = m_d->FESpace();
|
||||
mfem::Array<int> vdofs;
|
||||
|
||||
if (T.ElementType == mfem::ElementTransformation::ELEMENT) {
|
||||
m_fe = fes->GetFE(m_cached_elem_id);
|
||||
fes->GetElementVDofs(m_cached_elem_id, vdofs);
|
||||
} else {
|
||||
m_fe = fes->GetBE(m_cached_elem_id);
|
||||
fes->GetBdrElementVDofs(m_cached_elem_id, vdofs);
|
||||
}
|
||||
|
||||
m_d->GetSubVector(vdofs, m_elem_dofs);
|
||||
|
||||
const int nd = m_fe->GetDof();
|
||||
const int vd = fes->GetVDim();
|
||||
|
||||
m_dof_mat.UseExternalData(m_elem_dofs.GetData(), nd, vd);
|
||||
} else {
|
||||
m_cache_hits++;
|
||||
}
|
||||
}
|
||||
}
|
||||
297
libmeanfield/impl/physics/gravity.cpp
Normal file
297
libmeanfield/impl/physics/gravity.cpp
Normal file
@@ -0,0 +1,297 @@
|
||||
module;
|
||||
#include "mfem.hpp"
|
||||
#include <source_location>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <format>
|
||||
|
||||
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<int> 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<mfem::Coefficient> 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<mfem::FunctionCoefficient>(boundary_potential);
|
||||
auto boundary_integrator = std::make_unique<mfem::VectorFEBoundaryFluxLFIntegrator>(*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<mfem::Coefficient> 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<mapping::MappedScalarCoefficient>(*f.mapping, source_coeff);
|
||||
active_source_coeff = mapped_source_coeff.get();
|
||||
source_mapping_kind = quadrature::MappingKind::general;
|
||||
}
|
||||
|
||||
auto source_integrator = std::make_unique<mfem::DomainLFIntegrator>(*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<mfem::Coefficient> centrifugal_coeff;
|
||||
if (fem.has_mapping()) {
|
||||
centrifugal_coeff = std::make_unique<mapping::PhysicalPositionFunctionCoefficient>(*fem.mapping, rot);
|
||||
} else {
|
||||
centrifugal_coeff = std::make_unique<mfem::FunctionCoefficient>(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<int> empty_tdofs;
|
||||
|
||||
// ==========================================
|
||||
// 1. Partially Assemble the High-Order Mass Block
|
||||
// ==========================================
|
||||
f.gravity_context.m_form = std::make_unique<mfem::ParBilinearForm>(f.RT_fes.get());
|
||||
f.gravity_context.m_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
|
||||
std::unique_ptr<mfem::VectorFEMassIntegrator> hdiv_mass_integrator;
|
||||
|
||||
if (f.has_mapping()) {
|
||||
f.gravity_context.mapped_hdiv_mass_coeff = std::make_unique<mapping::MappedHDivMassCoefficient>(*f.mapping, f.mesh->Dimension());
|
||||
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>(*f.gravity_context.mapped_hdiv_mass_coeff);
|
||||
} else {
|
||||
f.gravity_context.mapped_hdiv_mass_coeff.reset();
|
||||
hdiv_mass_integrator = std::make_unique<mfem::VectorFEMassIntegrator>();
|
||||
}
|
||||
|
||||
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<mfem::ParMixedBilinearForm>(f.RT_fes.get(), f.L2_fes.get());
|
||||
f.gravity_context.b_form->SetAssemblyLevel(mfem::AssemblyLevel::PARTIAL);
|
||||
|
||||
auto divergence_discretization_integrator = std::make_unique<mfem::VectorFEDivergenceIntegrator>();
|
||||
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<mfem::TransposeOperator>(f.gravity_context.b_form.get());
|
||||
|
||||
f.gravity_context.block_A = std::make_unique<mfem::BlockOperator>(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<mfem::VectorFEDivergenceIntegrator>();
|
||||
|
||||
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<mfem::HypreParMatrix> b_matrix(b_preconditioner.ParallelAssemble());
|
||||
std::unique_ptr<mfem::HypreParMatrix> 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<mfem::OperatorJacobiSmoother>(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());
|
||||
}
|
||||
|
||||
}
|
||||
38
libmeanfield/impl/physics/solid.cpp
Normal file
38
libmeanfield/impl/physics/solid.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
module;
|
||||
#include "mean_field.h"
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::physics {
|
||||
double compute_moment_of_inertia(const fem::FEM &fem, const mfem::GridFunction &rho_ref) {
|
||||
double local_I = 0.0;
|
||||
|
||||
for (int i = 0; i < fem.mesh->GetNE(); i++) {
|
||||
if (fem.mesh->GetAttribute(i) == 3) continue;
|
||||
|
||||
mfem::ElementTransformation *T = 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);
|
||||
T->SetIntPoint(&ip);
|
||||
|
||||
const double rho_hat = rho_ref.GetValue(i, ip);
|
||||
|
||||
mfem::Vector x_phys;
|
||||
fem.mapping->GetPhysicalPoint(*T, ip, x_phys);
|
||||
|
||||
const double r_cyl_sq = x_phys(0) * x_phys(0) + x_phys(1) * x_phys(1);
|
||||
const double detJ = std::fabs(fem.mapping->ComputeDetJ(*T, ip));
|
||||
const double weight = T->Weight() * ip.weight * detJ;
|
||||
|
||||
local_I += rho_hat * r_cyl_sq * weight;
|
||||
}
|
||||
}
|
||||
|
||||
double global_I = 0.0;
|
||||
MPI_Allreduce(&local_I, &global_I, 1, MPI_DOUBLE, MPI_SUM, fem.H1_fes->GetComm());
|
||||
|
||||
return global_I;
|
||||
}
|
||||
}
|
||||
188
libmeanfield/impl/utils/domain.cpp
Normal file
188
libmeanfield/impl/utils/domain.cpp
Normal file
@@ -0,0 +1,188 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
|
||||
module mean_field;
|
||||
|
||||
namespace mean_field::utils {
|
||||
|
||||
bool GetReferencePoint(
|
||||
const fem::FEM &fem,
|
||||
const mfem::Vector &x_phys_target,
|
||||
mfem::Vector &x_ref
|
||||
) {
|
||||
const int dim = fem.mesh->Dimension();
|
||||
x_ref = x_phys_target;
|
||||
|
||||
mfem::Array<int> init_elem;
|
||||
mfem::Array<mfem::IntegrationPoint> init_ip;
|
||||
mfem::DenseMatrix init_P(dim, 1);
|
||||
init_P.SetCol(0, x_ref);
|
||||
fem.mesh->FindPoints(init_P, init_elem, init_ip, false);
|
||||
|
||||
if (init_elem.Size() == 0 || init_elem[0] < 0) {
|
||||
mfem::Vector origin(dim);
|
||||
origin = 0.0;
|
||||
|
||||
mfem::DenseMatrix P_origin(dim, 1);
|
||||
P_origin.SetCol(0, origin);
|
||||
mfem::Array<int> origin_elem;
|
||||
mfem::Array<mfem::IntegrationPoint> origin_ip;
|
||||
fem.mesh->FindPoints(P_origin, origin_elem, origin_ip, false);
|
||||
|
||||
if (origin_elem.Size() > 0 && origin_elem[0] >= 0 && !fem.mapping->IsIdentity()) {
|
||||
mfem::ElementTransformation *T0 = fem.mesh->GetElementTransformation(origin_elem[0]);
|
||||
T0->SetIntPoint(&origin_ip[0]);
|
||||
|
||||
mfem::DenseMatrix J0(dim, dim), J0_inv(dim, dim);
|
||||
fem.mapping->ComputeJacobian(*T0, J0);
|
||||
mfem::CalcInverse(J0, J0_inv);
|
||||
|
||||
J0_inv.Mult(x_phys_target, x_ref);
|
||||
}
|
||||
|
||||
init_P.SetCol(0, x_ref);
|
||||
fem.mesh->FindPoints(init_P, init_elem, init_ip, false);
|
||||
|
||||
if (init_elem.Size() == 0 || init_elem[0] < 0) {
|
||||
double norm = x_ref.Norml2();
|
||||
if (norm > 1e-15) {
|
||||
double scale = 0.9 * RADIUS / norm;
|
||||
if (scale < 1.0) {
|
||||
x_ref *= scale;
|
||||
}
|
||||
}
|
||||
|
||||
init_P.SetCol(0, x_ref);
|
||||
fem.mesh->FindPoints(init_P, init_elem, init_ip, false);
|
||||
|
||||
if (init_elem.Size() == 0 || init_elem[0] < 0) {
|
||||
x_ref = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constexpr int max_iter = 50;
|
||||
|
||||
mfem::Array<int> elem_ids;
|
||||
mfem::Array<mfem::IntegrationPoint> ips;
|
||||
mfem::DenseMatrix P(dim, 1);
|
||||
mfem::Vector d(dim);
|
||||
mfem::Vector residual(dim);
|
||||
mfem::Vector step(dim);
|
||||
|
||||
mfem::DenseMatrix J_map(dim, dim);
|
||||
mfem::DenseMatrix J_map_inv(dim, dim);
|
||||
|
||||
int find_failures = 0;
|
||||
|
||||
for (int iter = 0; iter < max_iter; ++iter) {
|
||||
P.SetCol(0, x_ref);
|
||||
fem.mesh->FindPoints(P, elem_ids, ips, false);
|
||||
|
||||
if (elem_ids.Size() == 0 || elem_ids[0] < 0) {
|
||||
find_failures++;
|
||||
if (find_failures > 10) return false;
|
||||
|
||||
double norm = x_ref.Norml2();
|
||||
if (norm > 1e-15) {
|
||||
x_ref *= 0.5 * RADIUS / norm;
|
||||
} else {
|
||||
x_ref = 0.0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
int elemID = elem_ids[0];
|
||||
const mfem::IntegrationPoint &ip = ips[0];
|
||||
|
||||
mfem::ElementTransformation *T = fem.mesh->GetElementTransformation(elemID);
|
||||
T->SetIntPoint(&ip);
|
||||
|
||||
mfem::Vector current_x_phys(dim);
|
||||
fem.mapping->GetPhysicalPoint(*T, ip, current_x_phys);
|
||||
|
||||
for (int i = 0; i < dim; ++i) {
|
||||
residual(i) = current_x_phys(i) - x_phys_target(i);
|
||||
}
|
||||
|
||||
if (constexpr double tol = 1e-12; residual.Norml2() < tol) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fem.mapping->ComputeJacobian(*T, J_map);
|
||||
mfem::CalcInverse(J_map, J_map_inv);
|
||||
J_map_inv.Mult(residual, step);
|
||||
|
||||
double alpha = 1.0;
|
||||
mfem::Vector x_ref_candidate(dim);
|
||||
bool found_valid = false;
|
||||
|
||||
for (int ls = 0; ls < 8; ++ls) {
|
||||
x_ref_candidate = x_ref;
|
||||
x_ref_candidate.Add(-alpha, step);
|
||||
|
||||
P.SetCol(0, x_ref_candidate);
|
||||
fem.mesh->FindPoints(P, elem_ids, ips, false);
|
||||
|
||||
if (elem_ids.Size() > 0 && elem_ids[0] >= 0) {
|
||||
found_valid = true;
|
||||
break;
|
||||
}
|
||||
alpha *= 0.5;
|
||||
}
|
||||
|
||||
if (found_valid) {
|
||||
x_ref = x_ref_candidate;
|
||||
} else {
|
||||
find_failures++;
|
||||
if (find_failures > 10) return false;
|
||||
if (double norm = x_ref.Norml2(); norm > 1e-15) {
|
||||
x_ref *= 0.5 * RADIUS / norm;
|
||||
} else {
|
||||
x_ref = 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
double EvalGridFunctionAtPoint(
|
||||
const fem::FEM &fem,
|
||||
const mfem::ParGridFunction &u,
|
||||
const mfem::Vector &x,
|
||||
const mapping::COORDINATE_SPACE vspace,
|
||||
const mapping::COORDINATE_SPACE rspace
|
||||
) {
|
||||
mfem::Vector x_search;
|
||||
if (vspace == mapping::COORDINATE_SPACE::PHYSICAL && fem.has_mapping()) {
|
||||
GetReferencePoint(fem, x, x_search);
|
||||
} else {
|
||||
x_search = x;
|
||||
}
|
||||
|
||||
mfem::Array<int> elem_ids;
|
||||
mfem::Array<mfem::IntegrationPoint> ips;
|
||||
mfem::DenseMatrix P(x_search.Size(), 1);
|
||||
P.SetCol(0, x_search);
|
||||
|
||||
fem.mesh->FindPoints(P, elem_ids, ips, false);
|
||||
|
||||
double local_val = 0.0;
|
||||
if (elem_ids.Size() > 0 && elem_ids[0] >= 0) {
|
||||
const double val = u.GetValue(elem_ids[0], ips[0]);
|
||||
if (rspace == mapping::COORDINATE_SPACE::PHYSICAL && !fem.has_mapping()) {
|
||||
MFEM_ABORT("Physical evaluation mode requested but no mapping provided. Check domain bounds and mapping setup.");
|
||||
}
|
||||
local_val = val;
|
||||
}
|
||||
|
||||
double global_val = 0.0;
|
||||
MPI_Allreduce(&local_val, &global_val, 1, MPI_DOUBLE, MPI_MAX, fem.H1_fes->GetComm());
|
||||
return global_val;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
124
libmeanfield/impl/utils/misc.cpp
Normal file
124
libmeanfield/impl/utils/misc.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
module;
|
||||
#include <mfem.hpp>
|
||||
#include <expected>
|
||||
|
||||
module mean_field;
|
||||
import :boundary.contexts;
|
||||
|
||||
namespace mean_field::utils {
|
||||
DOMAINS operator|(
|
||||
DOMAINS lhs,
|
||||
DOMAINS rhs
|
||||
) {
|
||||
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) | static_cast<uint8_t>(rhs));
|
||||
}
|
||||
|
||||
DOMAINS operator&(
|
||||
DOMAINS lhs,
|
||||
DOMAINS rhs
|
||||
) {
|
||||
return static_cast<DOMAINS>(static_cast<uint8_t>(lhs) & static_cast<uint8_t>(rhs));
|
||||
}
|
||||
|
||||
void populate_element_mask(
|
||||
const mfem::Mesh* mesh,
|
||||
const DOMAINS domain,
|
||||
mfem::Array<int> &mask
|
||||
) {
|
||||
const int max_attr = mesh->attributes.Max();
|
||||
mask.SetSize(max_attr);
|
||||
mask = 0;
|
||||
|
||||
if ((domain & DOMAINS::CORE) == DOMAINS::CORE && max_attr >= 1) {
|
||||
mask[0] = 1;
|
||||
}
|
||||
|
||||
if ((domain & DOMAINS::ENVELOPE) == DOMAINS::ENVELOPE && max_attr >= 2) {
|
||||
mask[1] = 1;
|
||||
}
|
||||
|
||||
if ((domain & DOMAINS::VACUUM) == DOMAINS::VACUUM && max_attr >= 3) {
|
||||
mask[2] = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void populate_domain_tdofs(
|
||||
const mfem::ParFiniteElementSpace *fes,
|
||||
const mfem::Array<int> &element_mask,
|
||||
mfem::Array<int> &ess_tdof
|
||||
) {
|
||||
mfem::Array<int> vdof_marker(fes->GetVSize());
|
||||
vdof_marker = 0;
|
||||
|
||||
for (int i = 0; i < fes->GetMesh()->GetNE(); i++) {
|
||||
const int attr = fes->GetMesh()->GetAttribute(i);
|
||||
|
||||
if (element_mask[attr - 1]) {
|
||||
mfem::Array<int> dofs;
|
||||
fes->GetElementVDofs(i, dofs);
|
||||
|
||||
for (int j = 0; j < dofs.Size(); j++) {
|
||||
int index = dofs[j];
|
||||
if (index < 0) index = -1 - index;
|
||||
vdof_marker[index] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fes->MarkerToList(vdof_marker, ess_tdof);
|
||||
}
|
||||
|
||||
|
||||
std::expected<boundary::Bounds, boundary::BoundsError> discover_bounds(
|
||||
const mfem::Mesh *mesh,
|
||||
const int vacuum_attr
|
||||
) {
|
||||
double local_min_r = std::numeric_limits<double>::max();
|
||||
double local_max_r = -std::numeric_limits<double>::max();
|
||||
bool found_vacuum = false;
|
||||
|
||||
for (int i = 0; i < mesh->GetNE(); ++i) {
|
||||
if (mesh->GetAttribute(i) == vacuum_attr) {
|
||||
found_vacuum = true;
|
||||
mfem::Array<int> vertices;
|
||||
mesh->GetElementVertices(i, vertices);
|
||||
for (const int v: vertices) {
|
||||
const double *coords = mesh->GetVertex(v);
|
||||
double r = std::sqrt(coords[0] * coords[0] + coords[1] * coords[1] + coords[2] * coords[2]);
|
||||
local_min_r = std::min(local_min_r, r);
|
||||
local_max_r = std::max(local_max_r, r);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double global_min_r, global_max_r;
|
||||
int global_found_vacuum;
|
||||
int l_found = found_vacuum ? 1 : 0;
|
||||
|
||||
MPI_Comm comm = MPI_COMM_WORLD;
|
||||
if (const auto *pmesh = dynamic_cast<const mfem::ParMesh *>(mesh)) {
|
||||
comm = pmesh->GetComm();
|
||||
}
|
||||
|
||||
MPI_Allreduce(&local_min_r, &global_min_r, 1, MPI_DOUBLE, MPI_MIN, comm);
|
||||
MPI_Allreduce(&local_max_r, &global_max_r, 1, MPI_DOUBLE, MPI_MAX, comm);
|
||||
MPI_Allreduce(&l_found, &global_found_vacuum, 1, MPI_INT, MPI_MAX, comm);
|
||||
|
||||
if (global_found_vacuum) {
|
||||
return boundary::Bounds(global_min_r, global_max_r);
|
||||
}
|
||||
return std::unexpected(boundary::BoundsError::CANNOT_FIND_VACUUM);
|
||||
}
|
||||
|
||||
int get_mesh_order(
|
||||
const mfem::Mesh &mesh
|
||||
) {
|
||||
if (mesh.GetNodes() != nullptr) {
|
||||
return mesh.GetNodes()->FESpace()->GetMaxElementOrder();
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user