Skip to content

Commit 0a77b85

Browse files
Sébastien LoiselSébastien Loisel
authored andcommitted
JuMP front end: support spectral geometries; bump to 1.0.1
The spectral families (spectral1d/spectral2d) build their multigrid hierarchy by basis truncation, so their amg does not accept dirichlet_nodes and their Dirichlet subspace is the fixed zero-trace space. optimize! now detects this (the amg method lacks the dirichlet_nodes kwarg) and maps whole-boundary equality constraints onto the hierarchy's :dirichlet subspace, empty ones onto :full, and rejects a partial-boundary Dirichlet set with an explanatory error (basis truncation cannot honor per-node conditions). The prolongator attribute is likewise rejected for these geometries. FEM paths are unchanged. Tests cross-validate spectral2d and spectral1d JuMP models against the classical assemble/mgb_solve to solver tolerance, and check the partial-boundary and prolongator rejections. Docs and the MGBModel/jump.md notes updated; the front end no longer says spectral geometries are unwired. This is the first release to ship the JuMP and Gmsh modeling front ends, which landed after the 1.0.0 tag.
1 parent c19ab3a commit 0a77b85

5 files changed

Lines changed: 91 additions & 11 deletions

File tree

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "MultiGridBarrier"
22
uuid = "9e2c1f1d-9131-4ad4-b32f-bd2a0b0ecd1e"
33
authors = ["Sébastien Loisel"]
4-
version = "1.0.0"
4+
version = "1.0.1"
55

66
[deps]
77
AlgebraicMultigrid = "2169fc97-5a83-5252-b627-83903c6c433c"

docs/src/jump.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,11 @@ notion of continuity is the geometry's connectivity `geom.t`, so slit domains
208208
built with explicit connectivity keep their slits. The model must be in
209209
conic form (epigraph slacks are yours to declare, as with any conic solver);
210210
pointwise equality requires `On`; variable bounds and products of variable
211-
expressions are rejected with explanatory errors. `dual` and spectral
212-
geometries are not wired up yet.
211+
expressions are rejected with explanatory errors. Spectral geometries work
212+
too, with one restriction inherited from their hierarchy: the spectral
213+
Dirichlet subspace is built by basis truncation, so a Dirichlet condition
214+
there must cover exactly the whole boundary (`find_boundary(geom)`). `dual`
215+
is not wired up yet.
213216

214217
## API reference
215218

ext/MultiGridBarrierJuMPExt/MultiGridBarrierJuMPExt.jl

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import MultiGridBarrier
2222
import MultiGridBarrier: Geometry, MultiGrid, Convex, amg, assemble, mgb_solve,
2323
convex_linear, convex_Euclidian_power, convex_piecewise, MGBConvergenceFailure,
2424
On, Broken, Continuous, Uniform, deriv, integral, set_start, mgb_solution,
25-
solver_log
25+
solver_log, find_boundary
2626
const MOI = JuMP.MOI
2727

2828
# ---------------------------------------------------------------------------
@@ -804,10 +804,38 @@ function JuMP.optimize!(m::MGBModel{T}) where {T}
804804
n = m.nnodes
805805
V = size(geom.x, 1)
806806

807-
amg_kw = Dict{Symbol,Any}(:dirichlet_nodes => low.dirichlet_nodes)
808-
haskey(m.attrs, "prolongator") && (amg_kw[:prolongator] = m.attrs["prolongator"])
809-
isempty(low.dirichlet_nodes) && pop!(amg_kw, :dirichlet_nodes)
810-
mg = amg(geom; amg_kw...)
807+
sv = low.state_variables
808+
if hasmethod(amg, Tuple{typeof(geom)}, (:dirichlet_nodes,))
809+
amg_kw = Dict{Symbol,Any}(:dirichlet_nodes => low.dirichlet_nodes)
810+
haskey(m.attrs, "prolongator") && (amg_kw[:prolongator] = m.attrs["prolongator"])
811+
isempty(low.dirichlet_nodes) && pop!(amg_kw, :dirichlet_nodes)
812+
mg = amg(geom; amg_kw...)
813+
else
814+
# Basis-truncation hierarchies (the spectral families): the Dirichlet
815+
# subspace is the fixed zero-trace space, so per-variable node sets
816+
# cannot be honored. Accept exactly whole-boundary conditions and map
817+
# them onto the hierarchy's :dirichlet / :full subspaces.
818+
haskey(m.attrs, "prolongator") &&
819+
_argerror("the \"prolongator\" attribute is not supported by this geometry's amg")
820+
bdset = Set(find_boundary(geom))
821+
sv = copy(low.state_variables)
822+
for i in 1:size(sv, 1)
823+
sub = sv[i, 2]
824+
startswith(String(sub), "dirichlet_") || continue
825+
pairs = low.dirichlet_nodes[sub]
826+
if isempty(pairs)
827+
sv[i, 2] = :full
828+
elseif Set(pairs) == bdset
829+
sv[i, 2] = :dirichlet
830+
else
831+
_argerror("this geometry builds its Dirichlet subspace by basis " *
832+
"truncation, so a Dirichlet condition must cover exactly the " *
833+
"whole boundary (find_boundary(geom)); variable $(sv[i, 1]) is " *
834+
"constrained on $(length(pairs)) of $(length(bdset)) boundary nodes")
835+
end
836+
end
837+
mg = amg(geom)
838+
end
811839

