Found by the adversarial review of the #407 fix (2026-07-24) — pre-existing, upstream of that change.
A Dirichlet BC that references mesh.t NaNs the solve:
poisson.add_dirichlet_bc(mesh.t, "Bottom")
poisson.solve(time=2.0) # DIVERGED_FNORM_NAN; wall values -inf
Mechanism (read in PETSc source): the solve-side boundary insertion goes through DMPlexSNESComputeBoundaryFEM, which hard-codes time=PETSC_MIN_REAL into DMPlexInsertBoundaryValues; that value lands as petsc_t (-1.8e308) in the JIT'd essential-BC kernel, so any BC expression containing mesh.t evaluates to garbage. solve()'s docstring claim that 'expressions using mesh.t evaluate at this time' is empirically false for Dirichlet BCs.
Note the working counter-evidence: _assemble_volume_reaction(time=2.0) (post-#407) inserts g(t) correctly via a direct DMPlexInsertBoundaryValues call with the real time — so the kernels and the insertion machinery are fine; only the solve path's hard-coded PETSC_MIN_REAL is the problem. Fix direction: insert essential values explicitly with the solver's non-dimensional time before SNESSolve (or set the DM time in a way the PETSc path picks up — UW_DMSetTime exists but the BoundaryFEM path ignores it).
Related latent detail: boundary_flux() never forwards a solve time to _assemble_volume_reaction, so once this is fixed, CBF on a time-dependent wall would read g(0) — forward the time in the same change.
Underworld development team with AI support from Claude Code
Found by the adversarial review of the #407 fix (2026-07-24) — pre-existing, upstream of that change.
A Dirichlet BC that references
mesh.tNaNs the solve:Mechanism (read in PETSc source): the solve-side boundary insertion goes through
DMPlexSNESComputeBoundaryFEM, which hard-codestime=PETSC_MIN_REALintoDMPlexInsertBoundaryValues; that value lands aspetsc_t(-1.8e308) in the JIT'd essential-BC kernel, so any BC expression containingmesh.tevaluates to garbage.solve()'s docstring claim that 'expressions using mesh.t evaluate at this time' is empirically false for Dirichlet BCs.Note the working counter-evidence:
_assemble_volume_reaction(time=2.0)(post-#407) inserts g(t) correctly via a directDMPlexInsertBoundaryValuescall with the real time — so the kernels and the insertion machinery are fine; only the solve path's hard-coded PETSC_MIN_REAL is the problem. Fix direction: insert essential values explicitly with the solver's non-dimensional time before SNESSolve (or set the DM time in a way the PETSc path picks up —UW_DMSetTimeexists but the BoundaryFEM path ignores it).Related latent detail:
boundary_flux()never forwards a solve time to_assemble_volume_reaction, so once this is fixed, CBF on a time-dependent wall would read g(0) — forward the time in the same change.Underworld development team with AI support from Claude Code