Skip to content

Commit f5891a7

Browse files
committed
Added methods to 'Connected' calculating eccentricity, radius, diamter, and centers.
1 parent 4e2adbe commit f5891a7

File tree

7 files changed

+221
-17
lines changed

7 files changed

+221
-17
lines changed

src/algo/dijkstra_shortest_paths.rs

+25-5
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@ where
99
{
1010
graph: &'a G,
1111
visited: Vec<G::Vertex>,
12-
// We keep is sorted with the lowest weight at the end for efficiency.
12+
// We keep it sorted with the lowest weight at the end for efficiency.
1313
queue: Vec<(W, (G::Vertex, G::Vertex, &'a G::EdgeWeight))>,
14-
get_weight: fn(&G::EdgeWeight) -> W,
14+
get_distance: fn(&G::EdgeWeight) -> W,
1515
}
1616

1717
impl<'a, G, W> DijkstraShortestPaths<'a, G, W>
1818
where
1919
G: 'a + Graph,
2020
W: PrimInt + Unsigned,
2121
{
22-
pub fn new(graph: &'a G, get_weight: fn(&G::EdgeWeight) -> W) -> Self
22+
pub fn new(graph: &'a G, get_distance: fn(&G::EdgeWeight) -> W) -> Self
2323
where
2424
G: HasVertex,
2525
{
2626
let mut dijk = Self {
2727
graph,
2828
visited: Vec::new(),
2929
queue: Vec::new(),
30-
get_weight,
30+
get_distance,
3131
};
3232
dijk.visit(graph.get_vertex(), W::zero());
3333
dijk
@@ -43,7 +43,7 @@ where
4343

4444
for (sink, weight) in edges
4545
{
46-
let new_weight = w + (self.get_weight)(weight);
46+
let new_weight = w + (self.get_distance)(weight);
4747
if let Some((old_weight, old_edge)) = self
4848
.queue
4949
.iter_mut()
@@ -62,6 +62,26 @@ where
6262
}
6363
self.queue.sort_by(|(w1, _), (w2, _)| w2.cmp(w1));
6464
}
65+
66+
/// Returns the vertices reachable from the designated vertex and the
67+
/// weighted distance to them
68+
pub fn distances(
69+
graph: &'a G,
70+
get_distance: fn(&G::EdgeWeight) -> W,
71+
) -> impl 'a + Iterator<Item = (G::Vertex, W)>
72+
where
73+
G: HasVertex,
74+
W: 'a,
75+
{
76+
let mut distances = vec![(graph.get_vertex(), W::zero())];
77+
78+
DijkstraShortestPaths::new(graph, get_distance).map(move |(so, si, w)| {
79+
let dist = distances.iter().find(|(v, _)| so == *v).unwrap().1;
80+
let new_dist = dist + get_distance(w);
81+
distances.push((si, new_dist));
82+
(si, new_dist)
83+
})
84+
}
6585
}
6686

6787
impl<'a, G> DijkstraShortestPaths<'a, G, G::EdgeWeight>

src/core/edge.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
/// For undirected graphs, which vertex is which has no meaning.
66
/// For directed graphs, an edge points from the `source` to the `sink`.
77
///
8-
/// This trait has a blanket implementation implementation for any pair `(V,V)`
8+
/// This trait has a blanket implementation for any pair `(V,V)`
99
/// or triple `(V,V,W)`. Therefore, the easiest way to create an edge is to
1010
/// simply use a pair. The triple can be used if the edge is weighted
1111
pub trait Edge<V>

src/core/graph.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ pub trait Graph
5151
{
5252
/// Type of the graphs vertices.
5353
///
54-
/// This type should be lightweight, as its passed around by-value
54+
/// This type should be lightweight, as it's passed around by-value
5555
/// (therefore must implement [`Copy`](https://doc.rust-lang.org/std/marker/trait.Copy.html)).
5656
/// Whether two vertices are equal is also a very common operation and
5757
/// should therefore also be light-weight.

src/core/property/connected.rs

+99-4
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,116 @@
11
use crate::{
2-
algo::Dfs,
2+
algo::{Dfs, DijkstraShortestPaths},
33
core::{
44
property::{
5-
proxy_remove_edge_where_weight, proxy_remove_vertex, DirectedGraph, HasVertexGraph,
6-
RemoveEdge, RemoveVertex, Unilateral, Weak,
5+
proxy_remove_edge_where_weight, proxy_remove_vertex, DirectedGraph, EdgeCount,
6+
HasVertex, HasVertexGraph, RemoveEdge, RemoveVertex, Unilateral, VertexInGraph, Weak,
77
},
88
proxy::ReverseGraph,
99
Ensure, Graph, GraphDerefMut,
1010
},
1111
};
12+
use num_traits::{PrimInt, Unsigned};
1213
use std::borrow::Borrow;
1314

1415
/// A marker trait for graphs that are connected.
1516
///
1617
/// A graph is connected if there is a path from any vertex to any other vertex.
1718
/// Graphs with one or zero vertices count as connected.
18-
pub trait Connected: Unilateral {}
19+
pub trait Connected: Unilateral
20+
{
21+
/// Calculates the maximum distance between the designated vertex and any other vertex ([the eccentricity](https://mathworld.wolfram.com/GraphEccentricity.html)).
22+
///
23+
/// Takes a closure that converts an edge's weight into a distance value.
24+
/// The distance between two vertices is equal to the distance of the
25+
/// edge(s) between them.
26+
fn eccentricity_weighted<W: PrimInt + Unsigned>(
27+
&self,
28+
get_distance: fn(&Self::EdgeWeight) -> W,
29+
) -> W
30+
where
31+
Self: EdgeCount + HasVertex + Sized,
32+
{
33+
// We search for all the shortest paths, the eccentricity is the longest one
34+
DijkstraShortestPaths::distances(self, get_distance).fold(W::zero(), |max_dist, (_, d2)| {
35+
if max_dist < d2
36+
{
37+
d2
38+
}
39+
else
40+
{
41+
max_dist
42+
}
43+
})
44+
}
45+
46+
/// Calculates the maximum eccentricity of the graph ([the diameter](https://mathworld.wolfram.com/GraphDiameter.html)).
47+
///
48+
/// Takes a closure that converts an edge's weight into a distance value.
49+
/// The distance between two vertices is equal to the distance of the
50+
/// edge(s) between them.
51+
fn diameter_weighted<W: PrimInt + Unsigned>(
52+
&self,
53+
get_distance: fn(&Self::EdgeWeight) -> W,
54+
) -> W
55+
where
56+
Self: EdgeCount + Sized,
57+
{
58+
self.all_vertices().fold(W::zero(), |max_ecc, v| {
59+
let new_ecc =
60+
VertexInGraph::ensure_unvalidated(self, v).eccentricity_weighted(get_distance);
61+
if new_ecc > max_ecc
62+
{
63+
new_ecc
64+
}
65+
else
66+
{
67+
max_ecc
68+
}
69+
})
70+
}
71+
72+
/// Calculates the minimum eccentricity of the graph ([the radius](https://mathworld.wolfram.com/GraphDiameter.html)).
73+
///
74+
/// Takes a closure that converts an edge's weight into a distance value.
75+
/// The distance between two vertices is equal to the distance of the
76+
/// edge(s) between them.
77+
fn radius_weighted<W: PrimInt + Unsigned>(&self, get_distance: fn(&Self::EdgeWeight) -> W) -> W
78+
where
79+
Self: EdgeCount + Sized,
80+
{
81+
self.all_vertices().fold(W::zero(), |min_ecc, v| {
82+
let new_ecc =
83+
VertexInGraph::ensure_unvalidated(self, v).eccentricity_weighted(get_distance);
84+
if new_ecc < min_ecc
85+
{
86+
new_ecc
87+
}
88+
else
89+
{
90+
min_ecc
91+
}
92+
})
93+
}
94+
95+
/// Returns the vertices with eccentricity equal to the radius ([the centers](https://mathworld.wolfram.com/GraphCenter.html)).
96+
///
97+
/// Takes a closure that converts an edge's weight into a distance value.
98+
/// The distance between two vertices is equal to the distance of the
99+
/// edge(s) between them.
100+
fn centers_weighted<'a, W: 'a + PrimInt + Unsigned>(
101+
&'a self,
102+
get_distance: fn(&Self::EdgeWeight) -> W,
103+
) -> impl Iterator<Item = Self::Vertex> + '_
104+
where
105+
Self: EdgeCount + Sized,
106+
{
107+
let radius = self.radius_weighted(get_distance);
108+
self.all_vertices().filter(move |v| {
109+
VertexInGraph::ensure_unvalidated(self, *v).eccentricity_weighted(get_distance)
110+
== radius
111+
})
112+
}
113+
}
19114

20115
#[derive(Clone, Debug)]
21116
pub struct ConnectedGraph<C: Ensure>(C);

src/core/property/has_vertex.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ pub trait HasVertex: Graph
2222
/// Ensures the underlying graph has at least 1 vertex.
2323
///
2424
/// Gives no guarantees on which vertex is returned by any given call to
25-
/// `get_vertex` if the the graph has multiple vertices.
25+
/// `get_vertex` if the graph has multiple vertices.
2626
#[derive(Clone)]
2727
pub struct HasVertexGraph<C: Ensure>(C);
2828

src/core/property/impl_ensurer.rs

+38
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,44 @@ macro_rules! impl_properties {
395395
}
396396
}
397397

398+
// VertexCount
399+
$crate::impl_properties!{
400+
@struct [ $struct ]
401+
@generic [ $($generics)* ]
402+
@delegate [ $delegate_type ]
403+
$(@exclude [ $($exclude_props)* ])?
404+
$(@include [ $($include_props)* ])?
405+
@bounds [
406+
<$delegate_type as $crate::core::GraphDeref>::Graph:
407+
$crate::core::property::VertexCount,
408+
$($bounds)*
409+
]
410+
@trait_id VertexCount [$crate::core::property]
411+
@implement {
412+
type Count = <<$delegate_type as $crate::core::GraphDeref>::Graph
413+
as $crate::core::property::VertexCount>::Count;
414+
}
415+
}
416+
417+
// EdgeCount
418+
$crate::impl_properties!{
419+
@struct [ $struct ]
420+
@generic [ $($generics)* ]
421+
@delegate [ $delegate_type ]
422+
$(@exclude [ $($exclude_props)* ])?
423+
$(@include [ $($include_props)* ])?
424+
@bounds [
425+
<$delegate_type as $crate::core::GraphDeref>::Graph:
426+
$crate::core::property::EdgeCount,
427+
$($bounds)*
428+
]
429+
@trait_id EdgeCount [$crate::core::property]
430+
@implement {
431+
type Count = <<$delegate_type as $crate::core::GraphDeref>::Graph
432+
as $crate::core::property::EdgeCount>::Count;
433+
}
434+
}
435+
398436
// Unique
399437
$crate::impl_properties!{
400438
@struct [ $struct ]

tests/core/property/connectedness.rs

+56-5
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@ use crate::mock_graph::{
55
MockDirectedness, MockEdgeWeight, MockGraph, MockVertexWeight,
66
};
77
use duplicate::duplicate_item;
8-
use graphene::core::{
9-
property::{
10-
AddEdge, Connected, ConnectedGraph, HasVertex, NewVertex, RemoveEdge, RemoveVertex,
11-
Unilateral, UnilateralGraph, VertexInGraph, Weak, WeakGraph,
8+
use graphene::{
9+
algo::DijkstraShortestPaths,
10+
core::{
11+
property::{
12+
AddEdge, Connected, ConnectedGraph, HasVertex, NewVertex, RemoveEdge, RemoveVertex,
13+
Unilateral, UnilateralGraph, VertexInGraph, Weak, WeakGraph,
14+
},
15+
Directed, Graph, Undirected,
1216
},
13-
Directed, EnsureUnloaded, ReleaseUnloaded, Undirected,
1417
};
1518
use static_assertions::assert_impl_all;
1619

@@ -47,6 +50,7 @@ use static_assertions::assert_impl_all;
4750
mod module
4851
{
4952
use super::*;
53+
use graphene::core::{EnsureUnloaded, ReleaseUnloaded};
5054

5155
/// Tests that the graph correctly identifies graphs with its connectedness.
5256
#[quickcheck]
@@ -227,6 +231,53 @@ mod module
227231
}
228232
}
229233

234+
#[duplicate_item(directedness; [Directed]; [Undirected])]
235+
mod __
236+
{
237+
use super::*;
238+
use graphene::core::Ensure;
239+
240+
/// Tests `eccentricity_weighted`
241+
#[quickcheck]
242+
fn eccentricity_weighted(
243+
Arb(g): Arb<VertexInGraph<ConnectedGraph<MockGraph<directedness>>>>,
244+
) -> bool
245+
{
246+
let eccentricity = g.eccentricity_weighted(|w| w.value);
247+
DijkstraShortestPaths::distances(&g, |w| w.value).all(|(_, dist)| dist <= eccentricity)
248+
}
249+
250+
/// Tests `diameter_weighted`
251+
#[quickcheck]
252+
fn diameter_weighted(Arb(g): Arb<ConnectedGraph<MockGraph<directedness>>>) -> bool
253+
{
254+
let diameter = g.diameter_weighted(|w| w.value);
255+
g.all_vertices().all(|v| {
256+
VertexInGraph::ensure_unvalidated(&g, v).eccentricity_weighted(|w| w.value) <= diameter
257+
})
258+
}
259+
260+
/// Tests `radius_weighted`
261+
#[quickcheck]
262+
fn radius_weighted(Arb(g): Arb<ConnectedGraph<MockGraph<directedness>>>) -> bool
263+
{
264+
let radius = g.radius_weighted(|w| w.value);
265+
g.all_vertices().all(|v| {
266+
VertexInGraph::ensure_unvalidated(&g, v).eccentricity_weighted(|w| w.value) >= radius
267+
})
268+
}
269+
270+
/// Tests `centers_weighted`
271+
#[quickcheck]
272+
fn centers_weighted(Arb(g): Arb<ConnectedGraph<MockGraph<directedness>>>) -> bool
273+
{
274+
let radius = g.radius_weighted(|w| w.value);
275+
g.centers_weighted(|w| w.value).all(|v| {
276+
VertexInGraph::ensure_unvalidated(&g, v).eccentricity_weighted(|w| w.value) == radius
277+
})
278+
}
279+
}
280+
230281
// Test that all Connected graphs are also unilateral and weak.
231282
assert_impl_all!(ConnectedGraph<MockGraph<MockDirectedness>>: Connected, Unilateral, Weak);
232283

0 commit comments

Comments
 (0)