diff --git a/src/graphics/GraphvizGraphs.jl b/src/graphics/GraphvizGraphs.jl index 2388c954f..5d535158a 100644 --- a/src/graphics/GraphvizGraphs.jl +++ b/src/graphics/GraphvizGraphs.jl @@ -340,77 +340,85 @@ const default_subgraph_edge_attrs = Dict(:color => "cornflowerblue") # Bipartite graphs ################## -""" Visualize a bipartite graph using Graphviz. +""" Convert a bipartite graph to a Graphviz graph. -Works for both directed and undirected bipartite graphs. Both types of vertices -in the bipartite graph become nodes in the Graphviz graph. - -# Arguments -- `prog="dot"`: Graphviz program to use -- `graph_attrs`: Graph-level Graphviz attributes -- `node_attrs`: Node-level Graphviz attributes -- `edge_attrs`: Edge-level Graphviz attributes -- `node_labels=false`: whether to label nodes and if so, which pair of - data attributes to use -- `edge_labels=false`: whether to label edges and if so, which data attribute - (undirected case) or pair of attributes (directed case) to use -- `invis_edge=true`: whether to add invisible edges between vertices of same - type, which ensures that the order of the nodes is preserved. +A simple default style is applied. For more control over the visual appearance, +first convert the graph to a property graph, define the Graphviz attributes as +needed, and finally convert the property graph to a Graphviz graph. """ -function to_graphviz(g::AbstractUndirectedBipartiteGraph; - prog::AbstractString="dot", graph_attrs::AbstractDict=Dict(), - node_attrs::AbstractDict=Dict(), edge_attrs::AbstractDict=Dict(), - node_labels::Union{Tuple{Symbol,Symbol},Bool}=false, - edge_labels::Union{Symbol,Bool}=false, kw...) - stmts, nodes1, nodes2 = bipartite_graphviz_nodes(g; - node_labels=node_labels, kw...) +function to_graphviz(g::HasBipartiteVertices; invis_edges::Bool=true, kw...) + to_graphviz(to_graphviz_property_graph(g; kw...); invis_edges) +end - for e in edges(g) - attrs = merge!(Dict(:constraint => "false"), edge_label(g, edge_labels, e)) - push!(stmts, Graphviz.Edge([nodes1[src(g,e)], nodes2[tgt(g,e)]], attrs)) - end +function to_graphviz_property_graph(g::AbstractBipartiteGraph; + prog::AbstractString="dot", graph_attrs::AbstractDict=Dict(), + node_attrs::AbstractDict=Dict(), edge_attrs::AbstractDict=Dict(), + node_labels::Union{Tuple{Symbol,Symbol},Bool}=false, edge_labels::Union{Tuple{Symbol,Symbol},Bool}=false) + + node1_labels, node2_labels = node_labels isa Tuple ? node_labels : (node_labels, node_labels) + edge12_labels, edge21_labels = edge_labels isa Tuple ? edge_labels : (edge_labels, edge_labels) + + BipartitePropertyGraph{Any}(g, v -> node_label(g, node1_labels, v), v -> node_label(g, node2_labels, v), + e -> edge_label(g, edge12_labels, e), e -> edge_label(g, edge21_labels, e); + prog = prog, + graph = merge!(default_graph_attrs(prog), graph_attrs), + node = merge!(default_node_attrs(node_labels), node_attrs), + edge = merge!(default_edge_attrs(prog), edge_attrs), + ) +end - Graphviz.Digraph("bipartite_graph", stmts, prog=prog, - graph_attrs = merge!(default_graph_attrs(prog), graph_attrs), - node_attrs = merge!(default_node_attrs(node_labels), node_attrs), - edge_attrs = merge!(default_edge_attrs(prog), edge_attrs)) +function to_graphviz_property_graph(g::AbstractUndirectedBipartiteGraph; + prog::AbstractString="dot", graph_attrs::AbstractDict=Dict(), + node_attrs::AbstractDict=Dict(), edge_attrs::AbstractDict=Dict(), + node_labels::Union{Tuple{Symbol,Symbol},Bool}=false, edge_labels::Union{Symbol,Bool}=false) + + node1_labels, node2_labels = node_labels isa Tuple ? node_labels : (node_labels, node_labels) + + BipartitePropertyGraph{Any}(g, v -> node_label(g, node1_labels, v), v -> node_label(g, node2_labels, v), + e -> edge_label(g, edge_labels, e); + prog = prog, + graph = merge!(default_graph_attrs(prog), graph_attrs), + node = merge!(default_node_attrs(node_labels), node_attrs), + edge = merge!(default_edge_attrs(prog), edge_attrs), + ) end -function to_graphviz(g::AbstractBipartiteGraph; - prog::AbstractString="dot", graph_attrs::AbstractDict=Dict(), - node_attrs::AbstractDict=Dict(), edge_attrs::AbstractDict=Dict(), - node_labels::Union{Tuple{Symbol,Symbol},Bool}=false, - edge_labels::Union{Tuple{Symbol,Symbol},Bool}=false, kw...) - stmts, nodes1, nodes2 = bipartite_graphviz_nodes(g; - node_labels=node_labels, kw...) +""" Convert a bipartite property graph to a Graphviz graph. + +This method is usually more convenient than direct AST manipulation for creating +simple Graphviz graphs. For more advanced features, like nested subgraphs, you +must use the Graphviz AST. +""" +function to_graphviz(g::AbstractBipartitePropertyGraph; kw...)::Graphviz.Graph + + stmts, nodes1, nodes2 = bipartite_graphviz_nodes(g; kw...) - edge12_labels, edge21_labels = edge_labels isa Tuple ? edge_labels : - (edge_labels, edge_labels) for e in edges₁₂(g) - attrs = merge!(Dict(:constraint => "false"), edge_label(g, edge12_labels, e)) + attrs = merge!(Dict(:constraint => "false"), e₁₂props(g, e)) push!(stmts, Graphviz.Edge([nodes1[src₁(g,e)], nodes2[tgt₂(g,e)]], attrs)) end for e in edges₂₁(g) - attrs = merge!(Dict(:constraint => "false"), edge_label(g, edge21_labels, e)) + attrs = merge!(Dict(:constraint => "false"), e₂₁props(g, e)) push!(stmts, Graphviz.Edge([nodes2[src₂(g,e)], nodes1[tgt₁(g,e)]], attrs)) end - Graphviz.Digraph("bipartite_graph", stmts, prog=prog, - graph_attrs = merge!(default_graph_attrs(prog), graph_attrs), - node_attrs = merge!(default_node_attrs(node_labels), node_attrs), - edge_attrs = merge!(default_edge_attrs(prog), edge_attrs)) + attrs = gprops(g) + Graphviz.Digraph( + "bipartite_graph", + stmts, + prog = get(attrs, :prog, "dot"), + graph_attrs = get(attrs, :graph, Dict()), + node_attrs = get(attrs, :node, Dict()), + edge_attrs = get(attrs, :edge, Dict()) + ) end -function bipartite_graphviz_nodes(g::HasBipartiteVertices; - node_labels::Union{Tuple{Symbol,Symbol},Bool}=false, - invis_edges::Bool=true) +function bipartite_graphviz_nodes(g::AbstractBipartitePropertyGraph; invis_edges::Bool=true) V₁, V₂ = vertices₁(g), vertices₂(g) - node1_labels, node2_labels = node_labels isa Tuple ? node_labels : - (node_labels, node_labels) # Vertices of type 1. nodes1 = map(V₁) do v - Graphviz.Node("n1_$v", node_label(g, node1_labels, v)) + Graphviz.Node("n1_$v", v₁props(g, v)) end edges1 = Graphviz.Edge[] if invis_edges @@ -423,7 +431,7 @@ function bipartite_graphviz_nodes(g::HasBipartiteVertices; # Vertices of type 2. nodes2 = map(V₂) do v - Graphviz.Node("n2_$v", node_label(g, node2_labels, v)) + Graphviz.Node("n2_$v", v₂props(g, v)) end edges2 = Graphviz.Edge[] if invis_edges @@ -435,7 +443,7 @@ function bipartite_graphviz_nodes(g::HasBipartiteVertices; graph_attrs=Graphviz.Attributes(:rank => "same")) stmts = Graphviz.Statement[cluster1, cluster2] - (stmts, map(n -> n.name, nodes1), map(n -> n.name, nodes2)) + return (stmts, map(n -> n.name, nodes1), map(n -> n.name, nodes2)) end default_node_shape(::Tuple{Symbol,Symbol}) = "ellipse" diff --git a/src/graphs/BipartiteGraphs.jl b/src/graphs/BipartiteGraphs.jl index cc3ddb3cf..1d0b7fcc5 100644 --- a/src/graphs/BipartiteGraphs.jl +++ b/src/graphs/BipartiteGraphs.jl @@ -251,7 +251,7 @@ function add_edges₂₁!(g::HasBipartiteGraph, srcs::AbstractVector{Int}, add_parts!(g, :E₂₁, n; src₂=srcs, tgt₁=tgts, kw...) end -function rem_vertices₁!(g::AbstractBipartiteGraph, vs; keep_edges::Bool=false) +function rem_vertices₁!(g::HasBipartiteGraph, vs; keep_edges::Bool=false) if !keep_edges rem_parts!(g, :E₁₂, unique!(sort!(flatten(incident(g, vs, :src₁))))) rem_parts!(g, :E₂₁, unique!(sort!(flatten(incident(g, vs, :tgt₁))))) @@ -259,7 +259,7 @@ function rem_vertices₁!(g::AbstractBipartiteGraph, vs; keep_edges::Bool=false) rem_parts!(g, :V₁, vs) end -function rem_vertices₂!(g::AbstractBipartiteGraph, vs; keep_edges::Bool=false) +function rem_vertices₂!(g::HasBipartiteGraph, vs; keep_edges::Bool=false) if !keep_edges rem_parts!(g, :E₂₁, unique!(sort!(flatten(incident(g, vs, :src₂))))) rem_parts!(g, :E₁₂, unique!(sort!(flatten(incident(g, vs, :tgt₂))))) @@ -269,22 +269,22 @@ end """ Remove edge from V₁ to V₂ in a bipartite graph. """ -rem_edge₁₂!(g::AbstractBipartiteGraph, e::Int) = rem_part!(g, :E₁₂, e) -rem_edge₁₂!(g::AbstractBipartiteGraph, src::Int, tgt::Int) = +rem_edge₁₂!(g::HasBipartiteGraph, e::Int) = rem_part!(g, :E₁₂, e) +rem_edge₁₂!(g::HasBipartiteGraph, src::Int, tgt::Int) = rem_edge₁₂!(g, first(edges₁₂(g, src, tgt))) """ Remove edge from V₁ to V₂ in a bipartite graph. """ -rem_edge₂₁!(g::AbstractBipartiteGraph, e::Int) = rem_part!(g, :E₂₁, e) -rem_edge₂₁!(g::AbstractBipartiteGraph, src::Int, tgt::Int) = +rem_edge₂₁!(g::HasBipartiteGraph, e::Int) = rem_part!(g, :E₂₁, e) +rem_edge₂₁!(g::HasBipartiteGraph, src::Int, tgt::Int) = rem_edge₂₁!(g, first(edges₂₁(g, src, tgt))) """ Remove edges from V₁ to V₂ in a bipartite graph. """ -rem_edges₁₂!(g::AbstractBipartiteGraph, es) = rem_parts!(g, :E₁₂, es) +rem_edges₁₂!(g::HasBipartiteGraph, es) = rem_parts!(g, :E₁₂, es) """ Remove edges from V₂ to V₁ in a bipartite graph. """ -rem_edges₂₁!(g::AbstractBipartiteGraph, es) = rem_parts!(g, :E₂₁, es) +rem_edges₂₁!(g::HasBipartiteGraph, es) = rem_parts!(g, :E₂₁, es) end diff --git a/src/graphs/PropertyGraphs.jl b/src/graphs/PropertyGraphs.jl index c7b781d71..91b8720cd 100644 --- a/src/graphs/PropertyGraphs.jl +++ b/src/graphs/PropertyGraphs.jl @@ -3,13 +3,24 @@ export AbstractPropertyGraph, PropertyGraph, SchPropertyGraph, SymmetricPropertyGraph, SchSymmetricPropertyGraph, ReflexiveEdgePropertyGraph, SchReflexivePropertyGraph, gprops, vprops, eprops, get_gprop, get_vprop, get_eprop, - set_gprop!, set_vprop!, set_eprop!, set_gprops!, set_vprops!, set_eprops! + set_gprop!, set_vprop!, set_eprop!, set_gprops!, set_vprops!, set_eprops!, + AbstractBipartitePropertyGraph, BipartitePropertyGraph, SchBipartitePropertyGraph, + v₁props, v₂props, e₁₂props, e₂₁props, get_v₁prop, get_v₂prop, + get_e₁₂prop, get_e₂₁prop, set_v₁prop!, set_v₂prop!, set_e₁₂prop!, set_e₂₁prop!, + set_v₁props!, set_v₂props!, set_e₁₂props!, set_e₂₁props! using ACSets using ...Theories using ..BasicGraphs import ..BasicGraphs: nv, ne, src, tgt, inv, edges, vertices, has_edge, has_vertex, add_edge!, add_edges!, add_vertex!, add_vertices! +using ..BipartiteGraphs +import ..BipartiteGraphs: nv₁, nv₂, vertices₁, vertices₂, ne₁₂, ne₂₁, edges₁₂, edges₂₁, + src₁, src₂, tgt₁, tgt₂, + add_vertex₁!, add_vertex₂!, add_vertices₁!, add_vertices₂!, + rem_vertex₁!, rem_vertex₂!, rem_vertices₁!, rem_vertices₂!, + add_edge₁₂!, add_edge₂₁!, add_edges₁₂!, add_edges₂₁!, + rem_edge₁₂!, rem_edge₂₁!, rem_edges₁₂!, rem_edges₂₁! # Data types ############ @@ -97,6 +108,43 @@ end @acset_type ReflexiveEdgePropertyGraph(SchReflexiveEdgePropertyGraph, index=[:src,:tgt]) <: AbstractReflexiveEdgePropertyGraph +""" Abstract type for bipartite graph with properties. + +Concrete types are [`BipartitePropertyGraph`](@ref). +""" +abstract type AbstractBipartitePropertyGraph{T} end + +@present SchBipartitePropertyGraph <: SchBipartiteGraph begin + Props::AttrType + v₁props::Attr(V₁,Props) + v₂props::Attr(V₂,Props) + e₁₂props::Attr(E₁₂,Props) + e₂₁props::Attr(E₂₁,Props) +end + +@abstract_acset_type __AbstractBipartitePropertyGraph <: HasBipartiteGraph + +const _AbstractBipartitePropertyGraph{T} = __AbstractBipartitePropertyGraph{S, Tuple{Dict{Symbol,T}}} where {S} + +@acset_type __BipartitePropertyGraph(SchBipartitePropertyGraph, index=[:src₁, :src₂, :tgt₁, :tgt₂]) <: __AbstractBipartitePropertyGraph + +const _BipartitePropertyGraph{T} = __BipartitePropertyGraph{Dict{Symbol,T}} + +""" Bipartite graph with properties. + +"Property graphs" are graphs with arbitrary named properties on the graph, +vertices, and edges. They are intended for applications with a large number of +ad-hoc properties. If you have a small number of known properties, it is better +and more efficient to create a specialized C-set type using `@acset_type`. +""" +struct BipartitePropertyGraph{T,G<:_AbstractBipartitePropertyGraph{T}} <: AbstractBipartitePropertyGraph{T} + graph::G + gprops::Dict{Symbol,T} +end + +BipartitePropertyGraph{T,G}(; kw...) where {T,G<:_AbstractBipartitePropertyGraph{T}} = BipartitePropertyGraph(G(), Dict{Symbol,T}(kw...)) +BipartitePropertyGraph{T}(; kw...) where T = BipartitePropertyGraph{T,_BipartitePropertyGraph{T}}(; kw...) + # Accessors and mutators ######################## @@ -211,6 +259,170 @@ function add_edges!(g::SymmetricPropertyGraph{T}, srcs::AbstractVector{Int}, inv=invs, eprops=eprops) end +# Bipartite property graphs + +# interface from BipartiteGraphs +@inline nv₁(g::AbstractBipartitePropertyGraph) = nv₁(g.graph) +@inline nv₂(g::AbstractBipartitePropertyGraph) = nv₂(g.graph) +@inline vertices₁(g::AbstractBipartitePropertyGraph) = vertices₁(g.graph) +@inline vertices₂(g::AbstractBipartitePropertyGraph) = vertices₂(g.graph) +@inline ne₁₂(g::AbstractBipartitePropertyGraph) = ne₁₂(g.graph) +@inline ne₁₂(g::AbstractBipartitePropertyGraph, src::Int, tgt::Int) = ne₁₂(g.graph, src, tgt) +@inline ne₂₁(g::AbstractBipartitePropertyGraph) = ne₂₁(g.graph) +@inline ne₂₁(g::AbstractBipartitePropertyGraph, src::Int, tgt::Int) = ne₂₁(g.graph, src, tgt) +@inline edges₁₂(g::AbstractBipartitePropertyGraph) = edges₁₂(g.graph) +@inline edges₁₂(g::AbstractBipartitePropertyGraph, src::Int, tgt::Int) = edges₁₂(g.graph, src, tgt) +@inline edges₂₁(g::AbstractBipartitePropertyGraph) = edges₂₁(g.graph) +@inline edges₂₁(g::AbstractBipartitePropertyGraph, src::Int, tgt::Int) = edges₂₁(g.graph, src, tgt) +@inline src₁(g::AbstractBipartitePropertyGraph, args...) = src₁(g.graph, args...) +@inline tgt₂(g::AbstractBipartitePropertyGraph, args...) = tgt₂(g.graph, args...) +@inline src₂(g::AbstractBipartitePropertyGraph, args...) = src₂(g.graph, args...) +@inline tgt₁(g::AbstractBipartitePropertyGraph, args...) = tgt₁(g.graph, args...) +@inline rem_vertex₁!(g::AbstractBipartitePropertyGraph, v::Int; kw...) = rem_vertex₁!(g.graph, v; kw...) +@inline rem_vertex₂!(g::AbstractBipartitePropertyGraph, v::Int; kw...) = rem_vertex₂!(g.graph, v; kw...) +@inline rem_vertices₁!(g::AbstractBipartitePropertyGraph, vs; keep_edges::Bool=false) = rem_vertices₁!(g.graph, vs; keep_edges) +@inline rem_vertices₂!(g::AbstractBipartitePropertyGraph, vs; keep_edges::Bool=false) = rem_vertices₂!(g.graph, vs; keep_edges) +@inline rem_edge₁₂!(g::AbstractBipartitePropertyGraph, e::Int) = rem_edge₁₂!(g.graph, e) +@inline rem_edge₁₂!(g::AbstractBipartitePropertyGraph, src::Int, tgt::Int) = rem_edge₁₂!(g.graph, src, tgt) +@inline rem_edge₂₁!(g::AbstractBipartitePropertyGraph, e::Int) = rem_edge₂₁!(g.graph, e) +@inline rem_edge₂₁!(g::AbstractBipartitePropertyGraph, src::Int, tgt::Int) = rem_edge₂₁!(g.graph, src, tgt) +@inline rem_edges₁₂!(g::AbstractBipartitePropertyGraph, es) = rem_edges₁₂!(g.graph, es) +@inline rem_edges₂₁!(g::AbstractBipartitePropertyGraph, es) = rem_edges₂₁!(g.graph, es) + +# interface from BasicGraphs +@inline nv(g::AbstractBipartitePropertyGraph) = nv(g.graph) +@inline vertices(g::AbstractBipartitePropertyGraph) = vertices(g.graph) +@inline ne(g::AbstractBipartitePropertyGraph) = ne(g.graph) +@inline edges(g::AbstractBipartitePropertyGraph) = edges(g.graph) + +add_vertex₁!(g::AbstractBipartitePropertyGraph{T}; kw...) where T = + add_vertex₁!(g, Dict{Symbol,T}(kw...)) +add_vertex₁!(g::AbstractBipartitePropertyGraph{T}, d::Dict{Symbol,T}) where T = + add_vertex₁!(g.graph, v₁props=d) + +add_vertex₂!(g::AbstractBipartitePropertyGraph{T}; kw...) where T = + add_vertex₂!(g, Dict{Symbol,T}(kw...)) +add_vertex₂!(g::AbstractBipartitePropertyGraph{T}, d::Dict{Symbol,T}) where T = + add_vertex₂!(g.graph, v₂props=d) + +add_vertices₁!(g::AbstractBipartitePropertyGraph{T}, n::Int; kw...) where T = + add_vertices₁!(g.graph, n, v₁props=[Dict{Symbol,T}(kw...) for _=1:n]) +add_vertices₂!(g::AbstractBipartitePropertyGraph{T}, n::Int; kw...) where T = + add_vertices₂!(g.graph, n, v₂props=[Dict{Symbol,T}(kw...) for _=1:n]) + +add_edge₁₂!(g::AbstractBipartitePropertyGraph{T}, src::Int, tgt::Int; kw...) where T = + add_edge₁₂!(g.graph, src, tgt, e₁₂props=Dict{Symbol,T}(kw...)) +add_edge₂₁!(g::AbstractBipartitePropertyGraph{T}, src::Int, tgt::Int; kw...) where T = + add_edge₂₁!(g.graph, src, tgt, e₂₁props=Dict{Symbol,T}(kw...)) + +add_edges₁₂!(g::AbstractBipartitePropertyGraph{T}, srcs::AbstractVector{Int}, + tgts::AbstractVector{Int}; kw...) where T = + add_edges₁₂!(g.graph, srcs, tgts, e₁₂props=[Dict{Symbol,T}(kw...) for _=1:length(srcs)]) + +add_edges₂₁!(g::AbstractBipartitePropertyGraph{T}, srcs::AbstractVector{Int}, + tgts::AbstractVector{Int}; kw...) where T = + add_edges₂₁!(g.graph, srcs, tgts, e₂₁props=[Dict{Symbol,T}(kw...) for _=1:length(srcs)]) + +# Property graph interface + +""" Graph-level properties of a bipartite property graph. +""" +gprops(g::AbstractBipartitePropertyGraph) = g.gprops + +""" Get graph-level property of a bipartite property graph. +""" +get_gprop(g::AbstractBipartitePropertyGraph, key::Symbol) = gprops(g)[key] + +""" Set graph-level property in a bipartite property graph. +""" +set_gprop!(g::AbstractBipartitePropertyGraph, key::Symbol, value) = + (gprops(g)[key] = value) + +""" Set multiple graph-level properties in a property graph. +""" +set_gprops!(g::AbstractBipartitePropertyGraph; kw...) = merge!(gprops(g), kw) +set_gprops!(g::AbstractBipartitePropertyGraph, d::AbstractDict) = merge!(gprops(g), d) + +""" Properties of vertex of type V₁ in a bipartite property graph. +""" +v₁props(g::AbstractBipartitePropertyGraph, v) = subpart(g.graph, v, :v₁props) + +""" Properties of vertex of type V₂ in a bipartite property graph. +""" +v₂props(g::AbstractBipartitePropertyGraph, v) = subpart(g.graph, v, :v₂props) + +""" Properties of edge from V₁ to V₂ in a bipartite property graph. +""" +e₁₂props(g::AbstractBipartitePropertyGraph, v) = subpart(g.graph, v, :e₁₂props) + +""" Properties of edge from V₂ to V₁ in a bipartite property graph. +""" +e₂₁props(g::AbstractBipartitePropertyGraph, v) = subpart(g.graph, v, :e₂₁props) + +""" Get property of vertex or vertices of type V₁ in a bipartite property graph. +""" +get_v₁prop(g::AbstractBipartitePropertyGraph, v, key::Symbol) = + broadcast(v) do v; v₁props(g,v)[key] end + +""" Get property of vertex or vertices of type V₂ in a bipartite property graph. +""" +get_v₂prop(g::AbstractBipartitePropertyGraph, v, key::Symbol) = + broadcast(v) do v; v₂props(g,v)[key] end + +""" Get property of edge or edges from V₁ to V₂ in a bipartite property graph. +""" +get_e₁₂prop(g::AbstractBipartitePropertyGraph, e, key::Symbol) = + broadcast(e) do e; e₁₂props(g,e)[key] end + +""" Get property of edge or edges from V₂ to V₁ in a bipartite property graph. +""" +get_e₂₁prop(g::AbstractBipartitePropertyGraph, e, key::Symbol) = + broadcast(e) do e; e₂₁props(g,e)[key] end + +""" Set property of vertex or vertices of type V₁ in a bipartite property graph. +""" +set_v₁prop!(g::AbstractBipartitePropertyGraph, v, key::Symbol, value) = + broadcast(v, value) do v, value; v₁props(g,v)[key] = value end + +""" Set property of vertex or vertices of type V₂ in a bipartite property graph. +""" +set_v₂prop!(g::AbstractBipartitePropertyGraph, v, key::Symbol, value) = + broadcast(v, value) do v, value; v₂props(g,v)[key] = value end + +""" Set property of edge or edges from V₁ to V₂ in a bipartite property graph. +""" +set_e₁₂prop!(g::AbstractBipartitePropertyGraph, e, key::Symbol, value) = + broadcast(e, value) do e, value; e₁₂props(g,e)[key] = value end + +""" Set property of edge or edges from V₂ to V₁ in a bipartite property graph. +""" +set_e₂₁prop!(g::AbstractBipartitePropertyGraph, e, key::Symbol, value) = + broadcast(e, value) do e, value; e₂₁props(g,e)[key] = value end + +""" Set multiple properties of a vertex of type V₁ in a bipartite property graph. +""" +set_v₁props!(g::AbstractBipartitePropertyGraph, v::Int; kw...) = merge!(v₁props(g,v), kw) +set_v₁props!(g::AbstractBipartitePropertyGraph, v::Int, d::AbstractDict) = + merge!(v₁props(g,v), d) + +""" Set multiple properties of a vertex of type V₂ in a bipartite property graph. +""" +set_v₂props!(g::AbstractBipartitePropertyGraph, v::Int; kw...) = merge!(v₂props(g,v), kw) +set_v₂props!(g::AbstractBipartitePropertyGraph, v::Int, d::AbstractDict) = + merge!(v₂props(g,v), d) + +""" Set multiple properties of an edge from V₁ to V₂ in a bipartite property graph. +""" +set_e₁₂props!(g::AbstractBipartitePropertyGraph, e::Int; kw...) = merge!(e₁₂props(g,e), kw) +set_e₁₂props!(g::AbstractBipartitePropertyGraph, e::Int, d::AbstractDict) = + merge!(e₁₂props(g,e), d) + +""" Set multiple properties of an edge from V₂ to V₁ in a bipartite property graph. +""" +set_e₂₁props!(g::AbstractBipartitePropertyGraph, e::Int; kw...) = merge!(e₂₁props(g,e), kw) +set_e₂₁props!(g::AbstractBipartitePropertyGraph, e::Int, d::AbstractDict) = + merge!(e₂₁props(g,e), d) + # Constructors from graphs ########################## @@ -250,4 +462,50 @@ end SymmetricPropertyGraph{T}(g::HasGraph; gprops...) where T = SymmetricPropertyGraph{T}(g, v->Dict(), e->Dict(); gprops...) +function BipartitePropertyGraph{T}(g::HasBipartiteVertices, make_v₁props, make_v₂props, + make_e₁₂props; gprops...) where {T} + pg = BipartitePropertyGraph{T}(; gprops...) + add_vertices₁!(pg, nv₁(g)) + add_vertices₂!(pg, nv₂(g)) + add_edges₁₂!(pg, src(g), tgt(g)) + for v in vertices₁(g) + set_v₁props!(pg, v, make_v₁props(v)) + end + for v in vertices₂(g) + set_v₂props!(pg, v, make_v₂props(v)) + end + for e in edges(g) + set_e₁₂props!(pg, e, make_e₁₂props(e)) + end + pg +end + +BipartitePropertyGraph{T}(g::HasBipartiteVertices; gprops...) where T = + BipartitePropertyGraph{T}(g, v₁->Dict(), v₂->Dict(), e₁₂->Dict(); gprops...) + +function BipartitePropertyGraph{T}(g::HasBipartiteGraph, make_v₁props, make_v₂props, + make_e₁₂props, make_e₂₁props; gprops...) where {T} + pg = BipartitePropertyGraph{T}(; gprops...) + add_vertices₁!(pg, nv₁(g)) + add_vertices₂!(pg, nv₂(g)) + add_edges₁₂!(pg, src₁(g), tgt₂(g)) + add_edges₂₁!(pg, src₂(g), tgt₁(g)) + for v in vertices₁(g) + set_v₁props!(pg, v, make_v₁props(v)) + end + for v in vertices₂(g) + set_v₂props!(pg, v, make_v₂props(v)) + end + for e in edges₁₂(g) + set_e₁₂props!(pg, e, make_e₁₂props(e)) + end + for e in edges₂₁(g) + set_e₂₁props!(pg, e, make_e₂₁props(e)) + end + pg +end + +BipartitePropertyGraph{T}(g::HasBipartiteGraph; gprops...) where T = + BipartitePropertyGraph{T}(g, v₁->Dict(), v₂->Dict(), e₁₂->Dict(), e₂₁->Dict(); gprops...) + end diff --git a/test/graphics/GraphvizGraphs.jl b/test/graphics/GraphvizGraphs.jl index 109668193..6807f6e04 100644 --- a/test/graphics/GraphvizGraphs.jl +++ b/test/graphics/GraphvizGraphs.jl @@ -234,4 +234,21 @@ gv = to_graphviz(f, draw_codom=true) @test gv.directed @test length(stmts(gv, Graphviz.Subgraph)) == 2 +# Functions between finite sets +############################### + +A = FinSet(4) +B = FinSet(3) +f = FinFunction([1,3,2,2], A, B) + +fv = to_graphviz(f, graph_attrs=Dict(:splines=>"false")) +@test length(stmts(fv, Graphviz.Subgraph)) == 2 +fv1, fv2 = stmts(fv, Graphviz.Subgraph) + +@test length(stmts(fv1, Graphviz.Edge)) == length(A) - 1 +@test length(stmts(fv2, Graphviz.Edge)) == length(B) - 1 +@test length(stmts(fv, Graphviz.Edge)) == 4 +@test length(stmts(fv1, Graphviz.Node)) == length(A) +@test length(stmts(fv2, Graphviz.Node)) == length(B) + end diff --git a/test/graphs/PropertyGraphs.jl b/test/graphs/PropertyGraphs.jl index 235e05d40..7a171b5fe 100644 --- a/test/graphs/PropertyGraphs.jl +++ b/test/graphs/PropertyGraphs.jl @@ -1,7 +1,7 @@ module TestPropertyGraphs using Test -using Catlab.Graphs.BasicGraphs, Catlab.Graphs.PropertyGraphs +using Catlab.Graphs.BasicGraphs, Catlab.Graphs.PropertyGraphs, Catlab.Graphs.BipartiteGraphs # Property graphs ################# @@ -47,4 +47,112 @@ add_edge!(g, 1, 2, c="car") @test eprops(g, 1) == Dict(:c => "car") @test eprops(g, 1) === eprops(g, 2) +# Bipartite property graphs +########################### + +bg = BipartitePropertyGraph{String}() +add_vertex₁!(bg, a="alphonse", b="elric") +add_vertices₁!(bg, 1, a="ed", b="elric") +add_vertices₁!(bg, 2, f="rei", l="ayanami") +add_vertex₂!(bg, a="trisha", b="elric") +add_vertex₂!(bg, a="rurouni", b="kenshin") +add_vertices₂!(bg, 1, a="van", b="hohenheim") + +@test_throws Exception add_edges₁₂!(bg, [1,1], [1,3,5], rel="childof") +add_edges₁₂!(bg, [1,1], [1,3], rel="childof") +@test_throws Exception add_edges₂₁!(bg, [1,3], [1,1,5], rel="parentof") +add_edges₂₁!(bg, [1,3], [1,1], rel="parentof") +add_edge₂₁!(bg, 3, 2, rel="parentof") + +@test nv₁(bg) == 4 +@test nv₂(bg) == 3 +@test nv(bg) == (4,3) +@test vertices₁(bg) == 1:4 +@test vertices₂(bg) == 1:3 +@test vertices(bg) == (vertices₁(bg), vertices₂(bg)) +@test ne(bg) == (2,3) +@test edges(bg) == (1:2, 1:3) + +e = add_edge₁₂!(bg, 1, 2, a="mistake") +rem_edge₁₂!(bg, e) +@test e ∉ edges₁₂(bg) +e = add_edges₁₂!(bg, [1,1], [2,2], a="mistake") +rem_edges₁₂!(bg, e) +@test e ∉ edges₁₂(bg) + +e = add_edge₂₁!(bg, 1, 2, a="mistake") +rem_edge₂₁!(bg, e) +@test e ∉ edges₂₁(bg) +e = add_edges₂₁!(bg, [1,1], [2,2], a="mistake") +rem_edges₂₁!(bg, e) +@test e ∉ edges₂₁(bg) +@test edges(bg) == (1:2, 1:3) + +@test gprops(bg) isa Dict +@test v₁props(bg, 1) == Dict(:a=>"alphonse", :b=>"elric") +@test v₂props(bg, 1) == Dict(:a=>"trisha", :b=>"elric") +@test e₁₂props(bg, 1) == Dict(:rel=>"childof") +@test e₂₁props(bg, 1) == Dict(:rel=>"parentof") +@test get_v₁prop(bg, 1, :a) == "alphonse" +@test get_v₂prop(bg, 1, :a) == "trisha" +@test get_e₁₂prop(bg, 1, :rel) == "childof" +@test get_e₂₁prop(bg, 1, :rel) == "parentof" + +set_v₁prop!(bg, 4, :f, "rei1") +@test get_v₁prop(bg, 4, :f) == "rei1" + +set_v₂prop!(bg, 2, :a, "himura") +@test get_v₂prop(bg, 2, :a) == "himura" + +set_e₁₂prop!(bg, 1, :rel, "childof1") +@test get_e₁₂prop(bg, 1, :rel) == "childof1" + +set_e₂₁prop!(bg, 1, :rel, "parentof1") +@test get_e₂₁prop(bg, 1, :rel) == "parentof1" + +set_v₁prop!(bg, 3:4, :f, "rei") +get_v₁prop(bg, 3:4, :f) == ["rei", "rei"] + +set_v₂prop!(bg, 2, :a, "kenshin") +get_v₂prop(bg, 2, :a) == "kenshin" + +set_e₁₂props!(bg, 1, rel="childof") +@test get_e₁₂prop(bg, 1:2, :rel) == ["childof", "childof"] + +set_e₂₁props!(bg, 1, rel="parentof") +@test get_e₂₁prop(bg, 1:3, :rel) == ["parentof", "parentof", "parentof"] + +# constructors from graphs +g = BipartiteGraph(2, 3) +add_edge₁₂!(g, 1, 2) +add_edge₂₁!(g, 1, 1) +add_edges₁₂!(g, [2,2], [3,3]) +add_edges₂₁!(g, [2,3], [1,1]) + +pg = BipartitePropertyGraph{String}(g, a="test") +@test gprops(pg) == Dict{Symbol,String}(:a=>"test") + +@test nv(pg) == (2, 3) +@test (ne₁₂(pg), ne₂₁(pg)) == (3,3) +@test (edges₁₂(pg), edges₂₁(pg)) == (1:3, 1:3) +@test ne(pg) == (3,3) +@test edges(pg) == (1:3, 1:3) + +g = UndirectedBipartiteGraph() +add_vertices₁!(g, 2) +add_vertices₂!(g, 3) +add_edge!(g, 1, 1) +add_edges!(g, [2,2], [2,3]) + +pg = BipartitePropertyGraph{String}(g, a="test") +@test gprops(pg) == Dict{Symbol,String}(:a=>"test") + +@test (nv₁(pg), nv₂(pg)) == (2,3) +@test (vertices₁(pg), vertices₂(pg)) == (1:2, 1:3) +@test nv(pg) == (2,3) +@test vertices(pg) == (1:2, 1:3) +@test ne(pg) == (3,0) +@test edges₁₂(pg) == (1:3) +@test (src₁(pg), tgt₂(pg)) == ([1,2,2], [1,2,3]) + end