use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
use super::types::{
AdaptiveMeshRefinement, AffineTriangleMap, DGMesh1D, DGMethodStiffness, DenseMatrix, FEMesh2D,
GalerkinStiffnessMatrix, GaussQuadrature, NitscheData1D, PoissonFESolver, StiffnessMatrix,
TriangularMesh2D,
};
pub fn app(f: Expr, a: Expr) -> Expr {
Expr::App(Box::new(f), Box::new(a))
}
pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
app(app(f, a), b)
}
pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
app(app2(f, a, b), c)
}
pub fn app4(f: Expr, a: Expr, b: Expr, c: Expr, d: Expr) -> Expr {
app(app3(f, a, b, c), d)
}
pub fn cst(s: &str) -> Expr {
Expr::Const(Name::str(s), vec![])
}
pub fn prop() -> Expr {
Expr::Sort(Level::zero())
}
pub fn type0() -> Expr {
Expr::Sort(Level::succ(Level::zero()))
}
pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
}
pub fn arrow(a: Expr, b: Expr) -> Expr {
pi(BinderInfo::Default, "_", a, b)
}
pub fn bvar(n: u32) -> Expr {
Expr::BVar(n)
}
pub fn nat_ty() -> Expr {
cst("Nat")
}
pub fn real_ty() -> Expr {
cst("Real")
}
pub fn bool_ty() -> Expr {
cst("Bool")
}
pub fn fn_ty(dom: Expr, cod: Expr) -> Expr {
arrow(dom, cod)
}
pub fn triangulation_ty() -> Expr {
arrow(nat_ty(), type0())
}
pub fn triangulation_element_ty() -> Expr {
type0()
}
pub fn mesh_vertex_ty() -> Expr {
type0()
}
pub fn is_conforming_ty() -> Expr {
arrow(cst("Triangulation"), prop())
}
pub fn is_shape_regular_ty() -> Expr {
arrow(cst("Triangulation"), arrow(real_ty(), prop()))
}
pub fn is_quasiuniform_ty() -> Expr {
arrow(cst("Triangulation"), arrow(real_ty(), prop()))
}
pub fn mesh_size_ty() -> Expr {
arrow(cst("Triangulation"), real_ty())
}
pub fn num_elements_ty() -> Expr {
arrow(cst("Triangulation"), nat_ty())
}
pub fn num_dof_ty() -> Expr {
arrow(cst("FiniteElementSpace"), nat_ty())
}
pub fn reference_triangle_ty() -> Expr {
type0()
}
pub fn reference_tetrahedron_ty() -> Expr {
type0()
}
pub fn reference_hexahedron_ty() -> Expr {
type0()
}
pub fn affine_map_ty() -> Expr {
arrow(type0(), arrow(type0(), prop()))
}
pub fn jacobian_determinant_ty() -> Expr {
arrow(prop(), real_ty())
}
pub fn shape_function_ty() -> Expr {
arrow(
type0(),
arrow(nat_ty(), fn_ty(real_ty(), fn_ty(real_ty(), real_ty()))),
)
}
pub fn shape_function_gradient_ty() -> Expr {
arrow(
type0(),
arrow(nat_ty(), fn_ty(real_ty(), fn_ty(real_ty(), type0()))),
)
}
pub fn lagrange_p1_space_ty() -> Expr {
arrow(cst("Triangulation"), type0())
}
pub fn lagrange_p2_space_ty() -> Expr {
arrow(cst("Triangulation"), type0())
}
pub fn finite_element_space_ty() -> Expr {
type0()
}
pub fn is_subspace_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(cst("H1Space"), prop()))
}
pub fn interpolation_operator_ty() -> Expr {
arrow(
cst("H1Space"),
arrow(cst("FiniteElementSpace"), cst("H1Space")),
)
}
pub fn galerkin_problem_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("LinearForm"), arrow(cst("FiniteElementSpace"), prop())),
)
}
pub fn linear_form_ty() -> Expr {
type0()
}
pub fn bilinear_form_base_ty() -> Expr {
type0()
}
pub fn is_coercive_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
}
pub fn is_bounded_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
}
pub fn stiffness_matrix_ty() -> Expr {
arrow(cst("FiniteElementSpace"), type0())
}
pub fn mass_matrix_ty() -> Expr {
arrow(cst("FiniteElementSpace"), type0())
}
pub fn cea_lemma_ty() -> Expr {
pi(
BinderInfo::Default,
"a",
cst("BilinearForm"),
pi(
BinderInfo::Default,
"alpha",
real_ty(),
pi(
BinderInfo::Default,
"M",
real_ty(),
pi(
BinderInfo::Default,
"Vh",
cst("FiniteElementSpace"),
arrow(
app2(cst("IsCoercive"), bvar(3), bvar(2)),
arrow(app2(cst("IsBounded"), bvar(4), bvar(2)), prop()),
),
),
),
),
)
}
pub fn interpolation_error_ty() -> Expr {
arrow(
cst("FiniteElementSpace"),
arrow(nat_ty(), arrow(real_ty(), prop())),
)
}
pub fn apriori_error_estimate_ty() -> Expr {
arrow(
cst("FiniteElementSpace"),
arrow(nat_ty(), arrow(real_ty(), prop())),
)
}
pub fn l2_error_estimate_ty() -> Expr {
arrow(
cst("FiniteElementSpace"),
arrow(nat_ty(), arrow(real_ty(), prop())),
)
}
pub fn aubin_nitsche_trick_ty() -> Expr {
prop()
}
pub fn fem_well_posedness_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("LinearForm"), arrow(cst("FiniteElementSpace"), prop())),
)
}
pub fn lax_milgram_discrete_ty() -> Expr {
pi(
BinderInfo::Default,
"a",
cst("BilinearForm"),
pi(
BinderInfo::Default,
"f",
cst("LinearForm"),
pi(
BinderInfo::Default,
"Vh",
cst("FiniteElementSpace"),
arrow(
app2(cst("IsCoercive"), bvar(2), real_ty()),
arrow(app2(cst("IsBounded"), bvar(3), real_ty()), prop()),
),
),
),
)
}
pub fn stability_estimate_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(cst("LinearForm"), prop()))
}
pub fn nitsche_bilinear_form_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), cst("BilinearForm")))
}
pub fn nitsche_consistency_ty() -> Expr {
arrow(cst("BilinearForm"), prop())
}
pub fn nitsche_coercivity_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
}
pub fn nitsche_optimal_convergence_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(nat_ty(), prop()))
}
pub fn dg_space_ty() -> Expr {
arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
}
pub fn interior_penalty_form_ty() -> Expr {
arrow(type0(), arrow(real_ty(), cst("BilinearForm")))
}
pub fn dg_coercivity_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
}
pub fn dg_consistency_ty() -> Expr {
arrow(cst("BilinearForm"), prop())
}
pub fn dg_error_estimate_ty() -> Expr {
arrow(type0(), arrow(nat_ty(), arrow(real_ty(), prop())))
}
pub fn upg_form_ty() -> Expr {
arrow(type0(), arrow(real_ty(), cst("BilinearForm")))
}
pub fn mixed_fe_problem_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(
cst("BilinearForm"),
arrow(cst("LinearForm"), arrow(cst("LinearForm"), prop())),
),
)
}
pub fn inf_sup_condition_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
}
pub fn brezzi_splitting_ty() -> Expr {
arrow(prop(), prop())
}
pub fn mixed_fem_error_estimate_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(
cst("BilinearForm"),
arrow(cst("FiniteElementSpace"), prop()),
),
)
}
pub fn taylor_hood_element_ty() -> Expr {
arrow(cst("Triangulation"), type0())
}
pub fn mini_element_ty() -> Expr {
arrow(cst("Triangulation"), type0())
}
pub fn residual_estimator_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(cst("H1Space"), real_ty()))
}
pub fn reliability_bound_ty() -> Expr {
arrow(prop(), prop())
}
pub fn efficiency_bound_ty() -> Expr {
arrow(prop(), prop())
}
pub fn build_fem_env(env: &mut Environment) {
let axioms: &[(&str, Expr)] = &[
("FiniteElementSpace", finite_element_space_ty()),
("BilinearForm", bilinear_form_base_ty()),
("LinearForm", linear_form_ty()),
("H1Space", type0()),
("H10Space", type0()),
("L2Space", type0()),
("Matrix", type0()),
("Triangulation", triangulation_ty()),
("TriangulationElement", triangulation_element_ty()),
("MeshVertex", mesh_vertex_ty()),
("IsConforming", is_conforming_ty()),
("IsShapeRegular", is_shape_regular_ty()),
("IsQuasiuniform", is_quasiuniform_ty()),
("MeshSize", mesh_size_ty()),
("NumElements", num_elements_ty()),
("NumDOF", num_dof_ty()),
("ReferenceTriangle", reference_triangle_ty()),
("ReferenceTetrahedron", reference_tetrahedron_ty()),
("ReferenceHexahedron", reference_hexahedron_ty()),
("AffineMap", affine_map_ty()),
("JacobianDeterminant", jacobian_determinant_ty()),
("ShapeFunction", shape_function_ty()),
("ShapeFunctionGradient", shape_function_gradient_ty()),
("LagrangeP1Space", lagrange_p1_space_ty()),
("LagrangeP2Space", lagrange_p2_space_ty()),
("IsSubspace", is_subspace_ty()),
("InterpolationOperator", interpolation_operator_ty()),
("GalerkinProblem", galerkin_problem_ty()),
("IsCoercive", is_coercive_ty()),
("IsBounded", is_bounded_ty()),
("StiffnessMatrix", stiffness_matrix_ty()),
("MassMatrix", mass_matrix_ty()),
("cea_lemma", cea_lemma_ty()),
("InterpolationError", interpolation_error_ty()),
("AprioriErrorEstimate", apriori_error_estimate_ty()),
("L2ErrorEstimate", l2_error_estimate_ty()),
("aubin_nitsche_trick", aubin_nitsche_trick_ty()),
("FEMWellPosedness", fem_well_posedness_ty()),
("lax_milgram_discrete", lax_milgram_discrete_ty()),
("StabilityEstimate", stability_estimate_ty()),
("NitscheBilinearForm", nitsche_bilinear_form_ty()),
("NitscheConsistency", nitsche_consistency_ty()),
("NitscheCoercivity", nitsche_coercivity_ty()),
(
"nitsche_optimal_convergence",
nitsche_optimal_convergence_ty(),
),
("DGSpace", dg_space_ty()),
("InteriorPenaltyForm", interior_penalty_form_ty()),
("DGCoercivity", dg_coercivity_ty()),
("DGConsistency", dg_consistency_ty()),
("DGErrorEstimate", dg_error_estimate_ty()),
("UPGForm", upg_form_ty()),
("MixedFEProblem", mixed_fe_problem_ty()),
("InfSupCondition", inf_sup_condition_ty()),
("brezzi_splitting", brezzi_splitting_ty()),
("MixedFEMErrorEstimate", mixed_fem_error_estimate_ty()),
("TaylorHoodElement", taylor_hood_element_ty()),
("MiniElement", mini_element_ty()),
("ResidualEstimator", residual_estimator_ty()),
("reliability_bound", reliability_bound_ty()),
("efficiency_bound", efficiency_bound_ty()),
];
for (name, ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(*name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
}
pub fn p1_shape(k: usize, xi: f64, eta: f64) -> f64 {
match k {
0 => 1.0 - xi - eta,
1 => xi,
2 => eta,
_ => 0.0,
}
}
pub fn p1_grad(k: usize) -> [f64; 2] {
match k {
0 => [-1.0, -1.0],
1 => [1.0, 0.0],
2 => [0.0, 1.0],
_ => [0.0, 0.0],
}
}
pub fn p2_shape(k: usize, xi: f64, eta: f64) -> f64 {
let lam1 = 1.0 - xi - eta;
let lam2 = xi;
let lam3 = eta;
match k {
0 => lam1 * (2.0 * lam1 - 1.0),
1 => lam2 * (2.0 * lam2 - 1.0),
2 => lam3 * (2.0 * lam3 - 1.0),
3 => 4.0 * lam1 * lam2,
4 => 4.0 * lam2 * lam3,
5 => 4.0 * lam1 * lam3,
_ => 0.0,
}
}
pub fn reference_triangle_quadrature() -> Vec<([f64; 2], f64)> {
vec![
([1.0 / 6.0, 1.0 / 6.0], 1.0 / 6.0),
([2.0 / 3.0, 1.0 / 6.0], 1.0 / 6.0),
([1.0 / 6.0, 2.0 / 3.0], 1.0 / 6.0),
]
}
#[allow(clippy::too_many_arguments)]
pub fn assemble_p1_stiffness(mesh: &TriangularMesh2D) -> (DenseMatrix, Vec<f64>) {
let n_v = mesh.num_vertices();
let mut k_global = DenseMatrix::zeros(n_v);
let mut f_global = vec![0.0; n_v];
let quad = reference_triangle_quadrature();
for tri_idx in 0..mesh.num_triangles() {
let [i0, i1, i2] = mesh.triangles[tri_idx];
let p0 = mesh.vertices[i0];
let p1 = mesh.vertices[i1];
let p2 = mesh.vertices[i2];
let fmap = AffineTriangleMap::new(p0, p1, p2);
let det_j = fmap.det_j();
let mut ke = [[0.0f64; 3]; 3];
let mut fe = [0.0f64; 3];
let grads_phys: [[f64; 2]; 3] = [
fmap.transform_grad(p1_grad(0)),
fmap.transform_grad(p1_grad(1)),
fmap.transform_grad(p1_grad(2)),
];
let area = det_j / 2.0;
for a in 0..3 {
for b in 0..3 {
let dot = grads_phys[a][0] * grads_phys[b][0] + grads_phys[a][1] * grads_phys[b][1];
ke[a][b] = dot * area;
}
for (pt, w) in &quad {
fe[a] += w * p1_shape(a, pt[0], pt[1]) * det_j;
}
}
let local_nodes = [i0, i1, i2];
for a in 0..3 {
for b in 0..3 {
k_global.add(local_nodes[a], local_nodes[b], ke[a][b]);
}
f_global[local_nodes[a]] += fe[a];
}
}
(k_global, f_global)
}
pub fn apply_dirichlet_bc(mat: &mut DenseMatrix, rhs: &mut Vec<f64>, is_boundary: &[bool]) {
for i in 0..mat.n {
if is_boundary[i] {
for j in 0..mat.n {
mat.data[i * mat.n + j] = 0.0;
mat.data[j * mat.n + i] = 0.0;
}
mat.data[i * mat.n + i] = 1.0;
rhs[i] = 0.0;
}
}
}
pub fn conjugate_gradient(mat: &DenseMatrix, rhs: &[f64], tol: f64, max_iter: usize) -> Vec<f64> {
let n = mat.n;
let mut x = vec![0.0; n];
let mut r = rhs.to_vec();
let mut p = r.clone();
let mut rr = dot(&r, &r);
for _ in 0..max_iter {
if rr < tol * tol {
break;
}
let ap = mat.matvec(&p);
let alpha = rr / dot(&p, &ap);
for i in 0..n {
x[i] += alpha * p[i];
r[i] -= alpha * ap[i];
}
let rr_new = dot(&r, &r);
let beta = rr_new / rr;
for i in 0..n {
p[i] = r[i] + beta * p[i];
}
rr = rr_new;
}
x
}
pub fn dot(a: &[f64], b: &[f64]) -> f64 {
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
pub fn element_residuals_1d(u: &[f64], h: f64, f: impl Fn(f64) -> f64) -> Vec<f64> {
let n = u.len();
if n < 3 {
return vec![];
}
(1..n - 1)
.map(|i| {
let x_i = i as f64 * h;
let laplacian_u = (u[i - 1] - 2.0 * u[i] + u[i + 1]) / (h * h);
let residual = f(x_i) + laplacian_u;
h * residual.abs()
})
.collect()
}
pub fn global_error_estimator(indicators: &[f64]) -> f64 {
indicators.iter().map(|e| e * e).sum::<f64>().sqrt()
}
pub fn petrov_galerkin_problem_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(
cst("LinearForm"),
arrow(
cst("FiniteElementSpace"),
arrow(cst("FiniteElementSpace"), prop()),
),
),
)
}
pub fn bubnov_galerkin_consistency_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("FiniteElementSpace"), prop()),
)
}
pub fn galerkin_orthogonality_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("H1Space"), arrow(cst("FiniteElementSpace"), prop())),
)
}
pub fn h_refinement_ty() -> Expr {
arrow(cst("Triangulation"), arrow(cst("Triangulation"), prop()))
}
pub fn p_refinement_ty() -> Expr {
arrow(
cst("FiniteElementSpace"),
arrow(nat_ty(), arrow(cst("FiniteElementSpace"), prop())),
)
}
pub fn hp_adaptivity_ty() -> Expr {
arrow(
cst("Triangulation"),
arrow(cst("FiniteElementSpace"), prop()),
)
}
pub fn exponential_convergence_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(real_ty(), prop()))
}
pub fn dorfler_marking_ty() -> Expr {
arrow(prop(), arrow(real_ty(), arrow(prop(), prop())))
}
pub fn nedelec_space_ty() -> Expr {
arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
}
pub fn raviart_thomas_space_ty() -> Expr {
arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
}
pub fn bdm_space_ty() -> Expr {
arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
}
pub fn de_rham_complex_ty() -> Expr {
arrow(cst("Triangulation"), prop())
}
pub fn hcurl_conforming_ty() -> Expr {
arrow(cst("FiniteElementSpace"), prop())
}
pub fn hdiv_conforming_ty() -> Expr {
arrow(cst("FiniteElementSpace"), prop())
}
pub fn zz_estimator_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(cst("H1Space"), real_ty()))
}
pub fn goal_oriented_estimator_ty() -> Expr {
arrow(
cst("FiniteElementSpace"),
arrow(cst("LinearForm"), real_ty()),
)
}
pub fn adaptive_convergence_ty() -> Expr {
arrow(
cst("FiniteElementSpace"),
arrow(real_ty(), arrow(real_ty(), prop())),
)
}
pub fn multigrid_preconditioner_ty() -> Expr {
arrow(cst("FiniteElementSpace"), type0())
}
pub fn domain_decomposition_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(nat_ty(), prop()))
}
pub fn feti_preconditioner_ty() -> Expr {
arrow(cst("FiniteElementSpace"), type0())
}
pub fn bddc_preconditioner_ty() -> Expr {
arrow(cst("FiniteElementSpace"), type0())
}
pub fn multigrid_optimality_ty() -> Expr {
arrow(type0(), prop())
}
pub fn bspline_basis_ty() -> Expr {
arrow(nat_ty(), arrow(nat_ty(), type0()))
}
pub fn nurbs_surface_ty() -> Expr {
arrow(type0(), arrow(type0(), type0()))
}
pub fn isogeometric_space_ty() -> Expr {
arrow(type0(), arrow(nat_ty(), type0()))
}
pub fn isogeometric_convergence_ty() -> Expr {
arrow(type0(), arrow(nat_ty(), arrow(real_ty(), prop())))
}
pub fn tspline_space_ty() -> Expr {
arrow(nat_ty(), type0())
}
pub fn space_time_fe_space_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(real_ty(), type0()))
}
pub fn parabolic_stability_ty() -> Expr {
arrow(type0(), prop())
}
pub fn wave_energy_conservation_ty() -> Expr {
arrow(type0(), prop())
}
pub fn space_time_error_estimate_ty() -> Expr {
arrow(type0(), arrow(nat_ty(), arrow(real_ty(), prop())))
}
pub fn newton_raphson_fem_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("LinearForm"), arrow(cst("FiniteElementSpace"), prop())),
)
}
pub fn arc_length_method_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
}
pub fn consistent_linearization_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("H1Space"), cst("BilinearForm")),
)
}
pub fn quadratic_convergence_newton_ty() -> Expr {
arrow(prop(), prop())
}
pub fn reissner_mindlin_plate_ty() -> Expr {
arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
}
pub fn kirchhoff_love_plate_ty() -> Expr {
arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
}
pub fn shear_locking_freedom_ty() -> Expr {
arrow(type0(), prop())
}
pub fn reduced_integration_ty() -> Expr {
arrow(cst("FiniteElementSpace"), arrow(nat_ty(), prop()))
}
pub fn fluid_structure_interaction_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(cst("BilinearForm"), prop()))
}
pub fn thermo_mechanical_coupling_ty() -> Expr {
arrow(
cst("BilinearForm"),
arrow(cst("BilinearForm"), arrow(cst("LinearForm"), prop())),
)
}
pub fn poro_elasticity_biot_ty() -> Expr {
arrow(cst("BilinearForm"), arrow(cst("BilinearForm"), prop()))
}
pub fn operator_splitting_ty() -> Expr {
arrow(prop(), prop())
}
pub fn build_fem_env_extended(env: &mut Environment) {
let axioms: &[(&str, Expr)] = &[
("PetrovGalerkinProblem", petrov_galerkin_problem_ty()),
(
"BubnovGalerkinConsistency",
bubnov_galerkin_consistency_ty(),
),
("GalerkinOrthogonality", galerkin_orthogonality_ty()),
("HRefinement", h_refinement_ty()),
("PRefinement", p_refinement_ty()),
("HPAdaptivity", hp_adaptivity_ty()),
("ExponentialConvergence", exponential_convergence_ty()),
("DorflerMarking", dorfler_marking_ty()),
("NedelecSpace", nedelec_space_ty()),
("RaviartThomasSpace", raviart_thomas_space_ty()),
("BDMSpace", bdm_space_ty()),
("DeRhamComplex", de_rham_complex_ty()),
("HCurlConforming", hcurl_conforming_ty()),
("HDivConforming", hdiv_conforming_ty()),
("ZZEstimator", zz_estimator_ty()),
("GoalOrientedEstimator", goal_oriented_estimator_ty()),
("AdaptiveConvergence", adaptive_convergence_ty()),
("MultigridPreconditioner", multigrid_preconditioner_ty()),
("DomainDecomposition", domain_decomposition_ty()),
("FETIPreconditioner", feti_preconditioner_ty()),
("BDDCPreconditioner", bddc_preconditioner_ty()),
("MultigridOptimality", multigrid_optimality_ty()),
("BSplineBasis", bspline_basis_ty()),
("NURBSSurface", nurbs_surface_ty()),
("IsogeometricSpace", isogeometric_space_ty()),
("IsogeometricConvergence", isogeometric_convergence_ty()),
("TSplineSpace", tspline_space_ty()),
("SpaceTimeFESpace", space_time_fe_space_ty()),
("ParabolicStability", parabolic_stability_ty()),
("WaveEnergyConservation", wave_energy_conservation_ty()),
("SpaceTimeErrorEstimate", space_time_error_estimate_ty()),
("NewtonRaphsonFEM", newton_raphson_fem_ty()),
("ArcLengthMethod", arc_length_method_ty()),
("ConsistentLinearization", consistent_linearization_ty()),
(
"QuadraticConvergenceNewton",
quadratic_convergence_newton_ty(),
),
("ReissnerMindlinPlate", reissner_mindlin_plate_ty()),
("KirchhoffLovePlate", kirchhoff_love_plate_ty()),
("ShearLockingFreedom", shear_locking_freedom_ty()),
("ReducedIntegration", reduced_integration_ty()),
(
"FluidStructureInteraction",
fluid_structure_interaction_ty(),
),
("ThermoMechanicalCoupling", thermo_mechanical_coupling_ty()),
("PoroElasticityBiot", poro_elasticity_biot_ty()),
("OperatorSplitting", operator_splitting_ty()),
];
for (name, ty) in axioms {
env.add(Declaration::Axiom {
name: Name::str(*name),
univ_params: vec![],
ty: ty.clone(),
})
.ok();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_mesh_area() {
let mesh = TriangularMesh2D::unit_square(4);
let area = mesh.total_area();
assert!((area - 1.0).abs() < 1e-12, "Total area = {}", area);
}
#[test]
fn test_mesh_size() {
let mesh = TriangularMesh2D::unit_square(4);
let h = mesh.mesh_size();
let h_expected = std::f64::consts::SQRT_2 / 4.0;
assert!(
(h - h_expected).abs() < 1e-12,
"Mesh size = {}, expected {}",
h,
h_expected
);
}
#[test]
fn test_p1_shape_partition_of_unity() {
let points = [(0.2, 0.3), (0.5, 0.3), (0.1, 0.8), (1.0 / 3.0, 1.0 / 3.0)];
for (xi, eta) in points {
let sum: f64 = (0..3).map(|k| p1_shape(k, xi, eta)).sum();
assert!(
(sum - 1.0).abs() < 1e-12,
"POU failed at ({}, {}): sum = {}",
xi,
eta,
sum
);
}
}
#[test]
fn test_p2_shape_partition_of_unity() {
let points = [(0.2, 0.2), (0.5, 0.1), (0.0, 0.5)];
for (xi, eta) in points {
let sum: f64 = (0..6).map(|k| p2_shape(k, xi, eta)).sum();
assert!(
(sum - 1.0).abs() < 1e-12,
"P2 POU failed at ({}, {}): sum = {}",
xi,
eta,
sum
);
}
}
#[test]
fn test_affine_map_and_jacobian() {
let fmap = AffineTriangleMap::new([0.0, 0.0], [1.0, 0.0], [0.0, 1.0]);
assert!((fmap.det_j() - 1.0).abs() < 1e-12);
let centroid = fmap.apply(1.0 / 3.0, 1.0 / 3.0);
assert!((centroid[0] - 1.0 / 3.0).abs() < 1e-12);
assert!((centroid[1] - 1.0 / 3.0).abs() < 1e-12);
}
#[test]
fn test_stiffness_assembly_diagonal() {
let mesh = TriangularMesh2D::unit_square(2);
let (k, _f) = assemble_p1_stiffness(&mesh);
for i in 0..k.n {
assert!(
k.get(i, i) >= 0.0,
"Diagonal entry [{}] = {} < 0",
i,
k.get(i, i)
);
}
}
#[test]
fn test_dg_advection_conservation() {
let mesh = DGMesh1D::uniform(0.0, 1.0, 10);
let u0: Vec<f64> = mesh
.centers
.iter()
.map(|&x| (2.0 * std::f64::consts::PI * x).sin())
.collect();
let mass0 = mesh.l1_norm(&u0);
let dt = 0.5 * mesh.h;
let u1 = mesh.dg_step(&u0, 1.0, dt);
let mass1 = mesh.l1_norm(&u1);
assert!(
mass1 <= mass0 + 1e-10,
"DG increased mass: {} > {}",
mass1,
mass0
);
}
#[test]
fn test_nitsche_coercivity_threshold() {
let n_data = NitscheData1D::new(10.0, 0.1, 0.0);
assert!(n_data.is_coercive());
let n_data_small = NitscheData1D::new(0.5, 0.1, 0.0);
assert!(!n_data_small.is_coercive());
}
#[test]
fn test_axiom_types_well_formed() {
assert!(matches!(cea_lemma_ty(), Expr::Pi(_, _, _, _)));
assert!(matches!(lax_milgram_discrete_ty(), Expr::Pi(_, _, _, _)));
assert!(matches!(triangulation_ty(), Expr::Pi(_, _, _, _)));
assert!(matches!(
mixed_fem_error_estimate_ty(),
Expr::Pi(_, _, _, _)
));
}
#[test]
fn test_build_fem_env_extended() {
let mut env = Environment::new();
build_fem_env(&mut env);
build_fem_env_extended(&mut env);
assert!(env
.get(&oxilean_kernel::Name::str("NedelecSpace"))
.is_some());
assert!(env
.get(&oxilean_kernel::Name::str("BDDCPreconditioner"))
.is_some());
assert!(env
.get(&oxilean_kernel::Name::str("IsogeometricSpace"))
.is_some());
assert!(env
.get(&oxilean_kernel::Name::str("ReissnerMindlinPlate"))
.is_some());
assert!(env
.get(&oxilean_kernel::Name::str("FluidStructureInteraction"))
.is_some());
}
#[test]
fn test_galerkin_stiffness_constant() {
let mesh = TriangularMesh2D::unit_square(2);
let gs = GalerkinStiffnessMatrix::constant(1.0, mesh.clone());
let k_gs = gs.assemble();
let (k_p1, _) = assemble_p1_stiffness(&mesh);
assert_eq!(k_gs.n, k_p1.n);
for i in 0..k_gs.n {
assert!(
(k_gs.get(i, i) - k_p1.get(i, i)).abs() < 1e-12,
"Diagonal [{i}] differs: {} vs {}",
k_gs.get(i, i),
k_p1.get(i, i)
);
}
}
#[test]
fn test_galerkin_stiffness_variable() {
let mesh = TriangularMesh2D::unit_square(2);
let gs1 = GalerkinStiffnessMatrix::constant(1.0, mesh.clone());
let gs2 = GalerkinStiffnessMatrix::constant(2.0, mesh.clone());
let k1 = gs1.assemble();
let k2 = gs2.assemble();
for i in 0..k1.n {
assert!(
(k2.get(i, i) - 2.0 * k1.get(i, i)).abs() < 1e-12,
"k=2 diagonal [{i}] should be 2× k=1"
);
}
}
#[test]
fn test_adaptive_refinement_reduces_mesh_size() {
let mesh = TriangularMesh2D::unit_square(2);
let n_before = mesh.num_triangles();
let mut amr = AdaptiveMeshRefinement::new(mesh, 1.0);
let refined = amr.step();
assert!(refined > 0, "should have refined at least one element");
assert!(
amr.mesh.num_triangles() > n_before,
"mesh should grow after refinement"
);
}
#[test]
fn test_adaptive_refinement_area_preserved() {
let mesh = TriangularMesh2D::unit_square(2);
let area_before = mesh.total_area();
let mut amr = AdaptiveMeshRefinement::new(mesh, 1.0);
amr.step();
let area_after = amr.mesh.total_area();
assert!(
(area_after - area_before).abs() < 1e-10,
"area before = {area_before}, after = {area_after}"
);
}
#[test]
fn test_dg_stiffness_coercive() {
let mesh = TriangularMesh2D::unit_square(2);
let dg = DGMethodStiffness::new(mesh, 100.0);
assert!(
dg.is_coercive(),
"DG stiffness with σ=100 should be coercive"
);
}
#[test]
fn test_dg_stiffness_dimension() {
let mesh = TriangularMesh2D::unit_square(2);
let n_tri = mesh.num_triangles();
let dg = DGMethodStiffness::new(mesh, 10.0);
let mat = dg.assemble();
assert_eq!(mat.n, 3 * n_tri, "DG matrix size should be 3 × n_triangles");
}
#[test]
fn test_new_axiom_types_are_pi_or_sort() {
for ty in [
petrov_galerkin_problem_ty(),
nedelec_space_ty(),
multigrid_preconditioner_ty(),
isogeometric_convergence_ty(),
newton_raphson_fem_ty(),
kirchhoff_love_plate_ty(),
fluid_structure_interaction_ty(),
] {
assert!(
matches!(ty, Expr::Pi(_, _, _, _) | Expr::Sort(_)),
"Expected Pi or Sort, got {:?}",
ty
);
}
}
}
#[allow(dead_code)]
pub fn dist(a: (f64, f64), b: (f64, f64)) -> f64 {
((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt()
}
#[cfg(test)]
mod tests_fem_extra {
use super::*;
fn unit_triangle_mesh() -> FEMesh2D {
let nodes = vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)];
let elems = vec![(0, 1, 2)];
FEMesh2D::new(nodes, elems)
}
#[test]
fn test_fem_mesh() {
let mesh = unit_triangle_mesh();
assert_eq!(mesh.n_nodes(), 3);
assert_eq!(mesh.n_elements(), 1);
let area = mesh.element_area(0);
assert!((area - 0.5).abs() < 1e-9, "Area should be 0.5, got {area}");
assert!((mesh.total_area() - 0.5).abs() < 1e-9);
}
#[test]
fn test_gauss_quadrature() {
let gl2 = GaussQuadrature::gauss_legendre_2();
assert_eq!(gl2.n_points(), 2);
let integral = gl2.integrate(|x| x * x);
assert!((integral - 2.0 / 3.0).abs() < 1e-6);
}
#[test]
fn test_stiffness_matrix() {
let mut k = StiffnessMatrix::new(3);
k.add_entry(0, 0, 2.0);
k.add_entry(0, 1, -1.0);
k.add_entry(1, 0, -1.0);
k.add_entry(1, 1, 2.0);
k.add_entry(1, 2, -1.0);
k.add_entry(2, 1, -1.0);
k.add_entry(2, 2, 1.0);
assert_eq!(k.n_nonzeros(), 7);
let r = k.apply(&[1.0, 1.0, 1.0]);
assert!((r[0] - 1.0).abs() < 1e-9);
}
#[test]
fn test_poisson_solver() {
let mesh = unit_triangle_mesh();
let mut solver = PoissonFESolver::new(mesh);
solver.add_dirichlet_bc(0, 0.0);
solver.add_dirichlet_bc(1, 0.0);
assert_eq!(solver.n_free_dofs(), 1);
assert!(solver.estimated_condition_number() > 0.0);
}
}