1use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::types::{
8 AdaptiveMeshRefinement, AffineTriangleMap, DGMesh1D, DGMethodStiffness, DenseMatrix, FEMesh2D,
9 GalerkinStiffnessMatrix, GaussQuadrature, NitscheData1D, PoissonFESolver, StiffnessMatrix,
10 TriangularMesh2D,
11};
12
13pub fn app(f: Expr, a: Expr) -> Expr {
14 Expr::App(Box::new(f), Box::new(a))
15}
16pub fn app2(f: Expr, a: Expr, b: Expr) -> Expr {
17 app(app(f, a), b)
18}
19pub fn app3(f: Expr, a: Expr, b: Expr, c: Expr) -> Expr {
20 app(app2(f, a, b), c)
21}
22pub fn app4(f: Expr, a: Expr, b: Expr, c: Expr, d: Expr) -> Expr {
23 app(app3(f, a, b, c), d)
24}
25pub fn cst(s: &str) -> Expr {
26 Expr::Const(Name::str(s), vec![])
27}
28pub fn prop() -> Expr {
29 Expr::Sort(Level::zero())
30}
31pub fn type0() -> Expr {
32 Expr::Sort(Level::succ(Level::zero()))
33}
34pub fn pi(bi: BinderInfo, name: &str, dom: Expr, body: Expr) -> Expr {
35 Expr::Pi(bi, Name::str(name), Box::new(dom), Box::new(body))
36}
37pub fn arrow(a: Expr, b: Expr) -> Expr {
38 pi(BinderInfo::Default, "_", a, b)
39}
40pub fn bvar(n: u32) -> Expr {
41 Expr::BVar(n)
42}
43pub fn nat_ty() -> Expr {
44 cst("Nat")
45}
46pub fn real_ty() -> Expr {
47 cst("Real")
48}
49pub fn bool_ty() -> Expr {
50 cst("Bool")
51}
52pub fn fn_ty(dom: Expr, cod: Expr) -> Expr {
53 arrow(dom, cod)
54}
55pub fn triangulation_ty() -> Expr {
60 arrow(nat_ty(), type0())
61}
62pub fn triangulation_element_ty() -> Expr {
67 type0()
68}
69pub fn mesh_vertex_ty() -> Expr {
73 type0()
74}
75pub fn is_conforming_ty() -> Expr {
81 arrow(cst("Triangulation"), prop())
82}
83pub fn is_shape_regular_ty() -> Expr {
89 arrow(cst("Triangulation"), arrow(real_ty(), prop()))
90}
91pub fn is_quasiuniform_ty() -> Expr {
96 arrow(cst("Triangulation"), arrow(real_ty(), prop()))
97}
98pub fn mesh_size_ty() -> Expr {
102 arrow(cst("Triangulation"), real_ty())
103}
104pub fn num_elements_ty() -> Expr {
108 arrow(cst("Triangulation"), nat_ty())
109}
110pub fn num_dof_ty() -> Expr {
114 arrow(cst("FiniteElementSpace"), nat_ty())
115}
116pub fn reference_triangle_ty() -> Expr {
121 type0()
122}
123pub fn reference_tetrahedron_ty() -> Expr {
127 type0()
128}
129pub fn reference_hexahedron_ty() -> Expr {
133 type0()
134}
135pub fn affine_map_ty() -> Expr {
140 arrow(type0(), arrow(type0(), prop()))
141}
142pub fn jacobian_determinant_ty() -> Expr {
147 arrow(prop(), real_ty())
148}
149pub fn shape_function_ty() -> Expr {
154 arrow(
155 type0(),
156 arrow(nat_ty(), fn_ty(real_ty(), fn_ty(real_ty(), real_ty()))),
157 )
158}
159pub fn shape_function_gradient_ty() -> Expr {
164 arrow(
165 type0(),
166 arrow(nat_ty(), fn_ty(real_ty(), fn_ty(real_ty(), type0()))),
167 )
168}
169pub fn lagrange_p1_space_ty() -> Expr {
175 arrow(cst("Triangulation"), type0())
176}
177pub fn lagrange_p2_space_ty() -> Expr {
183 arrow(cst("Triangulation"), type0())
184}
185pub fn finite_element_space_ty() -> Expr {
187 type0()
188}
189pub fn is_subspace_ty() -> Expr {
193 arrow(cst("FiniteElementSpace"), arrow(cst("H1Space"), prop()))
194}
195pub fn interpolation_operator_ty() -> Expr {
199 arrow(
200 cst("H1Space"),
201 arrow(cst("FiniteElementSpace"), cst("H1Space")),
202 )
203}
204pub fn galerkin_problem_ty() -> Expr {
210 arrow(
211 cst("BilinearForm"),
212 arrow(cst("LinearForm"), arrow(cst("FiniteElementSpace"), prop())),
213 )
214}
215pub fn linear_form_ty() -> Expr {
217 type0()
218}
219pub fn bilinear_form_base_ty() -> Expr {
221 type0()
222}
223pub fn is_coercive_ty() -> Expr {
227 arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
228}
229pub fn is_bounded_ty() -> Expr {
233 arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
234}
235pub fn stiffness_matrix_ty() -> Expr {
240 arrow(cst("FiniteElementSpace"), type0())
241}
242pub fn mass_matrix_ty() -> Expr {
247 arrow(cst("FiniteElementSpace"), type0())
248}
249pub fn cea_lemma_ty() -> Expr {
256 pi(
257 BinderInfo::Default,
258 "a",
259 cst("BilinearForm"),
260 pi(
261 BinderInfo::Default,
262 "alpha",
263 real_ty(),
264 pi(
265 BinderInfo::Default,
266 "M",
267 real_ty(),
268 pi(
269 BinderInfo::Default,
270 "Vh",
271 cst("FiniteElementSpace"),
272 arrow(
273 app2(cst("IsCoercive"), bvar(3), bvar(2)),
274 arrow(app2(cst("IsBounded"), bvar(4), bvar(2)), prop()),
275 ),
276 ),
277 ),
278 ),
279 )
280}
281pub fn interpolation_error_ty() -> Expr {
286 arrow(
287 cst("FiniteElementSpace"),
288 arrow(nat_ty(), arrow(real_ty(), prop())),
289 )
290}
291pub fn apriori_error_estimate_ty() -> Expr {
297 arrow(
298 cst("FiniteElementSpace"),
299 arrow(nat_ty(), arrow(real_ty(), prop())),
300 )
301}
302pub fn l2_error_estimate_ty() -> Expr {
308 arrow(
309 cst("FiniteElementSpace"),
310 arrow(nat_ty(), arrow(real_ty(), prop())),
311 )
312}
313pub fn aubin_nitsche_trick_ty() -> Expr {
319 prop()
320}
321pub fn fem_well_posedness_ty() -> Expr {
326 arrow(
327 cst("BilinearForm"),
328 arrow(cst("LinearForm"), arrow(cst("FiniteElementSpace"), prop())),
329 )
330}
331pub fn lax_milgram_discrete_ty() -> Expr {
336 pi(
337 BinderInfo::Default,
338 "a",
339 cst("BilinearForm"),
340 pi(
341 BinderInfo::Default,
342 "f",
343 cst("LinearForm"),
344 pi(
345 BinderInfo::Default,
346 "Vh",
347 cst("FiniteElementSpace"),
348 arrow(
349 app2(cst("IsCoercive"), bvar(2), real_ty()),
350 arrow(app2(cst("IsBounded"), bvar(3), real_ty()), prop()),
351 ),
352 ),
353 ),
354 )
355}
356pub fn stability_estimate_ty() -> Expr {
361 arrow(cst("BilinearForm"), arrow(cst("LinearForm"), prop()))
362}
363pub fn nitsche_bilinear_form_ty() -> Expr {
370 arrow(cst("BilinearForm"), arrow(real_ty(), cst("BilinearForm")))
371}
372pub fn nitsche_consistency_ty() -> Expr {
377 arrow(cst("BilinearForm"), prop())
378}
379pub fn nitsche_coercivity_ty() -> Expr {
384 arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
385}
386pub fn nitsche_optimal_convergence_ty() -> Expr {
392 arrow(cst("FiniteElementSpace"), arrow(nat_ty(), prop()))
393}
394pub fn dg_space_ty() -> Expr {
399 arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
400}
401pub fn interior_penalty_form_ty() -> Expr {
409 arrow(type0(), arrow(real_ty(), cst("BilinearForm")))
410}
411pub fn dg_coercivity_ty() -> Expr {
416 arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
417}
418pub fn dg_consistency_ty() -> Expr {
422 arrow(cst("BilinearForm"), prop())
423}
424pub fn dg_error_estimate_ty() -> Expr {
429 arrow(type0(), arrow(nat_ty(), arrow(real_ty(), prop())))
430}
431pub fn upg_form_ty() -> Expr {
437 arrow(type0(), arrow(real_ty(), cst("BilinearForm")))
438}
439pub fn mixed_fe_problem_ty() -> Expr {
445 arrow(
446 cst("BilinearForm"),
447 arrow(
448 cst("BilinearForm"),
449 arrow(cst("LinearForm"), arrow(cst("LinearForm"), prop())),
450 ),
451 )
452}
453pub fn inf_sup_condition_ty() -> Expr {
459 arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
460}
461pub fn brezzi_splitting_ty() -> Expr {
466 arrow(prop(), prop())
467}
468pub fn mixed_fem_error_estimate_ty() -> Expr {
473 arrow(
474 cst("BilinearForm"),
475 arrow(
476 cst("BilinearForm"),
477 arrow(cst("FiniteElementSpace"), prop()),
478 ),
479 )
480}
481pub fn taylor_hood_element_ty() -> Expr {
486 arrow(cst("Triangulation"), type0())
487}
488pub fn mini_element_ty() -> Expr {
493 arrow(cst("Triangulation"), type0())
494}
495pub fn residual_estimator_ty() -> Expr {
500 arrow(cst("FiniteElementSpace"), arrow(cst("H1Space"), real_ty()))
501}
502pub fn reliability_bound_ty() -> Expr {
506 arrow(prop(), prop())
507}
508pub fn efficiency_bound_ty() -> Expr {
513 arrow(prop(), prop())
514}
515pub fn build_fem_env(env: &mut Environment) {
516 let axioms: &[(&str, Expr)] = &[
517 ("FiniteElementSpace", finite_element_space_ty()),
518 ("BilinearForm", bilinear_form_base_ty()),
519 ("LinearForm", linear_form_ty()),
520 ("H1Space", type0()),
521 ("H10Space", type0()),
522 ("L2Space", type0()),
523 ("Matrix", type0()),
524 ("Triangulation", triangulation_ty()),
525 ("TriangulationElement", triangulation_element_ty()),
526 ("MeshVertex", mesh_vertex_ty()),
527 ("IsConforming", is_conforming_ty()),
528 ("IsShapeRegular", is_shape_regular_ty()),
529 ("IsQuasiuniform", is_quasiuniform_ty()),
530 ("MeshSize", mesh_size_ty()),
531 ("NumElements", num_elements_ty()),
532 ("NumDOF", num_dof_ty()),
533 ("ReferenceTriangle", reference_triangle_ty()),
534 ("ReferenceTetrahedron", reference_tetrahedron_ty()),
535 ("ReferenceHexahedron", reference_hexahedron_ty()),
536 ("AffineMap", affine_map_ty()),
537 ("JacobianDeterminant", jacobian_determinant_ty()),
538 ("ShapeFunction", shape_function_ty()),
539 ("ShapeFunctionGradient", shape_function_gradient_ty()),
540 ("LagrangeP1Space", lagrange_p1_space_ty()),
541 ("LagrangeP2Space", lagrange_p2_space_ty()),
542 ("IsSubspace", is_subspace_ty()),
543 ("InterpolationOperator", interpolation_operator_ty()),
544 ("GalerkinProblem", galerkin_problem_ty()),
545 ("IsCoercive", is_coercive_ty()),
546 ("IsBounded", is_bounded_ty()),
547 ("StiffnessMatrix", stiffness_matrix_ty()),
548 ("MassMatrix", mass_matrix_ty()),
549 ("cea_lemma", cea_lemma_ty()),
550 ("InterpolationError", interpolation_error_ty()),
551 ("AprioriErrorEstimate", apriori_error_estimate_ty()),
552 ("L2ErrorEstimate", l2_error_estimate_ty()),
553 ("aubin_nitsche_trick", aubin_nitsche_trick_ty()),
554 ("FEMWellPosedness", fem_well_posedness_ty()),
555 ("lax_milgram_discrete", lax_milgram_discrete_ty()),
556 ("StabilityEstimate", stability_estimate_ty()),
557 ("NitscheBilinearForm", nitsche_bilinear_form_ty()),
558 ("NitscheConsistency", nitsche_consistency_ty()),
559 ("NitscheCoercivity", nitsche_coercivity_ty()),
560 (
561 "nitsche_optimal_convergence",
562 nitsche_optimal_convergence_ty(),
563 ),
564 ("DGSpace", dg_space_ty()),
565 ("InteriorPenaltyForm", interior_penalty_form_ty()),
566 ("DGCoercivity", dg_coercivity_ty()),
567 ("DGConsistency", dg_consistency_ty()),
568 ("DGErrorEstimate", dg_error_estimate_ty()),
569 ("UPGForm", upg_form_ty()),
570 ("MixedFEProblem", mixed_fe_problem_ty()),
571 ("InfSupCondition", inf_sup_condition_ty()),
572 ("brezzi_splitting", brezzi_splitting_ty()),
573 ("MixedFEMErrorEstimate", mixed_fem_error_estimate_ty()),
574 ("TaylorHoodElement", taylor_hood_element_ty()),
575 ("MiniElement", mini_element_ty()),
576 ("ResidualEstimator", residual_estimator_ty()),
577 ("reliability_bound", reliability_bound_ty()),
578 ("efficiency_bound", efficiency_bound_ty()),
579 ];
580 for (name, ty) in axioms {
581 env.add(Declaration::Axiom {
582 name: Name::str(*name),
583 univ_params: vec![],
584 ty: ty.clone(),
585 })
586 .ok();
587 }
588}
589pub fn p1_shape(k: usize, xi: f64, eta: f64) -> f64 {
595 match k {
596 0 => 1.0 - xi - eta,
597 1 => xi,
598 2 => eta,
599 _ => 0.0,
600 }
601}
602pub fn p1_grad(k: usize) -> [f64; 2] {
606 match k {
607 0 => [-1.0, -1.0],
608 1 => [1.0, 0.0],
609 2 => [0.0, 1.0],
610 _ => [0.0, 0.0],
611 }
612}
613pub fn p2_shape(k: usize, xi: f64, eta: f64) -> f64 {
617 let lam1 = 1.0 - xi - eta;
618 let lam2 = xi;
619 let lam3 = eta;
620 match k {
621 0 => lam1 * (2.0 * lam1 - 1.0),
622 1 => lam2 * (2.0 * lam2 - 1.0),
623 2 => lam3 * (2.0 * lam3 - 1.0),
624 3 => 4.0 * lam1 * lam2,
625 4 => 4.0 * lam2 * lam3,
626 5 => 4.0 * lam1 * lam3,
627 _ => 0.0,
628 }
629}
630pub fn reference_triangle_quadrature() -> Vec<([f64; 2], f64)> {
634 vec![
635 ([1.0 / 6.0, 1.0 / 6.0], 1.0 / 6.0),
636 ([2.0 / 3.0, 1.0 / 6.0], 1.0 / 6.0),
637 ([1.0 / 6.0, 2.0 / 3.0], 1.0 / 6.0),
638 ]
639}
640#[allow(clippy::too_many_arguments)]
645pub fn assemble_p1_stiffness(mesh: &TriangularMesh2D) -> (DenseMatrix, Vec<f64>) {
646 let n_v = mesh.num_vertices();
647 let mut k_global = DenseMatrix::zeros(n_v);
648 let mut f_global = vec![0.0; n_v];
649 let quad = reference_triangle_quadrature();
650 for tri_idx in 0..mesh.num_triangles() {
651 let [i0, i1, i2] = mesh.triangles[tri_idx];
652 let p0 = mesh.vertices[i0];
653 let p1 = mesh.vertices[i1];
654 let p2 = mesh.vertices[i2];
655 let fmap = AffineTriangleMap::new(p0, p1, p2);
656 let det_j = fmap.det_j();
657 let mut ke = [[0.0f64; 3]; 3];
658 let mut fe = [0.0f64; 3];
659 let grads_phys: [[f64; 2]; 3] = [
660 fmap.transform_grad(p1_grad(0)),
661 fmap.transform_grad(p1_grad(1)),
662 fmap.transform_grad(p1_grad(2)),
663 ];
664 let area = det_j / 2.0;
665 for a in 0..3 {
666 for b in 0..3 {
667 let dot = grads_phys[a][0] * grads_phys[b][0] + grads_phys[a][1] * grads_phys[b][1];
668 ke[a][b] = dot * area;
669 }
670 for (pt, w) in &quad {
671 fe[a] += w * p1_shape(a, pt[0], pt[1]) * det_j;
672 }
673 }
674 let local_nodes = [i0, i1, i2];
675 for a in 0..3 {
676 for b in 0..3 {
677 k_global.add(local_nodes[a], local_nodes[b], ke[a][b]);
678 }
679 f_global[local_nodes[a]] += fe[a];
680 }
681 }
682 (k_global, f_global)
683}
684pub fn apply_dirichlet_bc(mat: &mut DenseMatrix, rhs: &mut Vec<f64>, is_boundary: &[bool]) {
687 for i in 0..mat.n {
688 if is_boundary[i] {
689 for j in 0..mat.n {
690 mat.data[i * mat.n + j] = 0.0;
691 mat.data[j * mat.n + i] = 0.0;
692 }
693 mat.data[i * mat.n + i] = 1.0;
694 rhs[i] = 0.0;
695 }
696 }
697}
698pub fn conjugate_gradient(mat: &DenseMatrix, rhs: &[f64], tol: f64, max_iter: usize) -> Vec<f64> {
701 let n = mat.n;
702 let mut x = vec![0.0; n];
703 let mut r = rhs.to_vec();
704 let mut p = r.clone();
705 let mut rr = dot(&r, &r);
706 for _ in 0..max_iter {
707 if rr < tol * tol {
708 break;
709 }
710 let ap = mat.matvec(&p);
711 let alpha = rr / dot(&p, &ap);
712 for i in 0..n {
713 x[i] += alpha * p[i];
714 r[i] -= alpha * ap[i];
715 }
716 let rr_new = dot(&r, &r);
717 let beta = rr_new / rr;
718 for i in 0..n {
719 p[i] = r[i] + beta * p[i];
720 }
721 rr = rr_new;
722 }
723 x
724}
725pub fn dot(a: &[f64], b: &[f64]) -> f64 {
726 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
727}
728pub fn element_residuals_1d(u: &[f64], h: f64, f: impl Fn(f64) -> f64) -> Vec<f64> {
732 let n = u.len();
733 if n < 3 {
734 return vec![];
735 }
736 (1..n - 1)
737 .map(|i| {
738 let x_i = i as f64 * h;
739 let laplacian_u = (u[i - 1] - 2.0 * u[i] + u[i + 1]) / (h * h);
740 let residual = f(x_i) + laplacian_u;
741 h * residual.abs()
742 })
743 .collect()
744}
745pub fn global_error_estimator(indicators: &[f64]) -> f64 {
747 indicators.iter().map(|e| e * e).sum::<f64>().sqrt()
748}
749pub fn petrov_galerkin_problem_ty() -> Expr {
755 arrow(
756 cst("BilinearForm"),
757 arrow(
758 cst("LinearForm"),
759 arrow(
760 cst("FiniteElementSpace"),
761 arrow(cst("FiniteElementSpace"), prop()),
762 ),
763 ),
764 )
765}
766pub fn bubnov_galerkin_consistency_ty() -> Expr {
771 arrow(
772 cst("BilinearForm"),
773 arrow(cst("FiniteElementSpace"), prop()),
774 )
775}
776pub fn galerkin_orthogonality_ty() -> Expr {
781 arrow(
782 cst("BilinearForm"),
783 arrow(cst("H1Space"), arrow(cst("FiniteElementSpace"), prop())),
784 )
785}
786pub fn h_refinement_ty() -> Expr {
791 arrow(cst("Triangulation"), arrow(cst("Triangulation"), prop()))
792}
793pub fn p_refinement_ty() -> Expr {
798 arrow(
799 cst("FiniteElementSpace"),
800 arrow(nat_ty(), arrow(cst("FiniteElementSpace"), prop())),
801 )
802}
803pub fn hp_adaptivity_ty() -> Expr {
808 arrow(
809 cst("Triangulation"),
810 arrow(cst("FiniteElementSpace"), prop()),
811 )
812}
813pub fn exponential_convergence_ty() -> Expr {
819 arrow(cst("FiniteElementSpace"), arrow(real_ty(), prop()))
820}
821pub fn dorfler_marking_ty() -> Expr {
826 arrow(prop(), arrow(real_ty(), arrow(prop(), prop())))
827}
828pub fn nedelec_space_ty() -> Expr {
834 arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
835}
836pub fn raviart_thomas_space_ty() -> Expr {
842 arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
843}
844pub fn bdm_space_ty() -> Expr {
848 arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
849}
850pub fn de_rham_complex_ty() -> Expr {
856 arrow(cst("Triangulation"), prop())
857}
858pub fn hcurl_conforming_ty() -> Expr {
863 arrow(cst("FiniteElementSpace"), prop())
864}
865pub fn hdiv_conforming_ty() -> Expr {
870 arrow(cst("FiniteElementSpace"), prop())
871}
872pub fn zz_estimator_ty() -> Expr {
878 arrow(cst("FiniteElementSpace"), arrow(cst("H1Space"), real_ty()))
879}
880pub fn goal_oriented_estimator_ty() -> Expr {
886 arrow(
887 cst("FiniteElementSpace"),
888 arrow(cst("LinearForm"), real_ty()),
889 )
890}
891pub fn adaptive_convergence_ty() -> Expr {
897 arrow(
898 cst("FiniteElementSpace"),
899 arrow(real_ty(), arrow(real_ty(), prop())),
900 )
901}
902pub fn multigrid_preconditioner_ty() -> Expr {
908 arrow(cst("FiniteElementSpace"), type0())
909}
910pub fn domain_decomposition_ty() -> Expr {
915 arrow(cst("FiniteElementSpace"), arrow(nat_ty(), prop()))
916}
917pub fn feti_preconditioner_ty() -> Expr {
922 arrow(cst("FiniteElementSpace"), type0())
923}
924pub fn bddc_preconditioner_ty() -> Expr {
929 arrow(cst("FiniteElementSpace"), type0())
930}
931pub fn multigrid_optimality_ty() -> Expr {
936 arrow(type0(), prop())
937}
938pub fn bspline_basis_ty() -> Expr {
943 arrow(nat_ty(), arrow(nat_ty(), type0()))
944}
945pub fn nurbs_surface_ty() -> Expr {
950 arrow(type0(), arrow(type0(), type0()))
951}
952pub fn isogeometric_space_ty() -> Expr {
957 arrow(type0(), arrow(nat_ty(), type0()))
958}
959pub fn isogeometric_convergence_ty() -> Expr {
965 arrow(type0(), arrow(nat_ty(), arrow(real_ty(), prop())))
966}
967pub fn tspline_space_ty() -> Expr {
972 arrow(nat_ty(), type0())
973}
974pub fn space_time_fe_space_ty() -> Expr {
979 arrow(cst("FiniteElementSpace"), arrow(real_ty(), type0()))
980}
981pub fn parabolic_stability_ty() -> Expr {
986 arrow(type0(), prop())
987}
988pub fn wave_energy_conservation_ty() -> Expr {
993 arrow(type0(), prop())
994}
995pub fn space_time_error_estimate_ty() -> Expr {
1000 arrow(type0(), arrow(nat_ty(), arrow(real_ty(), prop())))
1001}
1002pub fn newton_raphson_fem_ty() -> Expr {
1008 arrow(
1009 cst("BilinearForm"),
1010 arrow(cst("LinearForm"), arrow(cst("FiniteElementSpace"), prop())),
1011 )
1012}
1013pub fn arc_length_method_ty() -> Expr {
1018 arrow(cst("BilinearForm"), arrow(real_ty(), prop()))
1019}
1020pub fn consistent_linearization_ty() -> Expr {
1025 arrow(
1026 cst("BilinearForm"),
1027 arrow(cst("H1Space"), cst("BilinearForm")),
1028 )
1029}
1030pub fn quadratic_convergence_newton_ty() -> Expr {
1035 arrow(prop(), prop())
1036}
1037pub fn reissner_mindlin_plate_ty() -> Expr {
1043 arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
1044}
1045pub fn kirchhoff_love_plate_ty() -> Expr {
1050 arrow(cst("Triangulation"), arrow(nat_ty(), type0()))
1051}
1052pub fn shear_locking_freedom_ty() -> Expr {
1058 arrow(type0(), prop())
1059}
1060pub fn reduced_integration_ty() -> Expr {
1065 arrow(cst("FiniteElementSpace"), arrow(nat_ty(), prop()))
1066}
1067pub fn fluid_structure_interaction_ty() -> Expr {
1072 arrow(cst("BilinearForm"), arrow(cst("BilinearForm"), prop()))
1073}
1074pub fn thermo_mechanical_coupling_ty() -> Expr {
1079 arrow(
1080 cst("BilinearForm"),
1081 arrow(cst("BilinearForm"), arrow(cst("LinearForm"), prop())),
1082 )
1083}
1084pub fn poro_elasticity_biot_ty() -> Expr {
1089 arrow(cst("BilinearForm"), arrow(cst("BilinearForm"), prop()))
1090}
1091pub fn operator_splitting_ty() -> Expr {
1096 arrow(prop(), prop())
1097}
1098pub fn build_fem_env_extended(env: &mut Environment) {
1100 let axioms: &[(&str, Expr)] = &[
1101 ("PetrovGalerkinProblem", petrov_galerkin_problem_ty()),
1102 (
1103 "BubnovGalerkinConsistency",
1104 bubnov_galerkin_consistency_ty(),
1105 ),
1106 ("GalerkinOrthogonality", galerkin_orthogonality_ty()),
1107 ("HRefinement", h_refinement_ty()),
1108 ("PRefinement", p_refinement_ty()),
1109 ("HPAdaptivity", hp_adaptivity_ty()),
1110 ("ExponentialConvergence", exponential_convergence_ty()),
1111 ("DorflerMarking", dorfler_marking_ty()),
1112 ("NedelecSpace", nedelec_space_ty()),
1113 ("RaviartThomasSpace", raviart_thomas_space_ty()),
1114 ("BDMSpace", bdm_space_ty()),
1115 ("DeRhamComplex", de_rham_complex_ty()),
1116 ("HCurlConforming", hcurl_conforming_ty()),
1117 ("HDivConforming", hdiv_conforming_ty()),
1118 ("ZZEstimator", zz_estimator_ty()),
1119 ("GoalOrientedEstimator", goal_oriented_estimator_ty()),
1120 ("AdaptiveConvergence", adaptive_convergence_ty()),
1121 ("MultigridPreconditioner", multigrid_preconditioner_ty()),
1122 ("DomainDecomposition", domain_decomposition_ty()),
1123 ("FETIPreconditioner", feti_preconditioner_ty()),
1124 ("BDDCPreconditioner", bddc_preconditioner_ty()),
1125 ("MultigridOptimality", multigrid_optimality_ty()),
1126 ("BSplineBasis", bspline_basis_ty()),
1127 ("NURBSSurface", nurbs_surface_ty()),
1128 ("IsogeometricSpace", isogeometric_space_ty()),
1129 ("IsogeometricConvergence", isogeometric_convergence_ty()),
1130 ("TSplineSpace", tspline_space_ty()),
1131 ("SpaceTimeFESpace", space_time_fe_space_ty()),
1132 ("ParabolicStability", parabolic_stability_ty()),
1133 ("WaveEnergyConservation", wave_energy_conservation_ty()),
1134 ("SpaceTimeErrorEstimate", space_time_error_estimate_ty()),
1135 ("NewtonRaphsonFEM", newton_raphson_fem_ty()),
1136 ("ArcLengthMethod", arc_length_method_ty()),
1137 ("ConsistentLinearization", consistent_linearization_ty()),
1138 (
1139 "QuadraticConvergenceNewton",
1140 quadratic_convergence_newton_ty(),
1141 ),
1142 ("ReissnerMindlinPlate", reissner_mindlin_plate_ty()),
1143 ("KirchhoffLovePlate", kirchhoff_love_plate_ty()),
1144 ("ShearLockingFreedom", shear_locking_freedom_ty()),
1145 ("ReducedIntegration", reduced_integration_ty()),
1146 (
1147 "FluidStructureInteraction",
1148 fluid_structure_interaction_ty(),
1149 ),
1150 ("ThermoMechanicalCoupling", thermo_mechanical_coupling_ty()),
1151 ("PoroElasticityBiot", poro_elasticity_biot_ty()),
1152 ("OperatorSplitting", operator_splitting_ty()),
1153 ];
1154 for (name, ty) in axioms {
1155 env.add(Declaration::Axiom {
1156 name: Name::str(*name),
1157 univ_params: vec![],
1158 ty: ty.clone(),
1159 })
1160 .ok();
1161 }
1162}
1163#[cfg(test)]
1164mod tests {
1165 use super::*;
1166 #[test]
1167 fn test_mesh_area() {
1168 let mesh = TriangularMesh2D::unit_square(4);
1169 let area = mesh.total_area();
1170 assert!((area - 1.0).abs() < 1e-12, "Total area = {}", area);
1171 }
1172 #[test]
1173 fn test_mesh_size() {
1174 let mesh = TriangularMesh2D::unit_square(4);
1175 let h = mesh.mesh_size();
1176 let h_expected = std::f64::consts::SQRT_2 / 4.0;
1177 assert!(
1178 (h - h_expected).abs() < 1e-12,
1179 "Mesh size = {}, expected {}",
1180 h,
1181 h_expected
1182 );
1183 }
1184 #[test]
1185 fn test_p1_shape_partition_of_unity() {
1186 let points = [(0.2, 0.3), (0.5, 0.3), (0.1, 0.8), (1.0 / 3.0, 1.0 / 3.0)];
1187 for (xi, eta) in points {
1188 let sum: f64 = (0..3).map(|k| p1_shape(k, xi, eta)).sum();
1189 assert!(
1190 (sum - 1.0).abs() < 1e-12,
1191 "POU failed at ({}, {}): sum = {}",
1192 xi,
1193 eta,
1194 sum
1195 );
1196 }
1197 }
1198 #[test]
1199 fn test_p2_shape_partition_of_unity() {
1200 let points = [(0.2, 0.2), (0.5, 0.1), (0.0, 0.5)];
1201 for (xi, eta) in points {
1202 let sum: f64 = (0..6).map(|k| p2_shape(k, xi, eta)).sum();
1203 assert!(
1204 (sum - 1.0).abs() < 1e-12,
1205 "P2 POU failed at ({}, {}): sum = {}",
1206 xi,
1207 eta,
1208 sum
1209 );
1210 }
1211 }
1212 #[test]
1213 fn test_affine_map_and_jacobian() {
1214 let fmap = AffineTriangleMap::new([0.0, 0.0], [1.0, 0.0], [0.0, 1.0]);
1215 assert!((fmap.det_j() - 1.0).abs() < 1e-12);
1216 let centroid = fmap.apply(1.0 / 3.0, 1.0 / 3.0);
1217 assert!((centroid[0] - 1.0 / 3.0).abs() < 1e-12);
1218 assert!((centroid[1] - 1.0 / 3.0).abs() < 1e-12);
1219 }
1220 #[test]
1221 fn test_stiffness_assembly_diagonal() {
1222 let mesh = TriangularMesh2D::unit_square(2);
1223 let (k, _f) = assemble_p1_stiffness(&mesh);
1224 for i in 0..k.n {
1225 assert!(
1226 k.get(i, i) >= 0.0,
1227 "Diagonal entry [{}] = {} < 0",
1228 i,
1229 k.get(i, i)
1230 );
1231 }
1232 }
1233 #[test]
1234 fn test_dg_advection_conservation() {
1235 let mesh = DGMesh1D::uniform(0.0, 1.0, 10);
1236 let u0: Vec<f64> = mesh
1237 .centers
1238 .iter()
1239 .map(|&x| (2.0 * std::f64::consts::PI * x).sin())
1240 .collect();
1241 let mass0 = mesh.l1_norm(&u0);
1242 let dt = 0.5 * mesh.h;
1243 let u1 = mesh.dg_step(&u0, 1.0, dt);
1244 let mass1 = mesh.l1_norm(&u1);
1245 assert!(
1246 mass1 <= mass0 + 1e-10,
1247 "DG increased mass: {} > {}",
1248 mass1,
1249 mass0
1250 );
1251 }
1252 #[test]
1253 fn test_nitsche_coercivity_threshold() {
1254 let n_data = NitscheData1D::new(10.0, 0.1, 0.0);
1255 assert!(n_data.is_coercive());
1256 let n_data_small = NitscheData1D::new(0.5, 0.1, 0.0);
1257 assert!(!n_data_small.is_coercive());
1258 }
1259 #[test]
1260 fn test_axiom_types_well_formed() {
1261 assert!(matches!(cea_lemma_ty(), Expr::Pi(_, _, _, _)));
1262 assert!(matches!(lax_milgram_discrete_ty(), Expr::Pi(_, _, _, _)));
1263 assert!(matches!(triangulation_ty(), Expr::Pi(_, _, _, _)));
1264 assert!(matches!(
1265 mixed_fem_error_estimate_ty(),
1266 Expr::Pi(_, _, _, _)
1267 ));
1268 }
1269 #[test]
1270 fn test_build_fem_env_extended() {
1271 let mut env = Environment::new();
1272 build_fem_env(&mut env);
1273 build_fem_env_extended(&mut env);
1274 assert!(env
1275 .get(&oxilean_kernel::Name::str("NedelecSpace"))
1276 .is_some());
1277 assert!(env
1278 .get(&oxilean_kernel::Name::str("BDDCPreconditioner"))
1279 .is_some());
1280 assert!(env
1281 .get(&oxilean_kernel::Name::str("IsogeometricSpace"))
1282 .is_some());
1283 assert!(env
1284 .get(&oxilean_kernel::Name::str("ReissnerMindlinPlate"))
1285 .is_some());
1286 assert!(env
1287 .get(&oxilean_kernel::Name::str("FluidStructureInteraction"))
1288 .is_some());
1289 }
1290 #[test]
1291 fn test_galerkin_stiffness_constant() {
1292 let mesh = TriangularMesh2D::unit_square(2);
1293 let gs = GalerkinStiffnessMatrix::constant(1.0, mesh.clone());
1294 let k_gs = gs.assemble();
1295 let (k_p1, _) = assemble_p1_stiffness(&mesh);
1296 assert_eq!(k_gs.n, k_p1.n);
1297 for i in 0..k_gs.n {
1298 assert!(
1299 (k_gs.get(i, i) - k_p1.get(i, i)).abs() < 1e-12,
1300 "Diagonal [{i}] differs: {} vs {}",
1301 k_gs.get(i, i),
1302 k_p1.get(i, i)
1303 );
1304 }
1305 }
1306 #[test]
1307 fn test_galerkin_stiffness_variable() {
1308 let mesh = TriangularMesh2D::unit_square(2);
1309 let gs1 = GalerkinStiffnessMatrix::constant(1.0, mesh.clone());
1310 let gs2 = GalerkinStiffnessMatrix::constant(2.0, mesh.clone());
1311 let k1 = gs1.assemble();
1312 let k2 = gs2.assemble();
1313 for i in 0..k1.n {
1314 assert!(
1315 (k2.get(i, i) - 2.0 * k1.get(i, i)).abs() < 1e-12,
1316 "k=2 diagonal [{i}] should be 2× k=1"
1317 );
1318 }
1319 }
1320 #[test]
1321 fn test_adaptive_refinement_reduces_mesh_size() {
1322 let mesh = TriangularMesh2D::unit_square(2);
1323 let n_before = mesh.num_triangles();
1324 let mut amr = AdaptiveMeshRefinement::new(mesh, 1.0);
1325 let refined = amr.step();
1326 assert!(refined > 0, "should have refined at least one element");
1327 assert!(
1328 amr.mesh.num_triangles() > n_before,
1329 "mesh should grow after refinement"
1330 );
1331 }
1332 #[test]
1333 fn test_adaptive_refinement_area_preserved() {
1334 let mesh = TriangularMesh2D::unit_square(2);
1335 let area_before = mesh.total_area();
1336 let mut amr = AdaptiveMeshRefinement::new(mesh, 1.0);
1337 amr.step();
1338 let area_after = amr.mesh.total_area();
1339 assert!(
1340 (area_after - area_before).abs() < 1e-10,
1341 "area before = {area_before}, after = {area_after}"
1342 );
1343 }
1344 #[test]
1345 fn test_dg_stiffness_coercive() {
1346 let mesh = TriangularMesh2D::unit_square(2);
1347 let dg = DGMethodStiffness::new(mesh, 100.0);
1348 assert!(
1349 dg.is_coercive(),
1350 "DG stiffness with σ=100 should be coercive"
1351 );
1352 }
1353 #[test]
1354 fn test_dg_stiffness_dimension() {
1355 let mesh = TriangularMesh2D::unit_square(2);
1356 let n_tri = mesh.num_triangles();
1357 let dg = DGMethodStiffness::new(mesh, 10.0);
1358 let mat = dg.assemble();
1359 assert_eq!(mat.n, 3 * n_tri, "DG matrix size should be 3 × n_triangles");
1360 }
1361 #[test]
1362 fn test_new_axiom_types_are_pi_or_sort() {
1363 for ty in [
1364 petrov_galerkin_problem_ty(),
1365 nedelec_space_ty(),
1366 multigrid_preconditioner_ty(),
1367 isogeometric_convergence_ty(),
1368 newton_raphson_fem_ty(),
1369 kirchhoff_love_plate_ty(),
1370 fluid_structure_interaction_ty(),
1371 ] {
1372 assert!(
1373 matches!(ty, Expr::Pi(_, _, _, _) | Expr::Sort(_)),
1374 "Expected Pi or Sort, got {:?}",
1375 ty
1376 );
1377 }
1378 }
1379}
1380#[allow(dead_code)]
1381pub fn dist(a: (f64, f64), b: (f64, f64)) -> f64 {
1382 ((b.0 - a.0).powi(2) + (b.1 - a.1).powi(2)).sqrt()
1383}
1384#[cfg(test)]
1385mod tests_fem_extra {
1386 use super::*;
1387 fn unit_triangle_mesh() -> FEMesh2D {
1388 let nodes = vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)];
1389 let elems = vec![(0, 1, 2)];
1390 FEMesh2D::new(nodes, elems)
1391 }
1392 #[test]
1393 fn test_fem_mesh() {
1394 let mesh = unit_triangle_mesh();
1395 assert_eq!(mesh.n_nodes(), 3);
1396 assert_eq!(mesh.n_elements(), 1);
1397 let area = mesh.element_area(0);
1398 assert!((area - 0.5).abs() < 1e-9, "Area should be 0.5, got {area}");
1399 assert!((mesh.total_area() - 0.5).abs() < 1e-9);
1400 }
1401 #[test]
1402 fn test_gauss_quadrature() {
1403 let gl2 = GaussQuadrature::gauss_legendre_2();
1404 assert_eq!(gl2.n_points(), 2);
1405 let integral = gl2.integrate(|x| x * x);
1406 assert!((integral - 2.0 / 3.0).abs() < 1e-6);
1407 }
1408 #[test]
1409 fn test_stiffness_matrix() {
1410 let mut k = StiffnessMatrix::new(3);
1411 k.add_entry(0, 0, 2.0);
1412 k.add_entry(0, 1, -1.0);
1413 k.add_entry(1, 0, -1.0);
1414 k.add_entry(1, 1, 2.0);
1415 k.add_entry(1, 2, -1.0);
1416 k.add_entry(2, 1, -1.0);
1417 k.add_entry(2, 2, 1.0);
1418 assert_eq!(k.n_nonzeros(), 7);
1419 let r = k.apply(&[1.0, 1.0, 1.0]);
1420 assert!((r[0] - 1.0).abs() < 1e-9);
1421 }
1422 #[test]
1423 fn test_poisson_solver() {
1424 let mesh = unit_triangle_mesh();
1425 let mut solver = PoissonFESolver::new(mesh);
1426 solver.add_dirichlet_bc(0, 0.0);
1427 solver.add_dirichlet_bc(1, 0.0);
1428 assert_eq!(solver.n_free_dofs(), 1);
1429 assert!(solver.estimated_condition_number() > 0.0);
1430 }
1431}