From f5891a76c8320a656af2a6ca7aa5df97e0537b96 Mon Sep 17 00:00:00 2001 From: Emad Jacob Maroun Date: Sun, 10 Nov 2024 19:27:23 +0100 Subject: [PATCH] Added methods to 'Connected' calculating eccentricity, radius, diamter, and centers. --- src/algo/dijkstra_shortest_paths.rs | 30 ++++++-- src/core/edge.rs | 2 +- src/core/graph.rs | 2 +- src/core/property/connected.rs | 103 +++++++++++++++++++++++++-- src/core/property/has_vertex.rs | 2 +- src/core/property/impl_ensurer.rs | 38 ++++++++++ tests/core/property/connectedness.rs | 61 ++++++++++++++-- 7 files changed, 221 insertions(+), 17 deletions(-) diff --git a/src/algo/dijkstra_shortest_paths.rs b/src/algo/dijkstra_shortest_paths.rs index e1d939c..d832f1a 100644 --- a/src/algo/dijkstra_shortest_paths.rs +++ b/src/algo/dijkstra_shortest_paths.rs @@ -9,9 +9,9 @@ where { graph: &'a G, visited: Vec, - // We keep is sorted with the lowest weight at the end for efficiency. + // We keep it sorted with the lowest weight at the end for efficiency. queue: Vec<(W, (G::Vertex, G::Vertex, &'a G::EdgeWeight))>, - get_weight: fn(&G::EdgeWeight) -> W, + get_distance: fn(&G::EdgeWeight) -> W, } impl<'a, G, W> DijkstraShortestPaths<'a, G, W> @@ -19,7 +19,7 @@ where G: 'a + Graph, W: PrimInt + Unsigned, { - pub fn new(graph: &'a G, get_weight: fn(&G::EdgeWeight) -> W) -> Self + pub fn new(graph: &'a G, get_distance: fn(&G::EdgeWeight) -> W) -> Self where G: HasVertex, { @@ -27,7 +27,7 @@ where graph, visited: Vec::new(), queue: Vec::new(), - get_weight, + get_distance, }; dijk.visit(graph.get_vertex(), W::zero()); dijk @@ -43,7 +43,7 @@ where for (sink, weight) in edges { - let new_weight = w + (self.get_weight)(weight); + let new_weight = w + (self.get_distance)(weight); if let Some((old_weight, old_edge)) = self .queue .iter_mut() @@ -62,6 +62,26 @@ where } self.queue.sort_by(|(w1, _), (w2, _)| w2.cmp(w1)); } + + /// Returns the vertices reachable from the designated vertex and the + /// weighted distance to them + pub fn distances( + graph: &'a G, + get_distance: fn(&G::EdgeWeight) -> W, + ) -> impl 'a + Iterator + where + G: HasVertex, + W: 'a, + { + let mut distances = vec![(graph.get_vertex(), W::zero())]; + + DijkstraShortestPaths::new(graph, get_distance).map(move |(so, si, w)| { + let dist = distances.iter().find(|(v, _)| so == *v).unwrap().1; + let new_dist = dist + get_distance(w); + distances.push((si, new_dist)); + (si, new_dist) + }) + } } impl<'a, G> DijkstraShortestPaths<'a, G, G::EdgeWeight> diff --git a/src/core/edge.rs b/src/core/edge.rs index d5e6554..dedbb89 100644 --- a/src/core/edge.rs +++ b/src/core/edge.rs @@ -5,7 +5,7 @@ /// For undirected graphs, which vertex is which has no meaning. /// For directed graphs, an edge points from the `source` to the `sink`. /// -/// This trait has a blanket implementation implementation for any pair `(V,V)` +/// This trait has a blanket implementation for any pair `(V,V)` /// or triple `(V,V,W)`. Therefore, the easiest way to create an edge is to /// simply use a pair. The triple can be used if the edge is weighted pub trait Edge diff --git a/src/core/graph.rs b/src/core/graph.rs index f95fc60..39293af 100644 --- a/src/core/graph.rs +++ b/src/core/graph.rs @@ -51,7 +51,7 @@ pub trait Graph { /// Type of the graphs vertices. /// - /// This type should be lightweight, as its passed around by-value + /// This type should be lightweight, as it's passed around by-value /// (therefore must implement [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html)). /// Whether two vertices are equal is also a very common operation and /// should therefore also be light-weight. diff --git a/src/core/property/connected.rs b/src/core/property/connected.rs index 4266e86..b61152c 100644 --- a/src/core/property/connected.rs +++ b/src/core/property/connected.rs @@ -1,21 +1,116 @@ use crate::{ - algo::Dfs, + algo::{Dfs, DijkstraShortestPaths}, core::{ property::{ - proxy_remove_edge_where_weight, proxy_remove_vertex, DirectedGraph, HasVertexGraph, - RemoveEdge, RemoveVertex, Unilateral, Weak, + proxy_remove_edge_where_weight, proxy_remove_vertex, DirectedGraph, EdgeCount, + HasVertex, HasVertexGraph, RemoveEdge, RemoveVertex, Unilateral, VertexInGraph, Weak, }, proxy::ReverseGraph, Ensure, Graph, GraphDerefMut, }, }; +use num_traits::{PrimInt, Unsigned}; use std::borrow::Borrow; /// A marker trait for graphs that are connected. /// /// A graph is connected if there is a path from any vertex to any other vertex. /// Graphs with one or zero vertices count as connected. -pub trait Connected: Unilateral {} +pub trait Connected: Unilateral +{ + /// Calculates the maximum distance between the designated vertex and any other vertex ([the eccentricity](https://mathworld.wolfram.com/GraphEccentricity.html)). + /// + /// Takes a closure that converts an edge's weight into a distance value. + /// The distance between two vertices is equal to the distance of the + /// edge(s) between them. + fn eccentricity_weighted( + &self, + get_distance: fn(&Self::EdgeWeight) -> W, + ) -> W + where + Self: EdgeCount + HasVertex + Sized, + { + // We search for all the shortest paths, the eccentricity is the longest one + DijkstraShortestPaths::distances(self, get_distance).fold(W::zero(), |max_dist, (_, d2)| { + if max_dist < d2 + { + d2 + } + else + { + max_dist + } + }) + } + + /// Calculates the maximum eccentricity of the graph ([the diameter](https://mathworld.wolfram.com/GraphDiameter.html)). + /// + /// Takes a closure that converts an edge's weight into a distance value. + /// The distance between two vertices is equal to the distance of the + /// edge(s) between them. + fn diameter_weighted( + &self, + get_distance: fn(&Self::EdgeWeight) -> W, + ) -> W + where + Self: EdgeCount + Sized, + { + self.all_vertices().fold(W::zero(), |max_ecc, v| { + let new_ecc = + VertexInGraph::ensure_unvalidated(self, v).eccentricity_weighted(get_distance); + if new_ecc > max_ecc + { + new_ecc + } + else + { + max_ecc + } + }) + } + + /// Calculates the minimum eccentricity of the graph ([the radius](https://mathworld.wolfram.com/GraphDiameter.html)). + /// + /// Takes a closure that converts an edge's weight into a distance value. + /// The distance between two vertices is equal to the distance of the + /// edge(s) between them. + fn radius_weighted(&self, get_distance: fn(&Self::EdgeWeight) -> W) -> W + where + Self: EdgeCount + Sized, + { + self.all_vertices().fold(W::zero(), |min_ecc, v| { + let new_ecc = + VertexInGraph::ensure_unvalidated(self, v).eccentricity_weighted(get_distance); + if new_ecc < min_ecc + { + new_ecc + } + else + { + min_ecc + } + }) + } + + /// Returns the vertices with eccentricity equal to the radius ([the centers](https://mathworld.wolfram.com/GraphCenter.html)). + /// + /// Takes a closure that converts an edge's weight into a distance value. + /// The distance between two vertices is equal to the distance of the + /// edge(s) between them. + fn centers_weighted<'a, W: 'a + PrimInt + Unsigned>( + &'a self, + get_distance: fn(&Self::EdgeWeight) -> W, + ) -> impl Iterator + '_ + where + Self: EdgeCount + Sized, + { + let radius = self.radius_weighted(get_distance); + self.all_vertices().filter(move |v| { + VertexInGraph::ensure_unvalidated(self, *v).eccentricity_weighted(get_distance) + == radius + }) + } +} #[derive(Clone, Debug)] pub struct ConnectedGraph(C); diff --git a/src/core/property/has_vertex.rs b/src/core/property/has_vertex.rs index 92858a0..b9b3511 100644 --- a/src/core/property/has_vertex.rs +++ b/src/core/property/has_vertex.rs @@ -22,7 +22,7 @@ pub trait HasVertex: Graph /// Ensures the underlying graph has at least 1 vertex. /// /// Gives no guarantees on which vertex is returned by any given call to -/// `get_vertex` if the the graph has multiple vertices. +/// `get_vertex` if the graph has multiple vertices. #[derive(Clone)] pub struct HasVertexGraph(C); diff --git a/src/core/property/impl_ensurer.rs b/src/core/property/impl_ensurer.rs index dd73682..7250895 100644 --- a/src/core/property/impl_ensurer.rs +++ b/src/core/property/impl_ensurer.rs @@ -395,6 +395,44 @@ macro_rules! impl_properties { } } + // VertexCount + $crate::impl_properties!{ + @struct [ $struct ] + @generic [ $($generics)* ] + @delegate [ $delegate_type ] + $(@exclude [ $($exclude_props)* ])? + $(@include [ $($include_props)* ])? + @bounds [ + <$delegate_type as $crate::core::GraphDeref>::Graph: + $crate::core::property::VertexCount, + $($bounds)* + ] + @trait_id VertexCount [$crate::core::property] + @implement { + type Count = <<$delegate_type as $crate::core::GraphDeref>::Graph + as $crate::core::property::VertexCount>::Count; + } + } + + // EdgeCount + $crate::impl_properties!{ + @struct [ $struct ] + @generic [ $($generics)* ] + @delegate [ $delegate_type ] + $(@exclude [ $($exclude_props)* ])? + $(@include [ $($include_props)* ])? + @bounds [ + <$delegate_type as $crate::core::GraphDeref>::Graph: + $crate::core::property::EdgeCount, + $($bounds)* + ] + @trait_id EdgeCount [$crate::core::property] + @implement { + type Count = <<$delegate_type as $crate::core::GraphDeref>::Graph + as $crate::core::property::EdgeCount>::Count; + } + } + // Unique $crate::impl_properties!{ @struct [ $struct ] diff --git a/tests/core/property/connectedness.rs b/tests/core/property/connectedness.rs index a3d4380..8526bde 100644 --- a/tests/core/property/connectedness.rs +++ b/tests/core/property/connectedness.rs @@ -5,12 +5,15 @@ use crate::mock_graph::{ MockDirectedness, MockEdgeWeight, MockGraph, MockVertexWeight, }; use duplicate::duplicate_item; -use graphene::core::{ - property::{ - AddEdge, Connected, ConnectedGraph, HasVertex, NewVertex, RemoveEdge, RemoveVertex, - Unilateral, UnilateralGraph, VertexInGraph, Weak, WeakGraph, +use graphene::{ + algo::DijkstraShortestPaths, + core::{ + property::{ + AddEdge, Connected, ConnectedGraph, HasVertex, NewVertex, RemoveEdge, RemoveVertex, + Unilateral, UnilateralGraph, VertexInGraph, Weak, WeakGraph, + }, + Directed, Graph, Undirected, }, - Directed, EnsureUnloaded, ReleaseUnloaded, Undirected, }; use static_assertions::assert_impl_all; @@ -47,6 +50,7 @@ use static_assertions::assert_impl_all; mod module { use super::*; + use graphene::core::{EnsureUnloaded, ReleaseUnloaded}; /// Tests that the graph correctly identifies graphs with its connectedness. #[quickcheck] @@ -227,6 +231,53 @@ mod module } } +#[duplicate_item(directedness; [Directed]; [Undirected])] +mod __ +{ + use super::*; + use graphene::core::Ensure; + + /// Tests `eccentricity_weighted` + #[quickcheck] + fn eccentricity_weighted( + Arb(g): Arb>>>, + ) -> bool + { + let eccentricity = g.eccentricity_weighted(|w| w.value); + DijkstraShortestPaths::distances(&g, |w| w.value).all(|(_, dist)| dist <= eccentricity) + } + + /// Tests `diameter_weighted` + #[quickcheck] + fn diameter_weighted(Arb(g): Arb>>) -> bool + { + let diameter = g.diameter_weighted(|w| w.value); + g.all_vertices().all(|v| { + VertexInGraph::ensure_unvalidated(&g, v).eccentricity_weighted(|w| w.value) <= diameter + }) + } + + /// Tests `radius_weighted` + #[quickcheck] + fn radius_weighted(Arb(g): Arb>>) -> bool + { + let radius = g.radius_weighted(|w| w.value); + g.all_vertices().all(|v| { + VertexInGraph::ensure_unvalidated(&g, v).eccentricity_weighted(|w| w.value) >= radius + }) + } + + /// Tests `centers_weighted` + #[quickcheck] + fn centers_weighted(Arb(g): Arb>>) -> bool + { + let radius = g.radius_weighted(|w| w.value); + g.centers_weighted(|w| w.value).all(|v| { + VertexInGraph::ensure_unvalidated(&g, v).eccentricity_weighted(|w| w.value) == radius + }) + } +} + // Test that all Connected graphs are also unilateral and weak. assert_impl_all!(ConnectedGraph>: Connected, Unilateral, Weak);