Skip to content

Commit ea5e9b7

Browse files
authored
Merge pull request #1 from AJ0070/pr/split-source
2 parents 208f44b + 83f4aad commit ea5e9b7

5 files changed

Lines changed: 321 additions & 275 deletions

File tree

src/NetworkXGraphs.jl

Lines changed: 9 additions & 275 deletions
Original file line numberDiff line numberDiff line change
@@ -4,281 +4,15 @@ using Graphs
44
using PythonCall: Py, pynew, pycopy!, pybuiltins, pyconvert, pyimport
55

66
export AbstractNetworkXGraph,
7-
NetworkXGraph,
8-
NetworkXDiGraph,
9-
networkx_graph,
10-
refresh_index!
7+
NetworkXGraph,
8+
NetworkXDiGraph,
9+
networkx_graph,
10+
refresh_index!
1111

12-
"""
13-
NetworkXGraphs.PythonNetworkX
14-
15-
Sub-module providing direct access to the Python `networkx` package.
16-
Use this namespace when you need raw Python networkx objects or algorithms
17-
that are not yet wrapped by the Julia API.
18-
19-
# Example
20-
```julia
21-
using NetworkXGraphs
22-
nx = NetworkXGraphs.PythonNetworkX.networkx
23-
pyg = nx.complete_graph(5)
24-
```
25-
"""
26-
module PythonNetworkX
27-
using PythonCall: pynew, pycopy!, pyimport
28-
29-
"""The raw Python `networkx` module."""
30-
const networkx = pynew()
31-
32-
function __init__()
33-
pycopy!(networkx, pyimport("networkx"))
34-
end
35-
end # module PythonNetworkX
36-
37-
"""
38-
AbstractNetworkXGraph{T} <: Graphs.AbstractGraph{T}
39-
40-
Abstract supertype for wrappers around Python NetworkX graph objects.
41-
"""
42-
abstract type AbstractNetworkXGraph{T<:Integer} <: Graphs.AbstractGraph{T} end
43-
44-
"""
45-
NetworkXGraph{T}(pygraph)
46-
NetworkXGraph(pygraph)
47-
48-
Wrap an undirected Python `networkx.Graph` as a `Graphs.AbstractGraph`.
49-
50-
# Example
51-
```julia
52-
using NetworkXGraphs
53-
nx = NetworkXGraphs.PythonNetworkX.networkx
54-
pyg = nx.path_graph(5)
55-
gw = NetworkXGraph(pyg)
56-
```
57-
"""
58-
mutable struct NetworkXGraph{T<:Integer} <: AbstractNetworkXGraph{T}
59-
pygraph::Py
60-
nodes::Vector{Any}
61-
node_to_index::Dict{Any,T}
62-
end
63-
64-
"""
65-
NetworkXDiGraph{T}(pygraph)
66-
NetworkXDiGraph(pygraph)
67-
68-
Wrap a directed Python `networkx.DiGraph` as a `Graphs.AbstractGraph`.
69-
70-
# Example
71-
```julia
72-
using NetworkXGraphs
73-
nx = NetworkXGraphs.PythonNetworkX.networkx
74-
pyg = nx.DiGraph()
75-
pyg.add_edges_from([(1, 2), (2, 3)])
76-
gw = NetworkXDiGraph(pyg)
77-
```
78-
"""
79-
mutable struct NetworkXDiGraph{T<:Integer} <: AbstractNetworkXGraph{T}
80-
pygraph::Py
81-
nodes::Vector{Any}
82-
node_to_index::Dict{Any,T}
83-
end
84-
85-
function _node_to_index(nodes::Vector{Any}, ::Type{T}) where {T<:Integer}
86-
mapping = Dict{Any,T}()
87-
for (i, node) in enumerate(nodes)
88-
mapping[node] = T(i)
89-
end
90-
return mapping
91-
end
92-
93-
function refresh_index!(g::AbstractNetworkXGraph{T}) where {T<:Integer}
94-
g.nodes = pyconvert(Vector{Any}, pybuiltins.list(g.pygraph.nodes()))
95-
g.node_to_index = _node_to_index(g.nodes, T)
96-
return g
97-
end
98-
99-
function _refresh_index_from_nodes!(g::AbstractNetworkXGraph{T}) where {T<:Integer}
100-
g.node_to_index = _node_to_index(g.nodes, T)
101-
return g
102-
end
103-
104-
function NetworkXGraph{T}(pygraph::Py) where {T<:Integer}
105-
pyconvert(Bool, pygraph.is_directed()) &&
106-
throw(ArgumentError("Expected an undirected networkx.Graph."))
107-
g = NetworkXGraph{T}(pygraph, Any[], Dict{Any,T}())
108-
return refresh_index!(g)
109-
end
110-
111-
NetworkXGraph(pygraph::Py) = NetworkXGraph{Int}(pygraph)
112-
113-
function NetworkXDiGraph{T}(pygraph::Py) where {T<:Integer}
114-
!pyconvert(Bool, pygraph.is_directed()) &&
115-
throw(ArgumentError("Expected a directed networkx.DiGraph."))
116-
g = NetworkXDiGraph{T}(pygraph, Any[], Dict{Any,T}())
117-
return refresh_index!(g)
118-
end
119-
120-
NetworkXDiGraph(pygraph::Py) = NetworkXDiGraph{Int}(pygraph)
121-
122-
"""
123-
networkx_graph(g)
124-
125-
Convert a `Graphs.AbstractGraph` to a Python NetworkX graph object.
126-
Returns the underlying Python object for `AbstractNetworkXGraph` wrappers,
127-
or creates a new Python networkx graph for any other `Graphs.AbstractGraph`.
128-
"""
129-
networkx_graph(g::AbstractNetworkXGraph) = g.pygraph
130-
131-
function networkx_graph(g::Graphs.AbstractGraph)
132-
nx = PythonNetworkX.networkx
133-
pyg = Graphs.is_directed(g) ? nx.DiGraph() : nx.Graph()
134-
pyg.add_nodes_from(collect(Graphs.vertices(g)))
135-
pyg.add_edges_from([(Graphs.src(e), Graphs.dst(e)) for e in Graphs.edges(g)])
136-
return pyg
137-
end
138-
139-
Graphs.is_directed(::Type{<:NetworkXGraph}) = false
140-
Graphs.is_directed(::NetworkXGraph) = false
141-
Graphs.is_directed(::Type{<:NetworkXDiGraph}) = true
142-
Graphs.is_directed(::NetworkXDiGraph) = true
143-
144-
Graphs.edgetype(::AbstractNetworkXGraph{T}) where {T<:Integer} = Graphs.Edge{T}
145-
Graphs.nv(g::AbstractNetworkXGraph) = length(g.nodes)
146-
Graphs.ne(g::AbstractNetworkXGraph) = pyconvert(Int, g.pygraph.number_of_edges())
147-
Graphs.vertices(g::AbstractNetworkXGraph{T}) where {T<:Integer} = T.(1:Graphs.nv(g))
148-
Graphs.has_vertex(g::AbstractNetworkXGraph, v) = 1 <= v <= Graphs.nv(g)
149-
Graphs.eltype(::Type{G}) where {T<:Integer,G<:AbstractNetworkXGraph{T}} = T
150-
Graphs.eltype(::AbstractNetworkXGraph{T}) where {T<:Integer} = T
151-
152-
_node(g::AbstractNetworkXGraph, v::Integer) = g.nodes[Int(v)]
153-
154-
function Graphs.has_edge(g::AbstractNetworkXGraph, s, d)
155-
Graphs.has_vertex(g, s) || return false
156-
Graphs.has_vertex(g, d) || return false
157-
return pyconvert(Bool, g.pygraph.has_edge(_node(g, s), _node(g, d)))
158-
end
159-
160-
function _mapped_neighbors(g::AbstractNetworkXGraph{T}, pyiter) where {T<:Integer}
161-
py_ns = pyconvert(Vector{Any}, pybuiltins.list(pyiter))
162-
return T[g.node_to_index[n] for n in py_ns]
163-
end
164-
165-
function Graphs.outneighbors(g::NetworkXGraph{T}, v) where {T<:Integer}
166-
Graphs.has_vertex(g, v) || return T[]
167-
return _mapped_neighbors(g, g.pygraph.neighbors(_node(g, v)))
168-
end
169-
170-
Graphs.inneighbors(g::NetworkXGraph{T}, v) where {T<:Integer} = Graphs.outneighbors(g, v)
171-
172-
function Graphs.outneighbors(g::NetworkXDiGraph{T}, v) where {T<:Integer}
173-
Graphs.has_vertex(g, v) || return T[]
174-
return _mapped_neighbors(g, g.pygraph.successors(_node(g, v)))
175-
end
176-
177-
function Graphs.inneighbors(g::NetworkXDiGraph{T}, v) where {T<:Integer}
178-
Graphs.has_vertex(g, v) || return T[]
179-
return _mapped_neighbors(g, g.pygraph.predecessors(_node(g, v)))
180-
end
181-
182-
function Graphs.edges(g::AbstractNetworkXGraph{T}) where {T<:Integer}
183-
py_edges = pyconvert(Vector{Tuple{Any,Any}}, pybuiltins.list(g.pygraph.edges()))
184-
return Graphs.Edge{T}[
185-
Graphs.Edge{T}(g.node_to_index[u], g.node_to_index[v]) for (u, v) in py_edges
186-
]
187-
end
188-
189-
Graphs.has_self_loops(g::AbstractNetworkXGraph) =
190-
pyconvert(Int, PythonNetworkX.networkx.number_of_selfloops(g.pygraph)) > 0
191-
192-
function Graphs.add_vertex!(g::AbstractNetworkXGraph{T}) where {T<:Integer}
193-
new_index = T(Graphs.nv(g) + 1)
194-
label = new_index
195-
# Find a unique label if it already exists in the Python graph
196-
while pyconvert(Bool, g.pygraph.has_node(label))
197-
new_index += one(T)
198-
label = new_index
199-
end
200-
g.pygraph.add_node(label)
201-
push!(g.nodes, label)
202-
g.node_to_index[label] = T(length(g.nodes))
203-
return true
204-
end
205-
206-
function Graphs.add_edge!(g::AbstractNetworkXGraph, s, d)
207-
Graphs.has_vertex(g, s) || return false
208-
Graphs.has_vertex(g, d) || return false
209-
Graphs.has_edge(g, s, d) && return false
210-
g.pygraph.add_edge(_node(g, s), _node(g, d))
211-
return true
212-
end
213-
214-
function Graphs.rem_edge!(g::AbstractNetworkXGraph, s, d)
215-
if Graphs.has_edge(g, s, d)
216-
g.pygraph.remove_edge(_node(g, s), _node(g, d))
217-
return true
218-
end
219-
return false
220-
end
221-
222-
function Graphs.rem_vertex!(g::AbstractNetworkXGraph{T}, v) where {T<:Integer}
223-
Graphs.has_vertex(g, v) || return false
224-
label = _node(g, v)
225-
g.pygraph.remove_node(label)
226-
# O(1) removal: swap with last node and pop
227-
if v != length(g.nodes)
228-
last_label = g.nodes[end]
229-
g.nodes[v] = last_label
230-
g.node_to_index[last_label] = T(v)
231-
end
232-
pop!(g.nodes)
233-
delete!(g.node_to_index, label)
234-
return true
235-
end
236-
237-
function Graphs.rem_vertices!(g::AbstractNetworkXGraph{T}, vs; keep_order::Bool=true) where {T<:Integer}
238-
remove_set = Set{T}(T.(collect(vs)))
239-
old_vertices = collect(Graphs.vertices(g))
240-
for v in old_vertices
241-
if v in remove_set
242-
g.pygraph.remove_node(_node(g, v))
243-
end
244-
end
245-
g.nodes = [g.nodes[v] for v in old_vertices if !(v in remove_set)]
246-
_refresh_index_from_nodes!(g)
247-
vmap = zeros(T, length(old_vertices))
248-
new_index = one(T)
249-
for v in old_vertices
250-
if !(v in remove_set)
251-
vmap[v] = new_index
252-
new_index += one(T)
253-
end
254-
end
255-
return vmap
256-
end
257-
258-
function Graphs.squash(g::AbstractNetworkXGraph{T}) where {T<:Integer}
259-
copyg = typeof(g)(g.pygraph.copy())
260-
copyg.nodes = copy(g.nodes)
261-
_refresh_index_from_nodes!(copyg)
262-
return copyg, collect(Graphs.vertices(g))
263-
end
264-
265-
Graphs.zero(::Type{<:NetworkXGraph{T}}) where {T<:Integer} =
266-
NetworkXGraph{T}(PythonNetworkX.networkx.Graph())
267-
Graphs.zero(::Type{<:NetworkXDiGraph{T}}) where {T<:Integer} =
268-
NetworkXDiGraph{T}(PythonNetworkX.networkx.DiGraph())
269-
270-
function Base.copy(g::AbstractNetworkXGraph{T}) where {T<:Integer}
271-
copyg = typeof(g)(g.pygraph.copy())
272-
copyg.nodes = copy(g.nodes)
273-
copyg.node_to_index = copy(g.node_to_index)
274-
return copyg
275-
end
276-
277-
function Base.reverse(g::NetworkXDiGraph{T}) where {T<:Integer}
278-
reversed = NetworkXDiGraph{T}(g.pygraph.reverse(copy=true))
279-
reversed.nodes = copy(g.nodes)
280-
reversed.node_to_index = copy(g.node_to_index)
281-
return reversed
282-
end
12+
include("python_networkx.jl")
13+
include("types.jl")
14+
include("graph_api.jl")
15+
include("conversions.jl")
28316

28417
end # module NetworkXGraphs
18+

src/conversions.jl

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
"""
2+
networkx_graph(g)
3+
4+
Convert any `Graphs.AbstractGraph` to a raw Python NetworkX graph object.
5+
6+
For `AbstractNetworkXGraph` wrappers this simply returns the underlying
7+
`pygraph` without any copying. For any other `AbstractGraph`, a new
8+
Python `networkx.Graph` or `networkx.DiGraph` is created and populated
9+
with the vertices and edges of `g`.
10+
11+
See also: [`NetworkXGraph`](@ref), [`NetworkXDiGraph`](@ref)
12+
"""
13+
networkx_graph(g::AbstractNetworkXGraph) = g.pygraph
14+
15+
function networkx_graph(g::Graphs.AbstractGraph)
16+
nx = PythonNetworkX.networkx
17+
pyg = Graphs.is_directed(g) ? nx.DiGraph() : nx.Graph()
18+
pyg.add_nodes_from(collect(Graphs.vertices(g)))
19+
pyg.add_edges_from([(Graphs.src(e), Graphs.dst(e)) for e in Graphs.edges(g)])
20+
return pyg
21+
end

0 commit comments

Comments
 (0)