Skip to content

Commit

Permalink
🚧 Start implementing adjacency_matrix() + handling tombstone indices.
Browse files Browse the repository at this point in the history
  • Loading branch information
iago-lito committed Jul 31, 2024
1 parent f7ed717 commit 7443dca
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 27 deletions.
71 changes: 71 additions & 0 deletions src/Topologies/Topologies.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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,
Expand Down
46 changes: 46 additions & 0 deletions src/Topologies/deadmap.jl
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion src/Topologies/display.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
58 changes: 32 additions & 26 deletions src/Topologies/unchecked_queries.jl
Original file line number Diff line number Diff line change
Expand Up @@ -133,64 +133,70 @@ 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)
end) do (i, _neighbours)
(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)
end) do (i, _neighbours)
(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

Expand Down

0 comments on commit 7443dca

Please sign in to comment.