812840
pieces = Convex{T}[_piece(m, mg, low, c) for c in low.cones]
813841
if length(pieces) == 1 && low.cones[1].pairs === nothing
@@ -826,7 +854,7 @@ function JuMP.optimize!(m::MGBModel{T}) where {T}
826854
end
827855

828856
prob = assemble(mg;
829-
state_variables = low.state_variables,
857+
state_variables = sv,
830858
D = low.D,
831859
f_grid = low.f_grid,
832860
g_grid = low.g_grid,

src/jump_frontend.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@
1515
MGBModel(geom::Geometry)
1616
1717
Construct a JuMP model over a fixed MultiGridBarrier discretization. Requires
18-
`using JuMP` (which loads the modeling extension). Build it from any FEM
19-
`Geometry` (e.g. `fem2d_P2()`, `subdivide(fem2d_P1(), 4)`), then use the standard
18+
`using JuMP` (which loads the modeling extension). Build it from any FEM or
19+
spectral `Geometry` (e.g. `fem2d_P2()`, `subdivide(fem2d_P1(), 4)`,
20+
`spectral2d(n = 16)`; spectral Dirichlet conditions must cover the whole
21+
boundary), then use the standard
2022
JuMP macros (`@variable`, `@constraint`, `@objective`); `optimize!` lowers the
2123
model to `amg` → `assemble` → `mgb_solve`, constructing the AMG hierarchy
2224
automatically from the geometry and the Dirichlet constraints. All spatial data

test/test_jump.jl

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,53 @@ _maxdiff(ref, cols...) =
386386
@test cv[1] < maximum(value(uu)) + 1e-2 # ... and binds
387387
end
388388

389+
@testset "spectral geometries (whole-boundary Dirichlet)" begin
390+
# Spectral hierarchies build their Dirichlet subspace by basis
391+
# truncation, so the JuMP path maps whole-boundary equality constraints
392+
# onto :dirichlet and rejects partial-boundary sets. The 2D model
393+
# mirrors the classical assemble defaults, so it must match to solver
394+
# tolerance.
395+
gs = spectral2d(n = 8)
396+
bs = find_boundary(gs)
397+
g2 = x -> x[1]^2 + x[2]^2
398+
ms = MGBModel(gs); _quiet!(ms)
399+
@variable(ms, us); @variable(ms, ss, Broken())
400+
set_start(us, g2); set_start(ss, 100.0)
401+
@constraint(ms, us == Coef(ms, g2), On(bs))
402+
@constraint(ms, [deriv(us, :dx); deriv(us, :dy); ss] in EpiPower(1.5))
403+
@objective(ms, Min, integral(Coef(ms, 0.5) * us + ss))
404+
optimize!(ms)
405+
ref = mgb_solve(assemble(amg(gs); p = 1.5); verbose = false)
406+
@test maximum(abs.(value(us) .- ref.z[:, 1])) < _JUMP_MATCH_TOL
407+
408+
# 1D, with the same data given explicitly on both sides
409+
g1d = spectral1d(n = 8)
410+
m1 = MGBModel(g1d); _quiet!(m1)
411+
@variable(m1, w); @variable(m1, r, Broken())
412+
set_start(w, x -> x[1]^2); set_start(r, 100.0)
413+
@constraint(m1, w == Coef(m1, x -> x[1]^2), On(find_boundary(g1d)))
414+
@constraint(m1, [deriv(w, :dx); r] in EpiPower(1.5))
415+
@objective(m1, Min, integral(Coef(m1, 0.5) * w + r))
416+
optimize!(m1)
417+
ref1 = mgb_solve(assemble(amg(g1d); p = 1.5,
418+
f = x -> (0.5, 0.0, 1.0), g = x -> (x[1]^2, 100.0)); verbose = false)
419+
@test maximum(abs.(value(w) .- ref1.z[:, 1])) < _JUMP_MATCH_TOL
420+
421+
# partial-boundary Dirichlet is rejected with the truncation
422+
# explanation, and so is the prolongator attribute
423+
mpb = MGBModel(gs); _quiet!(mpb)
424+
@variable(mpb, up); @variable(mpb, sp, Broken())
425+
set_start(sp, 100.0)
426+
@constraint(mpb, up == Coef(mpb, 0.0), On(bs[1:5]))
427+
@constraint(mpb, [deriv(up, :dx); deriv(up, :dy); sp] in EpiPower(2.0))
428+
@objective(mpb, Min, integral(1.0 * sp))
429+
@test_throws ArgumentError optimize!(mpb)
430+
err = try optimize!(mpb); "" catch e; sprint(showerror, e) end
431+
@test occursin("whole boundary", err)
432+
set_attribute(mpb, "prolongator", :unused)
433+
@test_throws ArgumentError optimize!(mpb)
434+
end
435+
389436
@testset "convergence failure surfaces as OTHER_ERROR" begin
390437
# maxit too small for the barrier to reach its target t: optimize! must
391438
# catch MGBConvergenceFailure, report it, and refuse to hand out values.

0 commit comments

Comments
 (0)