use std::collections::{BTreeMap, BTreeSet};
use ifc_model::{EntityId, Model, Value};
use crate::error::SystemAnomaly;
use crate::release;
pub(crate) mod slot {
pub const RELATING: usize = 4;
pub const RELATED: usize = 5;
pub const REALIZING: usize = 6;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Connection {
pub id: EntityId,
pub relating: EntityId,
pub related: EntityId,
pub realizing: Option<EntityId>,
}
#[derive(Debug, Default, Clone)]
pub struct ConnectionGraph {
connections: Vec<Connection>,
adjacency: BTreeMap<EntityId, BTreeSet<EntityId>>,
}
impl ConnectionGraph {
pub fn build(model: &Model) -> (Self, Vec<SystemAnomaly>) {
let schema = release::resolve_or_ifc4(model);
let mut anomalies = Vec::new();
let mut graph = Self::default();
for &id in model.ids_of_type("IFCRELCONNECTSPORTS") {
let Some(entity) = model.get(id) else {
continue;
};
let relating = match entity.attributes.get(slot::RELATING) {
Some(Value::Ref(port)) => *port,
_ => continue,
};
let related = match entity.attributes.get(slot::RELATED) {
Some(Value::Ref(port)) => *port,
_ => continue,
};
let mut ok = true;
for port in [relating, related] {
match model.get(port) {
None => {
anomalies.push(SystemAnomaly::Dangling {
relation: id,
missing: port,
});
ok = false;
}
Some(e) if !schema.is_a(&e.type_name.to_ascii_uppercase(), "IFCPORT") => {
anomalies.push(SystemAnomaly::NotAPort {
relation: id,
entity: port,
type_name: e.type_name.to_ascii_uppercase(),
});
ok = false;
}
Some(_) => {}
}
}
if !ok {
continue;
}
let realizing = match entity.attributes.get(slot::REALIZING) {
Some(Value::Ref(element)) => Some(*element),
_ => None,
};
graph.connections.push(Connection {
id,
relating,
related,
realizing,
});
graph.adjacency.entry(relating).or_default().insert(related);
graph.adjacency.entry(related).or_default().insert(relating);
}
(graph, anomalies)
}
pub fn connections(&self) -> &[Connection] {
&self.connections
}
pub fn neighbours(&self, port: EntityId) -> Vec<EntityId> {
self.adjacency
.get(&port)
.map(|set| set.iter().copied().collect())
.unwrap_or_default()
}
pub fn reachable_from(&self, start: EntityId) -> Vec<EntityId> {
let mut seen = BTreeSet::new();
let mut queue = std::collections::VecDeque::new();
seen.insert(start);
queue.push_back(start);
while let Some(port) = queue.pop_front() {
for next in self.adjacency.get(&port).into_iter().flatten() {
if seen.insert(*next) {
queue.push_back(*next);
}
}
}
seen.into_iter().collect()
}
pub fn components(&self) -> Vec<Vec<EntityId>> {
let mut seen = BTreeSet::new();
let mut out = Vec::new();
for &port in self.adjacency.keys() {
if seen.contains(&port) {
continue;
}
let component = self.reachable_from(port);
seen.extend(component.iter().copied());
out.push(component);
}
out
}
}
#[derive(Debug, Default, Clone)]
pub struct NetworkGraph {
adjacency: BTreeMap<EntityId, BTreeSet<EntityId>>,
}
impl NetworkGraph {
pub fn build(graph: &ConnectionGraph, ports: &[crate::port::Port]) -> Self {
let mut adjacency: BTreeMap<EntityId, BTreeSet<EntityId>> = BTreeMap::new();
for connection in &graph.connections {
adjacency
.entry(connection.relating)
.or_default()
.insert(connection.related);
adjacency
.entry(connection.related)
.or_default()
.insert(connection.relating);
}
let mut by_element: BTreeMap<EntityId, Vec<EntityId>> = BTreeMap::new();
for port in ports {
if let Some(element) = port.element {
by_element.entry(element).or_default().push(port.id);
}
}
for members in by_element.values() {
for (i, &a) in members.iter().enumerate() {
for &b in &members[i + 1..] {
adjacency.entry(a).or_default().insert(b);
adjacency.entry(b).or_default().insert(a);
}
}
}
Self { adjacency }
}
pub fn reachable_from(&self, start: EntityId) -> Vec<EntityId> {
let mut seen = BTreeSet::new();
let mut queue = std::collections::VecDeque::new();
seen.insert(start);
queue.push_back(start);
while let Some(port) = queue.pop_front() {
for next in self.adjacency.get(&port).into_iter().flatten() {
if seen.insert(*next) {
queue.push_back(*next);
}
}
}
seen.into_iter().collect()
}
pub fn components(&self) -> Vec<Vec<EntityId>> {
let mut seen = BTreeSet::new();
let mut out = Vec::new();
for &port in self.adjacency.keys() {
if seen.contains(&port) {
continue;
}
let component = self.reachable_from(port);
seen.extend(component.iter().copied());
out.push(component);
}
out
}
}