-
Notifications
You must be signed in to change notification settings - Fork 139
/
Copy pathPartialShiftGraph.jl
313 lines (272 loc) · 12.8 KB
/
PartialShiftGraph.jl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# TODO: change Vector -> Set
const EdgeLabels = Dict{Tuple{Int, Int}, Vector{WeylGroupElem}}
function isless_lex(S1::Set{Set{Int}}, S2::Set{Set{Int}})
S_diff = collect(symdiff(S1, S2))
isempty(S_diff) && return false
set_cmp(a, b) = min(symdiff(a, b)...) in a
return sort(S_diff;lt=set_cmp)[1] in S1
end
"""
Given two simplicial complexes `K1`, `K2` return true if
`K1` is lexicographically less than `K2`
"""
isless_lex(K1::ComplexOrHypergraph, K2::ComplexOrHypergraph) = isless_lex(Set(facets(K1)), Set(facets(K2)))
@doc raw"""
partial_shift_graph_vertices(F::Field,K::SimplicialComplex, W::Union{WeylGroup, Vector{WeylGroupElem}};)
partial_shift_graph_vertices(F::Field,K::UniformHypergraph, W::Union{WeylGroup, Vector{WeylGroupElem}};)
Given a field `F` find the vertices of the partial shift graph starting from `K`
and discoverable from elements in `W`.
Returns a `Vector{SimplicialCompplex}` ordered lexicographically.
# Examples
```jldoctest
julia> K = simplicial_complex([[1, 2], [2, 3], [3, 4]])
Abstract simplicial complex of dimension 1 on 4 vertices
julia> shifts = partial_shift_graph_vertices(QQ, K, weyl_group(:A, 3))
6-element Vector{SimplicialComplex}:
Abstract simplicial complex of dimension 1 on 4 vertices
Abstract simplicial complex of dimension 1 on 4 vertices
Abstract simplicial complex of dimension 1 on 4 vertices
Abstract simplicial complex of dimension 1 on 4 vertices
Abstract simplicial complex of dimension 1 on 4 vertices
Abstract simplicial complex of dimension 1 on 4 vertices
julia> facets.(shifts)
6-element Vector{Vector{Set{Int64}}}:
[Set([3, 1]), Set([4, 1]), Set([2, 1])]
[Set([3, 1]), Set([2, 3]), Set([2, 1]), Set([4])]
[Set([3, 1]), Set([4, 2]), Set([2, 1])]
[Set([4, 3]), Set([3, 1]), Set([2, 1])]
[Set([2, 1]), Set([2, 3]), Set([4, 2])]
[Set([2, 1]), Set([2, 3]), Set([4, 3])]
```
"""
function partial_shift_graph_vertices(F::Field,
K::SimplicialComplex,
W::Union{WeylGroup, Vector{WeylGroupElem}})
current = K
visited = [current]
phi = isomorphism(PermGroup, parent(first(W)))
# by properties of algebraic shifting
# we know that K will be the last in this sorted list
# sorting here should also speed up unique according to julia docs
unvisited = unique(
x -> Set(facets(x)),
sort([exterior_shift(F, K, phi(w)) for w in W]; lt=isless_lex))[1:end - 1]
while !isempty(unvisited)
current = pop!(unvisited)
push!(visited, current)
shifts = unique(
x -> Set(facets(x)),
sort([exterior_shift(F, current, phi(w)) for w in W]; lt=isless_lex))[1:end - 1]
# dont visit things twice
new_facets = filter(x -> !(x in Set.(facets.(visited))), Set.(facets.(shifts)))
unvisited = simplicial_complex.(
union(Set.(facets.(unvisited)), new_facets))
end
return sort(collect(visited); lt=isless_lex)
end
function partial_shift_graph_vertices(F::Field,
K::UniformHypergraph,
W::Union{WeylGroup, Vector{WeylGroupElem}})
return partial_shift_graph_vertices(F, simplicial_complex(K), W)
end
""" Compute the multi edges, that is, for each complex `K` compute which
other complexes can be reached by applying the partial shifts `deltas`
to K, and store the shift matrices that give rise to each edge."""
function multi_edges(F::Field,
permutations::Vector{PermGroupElem},
complexes::Vector{Tuple{Int,T}},
complex_labels::Dict{Set{Set{Int}}, Int}
) where T <: ComplexOrHypergraph;
# For each complex K with index i, compute the shifted complex delta of K by w for each w in W.
# For nontrivial delta, place (i, delta) → w in a singleton dictionary, and eventually merge all dictionaries
# to obtain a dictionary (i, delta) → [w that yield the shift K → delta]
return reduce(
(d1, d2) -> mergewith!(vcat, d1, d2), (
Dict((i, complex_labels[Set(facets(delta))]) => [p])
for (i, K) in complexes
for (p, delta) in ((p, exterior_shift(F, K, p)) for p in permutations)
if !issetequal(facets(delta), facets(K)));
init=Dict{Tuple{Int, Int}, Vector{PermGroupElem}}()
) :: Dict{Tuple{Int, Int}, Vector{PermGroupElem}}
end
@doc raw"""
partial_shift_graph(F::Field, complexes::Vector{Simplicialcomplex}; parallel=false, show_progress=true)
partial_shift_graph(F::Field, complexes::Vector{Uniformhypergraph}; parallel=false, show_progress=true)
partial_shift_graph(F::Field, complexes::Vector{Simplicialcomplex}, W::Union{WeylGroup, Vector{WeylGroupElem}}; parallel=false, show_progress=true)
partial_shift_graph(F::Field, complexes::Vector{Uniformhypergraph}, W::Union{WeylGroup, Vector{WeylGroupElem}}; parallel=false, show_progress=true)
Constructs the partial shift graph on `complexes`.
Returns a tuple `(G, EL, VL)`, where `G` is a `Graph{Directed}`, `EL` is a `Dict{Tuple{Int Int}, Vector{Weylgroupelem}` and
`VL` is a lexicographically sorted `complexes`, hence is either a `Vector{SimplicialComplex}` or `Vector{Uniformhypergraph}`.
`EL` are the edges labels and `VL` are the vertex labels.
There is an edge from the vertex labelled `K` to the vertex labelled `L` if `L` is the partial shift of `K` by some `w` in `W`.
If `K` and `L` are the `i`th and `j`th entry of `VL`, resp.,
`EL[i,j]` contains all `w` in `W` such that `L` is the partial generic shift of `K` by `w`.
# Arguments
- `complexes`: A vector of simplicial complexes or uniform hypergraphs (all should have the same number of vertices).
- `W`: The user may provide a list `W` of Weyl group elements to be used to construct the shifts.
All elements of `W` should have the same parent.
`W` must be a subset the (same instance of the) symmetric group of order equal to the number of vertices of a complex in complexes (they should all be equal).
If `W` is not provided, the function will use the symmetric group of the same order as vertices in each complex complexes.
- `parallel`: run the process in parrallel using the `Distributed` package; make sure to do `@everywhere using Oscar`.
- `show_progress`: Set to `true` by default, can be used to suppress progress meter.
- `task_size`: While running in parallel serialization might become a bottleneck,
setting a higher task_size should increase performance if the processes are not running at maximum capacity
See [D-VJL24](@cite) for background.
# Examples
```jldoctest
julia> gamma(n,k,l) = uniform_hypergraph.(subsets(subsets(n, k), l), n)
gamma (generic function with 1 method)
julia> Ks = gamma(4,2,5)
6-element Vector{UniformHypergraph}:
UniformHypergraph(4, 2, [[1, 2], [1, 3], [1, 4], [2, 3], [3, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 3], [2, 3], [2, 4], [3, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 4], [2, 3], [2, 4], [3, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 3], [1, 4], [2, 4], [3, 4]])
UniformHypergraph(4, 2, [[1, 3], [1, 4], [2, 3], [2, 4], [3, 4]])
julia> G, EL, VL = partial_shift_graph(QQ, Ks; show_progress=false);
julia> collect(edges(G))
14-element Vector{Edge}:
Edge(2, 1)
Edge(3, 1)
Edge(3, 2)
Edge(4, 1)
Edge(4, 2)
Edge(5, 1)
Edge(5, 2)
Edge(5, 3)
Edge(5, 4)
Edge(6, 1)
Edge(6, 2)
Edge(6, 3)
Edge(6, 4)
Edge(6, 5)
julia> EL[6, 5]
4-element Vector{WeylGroupElem}:
s2
s1 * s2
s3 * s2
s1 * s3 * s2
julia> facets.(VL[[6, 5]])
2-element Vector{Vector{Set{Int64}}}:
[Set([3, 1]), Set([4, 1]), Set([2, 3]), Set([4, 2]), Set([4, 3])]
[Set([2, 1]), Set([4, 1]), Set([2, 3]), Set([4, 2]), Set([4, 3])]
```
"""
function partial_shift_graph(F::Field, complexes::Vector{T},
W::Union{WeylGroup, Vector{WeylGroupElem}};
parallel::Bool = false,
show_progress::Bool = true,
task_size::Int=100) where T <: ComplexOrHypergraph
# see TODO above about changing EdgeLabels type
# Deal with trivial case
if length(complexes) == 1
@req is_shifted(complexes[1]) "The list of complexes should be closed under shifting by elements of W"
return (
graph_from_adjacency_matrix(Directed, zeros(length(complexes),length(complexes))),
EdgeLabels(),
complexes) :: Tuple{Graph{Directed}, EdgeLabels, Vector{T}}
end
# maybe we provide a flag to skip if the complexes are already sorted?
complexes = sort(complexes;lt=Oscar.isless_lex)
ns_vertices = Set(n_vertices.(complexes))
@req length(ns_vertices) == 1 "All complexes are required to have the same number of vertices."
n = collect(ns_vertices)[1]
# inverse lookup K → index of K in complexes
complex_labels = Dict(Set(facets(K)) => index for (index, K) in enumerate(complexes))
W2 = only(unique(parent.(W)))
rs_type = root_system_type(root_system(W2))
@req rs_type[1][1] == :A && rs_type[1][2] == n - 1 "Only Weyl groups type A_$(n-1) are currently support and received type $(T[1])."
phi = isomorphism(PermGroup, parent(first(W)))
# oscar runs tests in parallel, which can make pmap run in parallel even if parallel=false
# next line fixes this issue
map_function = map
if parallel
# setup parallel parameters
channels = Oscar.params_channels(Union{PermGroup, Vector{SimplicialComplex}, Vector{UniformHypergraph}})
# setup parents needed to be sent to each process
Oscar.put_params(channels, codomain(phi))
map_function = pmap
end
try
edge_labels = reduce((d1, d2) -> mergewith!(vcat, d1, d2),
@showprogress enabled=show_progress map_function(
Ks -> multi_edges(F, phi.(W), Ks, complex_labels),
Iterators.partition(enumerate(complexes), task_size)))
graph = graph_from_edges(Directed, [[i,j] for (i,j) in keys(edge_labels)])
return (graph,
Dict(k => inv(phi).(v) for (k, v) in edge_labels),
complexes) :: Tuple{Graph{Directed}, EdgeLabels, Vector{T}}
catch e
if e isa KeyError
error("The list of complexes should be closed under shifting by elements of W")
end
rethrow(e)
end
end
function partial_shift_graph(F::Field, complexes::Vector{T};
kwargs...) where T <: ComplexOrHypergraph
# Deal with trivial case
if length(complexes) <= 1
return (graph_from_adjacency_matrix(Directed, zeros(length(complexes),length(complexes))), EdgeLabels())
end
n = n_vertices(complexes[1])
W = weyl_group(:A, n - 1)
return partial_shift_graph(F, complexes, W; kwargs...)
end
@doc raw"""
contracted_partial_shift_graph(G::Graph{Directed}, edge_labels::Dict{Tuple{Int, Int}, Vector{WeylGroupElem}})
Returns a triple `(CG, S, P)`, where `CG` is a graph that contains a vertex `v` for every vertex `S[v]` in `G`.
`S` is a list of indices for the sinks in the original graph `G`.
A vertex `i` is in `P[s]` if there exists an edge from `i` to `s` in `G` with `w0` in its edge label,
in this way `P` is a partition of the vertices of the orignal graph `G`.
There is an edge from `s` to `t` in `CG` whenever there is an edge from `i` to `j` in `G` and `i` in `P[s]` and `j` in `P[t]`.
See [D-VJL24](@cite) for background.
# Examples
```jldoctest
julia> gamma(n,k,l) = uniform_hypergraph.(subsets(subsets(n, k), l), n)
gamma (generic function with 1 method)
julia> Ks = gamma(4,2,5)
6-element Vector{UniformHypergraph}:
UniformHypergraph(4, 2, [[1, 2], [1, 3], [1, 4], [2, 3], [3, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 3], [2, 3], [2, 4], [3, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 3], [1, 4], [2, 3], [2, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 4], [2, 3], [2, 4], [3, 4]])
UniformHypergraph(4, 2, [[1, 2], [1, 3], [1, 4], [2, 4], [3, 4]])
UniformHypergraph(4, 2, [[1, 3], [1, 4], [2, 3], [2, 4], [3, 4]])
julia> G, EL, VL = partial_shift_graph(QQ, Ks; show_progress=false);
julia> contracted_partial_shift_graph(G, EL)
(Directed graph with 1 nodes and 0 edges, [1], [[5, 4, 6, 2, 3, 1]])
```
"""
function contracted_partial_shift_graph(G::Graph{Directed}, edge_labels::Dict{Tuple{Int, Int}, Vector{WeylGroupElem}})
n = n_vertices(G)
W = parent(first(first(values(edge_labels))))
w0 = longest_element(W)
# all arrows corresponding to edges that contain w0 in their edge labels
w0_action = Dict(i => j for ((i,j), ws) in edge_labels if w0 in ws)
sinks = findall(iszero, degree(G))
sinks_indices = Dict(s => i for (i,s) in enumerate(sinks))
for i in 1:n
if !haskey(w0_action, i)
@req i in sinks "Vertex $i is not a sink, but has no outbound edge with w0 in its edge label."
w0_action[i] = i
end
end
p = [Int[] for _ in sinks]
for (i, s) in w0_action
push!(p[sinks_indices[s]], i)
end
return (
graph_from_edges(Directed, [
[sinks_indices[s],sinks_indices[t]]
for (s,t) in (
(w0_action[i], (haskey(w0_action, j) ? w0_action[j] : j))
for (i, j) in keys(edge_labels)
)
if s != t
], length(sinks)),
sinks,
p
)
end