Skip to content

Commit

Permalink
Added 'Simple' property
Browse files Browse the repository at this point in the history
  • Loading branch information
Emoun committed Nov 10, 2024
1 parent 05acd06 commit 4e2adbe
Show file tree
Hide file tree
Showing 20 changed files with 366 additions and 119 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ delegate = "0.13.1"
tt-equal = "0.1"
tt-call = "1.0"
num-traits = "0.2"
duplicate = "2.0.0"

[dev-dependencies]
rand = "0.7"
quickcheck = "0.9"
quickcheck_macros = "0.9"
static_assertions = "1.1.0"
duplicate = "2.0.0"
18 changes: 17 additions & 1 deletion src/core/property/impl_ensurer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
///
/// Supported property traits:
/// Directed, Undirected, Unique, NoLoops, Reflexive, Weak, Unilateral,
/// Connected, Subgraph
/// Connected, Subgraph, Simple
///
/// Warning: The Ensure implementation assumes the struct has 1 public member.
/// If this is not the case, implement it yourself.
Expand Down Expand Up @@ -564,6 +564,22 @@ macro_rules! impl_properties {
}
}
}

// Simple
$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::Simple,
$($bounds)*
]
@trait_id Simple [$crate::core::property]
@implement {}
}
};

{
Expand Down
3 changes: 2 additions & 1 deletion src/core/property/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ mod has_vertex;
mod no_loops;
mod reflexive;
mod rooted;
mod simple;
mod subgraph;
mod unilateral;
mod unique;
mod weak;

pub use self::{
acyclic::*, base_props::*, connected::*, directedness_ensurers::*, has_vertex::*, no_loops::*,
reflexive::*, rooted::*, subgraph::*, unilateral::*, unique::*, weak::*,
reflexive::*, rooted::*, simple::*, subgraph::*, unilateral::*, unique::*, weak::*,
};
use crate::core::{
proxy::{EdgeProxyGraph, ProxyVertex, VertexProxyGraph},
Expand Down
77 changes: 77 additions & 0 deletions src/core/property/simple.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use crate::core::{
property::{AddEdge, NoLoops, NoLoopsGraph, Unique, UniqueGraph},
Ensure, Graph, GraphDerefMut, Undirected,
};
use duplicate::duplicate_item;
use std::borrow::Borrow;

/// A marker trait for [simple graphs](https://mathworld.wolfram.com/SimpleGraph.html)
pub trait Simple: NoLoops + Unique {}

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

impl<C: Ensure> SimpleGraph<C>
where
C::Graph: Graph<EdgeWeight = (), Directedness = Undirected>,
{
/// Constrains the given graph.
///
/// The given graph must be simple. This is not checked by this function.
pub fn unchecked(c: C) -> Self
{
Self(c)
}
}

impl<C: Ensure> Ensure for SimpleGraph<C>
where
C::Graph: Graph<EdgeWeight = (), Directedness = Undirected>,
{
fn ensure_unvalidated(c: Self::Ensured, _: ()) -> Self
{
Self(c)
}

fn validate(c: &Self::Ensured, _: &()) -> bool
{
NoLoopsGraph::<C>::validate(c, &()) && UniqueGraph::validate(c, &())
}
}

impl<C: Ensure + GraphDerefMut> AddEdge for SimpleGraph<C>
where
C::Graph: AddEdge<EdgeWeight = (), Directedness = Undirected>,
{
fn add_edge_weighted(
&mut self,
source: impl Borrow<Self::Vertex>,
sink: impl Borrow<Self::Vertex>,
weight: Self::EdgeWeight,
) -> Result<(), ()>
{
if !(source.borrow() != sink.borrow()
&& Self::can_add_edge(self, source.borrow(), sink.borrow()))
{
Err(())
}
else
{
self.0.graph_mut().add_edge_weighted(source, sink, weight)
}
}
}

#[duplicate_item(
Prop; [Unique]; [NoLoops]; [Simple];
)]
impl<C: Ensure> Prop for SimpleGraph<C> where
C::Graph: Graph<EdgeWeight = (), Directedness = Undirected>
{
}

impl_ensurer! {
use<C> SimpleGraph<C>: Ensure, Unique, NoLoops, Simple, AddEdge
as (self.0) : C
where C::Graph: Graph<EdgeWeight=(), Directedness=Undirected>
}
17 changes: 12 additions & 5 deletions src/core/property/unique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@ pub trait Unique: Graph
{
self.edges_between(v1, v2).next()
}

