Ferrite-FEM / Ferrite-FEM/Ferrite.jl
AMR: support subdomain-defined fields in ConformityConstraint (SubDofHandler / L2Projector set)
- Dominant language
- Julia
- Stars
- 453
- Forks
- 115
- Avg merge
- 1d 17h
- Merged PRs (30d)
- 11
Description
`ConformityConstraint` currently requires its field to live on **every** cell of the
non-conforming grid. Partial coverage — a `SubDofHandler` over a subset, or
`L2Projector(ip, grid; set = ...)` — is rejected up front:
```julia
# src/Adaptivity/constraints.jl
if any(iszero, dh.cell_to_subdofhandler) || _has_uncovered_hanging_vertex(dh.grid.conformity_info, vertices)
throw(ArgumentError("ConformityConstraint requires the field :$(cc.field_name) on every cell of the non-conforming grid, but it covers only part of it. ..."))
end
```
Coverage *by union* already works: several `SubDofHandler`s that jointly cover the grid are
fine. What is unsupported is a field defined on only part of the domain — which is a normal
thing to want (multi-phase problems, a field that only exists in one region, projecting
quadrature data on a subset).
### Why the guard exists
Without it this crashes rather than misbehaves. `_add_conformity_constraint` iterates all of
`grid.conformity_info` and looks nodes up in `EntityMaps.vertices`, which holds `0` for
vertices no `SubDofHandler` ever visited. That produces `AffineConstraint(0, …)` and then a
`BoundsError` at `ch.isconstrained[0]` inside `close!`. It is reachable through the
documented `L2Projector(ip, grid; set = ...)` path, since `close!(proj)` adds a
`ConformityConstraint` unconditionally.
The guard sits at the single chokepoint shared by both entry paths (user-built
`ConstraintHandler`, and `L2Projector`), so the failure became a clear error instead of a
`BoundsError`. That was the deliberate scope cut for #780 — this issue is about doing it
properly.
### What the semantics should be
Masters are always vertices of the coarse cell carrying the interface, which makes most
cases decidable from the dof pattern alone:
- **hanging dof is 0** → nothing to constrain, skip. (This is the case that crashes today.)
- **hanging dof present, some master is 0** → the coarse cell cannot be in the subdomain, so
the interface lies on the subdomain boundary. **Skipping is correct here**; raising an
error would reject legitimate use, e.g. `set` = the fine cells only.
- **all dofs present, but the coarse cell is not in the subdomain** (masters supplied
incidentally by the two fine cells) → *not* decidable from dofs alone. A dof-based skip
mildly overconstrains.
Resolving the third case exactly requires keeping the **coarse element id** in
`conformity_info` — `creategrid` has it in the `cons2` / `cons4` master `ERef`s and drops it
when flattening to the hanging-node dict — and then gating on `dh.cell_to_subdofhandler`.
Note `conformity_info` is expected to change shape anyway for higher-order interpolations
(#1409), so extending it with the owning element is best done together with that work rather
than as a separate migration.
### Also needs fixing along the way
- **Field-index mismatch**: `_add_conformity_constraint` resolves the interpolation via
`find_field(dh, name)` (which returns an index *local* to a `SubDofHandler`) but indexes
`entitymaps.vertices` with the *global* field position. With a single covering handler these
coincide; with genuine subdomains they do not.
- **Duplicate emission**: with several `SubDofHandler`s carrying the same field, the
constraint loop must not emit the same constraint once per handler.
### Acceptance
- a field on a strict subset of a non-conforming grid can be constrained correctly,
- `L2Projector(ip, grid; set = ...)` works on a `NonConformingGrid`,
- interfaces on the subdomain boundary are skipped, not errored,
- the current `ArgumentError` is removed (or narrowed to genuinely unsupported cases).
Contributor guide
Assessment
This issue has not been assessed yet.