diff --git a/Project.toml b/Project.toml index 2af6c6f1974b..62eb20332c8e 100644 --- a/Project.toml +++ b/Project.toml @@ -16,6 +16,7 @@ Markdown = "d6f4376e-aef5-505a-96c1-9c027394607a" Nemo = "2edaba10-b0f1-5616-af89-8c11ac63239a" Pkg = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" Polymake = "d720cf60-89b5-51f5-aff5-213f193123e7" +ProgressMeter = "92933f4c-e287-5a05-a399-4b506db050ca" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" RandomExtensions = "fb686558-2515-59ef-acaa-46db3789a887" Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b" @@ -37,6 +38,7 @@ Markdown = "1.6" Nemo = "0.47.1" Pkg = "1.6" Polymake = "0.11.20" +ProgressMeter = "1.10.2" Random = "1.6" RandomExtensions = "0.4.3" Serialization = "1.6" diff --git a/experimental/AlgebraicShifting/docs/src/partial_shift_graph.md b/experimental/AlgebraicShifting/docs/src/partial_shift_graph.md index c1de71984bd0..876ea0af7d97 100644 --- a/experimental/AlgebraicShifting/docs/src/partial_shift_graph.md +++ b/experimental/AlgebraicShifting/docs/src/partial_shift_graph.md @@ -7,4 +7,6 @@ DocTestSetup = Oscar.doctestsetup() ```@docs partial_shift_graph_vertices +partial_shift_graph +contracted_partial_shift_graph ``` diff --git a/experimental/AlgebraicShifting/src/AlgebraicShifting.jl b/experimental/AlgebraicShifting/src/AlgebraicShifting.jl index b1a0a4f7efb9..ceb1b21351aa 100644 --- a/experimental/AlgebraicShifting/src/AlgebraicShifting.jl +++ b/experimental/AlgebraicShifting/src/AlgebraicShifting.jl @@ -5,11 +5,12 @@ include("PartialShiftGraph.jl") export UniformHypergraph export compound_matrix +export contracted_partial_shift_graph export exterior_shift export face_size export generic_unipotent_matrix -export partial_shift_graph_vertices export partial_shift_graph +export partial_shift_graph_vertices export rothe_matrix export uniform_hypergraph diff --git a/experimental/AlgebraicShifting/src/PartialShiftGraph.jl b/experimental/AlgebraicShifting/src/PartialShiftGraph.jl index 543c0b5348c9..d1b6a3d862a2 100644 --- a/experimental/AlgebraicShifting/src/PartialShiftGraph.jl +++ b/experimental/AlgebraicShifting/src/PartialShiftGraph.jl @@ -12,7 +12,7 @@ end Given two simplicial complexes `K1`, `K2` return true if `K1` is lexicographically less than `K2` """ -isless_lex(K1::SimplicialComplex, K2::SimplicialComplex) = isless_lex(Set(facets(K1)), Set(facets(K2))) +isless_lex(K1::ComplexOrHypergraph, K2::ComplexOrHypergraph) = isless_lex(Set(facets(K1)), Set(facets(K2))) @doc raw""" partial_shift_graph_vertices(F::Field,::SimplicialComplex, W::Union{WeylGroup, Vector{WeylGroupElem}};) @@ -51,7 +51,8 @@ function partial_shift_graph_vertices(F::Field, current = K visited = [current] # by properties of algebraic shifting - # we know that K we be the last in this sorted list + # 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, w) for w in W]; lt=isless_lex))[1:end - 1] @@ -79,7 +80,7 @@ function multi_edges(F::Field, complexes::Vector{Tuple{Int,T}}, complex_labels::Dict{Set{Set{Int}}, Int} ) :: Dict{Tuple{Int, Int}, Vector{WeylGroupElem}} where T <: ComplexOrHypergraph; - # For each complex K with index i, compute the shifted complex delta of K by w for each w ∈ W. + # 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] reduce( @@ -102,38 +103,82 @@ end Constructs the partial shift graph on `complexes`. -Returns a tuple `(G, D)`, where `G` is a directed graph whose vertices correspond to the `complexes`, -such that there is an edge `K → L` if there exists a shift matrix `w` such that `L` is the partial generic shift of `K` by `w`. -If `K` and `L` are the `i`th and `j`th entry of `complexes`, resp., -`D[i,j]` contains all `w ∈ W` such that `L` is the partial generic shift of `K` by `w`. +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 of uniform hypergraphs (all belonging to the same ``Γ(n, k, l)``). +- `complexes`: A vector of simplicial complexes or uniform hypergraphs (all should have the same number of vertices). - `parallel :: Bool` (default: `false`) run the process in parrallel using the `Distributed` package; make sure to do `@everywhere using Oscar`. - `W`: The user may provide a list `W` of Weyl group elements to be used to construct the shifts. - `W` must be a subset the (same instance of the) symmetric group of the same order as the complexes. - If `W` is not provided, the function will use the symmetric group of the same order as the complexes. + 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. # Examples ``` 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, D = partial_shift_graph(QQ, Ks) +julia> G, EL, VL = partial_shift_graph(QQ, Ks); +Progress: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| Time: 0:00:00 + +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}: + s1 * s2 + s2 + s3 * s1 * s2 + 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) :: Tuple{Graph{Directed}, EdgeLabels} where T <: ComplexOrHypergraph; + parallel::Bool = false) :: Tuple{Graph{Directed}, EdgeLabels, Vector{ComplexOrHypergraph}} where T <: ComplexOrHypergraph; # Deal with trivial case if length(complexes) <= 1 return (graph_from_adjacency_matrix(Directed, zeros(length(complexes),length(complexes))), EdgeLabels()) 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)) @@ -142,31 +187,22 @@ function partial_shift_graph(F::Field, complexes::Vector{T}, W::Union{WeylGroup, @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])." task_size = 1 - map_function = map if parallel # setup parallel parameters channels = Oscar.params_channels(Union{RootSystem, WeylGroup, Vector{SimplicialComplex}, Vector{UniformHypergraph}}) # setup parents needed to be sent to each process Oscar.put_params(channels, root_system(W)) Oscar.put_params(channels, W) - map_function = pmap end - - # Basically, the following does the same as the following, but with a progress indicator: - # edge_tuples = multi_edges(W, collect(enumerate(complexes)), complex_labels) - edge_labels = multi_edges(F, W, collect(enumerate(complexes)), complex_labels) - #edge_labels = reduce( - # (d1, d2) -> mergewith!(vcat, d1, d2), - # # a progress bar here would be nice for the users, but this requires adding a dependency to Oscar - # # @showprogress map_function( - # map_function( - # Ks -> multi_edges(F, W, Ks, complex_labels), - # Iterators.partition(enumerate(complexes), task_size) - # ) - #) + # edge_tuples = multi_edges(F, W, collect(enumerate(complexes)), complex_labels) + # Basically, the following does the same as the preceeding, but with a progress indicator: + edge_labels = reduce((d1, d2) -> mergewith!(vcat, d1, d2), + @showprogress pmap( + Ks -> multi_edges(F, 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, edge_labels) + return (graph, edge_labels, complexes) end function partial_shift_graph(F::Field, complexes::Vector{T}; parallel=false) where T <: ComplexOrHypergraph @@ -179,3 +215,69 @@ function partial_shift_graph(F::Field, complexes::Vector{T}; parallel=false) whe W = weyl_group(:A, n - 1) return partial_shift_graph(F, complexes, W;parallel=parallel) 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]`. + +#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); +Progress: 100%|█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| Time: 0:00:00 + +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 diff --git a/experimental/AlgebraicShifting/src/UniformHypergraph.jl b/experimental/AlgebraicShifting/src/UniformHypergraph.jl index 8b77727fb676..52f67c541b91 100644 --- a/experimental/AlgebraicShifting/src/UniformHypergraph.jl +++ b/experimental/AlgebraicShifting/src/UniformHypergraph.jl @@ -68,7 +68,7 @@ n_vertices(K::UniformHypergraph) = K.n_vertices faces(K::UniformHypergraph) = K.faces # added for covenience when writting functions for Simplicial complex and Uniform Hypergraph -facets(K::UniformHypergraph) = K.faces +facets(K::UniformHypergraph) = Set.(K.faces) face_size(K::UniformHypergraph) = K.k function Base.hash(K :: UniformHypergraph, u :: UInt) diff --git a/src/Combinatorics/Graphs/functions.jl b/src/Combinatorics/Graphs/functions.jl index 747ddf20595f..fddf4156e179 100644 --- a/src/Combinatorics/Graphs/functions.jl +++ b/src/Combinatorics/Graphs/functions.jl @@ -1172,8 +1172,9 @@ end function graph_from_edges(::Type{T}, edges::Vector{Edge}, n_vertices::Int=-1) where {T <: Union{Directed, Undirected}} - - n_needed = maximum(reduce(append!,[[src(e),dst(e)] for e in edges])) + isempty(edges) && return Graph{T}(n_vertices) + + n_needed = maximum(reduce(append!,[[src(e),dst(e)] for e in edges];)) @req (n_vertices >= n_needed || n_vertices < 0) "n_vertices must be at least the maximum vertex in the edges" g = Graph{T}(max(n_needed, n_vertices)) diff --git a/src/imports.jl b/src/imports.jl index 41769e0ab4bf..ed98de755884 100644 --- a/src/imports.jl +++ b/src/imports.jl @@ -1,5 +1,6 @@ # standard packages using Pkg +using ProgressMeter using Random using RandomExtensions using UUIDs