/// Returns whether the given edge can be added to the graph without
/// violating uniqueness
fn can_add_edge<G: Graph>(
graph: &G,
source: impl Borrow<G::Vertex>,
sink: impl Borrow<G::Vertex>,
) -> bool
{
graph.edges_between(source, sink).next().is_none()
}
}

#[derive(Clone, Debug)]
Expand Down Expand Up @@ -76,10 +87,7 @@ where
weight: Self::EdgeWeight,
) -> Result<(), ()>
{
if self
.edges_between(source.borrow(), sink.borrow())
.next()
.is_some()
if !Self::can_add_edge(self, source.borrow(), sink.borrow())
{
return Err(());
}
Expand All @@ -92,5 +100,4 @@ impl<C: Ensure> Unique for UniqueGraph<C> {}
impl_ensurer! {
use<C> UniqueGraph<C>: Ensure, Unique, AddEdge
as (self.0) : C
where C: Ensure
}
93 changes: 54 additions & 39 deletions tests/core/property/unique.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,56 +5,71 @@ use crate::mock_graph::{
};
use duplicate::duplicate_item;
use graphene::core::{
property::{AddEdge, HasVertex, NewVertex, UniqueGraph, VertexInGraph},
property::{
AddEdge, HasVertex, NewVertex, Simple, SimpleGraph, Unique, UniqueGraph, VertexInGraph,
},
Directed, EnsureUnloaded, Graph, ReleaseUnloaded, Undirected,
};
use static_assertions::assert_impl_all;

#[duplicate_item(
directedness; [ Directed ]; [ Undirected ]
test_graph directedness_to_test edge_type;
[ UniqueGraph ] [[Directed ]; [ Undirected ]] [MockEdgeWeight];
[ SimpleGraph ] [[ Undirected ]] [()]
)]
mod __
{
use super::*;

/// Tests that UniqueGraph correctly identifies unique graphs.
#[quickcheck]
fn accept_unique(g: Arb<UniqueGraph<MockGraph<directedness>>>) -> bool
#[duplicate_item(
directedness; directedness_to_test
)]
mod __
{
UniqueGraph::validate(&g.0.release_all())
}
use super::*;

/// Tests that UniqueGraph correctly rejects non-unique graphs.
#[quickcheck]
fn reject_non_unique(g: Arb<NonUniqueGraph<directedness>>) -> bool
{
!UniqueGraph::validate(&g.0)
}
/// Tests that test_graph correctly identifies its own graphs.
#[quickcheck]
fn accept_property(g: Arb<test_graph<MockGraph<directedness, edge_type>>>) -> bool
{
test_graph::validate(&g.0.release_all())
}

/// Tests that a UniqueGraph accepts adding a non-duplicate edge
#[quickcheck]
fn accept_add_edge(
Arb(mut g): Arb<VertexInGraph<UniqueGraph<MockGraph<directedness>>>>,
v_weight: MockVertexWeight,
e_weight: MockEdgeWeight,
) -> bool
{
let v = g.get_vertex().clone();
// To ensure we add a non-duplicate edge,
// we create a new vertex and add an edge to it from an existing one.
let v2 = g.new_vertex_weighted(v_weight).unwrap();
let accepted = g.add_edge_weighted(&v, &v2, e_weight).is_ok();
accepted && g.edges_between(v, &v2).count() == 1
}
/// Tests that test_graph correctly rejects non-unique graphs.
#[quickcheck]
fn reject_non_unique(g: Arb<NonUniqueGraph<directedness, edge_type>>) -> bool
{
!test_graph::validate(&g.0)
}

