Adaptive Meshing
Adaptive refinement of quadrilateral (and hexahedral) meshes with Ferrite's AMR and of linear triangle meshes with newest vertex bisection (BisectionMesh of the AMR module in the Ferrite.jl fork that ModularEIT depends on). Refined quadrilateral meshes have hanging nodes; FerriteDiscretization condenses the conformity constraints, so forward models, objectives and solvers work unchanged. Bisection keeps triangle meshes conforming (also continuous σ spaces work) and carries facet and cell sets over. Theory: wiki articles Adaptive Meshing in EIT, Hanging Nodes, Newest Vertex Bisection, Residual Estimator for the Conductivity Equation, Zienkiewicz-Zhu Estimator, Goal-Oriented Error Estimation and Dörfler Marking.
am = AdaptiveMesh(generate_grid(Quadrilateral, (16, 16)); maxlevel = 6)
for step in 1:10
disc = FerriteDiscretization(current_grid(am))
fm = ForwardModel(disc, CompleteElectrodeModel(angular_electrodes(disc, 16), 1e-3))
σ = interpolate_function(disc, σfun)
I = trigonometric_patterns(fm, 8)
_, X = forward_neumann(fm, σ, I)
η = goal_oriented_indicator(disc, fm, σ, X; estimator = :residual, currents = I)
η[cell_levels(am) .>= max_level(am)] .= 0
refine_mesh!(am, dorfler_marking(η, 0.3))
endModularEITFerrite.AdaptiveMesh — Type
AdaptiveMesh(grid; maxlevel)Adaptively refinable mesh. Use refine_mesh! / coarsen_mesh! and build a new FerriteDiscretization on current_grid after each change.
- Quadrilateral or hexahedral
grid: forest of quadtrees/octrees from Ferrite's AMR, at mostmaxlevelrefinement levels (default 10; one level quarters a cell). Refined grids have hanging nodes. - Linear triangle
grid: newest vertex bisection, at mostmaxlevelbisections per cell (default 20; two bisections quarter a cell). Refined grids are conforming; facet and cell sets are carried over. Coarsening undoes bisections exactly.
Other cell types (tetrahedra, quadratic cells) are accepted with a warning and never refined.
ModularEITFerrite.current_grid — Function
current_grid(am::AdaptiveMesh)The current (possibly non-conforming) grid.
ModularEITFerrite.refine_mesh! — Function
refine_mesh!(am, cells)
refine_mesh!(am)Refine the cells cells of the current grid (or all cells) once and restore the 2:1 balance between neighbouring cells. Cell numbers refer to current_grid(am).
ModularEITFerrite.coarsen_mesh! — Function
coarsen_mesh!(am, cells)Coarsen: every complete family of sibling cells among cells is merged into its parent. On triangle meshes a family is the two or four cells around a vertex created by bisection (merging them undoes that bisection exactly).
ModularEITFerrite.cell_levels — Function
cell_levels(am::AdaptiveMesh)Refinement level of every cell of current_grid(am) (0 = cell of the initial grid).
ModularEITFerrite.max_level — Function
max_level(am::AdaptiveMesh)Maximum refinement level; cells at this level are not refined further. Exclude them from marking (e.g. set their indicator to zero) so that the adaptive loop keeps making progress.
ModularEITFerrite.is_nonconforming — Function
is_nonconforming(grid)Whether grid has hanging nodes (a non-conforming grid from adaptive refinement).
ModularEITFerrite.residual_indicator — Function
residual_indicator(disc, fm, σ, X, currents = nothing; mode = :neumann, normalize = false)Residual-based error indicator of the forward problem, one value η_K² per cell, summed over the patterns (columns of X, the states from forward_neumann or forward_dirichlet):
η_K² = Σₛ h_K² ‖∇⋅(σ∇uₛ)‖²_K + ½ Σ_F h_F ‖[σ∂ₙuₛ]‖²_F + Σ_{F ⊂ ∂Ω} h_F ‖gₛ - σ∂ₙuₛ‖²_F.The boundary current density gₛ comes from the electrode model: the current density of the continuum model and Iₗ/|eₗ| of the gap model (both need currents), (Uₗ - u)/zₗ of the complete electrode model, zero in the gaps. Point loads are not estimated, and Dirichlet boundaries (mode = :dirichlet) have no boundary term. normalize = true scales every pattern's indicator to unit sum. √Σ_K η_K² bounds the energy error up to a constant; it works on conforming and hanging-node meshes.
ModularEITFerrite.flux_recovery_indicator — Function
flux_recovery_indicator(disc, σ, X; normalize = false)Zienkiewicz–Zhu error indicator of the current density, one value per cell:
η_K² = Σₛ ∫_K |J*ₛ - σ ∇uₛ|² dx,where J*ₛ is the L² projection of the discrete current density σ∇uₛ onto continuous linear vector fields. X holds the states of all patterns (n × s, e.g. from forward_neumann; only the first ndofs_u(disc) rows, the potential, are used). Large values mark electrode edges and conductivity interfaces, where the discrete current density jumps. With normalize = true every pattern's indicator is scaled to unit sum before summing, so that low-energy patterns (high spatial frequency) weigh as much as the dominant low-frequency ones.
ModularEITFerrite.goal_oriented_indicator — Function
goal_oriented_indicator(disc, fm, σ, X; estimator = :recovery, currents = nothing,
solver = DirectSolver(), normalize = false)Goal-oriented indicator for the measured voltages, one value per cell:
η_K = η_K(u) η_K(z), η_K(u)² = Σₛ ‖J*ₛ - σ∇uₛ‖²_K, η_K(z)² = Σₘ ‖J*(zₘ) - σ∇zₘ‖²_K,where zₘ are the dual solutions of the measurements (A zₘ = Qᵀ Πᵀ eₘ, the adjoint fields of the Jacobian rows). Both factors are flux_recovery_indicators (estimator = :recovery) or residual_indicators (estimator = :residual, needs the injected currents). The voltage error is bounded by products of primal and dual errors, so cells are refined where errors are made and influence the measurements. X holds the current-driven states (n × s). normalize weighs every pattern and every measurement equally.
In benchmark/adaptive_meshing.jl the residual version is the cheapest effective choice (indicator cost about that of one forward solve with all patterns).
ModularEITFerrite.jump_indicator — Function
jump_indicator(disc, σ)Feature indicator of a piecewise constant conductivity, one value per cell: Σ_F |F| |σ_K - σ_K'| / 2 over the facets F of the cell. Refining where it is large resolves inclusion boundaries; cells with zero indicator are candidates for coarsening.
ModularEIT.dorfler_marking — Function
dorfler_marking(η, θ)Dörfler (bulk) marking: the smallest set of cells, taken in decreasing order of η, whose indicators sum to at least θ times the total.
ModularEITFerrite.transfer_conductivity — Function
transfer_conductivity(disc_old, σ_old, disc_new)Conductivity coefficients on disc_new from σ_old on disc_old (e.g. after refinement or coarsening): the L² projection of the old conductivity onto the new σ space, with the old conductivity evaluated at the quadrature points of the new mesh. For piecewise constants this is exact in both directions: refined cells inherit the value of their parent, coarsened cells get the mean of their children.
Wiki articles
Theory behind this page in the theory wiki: