Skip to content

Commit 874237c

Browse files
committed
Added is_partition
1 parent 3542699 commit 874237c

File tree

5 files changed

+148
-1
lines changed

5 files changed

+148
-1
lines changed

Project.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ FastGaussQuadrature = "442a2c76-b920-505d-bb47-c5924d526838"
1414
FileIO = "5789e2e9-d7fb-5bc7-8068-2c6fae9b9549"
1515
FillArrays = "1a297f60-69ca-5386-bcde-b61e274b549b"
1616
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
17+
Graphs = "86223c79-3864-5bf0-83f7-82e725a168b6"
1718
JLD2 = "033835bb-8acc-5ee8-8aae-3f567f8a3819"
1819
JSON = "682c06a0-de6a-54ab-a142-c8b1cf79cde6"
1920
LineSearches = "d3d80556-e9d4-5f37-9878-2ab0fcc64255"
@@ -49,6 +50,7 @@ FastGaussQuadrature = "0.4.5, 1"
4950
FileIO = "1.5"
5051
FillArrays = "1.11"
5152
ForwardDiff = "0.10.14, 1"
53+
Graphs = "1"
5254
JLD2 = "0.5"
5355
JSON = "0.21"
5456
LineSearches = "7.0.1"

src/Adaptivity/PolytopalRefinement.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
# are correctly oriented.
99

1010
function coarsen(model::Geometry.PolytopalDiscreteModel,ptopo::Geometry.PatchTopology; return_glue=false)
11+
@check Geometry.is_partition(ptopo) "The patch topology is not a valid partition of the model"
1112
new_polys, new_connectivity = generate_patch_polytopes(model,ptopo)
1213

