diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 433afe88..088b9cc7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,6 @@ jobs: matrix: version: - '1' - - '1.10' - 'pre' os: - ubuntu-latest diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b864fc7..ea114747 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,6 +70,8 @@ This gives users the option of reusing memory and improving performance. - If the algorithm was presented in a paper, include a reference to the paper (_e.g._, a proper academic citation along with an eprint link). +- When implementing a new graph invariant, just add a method to `graph_property`, instead of exporting a new function. If necessary, also add the property to GraphProperties.jl. + ## Git(Hub) usage ### Getting started on a package contribution diff --git a/Project.toml b/Project.toml index a5a3112e..e92736b8 100644 --- a/Project.toml +++ b/Project.toml @@ -6,6 +6,7 @@ version = "1.13.1" ArnoldiMethod = "ec485272-7323-5ecc-a04f-4719b315124d" DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" +GraphProperties = "bbdf290f-6e15-4dc1-adeb-6e1d1446781e" Inflate = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" Random = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" @@ -18,6 +19,7 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2" ArnoldiMethod = "0.4" Distributed = "1" DataStructures = "0.18, 0.19" +GraphProperties = "1" Inflate = "0.1.3" LinearAlgebra = "1" Random = "1" @@ -25,4 +27,7 @@ SharedArrays = "1" SimpleTraits = "0.9.1" SparseArrays = "1" Statistics = "1" -julia = "1.10" \ No newline at end of file +julia = "1.10" + +[sources] +GraphProperties = {url = "https://github.com/JuliaGraphs/GraphProperties.jl"} diff --git a/src/Graphs.jl b/src/Graphs.jl index a40a7816..91c8e886 100644 --- a/src/Graphs.jl +++ b/src/Graphs.jl @@ -69,6 +69,7 @@ import Base: Pair, Tuple, zero +using GraphProperties: GraphProperties, GraphProperty export # Interface @@ -434,6 +435,9 @@ export # vertexcover vertex_cover, + # graphproperties + graph_property, + # longestpaths dag_longest_path @@ -548,6 +552,7 @@ include("biconnectivity/biconnect.jl") include("biconnectivity/bridge.jl") include("graphcut/normalized_cut.jl") include("graphcut/karger_min_cut.jl") +include("graphproperties.jl") include("dominatingset/degree_dom_set.jl") include("dominatingset/minimal_dom_set.jl") include("independentset/degree_ind_set.jl") diff --git a/src/graphproperties.jl b/src/graphproperties.jl new file mode 100644 index 00000000..6a887c93 --- /dev/null +++ b/src/graphproperties.jl @@ -0,0 +1,71 @@ +""" + graph_property(graph::AbstractGraph, property_specification::GraphProperty{T}, [options = nothing])::Union{Nothing,Some{<:T}} + +Get the graph property specified by `property_specification` of the graph `graph`. + +Only some properties are implemented currently. + +A `nothing` return value may be returned in some cases, such as when a time limit specified in `options` was reached. + +Third-party packages may add methods. Only add three-argument methods, and only if you own the third argument, `options`. +""" +function graph_property end + +function graph_property(graph::AbstractGraph, prop_spec::GraphProperty) + return graph_property(graph, prop_spec, nothing) +end + +function graph_property(graph::AbstractGraph, ::GraphProperties.NumberOfVertices, ::Nothing) + return Some(nv(graph)) +end + +function graph_property(graph::AbstractGraph, ::GraphProperties.DegreeSequence, ::Nothing) + if is_directed(graph) + throw(ArgumentError("expected undirected graph")) + end + return Some(sort(degree(graph))) +end + +function graph_property(graph::AbstractGraph, ::GraphProperties.NumberOfEdges, ::Nothing) + if is_directed(graph) + throw(ArgumentError("expected undirected graph")) + end + return Some(ne(graph)) +end + +function graph_property(graph::AbstractGraph, ::GraphProperties.NumberOfArcs, ::Nothing) + if !is_directed(graph) + throw(ArgumentError("expected directed graph")) + end + return Some(ne(graph)) +end + +function graph_property( + graph::AbstractGraph, ::GraphProperties.NumberOfConnectedComponents, ::Nothing +) + if is_directed(graph) + throw(ArgumentError("expected undirected graph")) + end + # TODO: performance: avoid allocating the components + return Some(length(connected_components(graph))) +end + +function graph_property( + graph::AbstractGraph, ::GraphProperties.NumberOfWeaklyConnectedComponents, ::Nothing +) + if !is_directed(graph) + throw(ArgumentError("expected directed graph")) + end + # TODO: performance: avoid allocating the components + return Some(length(weakly_connected_components(graph))) +end + +function graph_property( + graph::AbstractGraph, ::GraphProperties.NumberOfStronglyConnectedComponents, ::Nothing +) + if !is_directed(graph) + throw(ArgumentError("expected directed graph")) + end + # TODO: performance: avoid allocating the components + return Some(length(strongly_connected_components(graph))) +end diff --git a/test/Project.toml b/test/Project.toml index d15e5d8d..4cd9a870 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -6,6 +6,7 @@ DataStructures = "864edb3b-99cc-5e75-8d2d-829cb0a9cfe8" DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b" Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +GraphProperties = "bbdf290f-6e15-4dc1-adeb-6e1d1446781e" Inflate = "d25df0c9-e2be-5dd7-82c8-3ad0b3e990b9" JET = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" JuliaFormatter = "98e50ef6-434e-11e9-1051-2b60c6c9e899" @@ -21,4 +22,7 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d" [compat] -JuliaFormatter = "1" \ No newline at end of file +JuliaFormatter = "1" + +[sources] +GraphProperties = {url = "https://github.com/JuliaGraphs/GraphProperties.jl"} diff --git a/test/graphproperties.jl b/test/graphproperties.jl new file mode 100644 index 00000000..3d7b18af --- /dev/null +++ b/test/graphproperties.jl @@ -0,0 +1,41 @@ +@testset "graph properties" begin + undirected_graph = ladder_graph(5) + directed_graph = wheel_digraph(5) + @testset "properties without parameter" begin + properties_both = (GraphProperties.NumberOfVertices,) + properties_undirected_only = ( + GraphProperties.DegreeSequence, + GraphProperties.NumberOfEdges, + GraphProperties.NumberOfConnectedComponents, + ) + properties_directed_only = ( + GraphProperties.NumberOfArcs, + GraphProperties.NumberOfWeaklyConnectedComponents, + GraphProperties.NumberOfStronglyConnectedComponents, + ) + properties = ( + properties_both..., properties_undirected_only..., properties_directed_only... + ) + for options in ((), (nothing,)) + for property in properties + local graphs + if property in properties_both + graphs = (undirected_graph, directed_graph) + elseif property in properties_undirected_only + graphs = (undirected_graph,) + elseif property in properties_directed_only + graphs = (directed_graph,) + end + invalid_inputs = setdiff((undirected_graph, directed_graph), graphs) + for graph in graphs + @test ((@inferred graph_property(graph, property(), options...)); true) + @test something(graph_property(graph, property(), options...)) isa + graph_property_type(property()) + end + for graph in invalid_inputs + @test_throws ArgumentError graph_property(graph, property(), options...) + end + end + end + end +end diff --git a/test/runtests.jl b/test/runtests.jl index 34bd1c53..7bf714b6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -16,6 +16,7 @@ using Statistics: mean, std using StableRNGs using Pkg using Unitful +using GraphProperties: GraphProperties, graph_property_type const testdir = dirname(@__FILE__) const KMf = typeof(u"1.0km") @@ -80,6 +81,7 @@ tests = [ "interface", "core", "operators", + "graphproperties", "degeneracy", "distance", "digraph/transitivity",