use std::collections::BTreeMap;
use bincode::{Decode, Encode};
use serde::{Deserialize, Serialize};
use super::address::{DimensionVector, RevisionId, SpaceId};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
pub struct HyperedgeId(pub u64);
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
pub struct HyperedgeKind(pub String);
impl HyperedgeKind {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Encode, Decode)]
pub struct EndpointRole(pub String);
impl EndpointRole {
pub fn new(value: impl Into<String>) -> Self {
Self(value.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Encode, Decode)]
pub struct EndpointRef {
pub role: EndpointRole,
pub space: SpaceId,
pub node: DimensionVector,
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
pub struct Hyperedge {
pub id: HyperedgeId,
pub kind: HyperedgeKind,
pub endpoints: Vec<EndpointRef>,
pub weight_milli: Option<i64>,
pub metadata: BTreeMap<String, String>,
pub valid_from: RevisionId,
pub valid_to: Option<RevisionId>,
}
impl Hyperedge {
pub fn validate(&self) -> Result<(), HyperedgeValidationError> {
if self.endpoints.len() < 2 {
return Err(HyperedgeValidationError::TooFewEndpoints);
}
if self.kind.0.trim().is_empty() {
return Err(HyperedgeValidationError::EmptyKind);
}
if self.endpoints.iter().any(|ep| ep.role.0.trim().is_empty()) {
return Err(HyperedgeValidationError::EmptyEndpointRole);
}
if let Some(valid_to) = self.valid_to {
if valid_to < self.valid_from {
return Err(HyperedgeValidationError::InvalidValidityWindow {
valid_from: self.valid_from,
valid_to,
});
}
}
Ok(())
}
pub fn is_active_at(&self, revision: RevisionId) -> bool {
revision >= self.valid_from
&& self.valid_to.map(|to| revision <= to).unwrap_or(true)
}
pub fn endpoint_centroid(&self) -> Option<(SpaceId, Vec<u32>)> {
use std::collections::HashMap;
let mut groups: HashMap<u64, Vec<&EndpointRef>> = HashMap::new();
for ep in &self.endpoints {
groups.entry(ep.space.0).or_default().push(ep);
}
let (space_id, eps) = groups
.into_iter()
.max_by(|a, b| a.1.len().cmp(&b.1.len()).then(b.0.cmp(&a.0)))?;
if eps.len() < 2 {
return None;
}
let dims = eps.iter().map(|e| e.node.coords.len()).min().unwrap_or(0);
if dims == 0 {
return None;
}
let mut sums = vec![0u64; dims];
for ep in &eps {
for (d, slot) in sums.iter_mut().enumerate() {
*slot += ep.node.coords[d] as u64;
}
}
let n = eps.len() as u64;
let centroid = sums.into_iter().map(|s| (s / n) as u32).collect();
Some((SpaceId(space_id), centroid))
}
}
#[derive(Debug)]
pub enum HyperedgeValidationError {
TooFewEndpoints,
EmptyKind,
EmptyEndpointRole,
InvalidValidityWindow {
valid_from: RevisionId,
valid_to: RevisionId,
},
}