|
| 1 | +```@meta |
| 2 | +CurrentModule = MultiGridBarrier |
| 3 | +``` |
| 4 | + |
| 5 | +# Modeling with JuMP |
| 6 | + |
| 7 | +`MultiGridBarrierJuMP` lets you state convex variational problems in |
| 8 | +[JuMP](https://jump.dev) syntax and solve them with the MultiGridBarrier |
| 9 | +multigrid interior-point method. It is a `JuMP.AbstractModel` extension: the |
| 10 | +standard macros (`@variable`, `@constraint`, `@objective`) and accessors |
| 11 | +(`value`, `objective_value`, `termination_status`) work unchanged, but no MOI |
| 12 | +model is ever built — `optimize!` lowers the model directly to the classical |
| 13 | +pipeline `amg` → `assemble` → `mgb_solve`. The AMG hierarchy is constructed |
| 14 | +automatically from the geometry and the Dirichlet constraints; it is never |
| 15 | +user-visible. |
| 16 | + |
| 17 | +!!! note "Status" |
| 18 | + Working draft, not yet a registered package. It ships in the `jump/` |
| 19 | + directory of the repository and is loaded with `include`. All six |
| 20 | + [Zoo](zoo.md) problems restated in JuMP syntax reproduce the classical |
| 21 | + constructors' solutions bit-for-bit (or to one ulp); the regression suite |
| 22 | + is `jump/test_zoo.jl`, and the full modeling reference is |
| 23 | + [`jump/README.md`](https://github.com/sloisel/MultiGridBarrier.jl/tree/main/jump). |
| 24 | + |
| 25 | +## Setup |
| 26 | + |
| 27 | +JuMP is not a dependency of MultiGridBarrier — add it to your environment |
| 28 | +(`pkg> add JuMP`), then: |
| 29 | + |
| 30 | +```@example jump |
| 31 | +using MultiGridBarrier, JuMP, PyPlot |
| 32 | +include(joinpath(dirname(dirname(pathof(MultiGridBarrier))), |
| 33 | + "jump", "MultiGridBarrierJuMP.jl")) |
| 34 | +using .MultiGridBarrierJuMP |
| 35 | +nothing # hide |
| 36 | +``` |
| 37 | + |
| 38 | +## Quick tour: the p-Laplacian |
| 39 | + |
| 40 | +```math |
| 41 | +\min \int \tfrac{1}{2} u + s \, dx |
| 42 | +\quad \text{s.t.} \quad s \geq \|\nabla u\|^{1.5}, |
| 43 | +\qquad u = x_1^2 + x_2^2 \text{ on } \partial\Omega . |
| 44 | +``` |
| 45 | + |
| 46 | +A model is built over a fixed discretization, so every piece of spatial data |
| 47 | +(`Coef`) is evaluated at the quadrature nodes at modeling time. Derivatives |
| 48 | +are written `deriv(u, :dx)` where the symbol is a key of `geom.operators`; |
| 49 | +the epigraph cone `[q...; slack] in EpiPower(p)` means |
| 50 | +`slack ≥ ‖q‖₂ᵖ` pointwise (slack **last**). |
| 51 | + |
| 52 | +```@example jump |
| 53 | +geom = subdivide(fem2d_P2(), 2) |
| 54 | +m = MGBModel(geom) |
| 55 | +set_attribute(m, "verbose", false) |
| 56 | +@variable(m, u) # conforming (inferred) |
| 57 | +@variable(m, s, Broken()) # broken slack: one dof per node |
| 58 | +set_start(u, x -> x[1]^2 + x[2]^2) # initial iterate & Dirichlet lift |
| 59 | +set_start(s, 100.0) |
| 60 | +@constraint(m, u == Coef(m, x -> x[1]^2 + x[2]^2), On(find_boundary(geom))) |
| 61 | +@constraint(m, [deriv(u, :dx); deriv(u, :dy); s] in EpiPower(1.5)) |
| 62 | +@objective(m, Min, integral(Coef(m, 0.5) * u + s)) |
| 63 | +optimize!(m) |
| 64 | +termination_status(m) |
| 65 | +``` |
| 66 | + |
| 67 | +```@example jump |
| 68 | +plot(mgb_solution(m)); savefig("jump_plaplace.svg"); nothing # hide |
| 69 | +close() # hide |
| 70 | +``` |
| 71 | + |
| 72 | + |
| 73 | +This is exactly the package's default problem, so we can compare against the |
| 74 | +classical API on the same geometry. The lowering produces the identical |
| 75 | +discrete problem, so the solutions agree bit-for-bit: |
| 76 | + |
| 77 | +```@example jump |
| 78 | +sol_ref = mgb_solve(assemble(amg(geom); p = 1.5); verbose = false) |
| 79 | +maximum(abs.(value(u) .- sol_ref.z[:, 1])) |
| 80 | +``` |
| 81 | + |
| 82 | +## Regions: constraints on part of the domain |
| 83 | + |
| 84 | +A constraint holds everywhere by default; adding `On(pairs)` restricts it to |
| 85 | +a node set given as `(vertex, element)` pairs — the same format as |
| 86 | +[`find_boundary`](@ref) and the low-level `dirichlet_nodes` API. Equality + |
| 87 | +`On` is a Dirichlet condition; inequality/cone + `On` becomes a piecewise |
| 88 | +barrier, active only on the region. Region *selection* is ordinary data |
| 89 | +preparation — build the pairs with a comprehension. |
| 90 | + |
| 91 | +Here is a membrane pushed upward by a uniform load, with an obstacle imposed |
| 92 | +only on the left half of the domain: |
| 93 | + |
| 94 | +```@example jump |
| 95 | +geom2 = subdivide(fem2d_P2(), 3) |
| 96 | +Vn, Nn = size(geom2.x, 1), size(geom2.x, 2) |
| 97 | +left = [(v, e) for e in 1:Nn for v in 1:Vn if geom2.x[v, e, 1] < 0] |
| 98 | +
|
| 99 | +m2 = MGBModel(geom2) |
| 100 | +set_attribute(m2, "verbose", false) |
| 101 | +@variable(m2, u2); @variable(m2, s2, Broken()) |
| 102 | +set_start(s2, 100.0) |
| 103 | +@constraint(m2, u2 == Coef(m2, 0.0), On(find_boundary(geom2))) |
| 104 | +@constraint(m2, [deriv(u2, :dx); deriv(u2, :dy); s2] in EpiPower(2.0)) |
| 105 | +@constraint(m2, u2 >= Coef(m2, x -> 0.25 - x[1]^2 - x[2]^2), On(left)) |
| 106 | +@objective(m2, Min, integral(Coef(m2, -1.0) * u2 + s2)) |
| 107 | +optimize!(m2) |
| 108 | +plot(mgb_solution(m2)); savefig("jump_obstacle.svg"); nothing # hide |
| 109 | +close() # hide |
| 110 | +``` |
| 111 | + |
| 112 | + |
| 113 | +The obstacle binds on its region (the infeasible start is handled by the |
| 114 | +feasibility phase automatically) and is genuinely absent elsewhere: |
| 115 | + |
| 116 | +```@example jump |
| 117 | +phi = value(Coef(m2, x -> 0.25 - x[1]^2 - x[2]^2)) |
| 118 | +lin = [v + (e - 1) * Vn for (v, e) in left] |
| 119 | +rgt = setdiff(1:length(phi), lin) |
| 120 | +println("min(u - φ) on the obstacle region: ", minimum(value(u2)[lin] .- phi[lin])) |
| 121 | +println("min(u - φ) off the region: ", minimum(value(u2)[rgt] .- phi[rgt])) |
| 122 | +``` |
| 123 | + |
| 124 | +## The Zoo, restated in JuMP |
| 125 | + |
| 126 | +Every [Zoo](zoo.md) problem is a few lines in this syntax; `jump/test_zoo.jl` |
| 127 | +checks all six against the classical constructors. Two examples. The minimal |
| 128 | +surface uses a *constant row* inside the cone — `s ≥ ‖(∇u, 1)‖` is the |
| 129 | +shifted Lorentz cone: |
| 130 | + |
| 131 | +```@example jump |
| 132 | +gu = x -> 0.5 * (x[1]^2 - x[2]^2) |
| 133 | +ms = MGBModel(geom) |
| 134 | +set_attribute(ms, "verbose", false) |
| 135 | +@variable(ms, v); @variable(ms, sv, Broken()) |
| 136 | +set_start(v, gu); set_start(sv, 10.0) |
| 137 | +@constraint(ms, v == Coef(ms, gu), On(find_boundary(geom))) |
| 138 | +@constraint(ms, [deriv(v, :dx); deriv(v, :dy); Coef(ms, 1.0); sv] in EpiPower(1.0)) |
| 139 | +@objective(ms, Min, integral(1.0 * sv)) |
| 140 | +optimize!(ms) |
| 141 | +ref = mgb_solve(Zoo.minimal_surface(amg(geom)); verbose = false) |
| 142 | +maximum(abs.(value(v) .- ref.z[:, 1])) |
| 143 | +``` |
| 144 | + |
| 145 | +Rudin–Osher–Fatemi denoising uses spatial *data inside a cone* — the |
| 146 | +fidelity slack is `r ≥ (u - f_data)²`: |
| 147 | + |
| 148 | +```@example jump |
| 149 | +fdata = x -> 0.5 * tanh(5 * x[1]) |
| 150 | +mr = MGBModel(geom) |
| 151 | +set_attribute(mr, "verbose", false) |
| 152 | +@variable(mr, w); @variable(mr, sw, Broken()); @variable(mr, r, Broken()) |
| 153 | +set_start(w, fdata); set_start(sw, 10.0); set_start(r, 10.0) |
| 154 | +fd = Coef(mr, fdata) |
| 155 | +@constraint(mr, w == fd, On(find_boundary(geom))) |
| 156 | +@constraint(mr, [deriv(w, :dx); deriv(w, :dy); sw] in EpiPower(1.0)) # s ≥ |∇u| |
| 157 | +@constraint(mr, [w - fd; r] in EpiPower(2.0)) # r ≥ (u-f)² |
| 158 | +@objective(mr, Min, integral(sw + Coef(mr, 0.5) * r)) |
| 159 | +optimize!(mr) |
| 160 | +ref = mgb_solve(Zoo.rof(amg(geom)); verbose = false) |
| 161 | +maximum(abs.(value(w) .- ref.z[:, 1])) |
| 162 | +``` |
| 163 | + |
| 164 | +## What lowers to what |
| 165 | + |
| 166 | +| model content | classical object | |
| 167 | +|---|---| |
| 168 | +| geometry passed to `MGBModel` | `Geometry` | |
| 169 | +| variables, kinds, Dirichlet constraints | `state_variables` + `dirichlet_nodes` → `amg(geom; dirichlet_nodes)` | |
| 170 | +| distinct atoms `(component, operator)` used anywhere | the `D` table | |
| 171 | +| each cone constraint | one `Convex` piece (`convex_linear` / `convex_Euclidian_power`) | |
| 172 | +| `On` regions on cones | `convex_piecewise` selector columns | |
| 173 | +| `integral(...)` objective | the cost grid `f_grid` | |
| 174 | +| starts + Dirichlet data | the initial/lift grid `g_grid` | |
| 175 | + |
| 176 | +Untagged variables are conforming if differentiated or Dirichlet-constrained |
| 177 | +and broken otherwise; `Broken()` / `Uniform()` override. The model must be in |
| 178 | +conic form (epigraph slacks are yours to declare, as with any conic solver); |
| 179 | +pointwise equality requires `On`; variable bounds and products of variable |
| 180 | +expressions are rejected with explanatory errors. `dual` and spectral |
| 181 | +geometries are not wired up yet. |
| 182 | + |
| 183 | +## Module reference |
| 184 | + |
| 185 | +```@autodocs |
| 186 | +Modules = [Main.MultiGridBarrierJuMP] |
| 187 | +Order = [:type, :function] |
| 188 | +``` |
0 commit comments