/// Tests that a UniqueGraph rejects adding a duplicate edge
#[quickcheck]
fn reject_add_edge(
Arb(g): Arb<EdgeIn<UniqueGraph<MockGraph<directedness>>>>,
weight: MockEdgeWeight,
) -> bool
{
let source = g.get_vertex();
let EdgeIn(mut g, sink, _) = g;
g.add_edge_weighted(source, sink, weight).is_err()
/// Tests that a test_graph accepts adding a non-duplicate edge
#[quickcheck]
fn accept_add_edge(
Arb(mut g): Arb<VertexInGraph<test_graph<MockGraph<directedness, edge_type>>>>,
v_weight: MockVertexWeight,
e_weight: edge_type,
) -> bool
{
let v = g.get_vertex().clone();
// To ensure we add a non-duplicate edge,
// we create a new vertex and add an edge to it from an existing one.
let v2 = g.new_vertex_weighted(v_weight).unwrap();
let accepted = g.add_edge_weighted(&v, &v2, e_weight).is_ok();
accepted && g.edges_between(v, &v2).count() == 1
}

/// Tests that a test_graph rejects adding a duplicate edge
#[quickcheck]
fn reject_add_edge(
Arb(g): Arb<EdgeIn<test_graph<MockGraph<directedness, edge_type>>>>,
weight: edge_type,
) -> bool
{
let source = g.get_vertex();
let EdgeIn(mut g, sink, _) = g;
g.add_edge_weighted(source, sink, weight).is_err()
}
}
}

// Test that all simple graphs are also unique.
assert_impl_all!(SimpleGraph<MockGraph<Undirected, ()>>: Simple, Unique);
26 changes: 17 additions & 9 deletions tests/mock_graph/arbitrary/combinations/edge_in.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::mock_graph::{
arbitrary::{GuidedArbGraph, Limit},
MockEdgeWeight, MockVertex, TestGraph,
MockType, MockVertex, TestGraph,
};
use graphene::{
core::{
Expand All @@ -11,21 +11,27 @@ use graphene::{
};
use quickcheck::{Arbitrary, Gen};
use rand::Rng;
use std::collections::HashSet;
use std::{collections::HashSet, fmt::Debug};

/// An arbitrary graph with an edge that is guaranteed to be in the graph (the
/// weight is a clone).
/// The source of the edge can be accessed through `.get_vertex`, the sink `.1`,
/// and the weight `.2`
#[derive(Clone, Debug)]
pub struct EdgeIn<G: GuidedArbGraph>(pub VertexInGraph<G>, pub MockVertex, pub MockEdgeWeight)
pub struct EdgeIn<G: GuidedArbGraph>(
pub VertexInGraph<G>,
pub MockVertex,
pub <G::Graph as Graph>::EdgeWeight,
)
where
G::Graph: TestGraph;
G::Graph: TestGraph,
<G::Graph as Graph>::EdgeWeight: MockType;

impl<G> graphene::core::Ensure for EdgeIn<G>
where
G: GuidedArbGraph,
G::Graph: TestGraph,
<G::Graph as Graph>::EdgeWeight: MockType,
{
fn ensure_unvalidated(c: Self::Ensured, _: ()) -> Self
{
Expand All @@ -46,13 +52,15 @@ impl_ensurer! {
as ( self.0) : VertexInGraph<G>
where
G: GuidedArbGraph,
G::Graph: TestGraph
G::Graph: TestGraph,
<G::Graph as Graph>::EdgeWeight: MockType
}

impl<Gr> GuidedArbGraph for EdgeIn<Gr>
where
Gr: GuidedArbGraph + GraphDerefMut,
Gr::Graph: TestGraph + GraphMut + AddEdge + RemoveEdge,
<Gr::Graph as Graph>::EdgeWeight: MockType,
{
fn choose_size<G: Gen>(
g: &mut G,
Expand Down Expand Up @@ -119,15 +127,15 @@ where
for w in self.edges_between(v1, v2)
{
// Remove edge
if !saw_reference_edge_before && w.value == self.2.value
if !saw_reference_edge_before && *w == self.2
{
// Cannot remove the reference edge, if its the only one
saw_reference_edge_before = true
}
else
{
let mut g = self.clone();
g.remove_edge_where_weight(v1, v2, |ref weight| w.value == weight.value)
g.remove_edge_where_weight(v1, v2, |ref weight| w == *weight)
.unwrap();
result.push(g);
}
Expand All @@ -137,9 +145,9 @@ where
let mut shrunk_graph = self.clone();
*shrunk_graph
.edges_between_mut(v1, v2)
.find(|w| w.value == self.2.value)
.find(|w| **w == self.2)
.unwrap() = s_w.clone();
if !shrunk_reference_weight_before && self.2.value == w.value
if !shrunk_reference_weight_before && self.2 == *w
{
shrunk_graph.2 = s_w;
// We only need to update the reference weight for one edge
Expand Down
Loading

0 comments on commit 4e2adbe

Please sign in to comment.