pargraph 0.2.0

Operator based parallel graph processing.
Documentation
// SPDX-FileCopyrightText: 2022 Thomas Kramer <code@tkramer.ch>
//
// SPDX-License-Identifier: GPL-3.0-or-later

//! Wrapper which provides a local view of a graph.
//! The local view can only see the active node and its neighborhood.
//!
//! Such local views are used to as inputs for graph operators to make sure that the operator
//! cannot access parts of the graph which it is not allowed to.

use petgraph::{
    Direction,
    data::DataMap,
    visit::{
        Data, GraphBase, GraphRef, IntoEdgeReferences, IntoEdges, IntoEdgesDirected, IntoNeighbors,
        IntoNeighborsDirected,
    },
};

use super::{BorrowDataCell, DataConflictErr, GraphElement, id_rwlock::IdLockReadGuard};

/// Local view of a graph.
/// Only the active node and its direct neighbours can be accessed.
///
/// # Panics
/// Panics on all accesses outside of the direct neigborhood of the active node.
#[derive(Copy, Clone)]
pub struct LocalGraphView<G: GraphRef, C = NoConflictDetection> {
    active_node: G::NodeId,
    graph_ref: G,
    _conflict_detection_type: std::marker::PhantomData<C>,
}

impl<G: GraphRef, C: ConflictDetectionType> LocalGraphView<G, C> {
    pub(crate) fn new(graph_ref: G, active_node: G::NodeId) -> Self {
        Self {
            active_node,
            graph_ref,
            _conflict_detection_type: Default::default(),
        }
    }
}

impl<G: GraphRef, C> LocalGraphView<G, C> {
    /// Get the ID of the active node.
    pub fn active_node(&self) -> G::NodeId {
        self.active_node
    }

    /// Return a reference to the base graph.
    pub fn base_graph(&self) -> G {
        self.graph_ref
    }
}

impl<G: GraphRef, C> GraphBase for LocalGraphView<G, C> {
    type EdgeId = G::EdgeId;

    type NodeId = G::NodeId;
}

impl<G: Data + GraphRef, C> Data for LocalGraphView<G, C> {
    type NodeWeight = G::NodeWeight;

    type EdgeWeight = G::EdgeWeight;
}

impl<G: IntoNeighbors, C> IntoNeighbors for &LocalGraphView<G, C> {
    type Neighbors = G::Neighbors;

    fn neighbors(self, a: Self::NodeId) -> Self::Neighbors {
        //if a == self.active_node {
        self.graph_ref.neighbors(a)
        //} else {
        //    panic!("Local graph view is not allowed to query nodes outside of the neighborhood.")
        //}
    }
}

impl<G: IntoNeighborsDirected, C> IntoNeighborsDirected for &LocalGraphView<G, C> {
    type NeighborsDirected = G::NeighborsDirected;

    fn neighbors_directed(self, a: Self::NodeId, d: Direction) -> Self::NeighborsDirected {
        //if a == self.active_node {
        self.graph_ref.neighbors_directed(a, d)
        //} else {
        //    panic!("Local graph view is not allowed to query nodes outside of the neighborhood.")
        //}
    }
}

impl<G: IntoEdgeReferences + IntoEdges, C> IntoEdgeReferences for &LocalGraphView<G, C> {
    type EdgeRef = G::EdgeRef;

    type EdgeReferences = G::Edges;

    fn edge_references(self) -> Self::EdgeReferences {
        self.graph_ref.edges(self.active_node)
    }
}

impl<G: IntoEdges, C> IntoEdges for &LocalGraphView<G, C> {
    type Edges = G::Edges;

    fn edges(self, a: Self::NodeId) -> Self::Edges {
        //if a == self.active_node {
        self.graph_ref.edges(a)
        //} else {
        //    panic!("Local graph view is not allowed to query nodes outside of the neighborhood.")
        //}
    }
}
impl<G: IntoEdgesDirected, C> IntoEdgesDirected for &LocalGraphView<G, C> {
    type EdgesDirected = G::EdgesDirected;

    fn edges_directed(self, a: Self::NodeId, d: Direction) -> Self::EdgesDirected {
        //if a == self.active_node {
        self.graph_ref.edges_directed(a, d)
        //} else {
        //    panic!("Local graph view is not allowed to query nodes outside of the neighborhood.")
        //}
    }
}

impl<G: DataMap + GraphRef, C> DataMap for LocalGraphView<G, C> {
    fn node_weight(&self, id: Self::NodeId) -> Option<&Self::NodeWeight> {
        self.graph_ref.node_weight(id)
    }

    fn edge_weight(&self, id: Self::EdgeId) -> Option<&Self::EdgeWeight> {
        self.graph_ref.edge_weight(id)
    }
}

impl<G> LocalGraphView<G, FullConflictDetection>
where
    G: GraphRef + DataMap,
    G::NodeWeight: BorrowDataCell,
{
    /// Try to get read access to a node weight.
    /// Returns `None` if there is no node weight.
    /// Returns `Some(Err(_))` if the data is currently locked for writing.
    /// Returns `Some(Ok(_))` on success.
    pub fn try_node_weight(&self, id: G::NodeId) -> Option<SyncNodeReadResult<'_, G>> {
        let w = self.graph_ref.node_weight(id);
        w.map(|w| {
            w.borrow_data_cell()
                .try_read()
                .map_err(|err| DataConflictErr {
                    _graph_element: GraphElement::NodeId(id),
                    err: err.into(),
                })
        })
    }
}

/// Guard type which allows safe read-only access to the explicitly synchronized part of the node/edge data.
pub type SyncReadGuard<'a, T> = IdLockReadGuard<'a, <T as BorrowDataCell>::UserData>;

/// Type which is returned when trying to get access to the weight of an edge in the graph.
pub type SyncEdgeReadResult<'a, G> = Result<
    SyncReadGuard<'a, <G as Data>::EdgeWeight>,
    DataConflictErr<<G as GraphBase>::NodeId, <G as GraphBase>::EdgeId>,
>;

/// Type which is returned when trying to get access to the weight of a node in the graph.
pub type SyncNodeReadResult<'a, G> = Result<
    SyncReadGuard<'a, <G as Data>::NodeWeight>,
    DataConflictErr<<G as GraphBase>::NodeId, <G as GraphBase>::EdgeId>,
>;

impl<G> LocalGraphView<G, FullConflictDetection>
where
    G: GraphRef + DataMap,
    G::EdgeWeight: BorrowDataCell,
{
    /// Try to get read access to an edge weight.
    /// Returns `None` if there is no edge weight.
    /// Returns `Some(Err(_))` if the data is currently locked for writing.
    /// Returns `Some(Ok(_))` on success.
    pub fn try_edge_weight(&self, id: G::EdgeId) -> Option<SyncEdgeReadResult<'_, G>> {
        let w = self.graph_ref.edge_weight(id);

        w.map(|w| {
            w.borrow_data_cell()
                .try_read()
                .map_err(|err| DataConflictErr {
                    _graph_element: GraphElement::EdgeId(id),
                    err: err.into(),
                })
        })
    }
}

/// Marker trait.
pub trait ConflictDetectionType {}

/// Marker struct.
pub struct FullConflictDetection;
impl ConflictDetectionType for FullConflictDetection {}

/// Marker struct.
pub struct NoConflictDetection;
impl ConflictDetectionType for NoConflictDetection {}