Skip to content

Commit 2ea27b8

Browse files
Sébastien LoiselSébastien Loisel
authored andcommitted
Add Gmsh mesh import as a package extension (MultiGridBarrierGmshExt)
Imports Gmsh meshes of real geometry (CAD shapes, holes, named boundaries) into a MultiGridBarrier Geometry. A weak-dependency extension like the CUDA and JuMP ones: loads automatically on `using MultiGridBarrier, Gmsh`, so the GPL Gmsh dependency never touches users who do not opt in. gmsh_import() (current gmsh model) / gmsh_import(path) return (; geometry, regions), where regions maps each Gmsh physical group's name to the (vertex, element) node pairs it covers — the same format as find_boundary, so named boundary parts plug into amg's dirichlet_nodes and named subdomains into the JuMP front end's On. Element-family mapping (single element type per mesh): - 3/6-node triangles -> fem2d_P1 / fem2d_P2 (curved P2 edges; the bubble node is placed at the P2 map's barycenter), - 4/9-node quadrilaterals -> tensor fem2d, k = 1/2 (curved Q2; non-planar quad meshes become embedded ambient=Val(3) surfaces), - 8/27-node hexahedra -> fem3d, k = 1/2. Node permutations from Gmsh's ordering are computed numerically against the reference tensor grid (no hardcoded tables); negatively-oriented elements are flipped; tensor connectivity comes from Gmsh node tags via tensor_dofmap. Orders >= 3, serendipity elements, tetrahedra, prisms and mixed-type meshes are rejected with actionable messages. - src/gmsh_frontend.jl: the gmsh_import stub, exported. - ext/MultiGridBarrierGmshExt/: the implementation. - Project.toml: Gmsh in [weakdeps] + [extensions] + [extras]/[targets] test (compat 0.3). - test/test_gmsh.jl: linear-reproduction tests across all families (affine Dirichlet data => affine p-harmonic solution, exact for isoparametric elements) — machine-eps on transfinite quads/hexes, ~1e-10 on unstructured triangles, plus curved-disk area/boundary checks, subdomain groups, and rejection paths. 22/22 in a green full Pkg.test. - docs: new Gmsh tab (square-with-a-hole mixed BCs, curved disk), home-page pointer, autodocs Filter update.
1 parent 7615786 commit 2ea27b8

10 files changed

Lines changed: 692 additions & 3 deletions

File tree

Project.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,17 +21,20 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
2121
[weakdeps]
2222
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
2323
CUDSS_jll = "4889d778-9329-5762-9fec-0578a5d30366"
24+
Gmsh = "705231aa-382f-11e9-3f0c-b7cb4346fdeb"
2425
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
2526

2627
[extensions]
2728
MultiGridBarrierCUDAExt = ["CUDA", "CUDSS_jll"]
29+
MultiGridBarrierGmshExt = "Gmsh"
2830
MultiGridBarrierJuMPExt = "JuMP"
2931

3032
[compat]
3133
AlgebraicMultigrid = "1"
3234
CUDA = "5"
3335
CUDSS_jll = "0.7"
3436
FFMPEG = "0.4"
37+
Gmsh = "0.3"
3538
JuMP = "1"
3639
PNGFiles = "0.4"
3740
PrecompileTools = "1"
@@ -46,8 +49,9 @@ julia = "1.10"
4649
CUDA = "052768ef-5323-5732-b1bb-66c8b64840ba"
4750
CUDSS_jll = "4889d778-9329-5762-9fec-0578a5d30366"
4851
Coverage = "a2441757-f6aa-5fb2-8edb-039e3f45d037"
52+
Gmsh = "705231aa-382f-11e9-3f0c-b7cb4346fdeb"
4953
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
5054
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
5155

5256
[targets]
53-
test = ["Test", "Coverage", "CUDA", "CUDSS_jll", "JuMP"]
57+
test = ["Test", "Coverage", "CUDA", "CUDSS_jll", "JuMP", "Gmsh"]

docs/Project.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[deps]
22
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
3+
Gmsh = "705231aa-382f-11e9-3f0c-b7cb4346fdeb"
34
JuMP = "4076af6c-e467-56ae-b986-b466b2749572"
45
MultiGridBarrier = "9e2c1f1d-9131-4ad4-b32f-bd2a0b0ecd1e"
56
PyPlot = "d330b81b-6aea-500a-939a-2ce795aea3ee"

docs/make.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ using MultiGridBarrier
77
using Documenter
88
using PyPlot
99
using JuMP # loads the MultiGridBarrierJuMPExt extension (used by the JuMP page)
10+
using Gmsh # loads the MultiGridBarrierGmshExt extension (used by the Gmsh page)
1011

