#![allow(clippy::similar_names)]
mod algorithm;
use std::collections::{HashMap, HashSet};
use algorithm::{
compute_network_hash, compute_wl_signatures, find_all_automorphisms, find_isomorphism_mapping,
subnetwork_backtrack,
};
pub use links_notation;
use links_notation::{parse_lino, LiNo, ParseError};
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub type LinkId = u64;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DoubletLink {
pub source: LinkId,
pub target: LinkId,
}
impl DoubletLink {
#[must_use]
pub const fn new(source: LinkId, target: LinkId) -> Self {
Self { source, target }
}
}
#[derive(Debug, Clone, Default)]
pub struct LinkNetwork {
links: Vec<DoubletLink>,
nodes: HashSet<LinkId>,
adjacency: HashMap<LinkId, HashSet<LinkId>>,
}
impl LinkNetwork {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn from_notation(notation: &str) -> Self {
let mut network = Self::new();
let mut name_to_id: HashMap<String, LinkId> = HashMap::new();
let mut next_id: LinkId = 1;
for line in notation.lines() {
let line = line.trim();
if line.is_empty() || line.starts_with('#') {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
if parts.len() >= 3 {
let source_name = parts[0];
let target_name = parts[parts.len() - 1];
let source_id = *name_to_id
.entry(source_name.to_string())
.or_insert_with(|| {
let id = next_id;
next_id += 1;
id
});
let target_id = *name_to_id
.entry(target_name.to_string())
.or_insert_with(|| {
let id = next_id;
next_id += 1;
id
});
network.add_link(source_id, target_id);
} else if parts.len() == 2 {
let source_name = parts[0];
let target_name = parts[1];
let source_id = *name_to_id
.entry(source_name.to_string())
.or_insert_with(|| {
let id = next_id;
next_id += 1;
id
});
let target_id = *name_to_id
.entry(target_name.to_string())
.or_insert_with(|| {
let id = next_id;
next_id += 1;
id
});
network.add_link(source_id, target_id);
}
}
network
}
pub fn from_lino(lino: &str) -> Result<Self, ParseError> {
let parsed = parse_lino(lino)?;
let mut network = Self::new();
let mut name_to_id: HashMap<String, LinkId> = HashMap::new();
let mut next_id: LinkId = 1;
let mut get_id = |name: &str| -> LinkId {
*name_to_id.entry(name.to_string()).or_insert_with(|| {
let id = next_id;
next_id += 1;
id
})
};
Self::extract_doublets_from_lino(&parsed, &mut network, &mut get_id);
Ok(network)
}
fn extract_doublets_from_lino<F>(lino: &LiNo<String>, network: &mut Self, get_id: &mut F)
where
F: FnMut(&str) -> LinkId,
{
match lino {
LiNo::Ref(_) => {
}
LiNo::Link { values, .. } => {
if values.len() == 2 {
if let (LiNo::Ref(source), LiNo::Ref(target)) = (&values[0], &values[1]) {
let source_id = get_id(source);
let target_id = get_id(target);
network.add_link(source_id, target_id);
return;
}
}
if values.len() == 3 {
if let (LiNo::Ref(source), LiNo::Ref(_), LiNo::Ref(target)) =
(&values[0], &values[1], &values[2])
{
let source_id = get_id(source);
let target_id = get_id(target);
network.add_link(source_id, target_id);
return;
}
}
for value in values {
Self::extract_doublets_from_lino(value, network, get_id);
}
}
}
}
pub fn add_link(&mut self, source: LinkId, target: LinkId) {
self.links.push(DoubletLink::new(source, target));
self.nodes.insert(source);
self.nodes.insert(target);
self.adjacency.entry(source).or_default().insert(target);
self.adjacency.entry(target).or_default().insert(source);
}
#[must_use]
pub fn node_count(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn link_count(&self) -> usize {
self.links.len()
}
#[must_use]
pub fn nodes(&self) -> &HashSet<LinkId> {
&self.nodes
}
#[must_use]
pub fn links(&self) -> &[DoubletLink] {
&self.links
}
#[must_use]
pub fn degree(&self, node: LinkId) -> usize {
self.adjacency.get(&node).map_or(0, HashSet::len)
}
#[must_use]
pub fn neighbors(&self, node: LinkId) -> Option<&HashSet<LinkId>> {
self.adjacency.get(&node)
}
#[must_use]
pub fn degree_sequence(&self) -> Vec<usize> {
let mut degrees: Vec<usize> = self.nodes.iter().map(|&n| self.degree(n)).collect();
degrees.sort_unstable();
degrees
}
}
#[derive(Debug, Clone)]
pub struct IsomorphismResult {
pub is_isomorphic: bool,
pub mapping: Option<HashMap<LinkId, LinkId>>,
}
impl IsomorphismResult {
#[must_use]
const fn not_isomorphic() -> Self {
Self {
is_isomorphic: false,
mapping: None,
}
}
#[must_use]
fn isomorphic(mapping: HashMap<LinkId, LinkId>) -> Self {
Self {
is_isomorphic: true,
mapping: Some(mapping),
}
}
}
#[must_use]
pub fn is_isomorphic(network1: &LinkNetwork, network2: &LinkNetwork) -> bool {
check_isomorphism(network1, network2).is_isomorphic
}
#[must_use]
pub fn check_isomorphism(network1: &LinkNetwork, network2: &LinkNetwork) -> IsomorphismResult {
if network1.node_count() != network2.node_count() {
return IsomorphismResult::not_isomorphic();
}
if network1.link_count() != network2.link_count() {
return IsomorphismResult::not_isomorphic();
}
if network1.node_count() == 0 {
return IsomorphismResult::isomorphic(HashMap::new());
}
if network1.degree_sequence() != network2.degree_sequence() {
return IsomorphismResult::not_isomorphic();
}
let hash1 = compute_network_hash(network1);
let hash2 = compute_network_hash(network2);
if hash1 != hash2 {
return IsomorphismResult::not_isomorphic();
}
let sig1 = compute_wl_signatures(network1, 3);
let sig2 = compute_wl_signatures(network2, 3);
let mut label_to_nodes1: HashMap<u64, Vec<LinkId>> = HashMap::new();
for (&node, &label) in &sig1 {
label_to_nodes1.entry(label).or_default().push(node);
}
let mut label_to_nodes2: HashMap<u64, Vec<LinkId>> = HashMap::new();
for (&node, &label) in &sig2 {
label_to_nodes2.entry(label).or_default().push(node);
}
let mut partition1: Vec<usize> = label_to_nodes1.values().map(Vec::len).collect();
let mut partition2: Vec<usize> = label_to_nodes2.values().map(Vec::len).collect();
partition1.sort_unstable();
partition2.sort_unstable();
if partition1 != partition2 {
return IsomorphismResult::not_isomorphic();
}
let mut nodes1: Vec<LinkId> = network1.nodes().iter().copied().collect();
nodes1.sort_by_key(|&n| network1.degree(n));
let mut nodes2: Vec<LinkId> = network2.nodes().iter().copied().collect();
nodes2.sort_by_key(|&n| network2.degree(n));
if let Some(mapping) = find_isomorphism_mapping(network1, network2, &nodes1, &nodes2) {
return IsomorphismResult::isomorphic(mapping);
}
IsomorphismResult::not_isomorphic()
}
#[must_use]
pub fn find_automorphisms(network: &LinkNetwork) -> Vec<HashMap<LinkId, LinkId>> {
let nodes: Vec<LinkId> = network.nodes().iter().copied().collect();
let sig = compute_wl_signatures(network, 3);
let mut automorphisms = Vec::new();
let mut mapping: HashMap<LinkId, LinkId> = HashMap::new();
let mut used: HashSet<LinkId> = HashSet::new();
find_all_automorphisms(
network,
&nodes,
&sig,
0,
&mut mapping,
&mut used,
&mut automorphisms,
);
automorphisms
}
#[must_use]
pub fn contains_subnetwork(network: &LinkNetwork, pattern: &LinkNetwork) -> bool {
if pattern.node_count() > network.node_count() {
return false;
}
if pattern.link_count() > network.link_count() {
return false;
}
if pattern.node_count() == 0 {
return true;
}
let pattern_nodes: Vec<LinkId> = pattern.nodes().iter().copied().collect();
let network_nodes: Vec<LinkId> = network.nodes().iter().copied().collect();
let mut mapping: HashMap<LinkId, LinkId> = HashMap::new();
let mut used: HashSet<LinkId> = HashSet::new();
subnetwork_backtrack(
network,
pattern,
&pattern_nodes,
&network_nodes,
0,
&mut mapping,
&mut used,
)
}