Skip to content

Commit

Permalink
Added methods to 'Connected' calculating eccentricity, radius, diamte…
Browse files Browse the repository at this point in the history
…r, and centers.
  • Loading branch information
Emoun committed Nov 10, 2024
1 parent 4e2adbe commit f5891a7
Show file tree
Hide file tree
Showing 7 changed files with 221 additions and 17 deletions.
30 changes: 25 additions & 5 deletions src/algo/dijkstra_shortest_paths.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,25 @@ where
{
graph: &'a G,
visited: Vec<G::Vertex>,
// 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>
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,
{
let mut dijk = Self {
graph,
visited: Vec::new(),
queue: Vec::new(),
get_weight,
get_distance,
};
dijk.visit(graph.get_vertex(), W::zero());
dijk
Expand All @@ -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()
Expand All @@ -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<Item = (G::Vertex, W)>
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>
Expand Down
2 changes: 1 addition & 1 deletion src/core/edge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<V>
Expand Down
2 changes: 1 addition & 1 deletion src/core/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
103 changes: 99 additions & 4 deletions src/core/property/connected.rs
Original file line number Diff line number Diff line change
@@ -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<W: PrimInt + Unsigned>(
&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<W: PrimInt + Unsigned>(
&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<W: PrimInt + Unsigned>(&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<Item = Self::Vertex> + '_
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: Ensure>(C);
Expand Down
2 changes: 1 addition & 1 deletion src/core/property/has_vertex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: Ensure>(C);

Expand Down
38 changes: 38 additions & 0 deletions src/core/property/impl_ensurer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
Expand Down
61 changes: 56 additions & 5 deletions tests/core/property/connectedness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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<VertexInGraph<ConnectedGraph<MockGraph<directedness>>>>,
) -> 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<ConnectedGraph<MockGraph<directedness>>>) -> 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<ConnectedGraph<MockGraph<directedness>>>) -> 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<ConnectedGraph<MockGraph<directedness>>>) -> 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<MockGraph<MockDirectedness>>: Connected, Unilateral, Weak);

Expand Down

0 comments on commit f5891a7

Please sign in to comment.