1112
DocMeta.setdocmeta!(MultiGridBarrier, :DocTestSetup, :(using MultiGridBarrier); recursive=true)
1213

@@ -25,6 +26,7 @@ makedocs(;
2526
"Home" => "index.md",
2627
"Zoo" => "zoo.md",
2728
"JuMP" => "jump.md",
29+
"Gmsh" => "gmsh.md",
2830
],
2931
)
3032

docs/src/gmsh.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
```@meta
2+
CurrentModule = MultiGridBarrier
3+
```
4+
5+
# Meshes from Gmsh
6+
7+
MultiGridBarrier's own constructors (`fem2d_P1`, `fem2d`, `fem3d`, …) build
8+
meshes of simple domains. For real geometry — CAD shapes, holes, local
9+
refinement, named boundary parts — use [Gmsh](https://gmsh.info), the standard
10+
open-source mesh generator. The `MultiGridBarrierGmshExt` extension loads
11+
automatically when both packages are imported and provides
12+
[`gmsh_import`](@ref), which converts the current Gmsh mesh (or a `.msh` file)
13+
into a `Geometry` plus named node sets:
14+
15+
- 3/6-node triangles → `fem2d_P1` / `fem2d_P2` (curved P2 edges supported),
16+
- 4/9-node quadrilaterals → tensor `fem2d` with `k = 1/2` (curved quads
17+
supported; non-planar quad meshes become embedded surfaces),
18+
- 8/27-node hexahedra → `fem3d` with `k = 1/2`.
19+
20+
Gmsh **physical groups** come back as `(vertex, element)` node-pair lists — the
21+
same format as [`find_boundary`](@ref) — so named boundary parts plug directly
22+
into `amg`'s `dirichlet_nodes`, and named subdomains into the JuMP front end's
23+
[`On`](@ref).
24+
25+
!!! note "Requires Gmsh"
26+
Add the [`Gmsh`](https://github.com/JuliaFEM/Gmsh.jl) package
27+
(`pkg> add Gmsh`) and load it (`using Gmsh: gmsh`). Gmsh itself is
28+
GPL-licensed; as an opt-in weak dependency it is only ever loaded if you
29+
load it.
30+
31+
## Example: square with a hole, mixed boundary conditions
32+
33+
Script the geometry through the `gmsh` API (or `gmsh.open` a `.geo`/`.msh`
34+
file), name the boundary parts, mesh, and import:
35+
36+
```@example gmsh
37+
using MultiGridBarrier, PyPlot
38+
using Gmsh: gmsh
39+
40+
gmsh.initialize()
41+
gmsh.option.setNumber("General.Terminal", 0)
42+
sq = gmsh.model.occ.addRectangle(-1.0, -1.0, 0.0, 2.0, 2.0)
43+
hole = gmsh.model.occ.addDisk(0.3, 0.2, 0.0, 0.4, 0.4)
44+
gmsh.model.occ.cut([(2, sq)], [(2, hole)])
45+
gmsh.model.occ.synchronize()
46+
# name the two boundary parts: the circle and the outer square
47+
circle = Int[]; outer = Int[]
48+
for (d, t) in gmsh.model.getEntities(1)
49+
xmin, ymin, _, xmax, ymax, _ = gmsh.model.getBoundingBox(d, t)
50+
if xmin > -0.9 && xmax < 0.9 && ymin > -0.9 && ymax < 0.9
51+
push!(circle, t)
52+
else
53+
push!(outer, t)
54+
end
55+
end
56+
gmsh.model.addPhysicalGroup(1, outer, -1, "outer")
57+
gmsh.model.addPhysicalGroup(1, circle, -1, "hole")
58+
gmsh.option.setNumber("Mesh.MeshSizeMax", 0.12)
59+
gmsh.model.mesh.generate(2)
60+
gm = gmsh_import()
61+
gmsh.finalize()
62+
length(gm.geometry.w) # number of quadrature nodes
63+
```
64+
65+
The named parts drive per-region Dirichlet conditions exactly like hand-built
66+
node sets: here `u = 0` on the outer square and `u = 1` on the hole, with the
67+
p-Laplace energy (`p = 1.5`):
68+
69+
```@example gmsh
70+
mg = amg(gm.geometry; dirichlet_nodes = Dict(:dirichlet => sort(vcat(gm.regions["outer"], gm.regions["hole"]))))
71+
onhole = Set(gm.regions["hole"])
72+
V, N = size(gm.geometry.x, 1), size(gm.geometry.x, 2)
73+
gvals = zeros(V * N)
74+
for (v, e) in gm.regions["hole"]
75+
gvals[v + (e - 1) * V] = 1.0
76+
end
77+
xf = reshape(gm.geometry.x, :, 2)
78+
sol = mgb_solve(assemble(mg; p = 1.5,
79+
g_grid = [gvals fill(100.0, V * N)],
80+
f = x -> (0.0, 0.0, 0.0, 1.0)); verbose = false)
81+
plot(sol); savefig("gmsh_hole.svg"); nothing # hide
82+
close() # hide
83+
```
84+
![](gmsh_hole.svg)
85+
86+
## Curved elements
87+
88+
Second-order meshes import as isoparametric elements: `setOrder(2)` before
89+
importing gives curved 6-node triangles (`fem2d_P2`) or curved 9-node quads
90+
(tensor `fem2d`, `k = 2`) whose boundary nodes lie on the true geometry. For
91+
all-quad meshes, set `Mesh.RecombineAll = 1` (with
92+
`Mesh.SubdivisionAlgorithm = 1` to guarantee no leftover triangles); for
93+
hexahedra use transfinite/swept volumes, or subdivide a tet mesh with
94+
`Mesh.SubdivisionAlgorithm = 2`. Orders ≥ 3, serendipity elements, and
95+
tetrahedra are rejected with actionable error messages.
96+
97+
```@example gmsh
98+
gmsh.initialize()
99+
gmsh.option.setNumber("General.Terminal", 0) # hide
100+
gmsh.model.occ.addDisk(0.0, 0.0, 0.0, 1.0, 1.0)
101+
gmsh.model.occ.synchronize()
102+
gmsh.model.addPhysicalGroup(1, [t for (d, t) in gmsh.model.getEntities(1)], -1, "circle")
103+
gmsh.option.setNumber("Mesh.MeshSizeMax", 0.25)
104+
gmsh.option.setNumber("Mesh.RecombineAll", 1)
105+
gmsh.option.setNumber("Mesh.SubdivisionAlgorithm", 1)
106+
gmsh.model.mesh.generate(2)
107+
gmsh.model.mesh.setOrder(2) # curved Q2 quads
108+
gmd = gmsh_import()
109+
gmsh.finalize()
110+
sum(gmd.geometry.w) - π # curved quadrature: area error of the disk
111+
```
112+
113+
## API reference
114+
115+
```@docs
116+
gmsh_import
117+
```

docs/src/index.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,11 @@ Prefer stating problems in an algebraic modeling language? The
256256
[JuMP front end](jump.md) accepts standard `@variable`/`@constraint`/`@objective`
257257
syntax and lowers it to this same pipeline, building the hierarchy automatically.
258258

259+
Need meshes of real geometry (CAD shapes, holes, named boundary parts)? The
260+
[Gmsh importer](gmsh.md) converts Gmsh meshes — triangles, quads, hexahedra,
261+
straight or curved order-2 — into a `Geometry`, and physical groups into named
262+
node sets for `dirichlet_nodes` and `On`.
263+
259264
### Meshes, coordinates, and connectivity
260265

261266
Fundamentally a mesh — and the `Geometry` that holds it — is a pair `(t, x)`:
@@ -376,7 +381,7 @@ Private = false
376381
Modules = [MultiGridBarrier]
377382
Order = [:type]
378383
Private = false
379-
Filter = t -> !(nameof(t) in (:MGBModel, :Coef, :EpiPower, :deriv, :integral, :set_start, :mgb_solution, :solver_log, :On, :Broken, :Uniform))
384+
Filter = t -> !(nameof(t) in (:MGBModel, :Coef, :EpiPower, :deriv, :integral, :set_start, :mgb_solution, :solver_log, :On, :Broken, :Uniform, :gmsh_import))
380385
```
381386

382387
# Functions reference
@@ -385,7 +390,7 @@ Filter = t -> !(nameof(t) in (:MGBModel, :Coef, :EpiPower, :deriv, :integral, :s
385390
Modules = [MultiGridBarrier]
386391
Order = [:function]
387392
Private = false
388-
Filter = t -> !(nameof(t) in (:MGBModel, :Coef, :EpiPower, :deriv, :integral, :set_start, :mgb_solution, :solver_log, :On, :Broken, :Uniform))
393+
Filter = t -> !(nameof(t) in (:MGBModel, :Coef, :EpiPower, :deriv, :integral, :set_start, :mgb_solution, :solver_log, :On, :Broken, :Uniform, :gmsh_import))
389394
```
390395

391396
# Index

0 commit comments

Comments
 (0)