diff --git a/src/Topologies/Topologies.jl b/src/Topologies/Topologies.jl index e89c20c16..329f3996d 100644 --- a/src/Topologies/Topologies.jl +++ b/src/Topologies/Topologies.jl @@ -9,6 +9,8 @@ argerr(mess) = throw(ArgumentError(mess)) # Mark removed nodes. struct Tombstone end +include("./deadmap.jl") + """ Values of this type are constructed from a model value, to represent its pure topology: @@ -245,6 +247,75 @@ end export remove_node! +#------------------------------------------------------------------------------------------- +""" + adjacency_matrix( + g::Topology, + source::Symbol, + edge::Symbol, + target::Symbol, + transpose = false, + prune = false, + ) + +Construct a sparse binary matrix representing a restriction of the topology +to the given source/target nodes compartment and the given edge compartment. +The result entry `[i, j]` is true if edge i → j exist (outgoing matrix). +If `transpose` is set, the entry is true if edge `j → i` exists instead (incoming matrix). +Entries are false if either `i` or `j` has been removed from the topology. +If `prune` is set, remove line/columns corresponding removed nodes. +""" +function adjacency_matrix( + g::Topology, + source::Symbol, + edge::Symbol, + target::Symbol, + transpose = false, + prune = false, +) + check_node_type(g, source) + check_node_type(g, target) + check_edge_type(g, edge) + si = U.node_type_index(g, source) + ti = U.node_type_index(g, target) + ei = U.edge_type_index(g, edge) + if prune + full_adjacency_matrix(g, si, ti, ei, transpose) + else + pruned_adjacency_matrix(g, si, ti, ei, transpose) + end +end +export adjacency_matrix + +function full_adjacency_matrix(g::Topology, s::Int, e::Int, t::Int, transpose::Bool) + n_source = U.n_nodes(g, s) + n_target = U.n_nodes(g, t) + n, m = transpose ? (n_target, n_source) : (n_source:n_target) + res = spzeros(n, m) + it = transpose ? U._outgoing_adjacency(g, s, e, t) : U._incoming_adjacency(g, s, e, t) + for (iabs, neighbours) in it + for jabs in neighbours + i = U.node_rel_index(g, iabs, s) + j = U.node_rel_index(g, jabs, t) + res[i, j] = true + end + end + res +end + +function pruned_adjacency_matrix( + g::Topology, + source::Int, + edge::Int, + target::Int, + transpose::Bool, +) + n_source = g.n_nodes[source] + n_target = g.n_nodes[target] + n, m = transpose ? (n_target, n_source) : (n_source:n_target) + res = spzeros(n, m) +end + #------------------------------------------------------------------------------------------- # Iterate over disconnected components within the topology. # Every component is yielded as a separate new topology, diff --git a/src/Topologies/deadmap.jl b/src/Topologies/deadmap.jl new file mode 100644 index 000000000..0ee2b3e12 --- /dev/null +++ b/src/Topologies/deadmap.jl @@ -0,0 +1,46 @@ +""" +Let a contiguous vector of data: + + 1 2 3 4 5 6 7 8 9 + U = [a, b, c, d, e, f, g, h, i] + +Let some entry be removed and replaced with "tombstones": + + 1 2 3 4 5 6 7 8 9 + V = [a, 🗙, c, d, 🗙, 🗙, g, h, 🗙] + 2 5 6 → (tombstone indices) + +Take the result of removing the tombstones to form a new contiguous vector: + + 1 2 3 4 5 → u (current indices) + W = [a, c, d, g, h] ↑ + 1 3 4 7 8 → w (former indices) + +This type keeps a O(ln(n)) mapping from `w` to `u` up to date, +under the form of two lists: + + # Compressed sorted list of tombstone indices. + t = [2, 6] # (5 and 9 elided because no data comes after them) + # List of indices shifts. + s = [0, 1, 2] # [1-shift after the `2` tombstone, 2-shift after the `5, 6` tombstones] + * (always start with 0) + +To find `u` given `w`: + + i = find(t, w) # Find index in `t` where `w` should be inserted to keep `t` sorted. + u = w - s[i - 1] +""" +struct DeadMap + n::Int # Length of U. + t::Vector{Int64} + s::Vector{Int64} + DeadMap(n::Int) = new(n, [], [0]) +end + +# All methods hereafter are unchecked and assume consistency of their input. + +function kill!(map::DeadMap, tomb::Int) + i = Base.Sort.searchsortedfirst(map.t, tomb) + # HERE: wait.. can the same data struct be used for u → w conversions? + +end diff --git a/src/Topologies/display.jl b/src/Topologies/display.jl index d41e98903..e043d2307 100644 --- a/src/Topologies/display.jl +++ b/src/Topologies/display.jl @@ -72,7 +72,7 @@ function Base.show(io::IO, ::MIME"text/plain", g::Topology) ) last = nothing # Save last in case we use vertical elision. i = 0 - for (i_source, _neighbours) in U._outgoing_edges_indices(g, i_type) + for (i_source, _neighbours) in U._outgoing_adjacency(g, i_type) i += 1 isempty(_neighbours) && continue source = U.node_label(g, Abs(i_source)) diff --git a/src/Topologies/unchecked_queries.jl b/src/Topologies/unchecked_queries.jl index c7ee79bb0..c2829430d 100644 --- a/src/Topologies/unchecked_queries.jl +++ b/src/Topologies/unchecked_queries.jl @@ -133,7 +133,7 @@ incoming_labels(g::G, node::AbsRef) = # Filter adjacency iterators given one particular edge type. # Also return twolevel iterators: focal node, then its neighbours. -function _outgoing_edges_indices(g::G, edge_type::IRef) +function _outgoing_adjacency(g::G, edge_type::IRef) i_type = edge_type_index(g, edge_type) imap(ifilter(enumerate(g.outgoing)) do (_, node) !(node isa Tombstone) @@ -141,7 +141,7 @@ function _outgoing_edges_indices(g::G, edge_type::IRef) (i, _neighbours[i_type]) end end -function _incoming_edges_indices(g::G, edge_type::IRef) +function _incoming_adjacency(g::G, edge_type::IRef) i_type = edge_type_index(g, edge_type) imap(ifilter(enumerate(g.incoming)) do (_, node) !(node isa Tombstone) @@ -149,48 +149,54 @@ function _incoming_edges_indices(g::G, edge_type::IRef) (i, _neighbours[i_type]) end end -outgoing_edges_indices(g::G, edge_type::IRef) = - imap(_outgoing_edges_indices(g, edge_type)) do (i_node, _neighbours) +outgoing_adjacency(g::G, edge_type::IRef) = + imap(_outgoing_adjacency(g, edge_type)) do (i_node, _neighbours) (Abs(i_node), imap(Abs, _neighbours)) end -incoming_edges_indices(g::G, edge_type::IRef) = - imap(_incoming_edges_indices(g, edge_type)) do (i_node, _neighbours) +incoming_adjacency(g::G, edge_type::IRef) = + imap(_incoming_adjacency(g, edge_type)) do (i_node, _neighbours) (Abs(i_node), imap(Abs, _neighbours)) end -outgoing_edges_labels(g::G, edge_type::IRef) = - imap(_outgoing_edges_indices(g, edge_type)) do (i_node, _neighbours) +outgoing_adjacency(g::G, edge_type::IRef) = + imap(_outgoing_adjacency(g, edge_type)) do (i_node, _neighbours) (node_label(g, i_node), imap(i -> node_label(g, i), _neighbours)) end incoming_edges_labels(g::G, edge_type::IRef) = - imap(_incoming_edges_indices(g, edge_type)) do (i_node, _neighbours) + imap(_incoming_adjacency(g, edge_type)) do (i_node, _neighbours) (node_label(g, i_node), imap(i -> node_label(g, i), _neighbours)) end -# Same, but filters for one particular node type. -function outgoing_edges_indices(g::G, edge_type::IRef, node_type::IRef) +# Same, but query particular end nodes types. +function outgoing_adjacency(g::G, source_type::IRef, edge_type::IRef, target_type::IRef) i_et = edge_type_index(g, edge_type) - range = _nodes_abs_range(g, node_type) - imap(ifilter(zip(range, g.outgoing[range])) do (_, node) - !(node isa Tombstone) - end) do (i_node, _neighbours) - (Abs(i_node), imap(Abs, ifilter(in(range), _neighbours[i_et]))) + src_range = _nodes_abs_range(g, source_type) + tgt_range = _nodes_abs_range(g, target_type) + imap( + ifilter(zip(src_range, g.outgoing[src_range])) do (_, node) + !(node isa Tombstone) + end, + ) do (i_node, _neighbours) + (Abs(i_node), imap(Abs, ifilter(in(tgt_range), _neighbours[i_et]))) end end -function incoming_edges_indices(g::G, edge_type::IRef, node_type::IRef) +function incoming_adjacency(g::G, source_type::IRef, edge_type::IRef, target_type::IRef) i_et = edge_type_index(g, edge_type) - range = _nodes_abs_range(g, node_type) - imap(ifilter(zip(range, g.incoming[range])) do (_, node) - !(node isa Tombstone) - end) do (i_node, _neighbours) - (Abs(i_node), imap(Abs, ifilter(in(range), _neighbours[i_et]))) + src_range = _nodes_abs_range(g, source_type) + tgt_range = _nodes_abs_range(g, target_type) + imap( + ifilter(zip(src_range, g.incoming[tgt_range])) do (_, node) + !(node isa Tombstone) + end, + ) do (i_node, _neighbours) + (Abs(i_node), imap(Abs, ifilter(in(src_range), _neighbours[i_et]))) end end -outgoing_edges_labels(g::G, edge_type::IRef, node_type::IRef) = - imap(outgoing_edges_indices(g, edge_type, node_type)) do (i_node, neighbours) +outgoing_adjacency(g::G, source_type::IRef, edge_type::IRef, target_type::IRef) = + imap(outgoing_adjacency(g, source_type, edge_type, target_type)) do (i_node, neighbours) (node_label(g, i_node), imap(i -> node_label(g, i), neighbours)) end -incoming_edges_labels(g::G, edge_type::IRef, node_type::IRef) = - imap(incoming_edges_indices(g, edge_type, node_type)) do (i_node, neighbours) +incoming_edges_labels(g::G, source_type::IRef, edge_type::IRef, target_type::IRef) = + imap(incoming_adjacency(g, source_type, edge_type, target_type)) do (i_node, neighbours) (node_label(g, i_node), imap(i -> node_label(g, i), neighbours)) end