315 lines
9.0 KiB
Rust
315 lines
9.0 KiB
Rust
//! N-ary relationship support (hyperedges)
|
|
//!
|
|
//! Extends the basic edge model to support relationships connecting multiple nodes
|
|
|
|
use crate::types::{NodeId, Properties, PropertyValue};
|
|
use bincode::{Decode, Encode};
|
|
use serde::{Deserialize, Serialize};
|
|
use std::collections::HashSet;
|
|
use uuid::Uuid;
|
|
|
|
/// Unique identifier for a hyperedge
|
|
pub type HyperedgeId = String;
|
|
|
|
/// Hyperedge connecting multiple nodes (N-ary relationship)
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
|
|
pub struct Hyperedge {
|
|
/// Unique identifier
|
|
pub id: HyperedgeId,
|
|
/// Node IDs connected by this hyperedge
|
|
pub nodes: Vec<NodeId>,
|
|
/// Hyperedge type/label (e.g., "MEETING", "COLLABORATION")
|
|
pub edge_type: String,
|
|
/// Natural language description of the relationship
|
|
pub description: Option<String>,
|
|
/// Property key-value pairs
|
|
pub properties: Properties,
|
|
/// Confidence/weight (0.0-1.0)
|
|
pub confidence: f32,
|
|
}
|
|
|
|
impl Hyperedge {
|
|
/// Create a new hyperedge with generated UUID
|
|
pub fn new<S: Into<String>>(nodes: Vec<NodeId>, edge_type: S) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4().to_string(),
|
|
nodes,
|
|
edge_type: edge_type.into(),
|
|
description: None,
|
|
properties: Properties::new(),
|
|
confidence: 1.0,
|
|
}
|
|
}
|
|
|
|
/// Create a new hyperedge with specific ID
|
|
pub fn with_id<S: Into<String>>(id: HyperedgeId, nodes: Vec<NodeId>, edge_type: S) -> Self {
|
|
Self {
|
|
id,
|
|
nodes,
|
|
edge_type: edge_type.into(),
|
|
description: None,
|
|
properties: Properties::new(),
|
|
confidence: 1.0,
|
|
}
|
|
}
|
|
|
|
/// Get the order of the hyperedge (number of nodes)
|
|
pub fn order(&self) -> usize {
|
|
self.nodes.len()
|
|
}
|
|
|
|
/// Check if hyperedge contains a specific node
|
|
pub fn contains_node(&self, node_id: &NodeId) -> bool {
|
|
self.nodes.contains(node_id)
|
|
}
|
|
|
|
/// Check if hyperedge contains all specified nodes
|
|
pub fn contains_all_nodes(&self, node_ids: &[NodeId]) -> bool {
|
|
node_ids.iter().all(|id| self.contains_node(id))
|
|
}
|
|
|
|
/// Check if hyperedge contains any of the specified nodes
|
|
pub fn contains_any_node(&self, node_ids: &[NodeId]) -> bool {
|
|
node_ids.iter().any(|id| self.contains_node(id))
|
|
}
|
|
|
|
/// Get unique nodes (removes duplicates)
|
|
pub fn unique_nodes(&self) -> HashSet<&NodeId> {
|
|
self.nodes.iter().collect()
|
|
}
|
|
|
|
/// Set the description
|
|
pub fn set_description<S: Into<String>>(&mut self, description: S) -> &mut Self {
|
|
self.description = Some(description.into());
|
|
self
|
|
}
|
|
|
|
/// Set the confidence
|
|
pub fn set_confidence(&mut self, confidence: f32) -> &mut Self {
|
|
self.confidence = confidence.clamp(0.0, 1.0);
|
|
self
|
|
}
|
|
|
|
/// Set a property
|
|
pub fn set_property<K, V>(&mut self, key: K, value: V) -> &mut Self
|
|
where
|
|
K: Into<String>,
|
|
V: Into<PropertyValue>,
|
|
{
|
|
self.properties.insert(key.into(), value.into());
|
|
self
|
|
}
|
|
|
|
/// Get a property
|
|
pub fn get_property(&self, key: &str) -> Option<&PropertyValue> {
|
|
self.properties.get(key)
|
|
}
|
|
|
|
/// Remove a property
|
|
pub fn remove_property(&mut self, key: &str) -> Option<PropertyValue> {
|
|
self.properties.remove(key)
|
|
}
|
|
|
|
/// Check if hyperedge has a property
|
|
pub fn has_property(&self, key: &str) -> bool {
|
|
self.properties.contains_key(key)
|
|
}
|
|
|
|
/// Get all property keys
|
|
pub fn property_keys(&self) -> Vec<&String> {
|
|
self.properties.keys().collect()
|
|
}
|
|
|
|
/// Clear all properties
|
|
pub fn clear_properties(&mut self) {
|
|
self.properties.clear();
|
|
}
|
|
|
|
/// Get the number of properties
|
|
pub fn property_count(&self) -> usize {
|
|
self.properties.len()
|
|
}
|
|
}
|
|
|
|
/// Builder for creating hyperedges with fluent API
|
|
pub struct HyperedgeBuilder {
|
|
hyperedge: Hyperedge,
|
|
}
|
|
|
|
impl HyperedgeBuilder {
|
|
/// Create a new builder
|
|
pub fn new<S: Into<String>>(nodes: Vec<NodeId>, edge_type: S) -> Self {
|
|
Self {
|
|
hyperedge: Hyperedge::new(nodes, edge_type),
|
|
}
|
|
}
|
|
|
|
/// Create builder with specific ID
|
|
pub fn with_id<S: Into<String>>(id: HyperedgeId, nodes: Vec<NodeId>, edge_type: S) -> Self {
|
|
Self {
|
|
hyperedge: Hyperedge::with_id(id, nodes, edge_type),
|
|
}
|
|
}
|
|
|
|
/// Set description
|
|
pub fn description<S: Into<String>>(mut self, description: S) -> Self {
|
|
self.hyperedge.set_description(description);
|
|
self
|
|
}
|
|
|
|
/// Set confidence
|
|
pub fn confidence(mut self, confidence: f32) -> Self {
|
|
self.hyperedge.set_confidence(confidence);
|
|
self
|
|
}
|
|
|
|
/// Set a property
|
|
pub fn property<K, V>(mut self, key: K, value: V) -> Self
|
|
where
|
|
K: Into<String>,
|
|
V: Into<PropertyValue>,
|
|
{
|
|
self.hyperedge.set_property(key, value);
|
|
self
|
|
}
|
|
|
|
/// Build the hyperedge
|
|
pub fn build(self) -> Hyperedge {
|
|
self.hyperedge
|
|
}
|
|
}
|
|
|
|
/// Hyperedge role assignment for directed N-ary relationships
|
|
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
|
|
pub struct HyperedgeWithRoles {
|
|
/// Base hyperedge
|
|
pub hyperedge: Hyperedge,
|
|
/// Role assignments: node_id -> role
|
|
pub roles: std::collections::HashMap<NodeId, String>,
|
|
}
|
|
|
|
impl HyperedgeWithRoles {
|
|
/// Create a new hyperedge with roles
|
|
pub fn new(hyperedge: Hyperedge) -> Self {
|
|
Self {
|
|
hyperedge,
|
|
roles: std::collections::HashMap::new(),
|
|
}
|
|
}
|
|
|
|
/// Assign a role to a node
|
|
pub fn assign_role<S: Into<String>>(&mut self, node_id: NodeId, role: S) -> &mut Self {
|
|
self.roles.insert(node_id, role.into());
|
|
self
|
|
}
|
|
|
|
/// Get the role of a node
|
|
pub fn get_role(&self, node_id: &NodeId) -> Option<&String> {
|
|
self.roles.get(node_id)
|
|
}
|
|
|
|
/// Get all nodes with a specific role
|
|
pub fn nodes_with_role(&self, role: &str) -> Vec<&NodeId> {
|
|
self.roles
|
|
.iter()
|
|
.filter(|(_, r)| r.as_str() == role)
|
|
.map(|(id, _)| id)
|
|
.collect()
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_hyperedge_creation() {
|
|
let nodes = vec![
|
|
"node1".to_string(),
|
|
"node2".to_string(),
|
|
"node3".to_string(),
|
|
];
|
|
let hedge = Hyperedge::new(nodes, "MEETING");
|
|
|
|
assert!(!hedge.id.is_empty());
|
|
assert_eq!(hedge.order(), 3);
|
|
assert_eq!(hedge.edge_type, "MEETING");
|
|
assert_eq!(hedge.confidence, 1.0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hyperedge_contains() {
|
|
let nodes = vec![
|
|
"node1".to_string(),
|
|
"node2".to_string(),
|
|
"node3".to_string(),
|
|
];
|
|
let hedge = Hyperedge::new(nodes, "MEETING");
|
|
|
|
assert!(hedge.contains_node(&"node1".to_string()));
|
|
assert!(hedge.contains_node(&"node2".to_string()));
|
|
assert!(!hedge.contains_node(&"node4".to_string()));
|
|
|
|
assert!(hedge.contains_all_nodes(&["node1".to_string(), "node2".to_string()]));
|
|
assert!(!hedge.contains_all_nodes(&["node1".to_string(), "node4".to_string()]));
|
|
|
|
assert!(hedge.contains_any_node(&["node1".to_string(), "node4".to_string()]));
|
|
assert!(!hedge.contains_any_node(&["node4".to_string(), "node5".to_string()]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_hyperedge_builder() {
|
|
let nodes = vec!["node1".to_string(), "node2".to_string()];
|
|
let hedge = HyperedgeBuilder::new(nodes, "COLLABORATION")
|
|
.description("Team collaboration on project X")
|
|
.confidence(0.95)
|
|
.property("project", "X")
|
|
.property("duration", 30i64)
|
|
.build();
|
|
|
|
assert_eq!(hedge.edge_type, "COLLABORATION");
|
|
assert_eq!(hedge.confidence, 0.95);
|
|
assert!(hedge.description.is_some());
|
|
assert_eq!(
|
|
hedge.get_property("project"),
|
|
Some(&PropertyValue::String("X".to_string()))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_hyperedge_with_roles() {
|
|
let nodes = vec![
|
|
"alice".to_string(),
|
|
"bob".to_string(),
|
|
"charlie".to_string(),
|
|
];
|
|
let hedge = Hyperedge::new(nodes, "MEETING");
|
|
|
|
let mut hedge_with_roles = HyperedgeWithRoles::new(hedge);
|
|
hedge_with_roles.assign_role("alice".to_string(), "organizer");
|
|
hedge_with_roles.assign_role("bob".to_string(), "participant");
|
|
hedge_with_roles.assign_role("charlie".to_string(), "participant");
|
|
|
|
assert_eq!(
|
|
hedge_with_roles.get_role(&"alice".to_string()),
|
|
Some(&"organizer".to_string())
|
|
);
|
|
|
|
let participants = hedge_with_roles.nodes_with_role("participant");
|
|
assert_eq!(participants.len(), 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_unique_nodes() {
|
|
let nodes = vec![
|
|
"node1".to_string(),
|
|
"node2".to_string(),
|
|
"node1".to_string(), // duplicate
|
|
];
|
|
let hedge = Hyperedge::new(nodes, "TEST");
|
|
|
|
let unique = hedge.unique_nodes();
|
|
assert_eq!(unique.len(), 2);
|
|
}
|
|
}
|