1314
vertex_coords = get_vertex_coordinates(get_grid_topology(model))
@@ -84,7 +85,7 @@ function generate_patch_polytopes(
8485

8586
new_connectivity = Vector{Vector{Int32}}(undef, npatches)
8687
new_polys = Vector{GeneralPolytope{D}}(undef, npatches)
87-
Threads.@threads for patch in 1:npatches
88+
for patch in 1:npatches
8889
cells = view(patch_cells,patch)
8990
if isone(length(cells))
9091
new_polys[patch] = polys[first(cells)]

src/Geometry/Geometry.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ using FillArrays
1212
using LinearAlgebra:
1313
using Statistics: mean
1414
using DataStructures: SortedSet
15+
using SparseArrays: sparse
16+
using Graphs: Graph, is_connected
1517

1618
using Gridap.Helpers
1719
using Gridap.Arrays

src/Geometry/GridTopologies.jl

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -556,6 +556,75 @@ function restrict(topo::GridTopology,parent_cell_to_mask::AbstractVector{Bool})
556556
restrict(topo, cell_to_parent_cell)
557557
end
558558

559+
"""
560+
compute_graph(topo::GridTopology, d_node::Integer, d_edge::Integer; self_loops=true, Tv=Int8)
561+
562+
Computes the adjacency graph between the `d_node` entities w.r.t the `d_edge` entities. The
563+
graph is returned as a sparse matrix, where nonzero entries indicate an adjacency between the nodes.
564+
565+
For instance for a D-dimensional topology and pairs `(d_node,d_edge)` we have:
566+
567+
- `(1,0)`: graph of edge-to-edge adjacencies where two edges (dim 1) are connected if they share a node (dim 0).
568+
- `(D,D-1)`: graph of cell-to-cell adjacencies where two cells (dim D) are connected if they share a facet (dim D-1).
569+
570+
Weights are defaulted to `one(Tv)`. If `self_loops` is `true`, self-loops (diagonal entries) are included in the graph.
571+
572+
"""
573+
function compute_graph(topo::GridTopology, d_node::Integer, d_edge::Integer; self_loops=true, Tv=Int8)
574+
edge_to_nodes = Geometry.get_faces(topo, d_edge, d_node)
575+
node_to_edges = Geometry.get_faces(topo, d_node, d_edge)
576+
compute_graph(node_to_edges, edge_to_nodes; self_loops, Tv)
577+
end
578+
579+
function compute_graph(
580+
node_to_edges::Table,edge_to_nodes::Table;Tv=Int8,self_loops=false
581+
)
582+
nodes = eachindex(node_to_edges)
583+
node_to_lnode = eachindex(node_to_edges)
584+
return compute_graph(
585+
nodes,node_to_edges,edge_to_nodes;node_to_lnode,Tv,self_loops
586+
)
587+
end
588+
589+
function compute_graph(
590+
nodes,node_to_edges::Table,edge_to_nodes::Table;
591+
node_to_lnode = Dict{Int32,Int32}(n => i for (i,n) in enumerate(nodes)),
592+
self_loops=true,
593+
Tv = Int8,
594+
)
595+
ndata = 0
596+
for node in nodes
597+
ndata += self_loops
598+
for edge in view(node_to_edges,node)
599+
for nbor in view(edge_to_nodes,edge)
600+
ndata += (nbor != node) && (nbor in nodes)
601+
end
602+
end
603+
end
604+
I = zeros(eltype(nodes),ndata)
605+
J = zeros(eltype(nodes),ndata)
606+
p = 0
607+
for (lnode,node) in enumerate(nodes)
608+
if self_loops
609+
p += 1
610+
I[p] = lnode
611+
J[p] = lnode
612+
end
613+
for edge in view(node_to_edges,node)
614+
for nbor in view(edge_to_nodes,edge)
615+
if (nbor != node) && (nbor in nodes)
616+
p += 1
617+
I[p] = lnode
618+
J[p] = node_to_lnode[nbor]
619+
end
620+
end
621+
end
622+
end
623+
V = ones(Tv,ndata)
624+
n = length(nodes)
625+
return sparse(I,J,V,n,n)
626+
end
627+
559628
# Helpers
560629

561630
function _compute_cell_perm_indices!(

src/Geometry/PatchTriangulations.jl

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,79 @@ function patch_extend(ptopo::PatchTopology{Dc},patch_to_data,Df=Dc) where Dc
101101
return pface_to_data
102102
end
103103

104+
"""
105+
is_cover(ptopo::PatchTopology)
106+
is_cover(topo::GridTopology, patch_cells::Table)
107+
108+
Returns `true` if the given patch topology is a cover of the underlying topology, i.e
109+
if every cell in the topology is contained in at least one patch.
110+
"""
111+
function is_cover(ptopo::PatchTopology)
112+
patch_cells = Geometry.get_patch_cells(ptopo)
113+
is_cover(ptopo.topo, patch_cells)
114+
end
115+
116+
function is_cover(topo::GridTopology, patch_cells)
117+
cache = array_cache(patch_cells)
118+
cell_is_covered = fill(false, num_cells(topo))
119+
for patch in eachindex(patch_cells)
120+
cells = getindex!(cache, patch_cells, patch)
121+
cell_is_covered[cells] .= true
122+
end
123+
return all(cell_is_covered)
124+
end
125+
126+
"""
127+
is_partition(ptopo::PatchTopology; kwargs...)
128+
is_partition(topo::GridTopology, patch_cells::Table; fail_fast = true)
129+
130+
Check if the given patch topology is a valid partition of the underlying topology.
131+
132+
To be a valid partition, the patches
133+
134+
- must cover the whole topology
135+
- must be disjoint (i.e non-overlapping)
136+
- must be connected (a patch cannot be split)
137+
138+
If `fail_fast` is `true`, the function will exit as soon as it finds a patch that is not connected.
139+
Otherwise, it will check all patches and print a warning with the indices of the bad patches.
140+
141+
"""
142+
function is_partition(ptopo::PatchTopology; kwargs...)
143+
patch_cells = Geometry.get_patch_cells(ptopo)
144+
is_partition(ptopo.topo, patch_cells; kwargs...)
145+
end
146+
147+
function is_partition(topo::GridTopology, patch_cells; fail_fast = true)
148+
if !is_cover(topo, patch_cells) || !isequal(sum(length, patch_cells), num_cells(topo))
149+
return false # Not a cover or not disjoint
150+
end
151+
152+
# Check patch connectivity
153+
# We could return false at the first bad patch, but I'd rather get
154+
# all bad patch indices for debugging purposes.
155+
D = num_cell_dims(topo)
156+
cell_to_facets = get_faces(topo,D,D-1)
157+
facet_to_cells = get_faces(topo,D-1,D)
158+
159+
cache = array_cache(patch_cells)
160+
bad_patches = Int[]
161+
for patch in eachindex(patch_cells)
162+
cells = getindex!(cache, patch_cells, patch)
163+
G = Graph(compute_graph(Set(cells), cell_to_facets, facet_to_cells))
164+
if !is_connected(G)
165+
push!(bad_patches, patch)
166+
fail_fast && return false # Stop at the first bad patch
167+
end
168+
end
169+
170+
if !isempty(bad_patches)
171+
@warn "The following patches are not connected: $(bad_patches)"
172+
return false
173+
end
174+
return true
175+
end
176+
104177
function generate_patch_faces(ptopo::PatchTopology{Dc},Df) where Dc
105178
cell_to_faces = get_faces(ptopo.topo,Dc,Df)
106179
patch_cells = get_patch_cells(ptopo)

0 commit comments

Comments
 (0)