diff --git a/plugins/mindstudio-insight-plugins/ModelVis/Cargo.lock b/plugins/mindstudio-insight-plugins/ModelVis/Cargo.lock index b31253b3caa4851ac5694741463e3d0cb823c0f4..f3ed3c5e4cb52e3d5272891a1ce744fa4adb2ba1 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/Cargo.lock +++ b/plugins/mindstudio-insight-plugins/ModelVis/Cargo.lock @@ -199,6 +199,18 @@ dependencies = [ "serde", ] +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -513,6 +525,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -633,6 +664,21 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", + "rayon", +] + [[package]] name = "deranged" version = "0.4.0" @@ -1004,6 +1050,12 @@ dependencies = [ "smartstring", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futf" version = "0.1.5" @@ -1427,6 +1479,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.3" @@ -1704,9 +1762,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" +checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9" dependencies = [ "equivalent", "hashbrown 0.15.3", @@ -1893,11 +1951,15 @@ dependencies = [ [[package]] name = "layout" -version = "0.0.1" +version = "0.0.5" dependencies = [ "ahash", - "anyhow", + "bitvec", + "dashmap", + "indexmap 2.11.0", "mimalloc", + "perf", + "rayon", "smallvec 2.0.0-alpha.11", ] @@ -2548,6 +2610,15 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +[[package]] +name = "perf" +version = "0.0.1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.101", +] + [[package]] name = "pest" version = "2.8.1" @@ -2599,7 +2670,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" dependencies = [ "fixedbitset", - "indexmap 2.9.0", + "indexmap 2.11.0", ] [[package]] @@ -2761,7 +2832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac26e981c03a6e53e0aee43c113e3202f5581d5360dae7bd2c70e800dd0451d" dependencies = [ "base64 0.22.1", - "indexmap 2.9.0", + "indexmap 2.11.0", "quick-xml", "serde", "time", @@ -2983,6 +3054,12 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.7.3" @@ -3099,6 +3176,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.12" @@ -3424,7 +3521,7 @@ dependencies = [ "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.9.0", + "indexmap 2.11.0", "serde", "serde_derive", "serde_json", @@ -3450,7 +3547,7 @@ version = "0.9.34+deprecated" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.11.0", "itoa 1.0.15", "ryu", "serde", @@ -3563,9 +3660,6 @@ name = "smallvec" version = "2.0.0-alpha.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87b96efa4bd6bdd2ff0c6615cc36fc4970cbae63cfd46ddff5cee35a1b4df570" -dependencies = [ - "serde", -] [[package]] name = "smartstring" @@ -3796,6 +3890,12 @@ dependencies = [ "syn 2.0.101", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "target-lexicon" version = "0.12.16" @@ -4089,7 +4189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d321dbc6f998d825ab3f0d62673e810c861aac2d0de2cc2c395328f1d113b4" dependencies = [ "embed-resource", - "indexmap 2.9.0", + "indexmap 2.11.0", "toml", ] @@ -4273,7 +4373,7 @@ version = "0.19.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.11.0", "toml_datetime", "winnow 0.5.40", ] @@ -4284,7 +4384,7 @@ version = "0.20.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70f427fce4d84c72b5b732388bf4a9f4531b53f74e2887e3ecb2481f68f66d81" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.11.0", "toml_datetime", "winnow 0.5.40", ] @@ -4295,7 +4395,7 @@ version = "0.22.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "310068873db2c5b3e7659d2cc35d21855dbafa50d1ce336397c666e3cb08137e" dependencies = [ - "indexmap 2.9.0", + "indexmap 2.11.0", "serde", "serde_spanned", "toml_datetime", @@ -5349,6 +5449,15 @@ dependencies = [ "x11-dl", ] +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "x11" version = "2.21.0" diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/Cargo.toml b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/Cargo.toml index 34459ef3e8ae6475babd889136eae33d7eb4b1bb..f7bc6a80860656b1a05e575f0a6f00872c1043ef 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/Cargo.toml +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/Cargo.toml @@ -1,10 +1,14 @@ [package] -name = "layout" -version = "0.0.1" edition = "2024" +name = "layout" +version = "0.0.5" [dependencies] -mimalloc = { workspace = true } -smallvec = { version = "2.0.0-alpha.11", features = ["serde", "std"] } -ahash = { workspace = true, features = ["serde"] } -anyhow = { workspace = true } +rayon = "1.10.0" +indexmap = "2.10.0" +dashmap = { version = "6", features = ["inline", "rayon"] } +ahash.workspace = true +perf = { path = "../../rust/perf" } +mimalloc.workspace = true +smallvec = { version = "2.0.0-alpha.11" } +bitvec = {version = "1.0"} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/acyclic.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/acyclic.rs index 78469c618da188cc67afddf17edf4dc70aa65a97..a5c2aaec1619ffdc1607c56fa9d0eb249a874de5 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/acyclic.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/acyclic.rs @@ -1,199 +1,67 @@ -//! Graph acyclification utilities for removing and restoring cycles. -//! -//! This module provides algorithms to convert cyclic graphs to DAGs (Directed Acyclic Graphs) -//! by finding feedback arc sets (FAS), with support for cycle restoration. Implemented algorithms: -//! - Depth-First Search (DFS) based FAS detection (`dfs_fas`) -//! - Greedy FAS algorithm (unimplemented placeholder) -//! -//! # Key Concepts -//! - **Feedback Arc Set**: Set of edges whose removal makes the graph acyclic -//! - **Edge Reversal**: Strategy to maintain graph connectivity while breaking cycles - -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use std::mem; use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; -use std::cmp::Reverse; - -use super::{Edge, Graph, Key}; -use crate::Acyclicer::*; -impl Graph { - /// Converts the graph to a directed acyclic graph (DAG) by reversing edges. - /// - /// The algorithm used depends on the configured `acyclicer`: - /// - Greedy: Uses greedy heuristic (currently unimplemented) - /// - Default: Uses depth-first search (DFS) to find feedback arc set - /// - /// # Behavior - /// 1. Identifies feedback edges using selected algorithm - /// 2. Removes feedback edges from original orientation - /// 3. Reinserts edges in reversed orientation with `reversed` flag set - /// - /// # Notes - /// - Original graph structure can be restored with [`restore_cycles`] - /// - Modified edges maintain their metadata with `reversed` marker - pub(super) fn make_acyclic(&mut self) { - let Some(edges) = (match self.config.acyclicer { - Greedy => Some(self.greedy_fas()), - Dfs | NoAcyclicer => Some(self.dfs_fas()), - }) else { - return; - }; +use crate::{EdgeKey, GraphEdge, LayoutGraph, NodeKey}; - for edge in edges { - if let Some(mut graph_edge) = self.edge1(edge).cloned() { - self.remove_edge1(edge); - graph_edge.reversed = true; - self.set_edge(edge.target, edge.source, Some(graph_edge)); - } +impl LayoutGraph { + pub(super) fn acyclic_run(&mut self) { + let fas = self.dfs_fas(); + for ek in fas { + let Some(edge) = self.remove_edge(&ek.source, &ek.target) else { continue }; + self.acyclic.push(ek); + self.set_edge(ek.target, ek.source, Some(edge)); } } - /// Finds feedback arc set using depth-first search traversal. - /// - /// # Algorithm - /// 1. Maintains two visitation states: global and current traversal - /// 2. Tracks back edges during DFS that connect to already-visited nodes - /// 3. Collects these back edges as the feedback arc set - /// - /// # Complexity - /// - Time: O(V + E) - /// - Space: O(V) - fn dfs_fas(&mut self) -> Vec { - let mut fas: Vec = vec![]; - let mut visited: HashSet = HashSet::new(); - /// (node_id, out_edges, edge_index) - let mut stack: Vec<(Key, Vec, usize)> = vec![]; - - for &key in self.nodes.keys() { - if visited.contains(&key) { - continue; - } - - let mut local_stack: Vec = vec![key]; - let mut local_visited: HashSet = HashSet::new(); - local_visited.insert(key); - - while let Some(current_node) = local_stack.pop() { - if visited.insert(current_node) { - let out_edges = self.out_edges(¤t_node); - stack.push((current_node, out_edges, 0)); - } - - if let Some((_, out_edges, edge_index)) = stack.last_mut() { - if *edge_index < out_edges.len() { - let edge = out_edges[*edge_index]; - *edge_index += 1; - match local_visited.insert(edge.target) { - true => local_stack.push(edge.target), - false => fas.push(edge), - } - } else { - stack.pop(); - } - } - } + fn dfs_inner( + &mut self, + node_id: NodeKey, + stack: &mut HashMap, + visited: &mut HashSet, + fas: &mut Vec, + ) { + if visited.contains(&node_id) { + return; } - fas - } - - /// Restores graph to the original cyclic state - /// by reversing previously modified edges. - /// - /// # Operation - /// 1. Scans all edges in graph - /// 2. Flip any edge with `reversed = true` back to original orientation - /// 3. Maintains all edge attributes during reversal - /// - /// # Invariants - /// - After execution, all edges will have `reversed = false` - /// - Graph topology returns to pre-acyclification state - pub(super) fn restore_cycles(&mut self) -> Option<()> { - for e in self.edges() { - let edge = self.edge_mut1(e)?; - if edge.reversed { - let mut label = edge.clone(); - label.reversed = false; - self.set_edge(e.target, e.source, Some(label)); + visited.insert(node_id); + stack.insert(node_id, true); + let out_edges = self.out_edges(&node_id); + for ek in out_edges.into_iter() { + if stack.contains_key(&ek.target) { + fas.push(ek); + } else { + self.dfs_inner(ek.target, stack, visited, fas); } } - - None + stack.remove(&node_id); } - fn greedy_fas(&mut self) -> Vec { - let mut in_deg: HashMap = HashMap::new(); - let mut out_deg: HashMap = HashMap::new(); - let mut src_edges: HashMap> = HashMap::new(); + fn dfs_fas(&mut self) -> Vec { + let mut fas: Vec = vec![]; + let mut stack = HashMap::new(); + let mut visited = HashSet::new(); - for edge in self.edges() { - *out_deg.entry(edge.source).or_insert(0) += 1; - *in_deg.entry(edge.target).or_insert(0) += 1; - src_edges.entry(edge.source).or_default().push(edge); + for node_id in self.nodes() { + self.dfs_inner(node_id, &mut stack, &mut visited, &mut fas); } + fas + } - let mut nodes: Vec = self.nodes.keys().copied().collect(); - nodes.sort_by_cached_key(|&n| { - let out = out_deg.get(&n).unwrap_or(&0); - let ins = in_deg.get(&n).unwrap_or(&0); - ((*out as isize - *ins as isize).abs(), Reverse(n)) - }); - - let mut fas = Vec::new(); - let mut removed = HashSet::new(); - - while let Some(node) = nodes.pop() { - if removed.contains(&node) || !out_deg.contains_key(&node) { - continue; - } + pub(super) fn acyclic_undo(&mut self) { + let edges_ptr = &self.edges as *const HashMap; - let out = *out_deg.get(&node).unwrap_or(&0); - let ins = *in_deg.get(&node).unwrap_or(&0); + unsafe { + let edges_ref = &*edges_ptr; - if out > ins { - if let Some(edges) = src_edges.get(&node) { - for edge in edges { - if !removed.contains(&edge.target) { - fas.push(*edge); - in_deg.entry(edge.target).and_modify(|x| *x -= 1); - } - } - } - } else { - for edge in self.in_edges(&node) { - if !removed.contains(&edge.source) { - fas.push(edge); - out_deg.entry(edge.source).and_modify(|x| *x -= 1); - } + for ek in mem::take(&mut self.acyclic) { + let mut ge = self.remove_edge(&ek.source, &ek.target).unwrap(); + if let Some(points) = &mut ge.points { + points.reverse() } + self.set_edge(ek.target, ek.source, Some(ge)) } - - removed.insert(node); - out_deg.remove(&node); - in_deg.remove(&node); - - nodes.sort_by_cached_key(|&n| { - let out = out_deg.get(&n).unwrap_or(&0); - let ins = in_deg.get(&n).unwrap_or(&0); - ((*out as isize - *ins as isize).abs(), Reverse(n)) - }); } - - fas } - } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/add_border_segments.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/add_border_segments.rs new file mode 100644 index 0000000000000000000000000000000000000000..902fa095414f7adc95edb44dc571d275c3b9341e --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/add_border_segments.rs @@ -0,0 +1,55 @@ +use ahash::{HashMap, HashMapExt}; + +use crate::{Dummy, GRAPH_NODE, GraphEdge, GraphNode, LayoutGraph, NodeKey, graph::NodeBorder}; + +impl LayoutGraph { + pub(super) fn add_border_segments(&mut self) { + fn dfs(v: &NodeKey, g: &mut LayoutGraph) { + let children = g.children(v); + if children.len() > 0 { + for cv in children.iter() { + dfs(cv, g); + } + } + let node_borders_ptr = &mut g.node_borders as *mut HashMap; + unsafe { + let node_borders_mut = &mut *node_borders_ptr; + + let node = g.node_mut(v).unwrap(); + if let Some(limit) = node.rank_limit { + node_borders_mut.insert(*v, NodeBorder::default()); + let mut rank = limit.min; + let max_rank = limit.max + 1; + while rank < max_rank { + g.add_border_node(v, rank); + rank += 1; + } + } + } + } + + let children = self.children(&GRAPH_NODE); + for v in children.iter() { + dfs(v, self); + } + } + + fn add_border_node(&mut self, sg: &NodeKey, rank: i32) { + let mut node = GraphNode::default(); + node.rank = Some(rank); + node.border_at_left = true; + + let curr = self.add_dummy_node(Dummy::Border, node); + + let sg_border = self.node_borders.get_mut(sg).unwrap(); + + sg_border.left.insert(rank, curr); + + if let Some(&prev) = sg_border.left.get(&(rank - 1)) { + let graph_edge = GraphEdge { weight: 1.0, ..GraphEdge::default() }; + self.set_edge(prev, curr, Some(graph_edge)); + } + + self.set_parent(&curr, Some(*sg)); + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/algo/mod.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/algo/mod.rs deleted file mode 100644 index 619096132e79e41bb5e5110a8b65c4d52090f4ac..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/algo/mod.rs +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::{HashSetExt, HashSet}; - -use crate::{Graph, Key}; - -impl Graph { - #[inline] - pub(super) fn preorder(&self, keys: &[Key]) -> Vec { - self.traverse(keys, false) - } - - #[inline] - pub(super) fn postorder(&self, keys: &[Key]) -> Vec { - self.traverse(keys, true) - } - - fn traverse(&self, keys: &[Key], postorder: bool) -> Vec { - let mut acc: Vec = Vec::with_capacity(keys.len() * 2); - let mut visited: HashSet = HashSet::new(); - let mut stack = vec![]; - - for &key in keys { - if visited.contains(&key) { - continue; - } - stack.push(key); - - while let Some(curr) = stack.pop() { - if visited.insert(curr) { - if !postorder { - acc.push(curr); - } - - let mut neighbors = self.navigation(&curr); - if !postorder { - neighbors.reverse(); - } - stack.extend(neighbors); - } - } - } - - if postorder { - acc.reverse(); - } - - acc - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/coordinate_system.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/coordinate_system.rs index 8630a3a2104368daf87de34918b11a8f184d2842..0fe33b749c6b6d1ceaa11ac5f08ce0860f27379c 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/coordinate_system.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/coordinate_system.rs @@ -1,75 +1,59 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - use std::mem; -use crate::{graph::Graph, RankDir::*}; +use crate::{LayoutGraph, RankDir::*}; -impl Graph { +impl LayoutGraph { pub fn coordinate_adjust(&mut self) { - let rank_dir = self.config.rankdir; - + let rank_dir = self.config.rank_dir; if rank_dir == LR || rank_dir == RL { self.swap_width_height(); } } - pub fn undo_coordinate_adjust(&mut self) { - let rank_dir = self.config.rankdir; - + pub fn coordinate_undo(&mut self) { + let rank_dir = self.config.rank_dir; if rank_dir == BT || rank_dir == RL { self.reverse_y(); } if rank_dir == LR || rank_dir == RL { - self.swap_xy(); + self.swap_x_y(); self.swap_width_height(); } } fn swap_width_height(&mut self) { for node in self.nodes.values_mut() { - mem::swap(&mut node.width, &mut node.height); + mem::swap(&mut node.width, &mut node.height) } } fn reverse_y(&mut self) { for node in self.nodes.values_mut() { - node.y = -node.y; + node.y = -node.y } - for edge in self.edge_values.values_mut() { + for edge in self.edges.values_mut() { if let Some(points) = &mut edge.points { for point in points { - point.y = -point.y; + point.y = -point.y } } } } - fn swap_xy(&mut self) { + fn swap_x_y(&mut self) { for node in self.nodes.values_mut() { - mem::swap(&mut node.x, &mut node.y); + mem::swap(&mut node.x, &mut node.y) } - for edge in self.edge_values.values_mut() { + for edge in self.edges.values_mut() { if let Some(points) = &mut edge.points { for point in points { - mem::swap(&mut point.x, &mut point.y); + mem::swap(&mut point.x, &mut point.y) } } } } } + diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/config.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/config.rs deleted file mode 100644 index b8222df9237daeebd1c880a51b5472e17456869f..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/config.rs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::{Acyclicer, Acyclicer::NoAcyclicer, RankDir, RankDir::TB, Ranker, Ranker::TightTree}; - -#[derive(Debug, Copy, Clone)] -pub struct GraphConfig { - pub nodesep: f32, - pub edgesep: f32, - pub ranksep: f32, - pub rankdir: RankDir, - pub acyclicer: Acyclicer, - pub ranker: Ranker, - pub node_rank_factor: f32, -} - -impl Default for GraphConfig { - fn default() -> Self { - Self { - nodesep: 20.0, - edgesep: 20.0, - ranksep: 20.0, - rankdir: TB, - acyclicer: NoAcyclicer, - ranker: TightTree, - node_rank_factor: 0.0, - } - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..5345c83af002c10c6d7156f5c6e2388e043f96b9 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/graph.rs @@ -0,0 +1,260 @@ +use std::collections::LinkedList; +use std::hash::Hash; + +use crate::{keyof, unique_id, Dummy, EdgeKey, GraphConfig, GraphEdge, GraphNode, NodeKey, Point, EMPTY_NODEKEY, GRAPH_NODE}; +use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; + +#[derive(Debug, Clone)] +pub(crate) struct DummyChain { + pub(crate) chain: LinkedList, + pub(crate) ek: EdgeKey, + pub(crate) edge: GraphEdge, +} + +impl DummyChain { + pub(crate) fn with_capacity(capacity: usize, ek: EdgeKey, edge: GraphEdge) -> Self { + Self { chain: LinkedList::new(), ek, edge } + } +} + +pub(crate) struct SelfEdge { + pub(crate) ek: EdgeKey, + pub(crate) edge: GraphEdge, +} + +impl SelfEdge { + pub(crate) fn new(ek: EdgeKey, edge: GraphEdge) -> Self { + Self { ek, edge } + } +} + +#[derive(Default)] +pub(crate) struct NodeBorder { + pub(crate) top: Option, + pub(crate) bottom: Option, + pub(crate) left: HashMap, + pub(crate) right: HashMap, +} + +#[derive(Default)] +pub struct LayoutGraph { + pub config: GraphConfig, + pub nodes: HashMap, + pub edges: HashMap, + pub(crate) predecessors: HashMap>, + pub(crate) successors: HashMap>, + parent_map: HashMap, + children_map: HashMap>, + pub(crate) dummy_chains: Vec, + pub(crate) acyclic: Vec, + pub(crate) nesting_edges: Vec, + pub(crate) self_edges: HashMap>, + pub(crate) node_borders: HashMap, +} + +impl LayoutGraph { + pub fn nodes(&self) -> Vec { + self.nodes.keys().copied().collect() + } + + pub fn set_node(&mut self, key: NodeKey, value: Option) { + if self.nodes.contains_key(&key) { + if let Some(value) = value { + self.nodes.insert(key, value); + } + return; + } + + self.nodes.insert(key, value.unwrap_or_default()); + + self.predecessors.insert(key, HashSet::new()); + self.successors.insert(key, HashSet::new()); + } + + pub fn node(&self, key: &NodeKey) -> Option<&GraphNode> { + self.nodes.get(key) + } + + pub fn node_mut(&mut self, key: &NodeKey) -> Option<&mut GraphNode> { + self.nodes.get_mut(key) + } + + pub fn has_node(&self, key: &NodeKey) -> bool { + self.nodes.contains_key(key) + } + + pub fn remove_node(&mut self, key: &NodeKey) -> Option { + match self.nodes.remove(key) { + None => None, + val => { + if let Some(sources) = self.predecessors.remove(key) { + for source in sources { + self.remove_edge(&source, key); + } + }; + + if let Some(sinks) = self.successors.remove(key) { + for sink in sinks { + self.remove_edge(key, &sink); + } + }; + + val + } + } + } + + pub(crate) fn remove_dummy(&mut self, key: &NodeKey) -> Point { + unsafe { + let node = self.nodes.remove(key).unwrap_unchecked(); + + let sources = self.predecessors.remove(key).unwrap_unchecked(); + let sinks = self.successors.remove(key).unwrap_unchecked(); + + for source in sources { + self.remove_edge(&source, key); + } + for sink in sinks { + self.remove_edge(key, &sink); + } + + Point { x: node.x, y: node.y } + } + } + + pub fn set_parent(&mut self, key: &NodeKey, parent: Option) { + let parent = parent.unwrap_or(GRAPH_NODE); + self.set_node(parent, None); + + self.set_node(*key, None); + self.remove_from_parents_child_list(key); + self.parent_map.insert(*key, parent); + self.children_map.entry(parent).or_default().insert(*key); + } + + pub fn remove_from_parents_child_list(&mut self, key: &NodeKey) { + if let Some(parent) = self.parent_map.get(key) { + if let Some(children) = self.children_map.get_mut(parent) { + children.remove(key); + } + } + } + + pub fn parent(&self, key: &NodeKey) -> Option<&NodeKey> { + None + } + + pub fn children(&self, key: &NodeKey) -> Vec { + if key == &GRAPH_NODE { + return self.nodes.keys().copied().collect(); + } + vec![] + } + + pub fn successors(&self, key: &NodeKey) -> Vec { + self.successors + .get(key) + .map(|x| x.into_iter().copied().collect()) + .unwrap_or_default() + } + + pub fn next_successor(&self, key: &NodeKey) -> NodeKey { + self.successors + .get(key) + .and_then(|x| x.into_iter().next()) + .copied() + .unwrap_or(EMPTY_NODEKEY) + } + + pub fn edges(&self) -> Vec { + self.edges.keys().copied().collect() + } + + pub fn set_edge(&mut self, source: NodeKey, target: NodeKey, value: Option) { + let ek = keyof(source, target); + if self.edges.contains_key(&ek) { + if let Some(value) = value { + self.edges.insert(ek, value); + } + return; + } + + // It didn't exist, so we need to create it. + // First ensure the nodes exist. + // self.set_node(source, None); + // self.set_node(target, None); + + self.edges.insert(ek, value.unwrap_or_default()); + + if let Some(preds) = self.predecessors.get_mut(&target) { + preds.insert(source); + } + if let Some(sucs) = self.successors.get_mut(&source) { + sucs.insert(target); + } + } + + pub(crate) fn add_dummy_node(&mut self, node_type: Dummy, mut node: GraphNode) -> NodeKey { + let mut node_id = unique_id(); + while self.has_node(&node_id) { + node_id = unique_id(); + } + + node.dummy = Some(node_type); + self.set_node(node_id, Some(node)); + node_id + } + + pub fn add_dummy_edge(&mut self, source: NodeKey, target: NodeKey, value: GraphEdge) { + let ek = keyof(source, target); + + self.edges.insert(ek, value); + + if let Some(preds) = self.predecessors.get_mut(&target) { + preds.insert(source); + } + if let Some(sucs) = self.successors.get_mut(&source) { + sucs.insert(target); + } + } + + pub fn remove_edge(&mut self, source: &NodeKey, target: &NodeKey) -> Option { + let ek = keyof(*source, *target); + if let Some(edge) = self.edges.remove(&ek) { + let v = ek.source; + let w = ek.target; + if let Some(sources) = self.predecessors.get_mut(&w) { + sources.remove(&v); + } + if let Some(sinks) = self.successors.get_mut(&v) { + sinks.remove(&w); + } + + return Some(edge); + } + + None + } + + pub fn out_edges(&self, key: &NodeKey) -> Vec { + if let Some(out_edges) = self.successors.get(key) { + return out_edges.into_iter().map(|s| keyof(*key, *s)).collect(); + } + + vec![] + } + + pub fn in_edges_iter(&self, key: &NodeKey) -> impl Iterator { + self.predecessors + .get(key) + .into_iter() + .flat_map(|map| map.into_iter().map(|s| keyof(*s, *key))) + } + + pub fn out_edges_iter(&self, key: &NodeKey) -> impl Iterator { + self.successors + .get(key) + .into_iter() + .flat_map(|map| map.into_iter().map(|s| keyof(*key, *s))) + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/key.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/key.rs deleted file mode 100644 index b8576f7cbc85d054d1bb4c3ba881d19d403192c1..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/key.rs +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -/// ## Performance -/// #### String -/// it's challenging to avoid a large number of clone operations, -/// and performance deteriorates sharply. -/// #### SmartString -/// [`SmartString::clone`] usually copies 24 bytes with [`clone`] everywhere. -/// In addition, [`SmartString`] doesn't derive [`Copy`] trait. -/// #### usize -/// only 8 bytes need to be copied. -/// All parameters are passed by value, -/// and the number of clones in the code is greatly reduced. -/// At the same time, most struct can also derive the [`Copy`] trait. -pub type Key = usize; - -#[inline] -pub fn normalize_st(s: Key, t: Key) -> (Key, Key) { - if t < s { (t, s) } else { (s, t) } -} - -pub trait KeyCodecExt { - fn of(source: Key, target: Key) -> Key; - - fn source(self) -> Key; - - fn target(self) -> Key; - - fn decode(self) -> (Key, Key); -} - -/// # Memory Layout -/// -/// The **Key** is represented using 64 bits, with the following layout: -/// -/// | Field | Size (bits) | Description | -/// |-----------------|-------------|--------------------------------------------------| -/// | Reserved | 16 | Reserved and unused.| -/// | Usable | 48 | Used to store keys.| -/// | | | - For `NodeKey`, it occupies the higher 24 bits. | -/// | | | - For `EdgeKey`, source and target each occupy 24 bits.| -/// -/// This allows handling up to 16 million nodes, which is enough for all scenarios. -impl KeyCodecExt for Key { - #[inline] - fn of(source: Key, target: Key) -> Key { - source.wrapping_shl(24) + target - } - - #[inline] - fn source(self) -> Key { - self >> 24 - } - - #[inline] - fn target(self) -> Key { - self & 0xFFFFFF - } - - #[inline] - fn decode(self) -> (Key, Key) { - let s = self >> 24; - let t = self & 0xFFFFFF; - - (s, t) - } -} - -pub const EMPTY_KEY: Key = 1 << 48; -pub const EMPTY_ROOT: Key = EMPTY_KEY + 1; diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/mod.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/mod.rs index e3a09e01ce8bdf98cce7c1fc2182244bae5e2d56..1eca45b352075ff782f84090e25fe64d08f87cec 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/mod.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/mod.rs @@ -1,432 +1,9 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -mod config; -mod key; -mod node_edge; - -use std::{cmp::PartialEq, fmt::Debug}; - -use ahash::{HashMap, HashMapExt}; -pub use config::*; -pub use key::*; -pub use node_edge::*; - -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Edge { - pub source: Key, - pub target: Key, -} - -impl Edge { - #[inline] - pub fn of(source: Key, target: Key) -> Self { - Self { source, target } - } - - #[inline] - pub fn to_key(self) -> Key { - Key::of(self.source, self.target) - } -} - -/// ### Compiler -/// Currently all hashMaps are stored in this structure. -/// -/// It's challenging to avoid -/// **can't borrow as mutable while borrowed as immutable**. -/// -/// ### Performance -/// Currently, almost all data is stored in [`HashMap`]. -/// There may be a better solution. -/// Node's Key is usize, which is naturally an array index. -/// If store all Nodes in Vec, -/// which can achieve extremely high performance. -/// -/// At the same time, there are some points to note: -/// -/// 1. Vec should store [`Option`] instead of Node, -/// or add an [`is_deleted`] field to Node, -/// so that better cache affinity and performance can be obtained, -/// and the code is more concise. -/// -/// 2. Under the premise of the first point, -/// we can convert all insert operations into push, -/// so that there's no [`Memory-Move`]. -/// -/// 3. Implement [`Indexing`] is needed -/// while the expansion of 10w+ Nodes is a disaster. -/// In our scenario, only one level of indexing is needed. -/// The maximum capacity of each array can be 1024. -/// Use [`LinkedList`] to store these arrays. -/// -/// 4. After implementing [`Indexing`] -/// almost all operations have a [`branch-judgment`]. -/// We should think of some ways to eliminate this performance consumption. -/// Cumbersome and repetitive code is also acceptable. -/// Or use [`likely-unlikely`] to prompt the compiler -#[derive(Debug, Default)] -pub struct Graph { - pub is_directed: bool, - pub is_compound: bool, - pub config: GraphConfig, - pub width: f32, - pub height: f32, - pub nodes: HashMap, - pub in_map: HashMap>, - pub predecessors: HashMap>, - pub out_map: HashMap>, - pub successors: HashMap>, - pub edges: HashMap, - pub edge_values: HashMap, - pub parent_map: HashMap, - pub children_map: HashMap>, - pub selfedge_map: HashMap>, - pub nesting_root: Option, - pub root: Option, - pub dummy_chains: Option>, -} - -impl Graph { - #[inline] - fn of() -> Self { - Self { is_directed: true, ..Self::default() } - } -} - -impl Graph { - #[inline] - pub fn new(directed: bool, compound: bool) -> Self { - let mut graph = Self::of(); - - graph.is_directed = directed; - graph.is_compound = compound; - - if compound { - graph.parent_map = HashMap::new(); - graph.children_map = HashMap::new(); - graph.children_map.insert(EMPTY_ROOT, vec![]); - } - - graph - } - - #[inline] - pub fn nodes(&self) -> Vec { - self.nodes.keys().copied().collect() - } - - #[inline] - pub fn sources(&self) -> Vec { - self.nodes - .keys() - .filter(|&n| self.in_map.get(n).map_or(true, |m| m.is_empty())) - .copied() - .collect() - } - - #[inline] - pub fn sinks(&self) -> Vec { - self.nodes - .keys() - .filter(|&n| self.out_map.get(n).map_or(true, |m| m.is_empty())) - .copied() - .collect() - } - - #[inline] - pub fn set_node(&mut self, key: Key, value: Option) -> &mut Self { - if self.nodes.contains_key(&key) { - value.map(|new_node| self.nodes.insert(key, new_node)); - return self; - } - - self.nodes.insert(key, value.unwrap_or_default()); - - if self.is_compound { - self.parent_map.insert(key, EMPTY_ROOT); - self.children_map.insert(key, vec![]); - self.children_map.entry(EMPTY_ROOT).or_default(); - } - - self.in_map.insert(key, vec![]); - self.predecessors.insert(key, HashMap::new()); - self.out_map.insert(key, vec![]); - self.successors.insert(key, HashMap::new()); - - self - } - - #[inline] - pub fn node(&self, id: &Key) -> Option<&GraphNode> { - self.nodes.get(id) - } - - #[inline] - pub fn node_mut(&mut self, id: &Key) -> Option<&mut GraphNode> { - self.nodes.get_mut(id) - } - - #[inline] - pub fn has_node(&self, id: &Key) -> bool { - self.nodes.contains_key(id) - } - - pub fn remove_node(&mut self, id: &Key) { - if let Some(_) = self.nodes.remove(id) { - if self.is_compound { - self.remove_from_parents_child_list(id); - self.parent_map.remove(id); - for child_id in self.children(id) { - self.set_parent(child_id, None); - } - self.children_map.remove(id); - } - - self.in_map.remove(id).map(|in_edges| { - for edge in in_edges { - self.remove_edge1(edge); - } - }); - - self.predecessors.remove(id); - - self.out_map.remove(id).map(|out_edges| { - for edge in out_edges { - self.remove_edge1(edge); - } - }); - - self.successors.remove(id); - } - } - - pub fn set_parent(&mut self, id: Key, parent: Option) -> &mut Self { - let ancestor = match parent { - Some(p) => { - let mut current = p; - while let Some(new_ancestor) = self.parent(¤t) { - current = new_ancestor; - } - current - } - None => EMPTY_ROOT, - }; - - self.set_node(id, None); - self.remove_from_parents_child_list(&id); - self.parent_map.insert(id, ancestor); - self.children_map.entry(ancestor).or_default().push(id); - - self - } - - #[inline] - fn remove_from_parents_child_list(&mut self, id: &Key) { - self.parent_map - .get(id) - .map(|p| self.children_map.get_mut(p).map(|c| c.retain(|c| c != id))); - } - - #[inline] - pub fn parent(&self, id: &Key) -> Option { - self.is_compound - .then(|| self.parent_map.get(id).filter(|&&p| p != EMPTY_ROOT).copied())? - } - - pub fn children(&self, id: &Key) -> Vec { - match (self.is_compound, id == &EMPTY_ROOT) { - (true, _) => self - .children_map - .get(id) - .map_or(vec![], |children| children.iter().copied().collect()), - (false, true) => self.nodes.keys().copied().collect(), - _ => vec![], - } - } - - #[inline] - pub fn predecessors(&self, id: &Key) -> Vec { - self.predecessors.get(id).unwrap().keys().copied().collect() - } - - #[inline] - pub fn successors(&self, id: &Key) -> Vec { - self.successors.get(id).unwrap().keys().copied().collect() - } - - #[inline] - pub fn neighbors(&self, id: &Key) -> Vec { - let mut ret = self.predecessors(id); - ret.extend(self.successors(id)); - ret - } - - #[inline] - pub fn navigation(&self, id: &Key) -> Vec { - if self.is_directed { self.successors(id) } else { self.neighbors(id) } - } - - #[inline] - pub fn edges(&self) -> Vec { - self.edges.values().copied().collect() - } - - pub fn set_edge(&mut self, source: Key, target: Key, edge: Option) -> &mut Self { - let key = Key::of(source, target); - if self.edge_values.contains_key(&key) { - if let Some(edge) = edge { - self.edge_values.insert(key, edge); - } - return self; - } - - self.set_node(source, None); - self.set_node(target, None); - - if let Some(mut edge) = edge { - edge.source = source; - edge.target = target; - self.edge_values.insert(key, edge); - } else { - self.edge_values.insert(key, GraphEdge::of(source, target)); - } - - let edge = Edge::of(source, target); - - self.edges.insert(key, edge); - if let Some(preds) = self.predecessors.get_mut(&target) { - preds.entry(source).and_modify(|c| *c += 1).or_insert(1); - } - if let Some(succ) = self.successors.get_mut(&source) { - succ.entry(target).and_modify(|c| *c += 1).or_insert(1); - } - - self.in_map.entry(target).or_default().push(edge); - self.out_map.entry(source).or_default().push(edge); - - self - } - - #[inline] - pub fn set_edge_undirected( - &mut self, - source: Key, - target: Key, - edge: Option, - ) -> &mut Self { - let (source, target) = normalize_st(source, target); - - self.set_edge(source, target, edge) - } - - #[inline] - pub fn edge(&self, source: Key, target: Key) -> Option<&GraphEdge> { - let key = Key::of(source, target); - self.edge_values.get(&key) - } - - #[inline] - pub fn edge_mut(&mut self, source: Key, target: Key) -> Option<&mut GraphEdge> { - let key = Key::of(source, target); - self.edge_values.get_mut(&key) - } - - #[inline] - pub fn has_edge(&self, source: Key, target: Key) -> bool { - let key = Key::of(source, target); - self.edge_values.contains_key(&key) - } - - pub fn remove_edge(&mut self, source: Key, target: Key) -> &mut Self { - let key = Key::of(source, target); - - if let Some(edge) = self.edges.get(&key) { - let s = &edge.source; - let t = &edge.target; - - if let Some(in_edges) = self.in_map.get_mut(t) { - in_edges.retain(|e| e != edge) - } - if let Some(out_edges) = self.out_map.get_mut(s) { - out_edges.retain(|e| e != edge) - } - - if let Some(pred) = self.predecessors.get_mut(t) { - decrement_or_remove(pred, &s) - } - - if let Some(suc) = self.successors.get_mut(s) { - decrement_or_remove(suc, &t) - } - - self.edge_values.remove(&key); - self.edges.remove(&key); - } - - self - } - - #[inline] - pub fn edge1(&self, edge: Edge) -> Option<&GraphEdge> { - self.edge(edge.source, edge.target) - } - - #[inline] - pub fn edge_mut1(&mut self, edge: Edge) -> Option<&mut GraphEdge> { - self.edge_mut(edge.source, edge.target) - } - - #[inline] - pub fn set_edge1( - &mut self, - Edge { source, target }: Edge, - edge: Option, - ) -> &mut Self { - self.set_edge(source, target, edge) - } - - #[inline] - pub fn remove_edge1(&mut self, edge: Edge) -> &mut Self { - self.remove_edge(edge.source, edge.target) - } - - #[inline] - pub fn in_edges(&self, key: &Key) -> Vec { - self.in_map[key].clone() - } - - #[inline] - pub fn out_edges(&self, key: &Key) -> Vec { - self.out_map[key].clone() - } - - #[inline] - pub fn node_edges(&self, key: &Key) -> Vec { - let mut ret = self.in_edges(key); - ret.extend(self.out_edges(key)); - ret - } -} - -#[inline] -fn decrement_or_remove(map: &mut HashMap, k: &Key) { - if let Some(value) = map.get_mut(k) { - *value -= 1; - if *value <= 0 { - map.remove(k); - } - } -} +pub mod graph; +pub mod node; +pub mod position_graph; +pub mod rank_graph; + +pub use graph::*; +pub use node::*; +pub use position_graph::*; +pub use rank_graph::*; diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/node.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/node.rs new file mode 100644 index 0000000000000000000000000000000000000000..1e64419e7d5f6bfb99985dc5f0417b599d510bc7 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/node.rs @@ -0,0 +1,133 @@ +use std::{ + hash::{Hash, Hasher}, + sync::atomic::{AtomicU32, Ordering}, +}; + +use smallvec::SmallVec; + +pub type NodeKey = u32; + +pub const GRAPH_NODE: NodeKey = u32::MAX; +pub const EMPTY_NODEKEY: NodeKey = u32::MAX - 1; + +static UNIQUE_COUNTER: AtomicU32 = AtomicU32::new(u32::MAX - 2); + +pub fn unique_id() -> NodeKey { + UNIQUE_COUNTER.fetch_sub(1, Ordering::Relaxed) +} + +#[derive(Debug, Clone, Copy, Default, Eq, PartialEq)] +pub struct EdgeKey { + pub source: NodeKey, + pub target: NodeKey, +} + +impl Hash for EdgeKey { + fn hash(&self, state: &mut H) { + let combined = ((self.source as u64) << 32) | (self.target as u64); + combined.hash(state); + } +} + +pub fn keyof(source: NodeKey, target: NodeKey) -> EdgeKey { + EdgeKey { source, target } +} + +#[derive(Default, Debug, Clone, Copy, Eq, PartialEq)] +pub enum Dummy { + Root, + Edge, + EdgeProxy, + Border, + #[default] + SelfEdge, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct RankLimit { + pub min: i32, + pub max: i32, +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct GraphNode { + pub x: f32, + pub y: f32, + pub width: f32, + pub height: f32, + pub dummy: Option, + pub rank: Option, + pub rank_limit: Option, + pub order: Option, + pub border_at_left: bool, +} + +impl GraphNode { + pub fn of(x: f32, y: f32, width: f32, height: f32) -> GraphNode { + GraphNode { x, y, width, height, ..GraphNode::default() } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct Point { + pub x: f32, + pub y: f32, +} + +impl Point { + #[inline] + pub fn new(x: f32, y: f32) -> Self { + Self { x, y } + } +} + +#[derive(Debug, Clone)] +pub struct GraphEdge { + pub min_len: f32, + pub weight: f32, + pub points: Option>, +} + +impl Default for GraphEdge { + fn default() -> Self { + Self { min_len: 1.0, weight: 1.0, points: None } + } +} + +impl GraphEdge { + pub fn with_weight(weight: f32) -> Self { + Self { min_len: 1.0, weight, points: None } + } +} + +#[derive(Default, Debug, Copy, Clone, Eq, PartialEq)] +pub enum RankDir { + LR, + RL, + #[default] + TB, + BT, +} + +#[derive(Debug, Clone, Copy)] +pub struct GraphConfig { + pub node_sep: f32, + pub edge_sep: f32, + pub rank_sep: f32, + pub rank_dir: RankDir, + pub nesting_root: Option, + pub node_rank_factor: Option, +} + +impl Default for GraphConfig { + fn default() -> Self { + Self { + node_sep: 50.0, + edge_sep: 20.0, + rank_sep: 50.0, + rank_dir: RankDir::TB, + nesting_root: None, + node_rank_factor: None, + } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/node_edge.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/node_edge.rs deleted file mode 100644 index 1c3553076099b938cdf47f1b252e6bcce0c23fb0..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/node_edge.rs +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use smallvec::SmallVec; - -use super::{Edge, Key, EMPTY_KEY}; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Dummy { - Root, - Border, - Edge, - EdgeProxy, - SelfEdge, -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Ranker { - TightTree, - LongestPath, - NetworkSimplex, -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum RankDir { - LR, - RL, - TB, - BT, -} - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum Acyclicer { - Greedy, - Dfs, - NoAcyclicer, -} - -#[derive(Debug, Copy, Clone, Default)] -pub struct GraphNode { - pub x: f32, - pub y: f32, - pub width: f32, - pub height: f32, - pub dummy: Option, - pub rank: Option, - pub min_rank: Option, - pub max_rank: Option, - pub order: Option, - pub border_top: Option, - pub border_bottom: Option, - pub low: Option, - pub lim: Option, - pub parent: Option, - pub edge: Option, -} - -impl GraphNode { - pub fn of(x: f32, y: f32, width: f32, height: f32) -> Self { - Self { x, y, width, height, ..GraphNode::default() } - } -} - -#[derive(Debug, Copy, Clone, Default)] -pub struct Point { - pub x: f32, - pub y: f32, -} - -impl Point { - #[inline] - pub fn of(x: f32, y: f32) -> Self { - Self { x, y } - } - - #[inline] - pub fn from(node: &GraphNode) -> Self { - Self { x: node.x, y: node.y } - } -} - -#[derive(Debug, Clone)] -pub struct GraphEdge { - pub source: Key, - pub target: Key, - pub reversed: bool, - pub minlen: Option, - pub weight: Option, - pub rank: Option, - pub nesting: bool, - pub cutvalue: Option, - /// Move this field out of the structure and manage it uniformly, - /// so GraphEdge can derive the Copy trait - pub points: Option>, -} - -impl Default for GraphEdge { - fn default() -> Self { - Self { - source: EMPTY_KEY, - target: EMPTY_KEY, - reversed: false, - minlen: Some(1), - weight: Some(1.0), - rank: None, - nesting: false, - cutvalue: None, - points: None, - } - } -} - -impl GraphEdge { - pub fn of(source: Key, target: Key) -> Self { - Self { source, target, ..Self::default() } - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/position_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/position_graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..68e2c6d1fe3545eb1b9982d4d14b3e18fa639941 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/position_graph.rs @@ -0,0 +1,83 @@ +use ahash::HashMap; + +use crate::{Dummy, GraphNode, LayoutGraph, NodeKey}; + +#[derive(Debug, Default)] +pub struct PositionGraph { + pub node_sep: f32, + pub edge_sep: f32, + pub rank_sep: f32, + pub nodes: HashMap, + pub predecessors: HashMap>, + pub successors: HashMap>, +} + +impl PositionGraph { + pub fn set_node(&mut self, key: NodeKey, value: PosGraphNode) { + self.nodes.insert(key, value); + + self.predecessors.insert(key, vec![]); + self.successors.insert(key, vec![]); + } + + pub fn node(&self, v: &NodeKey) -> Option<&PosGraphNode> { + self.nodes.get(v) + } + + pub fn predecessors(&self, key: &NodeKey) -> &[NodeKey] { + self.predecessors.get(key).map(|x| x.as_slice()).unwrap_or(&[]) + } + + pub fn successors(&self, key: &NodeKey) -> &[NodeKey] { + self.successors.get(key).map(|x| x.as_slice()).unwrap_or(&[]) + } + + pub fn set_edge(&mut self, source: NodeKey, target: NodeKey) { + if let Some(preds) = self.predecessors.get_mut(&target) { + preds.push(source); + } + if let Some(sucs) = self.successors.get_mut(&source) { + sucs.push(target); + } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct PosGraphNode { + pub y: f32, + pub width: f32, + pub height: f32, + pub dummy: Option, + pub rank: i32, + pub order: Option, + pub border_at_left: bool, +} + +impl From<&GraphNode> for PosGraphNode { + fn from( + &GraphNode { y, width, height, dummy, rank, order, border_at_left, .. }: &GraphNode, + ) -> Self { + Self { y, width, height, dummy, rank: rank.unwrap(), order, border_at_left } + } +} + +impl LayoutGraph { + pub(crate) fn as_position_graph(&mut self) -> PositionGraph { + let mut pg = PositionGraph::default(); + pg.node_sep = self.config.node_sep; + pg.edge_sep = self.config.edge_sep; + pg.rank_sep = self.config.rank_sep; + + for (key, node) in &self.nodes { + if self.children(key).is_empty() { + pg.set_node(*key, PosGraphNode::from(node)); + } + } + + for (&ek, edge) in &self.edges { + pg.set_edge(ek.source, ek.target); + } + + pg + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/rank_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/rank_graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..c68b34421b4b3244d92c300e68334f9dc4879a84 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/graph/rank_graph.rs @@ -0,0 +1,218 @@ +use std::mem; + +use ahash::{HashMap, HashSet, HashSetExt}; + +use crate::{ + EMPTY_NODEKEY, EdgeKey, GRAPH_NODE, GraphEdge, GraphNode, LayoutGraph, NodeKey, keyof, +}; + +#[derive(Debug, Default)] +pub struct RankGraph { + pub nodes: HashMap, + pub edges: HashMap, + pub predecessors: HashMap>, + pub successors: HashMap>, +} + +impl RankGraph { + pub fn nodes(&self) -> Vec { + self.nodes.keys().copied().collect() + } + + pub fn next_node(&self) -> NodeKey { + self.nodes.keys().next().copied().unwrap_or(EMPTY_NODEKEY) + } + + pub fn sources(&self) -> Vec { + self.nodes + .keys() + .filter(|n| { + if let Some(in_edges) = self.predecessors.get(n) { + return in_edges.len() == 0; + } + return true; + }) + .map(|&key| key) + .collect() + } + + pub fn set_node(&mut self, key: NodeKey, value: Option) { + if self.nodes.contains_key(&key) { + if value.is_some() { + self.nodes.insert(key, value.unwrap()); + } + return; + } + + self.nodes.insert(key, value.unwrap_or_default()); + + self.predecessors.insert(key, HashSet::new()); + self.successors.insert(key, HashSet::new()); + } + + pub fn node(&self, v: &NodeKey) -> Option<&RankGraphNode> { + self.nodes.get(v) + } + + pub fn node_mut(&mut self, v: &NodeKey) -> Option<&mut RankGraphNode> { + self.nodes.get_mut(v) + } + + pub fn children(&self, key: &NodeKey) -> Vec { + if key == &GRAPH_NODE { + return self.nodes.keys().copied().collect(); + } + vec![] + } + + pub fn predecessors(&self, key: &NodeKey) -> Vec { + self.predecessors + .get(key) + .map(|x| x.into_iter().copied().collect()) + .unwrap_or_default() + } + + pub fn successors(&self, key: &NodeKey) -> Vec { + self.successors + .get(key) + .map(|x| x.into_iter().copied().collect()) + .unwrap_or_default() + } + + pub fn neighbors(&self, key: &NodeKey) -> Vec { + let mut union: HashSet = HashSet::new(); + union.extend(self.predecessors(key)); + union.extend(self.successors(key)); + + union.into_iter().collect() + } + + pub fn edges(&self) -> Vec { + self.edges.keys().copied().collect() + } + + pub fn set_edge(&mut self, source: NodeKey, target: NodeKey, value: RankGraphEdge) { + let ek = keyof(source, target); + if self.edges.contains_key(&ek) { + self.edges.insert(ek, value); + return; + } + + // It didn't exist, so we need to create it. + // First ensure the nodes exist. + self.set_node(source, None); + self.set_node(target, None); + + self.edges.insert(ek, value); + + if let Some(preds) = self.predecessors.get_mut(&target) { + preds.insert(source); + } + if let Some(sucs) = self.successors.get_mut(&source) { + sucs.insert(target); + } + } + + pub fn edge(&self, source: &NodeKey, target: &NodeKey) -> Option<&RankGraphEdge> { + let ek = keyof(*source, *target); + self.edges.get(&ek) + } + + pub fn edge1(&self, ek: &EdgeKey) -> Option<&RankGraphEdge> { + self.edges.get(ek) + } + + pub fn edge_mut(&mut self, source: &NodeKey, target: &NodeKey) -> Option<&mut RankGraphEdge> { + let ek = keyof(*source, *target); + self.edges.get_mut(&ek) + } + + pub fn has_edge(&self, source: &NodeKey, target: &NodeKey) -> bool { + let ek = keyof(*source, *target); + self.edges.contains_key(&ek) + } + + pub fn in_edges(&self, key: &NodeKey) -> Vec { + if let Some(in_edges) = self.predecessors.get(key) { + return in_edges.into_iter().map(|s| keyof(*s, *key)).collect(); + } + + vec![] + } + + pub fn out_edges(&self, key: &NodeKey) -> Vec { + if let Some(out_edges) = self.successors.get(key) { + return out_edges.into_iter().map(|s| keyof(*key, *s)).collect(); + } + + vec![] + } + + pub fn node_edges(&self, v: &NodeKey) -> Vec { + let mut in_edges = self.in_edges(v); + in_edges.extend(self.out_edges(v)); + + in_edges + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct RankGraphNode { + pub rank: Option, + pub parent: Option, +} + +impl From<&GraphNode> for RankGraphNode { + fn from(value: &GraphNode) -> Self { + Self { rank: value.rank, parent: None } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub struct RankGraphEdge { + pub min_len: f32, + pub weight: f32, +} + +impl From<&GraphEdge> for RankGraphEdge { + fn from(value: &GraphEdge) -> Self { + Self { min_len: value.min_len, weight: value.weight } + } +} + +impl LayoutGraph { + pub(crate) fn as_rank_graph(&self) -> RankGraph { + let mut rg = RankGraph::default(); + + for (key, node) in &self.nodes { + if self.children(key).is_empty() { + rg.set_node(*key, Some(RankGraphNode::from(node))); + } + } + + for (&ek, edge) in &self.edges { + rg.set_edge(ek.source, ek.target, RankGraphEdge::from(edge)); + } + + rg + } +} + +impl RankGraph { + pub(crate) fn transfer_node_edge(&mut self, dest: &mut LayoutGraph) { + for (key, node) in mem::take(&mut self.nodes) { + if self.children(&key).is_empty() { + if let Some(tn) = dest.node_mut(&key) { + tn.rank = node.rank; + } + } + } + + for (ek, edge) in mem::take(&mut self.edges) { + if let Some(te) = dest.edges.get_mut(&ek) { + te.min_len = edge.min_len; + te.weight = edge.weight; + } + } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/iter_ext.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/iter_ext.rs new file mode 100644 index 0000000000000000000000000000000000000000..b6d49e9c153ee0133b75570fb8542370a75cf16c --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/iter_ext.rs @@ -0,0 +1,38 @@ +use std::iter::Rev; + +pub(crate) enum ConditionalRev { + Original(I), + Reversed(Rev), +} + +impl Iterator for ConditionalRev { + type Item = I::Item; + + fn next(&mut self) -> Option { + match self { + ConditionalRev::Original(iter) => iter.next(), + ConditionalRev::Reversed(rev_iter) => rev_iter.next(), + } + } + + fn size_hint(&self) -> (usize, Option) { + match self { + ConditionalRev::Original(iter) => iter.size_hint(), + ConditionalRev::Reversed(rev_iter) => rev_iter.size_hint(), + } + } +} + +pub(crate) trait RevIfExt: Iterator { + fn rev_if(self, condition: bool) -> ConditionalRev + where + Self: Sized + DoubleEndedIterator, + { + match condition { + true => ConditionalRev::Reversed(self.rev()), + false => ConditionalRev::Original(self), + } + } +} + +impl RevIfExt for I {} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs index bf91c16a290017f15e2428eb38bd940f3dddf13d..b11921a0a5af388e6e08f505ee26d6b433898a9b 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/lib.rs @@ -1,73 +1,53 @@ +#![feature(layout_for_ptr)] #![feature(let_chains)] -#![allow(unused_doc_comments)] - -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +extern crate core; mod acyclic; -mod algo; +mod add_border_segments; mod coordinate_system; pub mod graph; +mod iter_ext; +mod macros; +mod memory_profiler; mod nesting_graph; mod normalize; mod order; mod parent_dummy_chains; mod position; mod rank; -mod selfedge; -mod utils; +mod self_edge; +mod util; pub use graph::*; -use mimalloc::MiMalloc; -use utils::*; +use memory_profiler::TrackingAllocator; -/// ### Performance -/// When there are many nodes, most of the performance consumption -/// is Vec, HashMap memory allocation and memory transfer when expanding. -/// -/// And it's often memory allocation of very large objects. -/// -/// [`mimalloc`] is also challenging to achieve large performance improvement, -/// but at the initial stage of expansion, it can Improve performance #[global_allocator] -static GLOBAL: MiMalloc = MiMalloc; +static GLOBAL: TrackingAllocator = TrackingAllocator; -pub fn layout(graph: &mut Graph) { +#[perf::profile_lines] +pub fn layout(graph: &mut LayoutGraph) { graph.make_space_for_edge_labels(); graph.remove_self_edges(); - graph.make_acyclic(); - graph.nesting_run(); - let mut ncg: Graph = graph.as_non_compound(); - ncg.rank(); - ncg.transfer_node_edges(graph); + graph.acyclic_run(); + graph.nesting_graph_run(); + graph.rank(); graph.remove_empty_ranks(); - graph.nesting_cleanup(); + graph.nesting_graph_cleanup(); graph.normalize_ranks(); graph.assign_rank_min_max(); - graph.remove_edge_proxies(); - graph.normalize(); + graph.remove_edge_label_proxies(); + graph.normalize_run(); graph.parent_dummy_chains(); + graph.add_border_segments(); graph.order(); graph.insert_self_edges(); graph.coordinate_adjust(); graph.position(); graph.position_self_edges(); - graph.denormalize(); - graph.undo_coordinate_adjust(); + graph.remove_border_nodes(); + graph.normalize_undo(); + graph.coordinate_undo(); graph.translate_graph(); graph.assign_node_intersects(); - graph.reverse_points_for_reversed_edges(); - graph.restore_cycles(); + graph.acyclic_undo(); } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/macros.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/macros.rs new file mode 100644 index 0000000000000000000000000000000000000000..c58654c05941b6429c692b33c56695d3f232d7b8 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/macros.rs @@ -0,0 +1,4 @@ +#[macro_export] +macro_rules! build_layer_matrix { + () => {}; +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/memory_profiler.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/memory_profiler.rs new file mode 100644 index 0000000000000000000000000000000000000000..2d95ac08e468ac83b8ff837e6313dbded546f241 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/memory_profiler.rs @@ -0,0 +1,227 @@ +use std::{ + alloc::{GlobalAlloc, Layout, System}, + sync::atomic::{AtomicUsize, Ordering}, + time::Duration, +}; + +static ALLOCATED: AtomicUsize = AtomicUsize::new(0); +static DEALLOCATED: AtomicUsize = AtomicUsize::new(0); +static ALLOCATION_COUNT: AtomicUsize = AtomicUsize::new(0); +static DEALLOCATION_COUNT: AtomicUsize = AtomicUsize::new(0); + +pub fn reset_counters() { + ALLOCATED.store(0, Ordering::Relaxed); + DEALLOCATED.store(0, Ordering::Relaxed); + ALLOCATION_COUNT.store(0, Ordering::Relaxed); + DEALLOCATION_COUNT.store(0, Ordering::Relaxed); +} + +pub fn print_separator(title: &str) { + println!("\n{}{}=== {} ==={}\n", Colors::BOLD, Colors::FUNCTION, title, Colors::RESET); +} + +pub fn print_timing_summary(total_time: Duration) { + let time_color = if total_time.as_millis() < 100 { + Colors::TIME_FAST + } else if total_time.as_millis() < 1000 { + Colors::TIME_MEDIUM + } else { + Colors::TIME_SLOW + }; + + println!( + "{}{}Total execution time:{} {}{:?}{}", + Colors::BOLD, + Colors::LABEL, + Colors::RESET, + time_color, + total_time, + Colors::RESET + ); +} + +pub struct Colors; + +impl Colors { + pub const RESET: &'static str = "\x1b[0m"; + pub const BOLD: &'static str = "\x1b[1m"; + + pub const FUNCTION: &'static str = "\x1b[1;36m"; + pub const BRACKET: &'static str = "\x1b[1;33m"; + + pub const TIME_FAST: &'static str = "\x1b[32m"; + pub const TIME_MEDIUM: &'static str = "\x1b[33m"; + pub const TIME_SLOW: &'static str = "\x1b[31m"; + + pub const MEMORY_LOW: &'static str = "\x1b[32m"; + pub const MEMORY_MEDIUM: &'static str = "\x1b[33m"; + pub const MEMORY_HIGH: &'static str = "\x1b[31m"; + + pub const NET_POSITIVE: &'static str = "\x1b[91m"; + pub const NET_ZERO: &'static str = "\x1b[32m"; + pub const NET_NEGATIVE: &'static str = "\x1b[36m"; + + pub const LABEL: &'static str = "\x1b[37m"; +} + +#[derive(Debug, Clone, Copy)] +pub struct MemoryStats { + pub allocated: usize, + pub deallocated: usize, + pub allocation_count: usize, + pub deallocation_count: usize, +} + +impl MemoryStats { + pub fn net_allocated(&self) -> i64 { + self.allocated as i64 - self.deallocated as i64 + } +} + +impl std::ops::Sub for MemoryStats { + type Output = MemoryStatsDiff; + + fn sub(self, other: MemoryStats) -> MemoryStatsDiff { + MemoryStatsDiff { + allocated: self.allocated.saturating_sub(other.allocated), + deallocated: self.deallocated.saturating_sub(other.deallocated), + allocation_count: self.allocation_count.saturating_sub(other.allocation_count), + deallocation_count: self.deallocation_count.saturating_sub(other.deallocation_count), + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct MemoryStatsDiff { + pub allocated: usize, + pub deallocated: usize, + pub allocation_count: usize, + pub deallocation_count: usize, +} + +impl MemoryStatsDiff { + pub fn net_allocated(&self) -> i64 { + self.allocated as i64 - self.deallocated as i64 + } +} + +pub struct TrackingAllocator; + +unsafe impl GlobalAlloc for TrackingAllocator { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let ptr = System.alloc(layout); + if !ptr.is_null() { + ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed); + ALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed); + } + ptr + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + System.dealloc(ptr, layout); + DEALLOCATED.fetch_add(layout.size(), Ordering::Relaxed); + DEALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed); + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + let ptr = System.alloc_zeroed(layout); + if !ptr.is_null() { + ALLOCATED.fetch_add(layout.size(), Ordering::Relaxed); + ALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed); + } + ptr + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + let new_ptr = System.realloc(ptr, layout, new_size); + if !new_ptr.is_null() { + DEALLOCATED.fetch_add(layout.size(), Ordering::Relaxed); + DEALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed); + + ALLOCATED.fetch_add(new_size, Ordering::Relaxed); + ALLOCATION_COUNT.fetch_add(1, Ordering::Relaxed); + } + new_ptr + } +} + +pub fn get_memory_stats() -> MemoryStats { + MemoryStats { + allocated: ALLOCATED.load(Ordering::Relaxed), + deallocated: DEALLOCATED.load(Ordering::Relaxed), + allocation_count: ALLOCATION_COUNT.load(Ordering::Relaxed), + deallocation_count: DEALLOCATION_COUNT.load(Ordering::Relaxed), + } +} + +pub fn print_colored_profile( + function_name: &str, + duration: Duration, + allocated: usize, + deallocated: usize, + net: i64, +) { + let time_color = if duration.as_millis() < 1 { + Colors::TIME_FAST + } else if duration.as_millis() < 10 { + Colors::TIME_MEDIUM + } else { + Colors::TIME_SLOW + }; + + let memory_color = if allocated < 1024 { + Colors::MEMORY_LOW + } else if allocated < 1024 * 1024 { + Colors::MEMORY_MEDIUM + } else { + Colors::MEMORY_HIGH + }; + + let net_color = if net > 0 { + Colors::NET_POSITIVE + } else if net == 0 { + Colors::NET_ZERO + } else { + Colors::NET_NEGATIVE + }; + + let allocated_str = format_number(allocated); + let deallocated_str = format_number(deallocated); + let net_str = if net >= 0 { + format!("+{}", format_number(net as usize)) + } else { + format!("-{}", format_number((-net) as usize)) + }; + + println!( + "{}{}{} | {}Time: {:?}{} | {}Memory: {} allocated, {} deallocated{} | {}Net: {} bytes{}", + Colors::FUNCTION, + function_name, + Colors::RESET, + time_color, + duration, + Colors::RESET, + memory_color, + allocated_str, + deallocated_str, + Colors::RESET, + net_color, + net_str, + Colors::RESET + ); +} + +fn format_number(num: usize) -> String { + let num_str = num.to_string(); + let mut result = String::new(); + let len = num_str.len(); + + for (i, ch) in num_str.chars().enumerate() { + if i > 0 && (len - i) % 3 == 0 { + result.push(','); + } + result.push(ch); + } + + result +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/nesting_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/nesting_graph.rs index 18d26d256fada6d25813cc0d5faf7849587e37a5..9fd3e9c04a78eb1da89d84b7df83c5027dd9cbe7 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/nesting_graph.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/nesting_graph.rs @@ -1,231 +1,168 @@ -//! ### Idea from Sander's "Layout of Compound Directed Graphs." -//! -//! A nesting graph creates dummy nodes for the tops and bottoms of subgraphs, -//! adds appropriate edges to ensure that all cluster nodes are placed between -//! these boundaries, and ensures that the graph is connected. -//! -//! In addition, through the use of the minlen property, that nodes -//! and subgraph border nodes to not end up on the same rank. -//! -//! Pre-Conditions: -//! -//! 1. Input graph is a DAG -//! 2. Nodes in the input graph has a minlen attribute -//! -//! Post-Conditions: -//! -//! 1. The Input graph is connected. -//! 2. Dummy nodes are added for the tops and bottoms of subgraphs. -//! 3. The minlen attribute for nodes is adjusted to ensure nodes do not -//! get placed on the same rank as subgraph border nodes. - -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use std::mem; use ahash::{HashMap, HashMapExt}; -use crate::{ - Dummy::{Border, Root}, - EMPTY_ROOT, Graph, GraphEdge, GraphNode, Key, -}; - -impl Graph { - pub(super) fn nesting_run(&mut self) { - let root = self.init_nesting(); - let node_sep = self.calculate_node_sep(); - self.adjust_edge_minlen(node_sep); - self.process_children(root, node_sep); - self.config.node_rank_factor = node_sep as f32; - } - - pub(super) fn nesting_cleanup(&mut self) { - if let Some(root) = &self.nesting_root.clone() { - self.remove_node(root); - } - self.nesting_root = None; - - for edge in self.edges() { - if let Some(ge) = self.edge1(edge) { - ge.nesting.then(|| self.remove_edge1(edge)); +use crate::{Dummy, GRAPH_NODE, GraphEdge, GraphNode, LayoutGraph, NodeKey, keyof}; + +impl LayoutGraph { + pub(super) fn nesting_graph_run(&mut self) { + let graph_node = GraphNode::default(); + let root = self.add_dummy_node(Dummy::Root, graph_node); + let depths = self.tree_depths(); + let mut height: usize = 0; + for depth in depths.values() { + if depth > &height { + height = depth.to_owned(); } } - } + if height > 0 { + height -= 1; + } - fn init_nesting(&mut self) -> Key { - let root = self.add_dummy_node(Root, GraphNode::default()); - self.nesting_root = Some(root); - root - } + let node_sep = (2 * height + 1) as f32; + self.config.nesting_root = Some(root); - fn calculate_node_sep(&self) -> i32 { - let (_, max_height) = self.tree_depths(); - (2 * max_height + 1) as i32 - } + // Multiply minlen by nodeSep to align nodes on non-border ranks. - fn adjust_edge_minlen(&mut self, node_sep: i32) { - for edge in self.edge_values.values_mut() { - edge.minlen = Some(edge.minlen.unwrap() * node_sep); + for edge in self.edges.values_mut() { + edge.min_len *= node_sep } - } - fn process_children(&mut self, root: Key, node_sep: i32) { + // Calculate a weight that is sufficient to keep subgraphs vertically compact let weight = self.sum_weights() + 1.0; - let mut stack: Vec = self.children(&EMPTY_ROOT).into_iter().collect(); - - let (depths, max_height) = self.tree_depths(); - - while let Some(key) = stack.pop() { - if self.children(&key).is_empty() { - if key != root { - self.set_edge(root, key, Some(GraphEdge::with_minlen(node_sep))); - } - continue; - } - let (top, bottom) = self.link_border_nodes(key); - self.update_node_borders(key, top, bottom); - - for k in self.children(&key) { - let (child_top, child_bottom, this_weight) = self.nodes[&k].top_bottom(k, weight); - let minlen = - if child_top == child_bottom { max_height - depths[&k] + 1 } else { 1 }; - self.add_border_edge(minlen, this_weight, top, bottom, child_top, child_bottom); - stack.push(k); - } - - if self.parent(&key).is_none() { - self.set_edge( - root, - top, - Some(GraphEdge::with_minlen_nesting(depths[&key] + max_height)), - ); - } + // Create border nodes and link them up + let children = self.children(&GRAPH_NODE); + for child_id in children { + self.dfs(root, node_sep, weight, height, &depths, child_id); } - } - fn link_border_nodes(&mut self, key: Key) -> (Key, Key) { - let top = self.add_border_node(); - let bottom = self.add_border_node(); - self.set_parent(top, Some(key)); - self.set_parent(bottom, Some(key)); - (top, bottom) + // Save the multiplier for node layers for later removal of empty border + // layers. + self.config.node_rank_factor = Some(node_sep); } - fn update_node_borders(&mut self, key: Key, top: Key, bottom: Key) { - if let Some(node) = self.node_mut(&key) { - node.border_top = Some(top); - node.border_bottom = Some(bottom); + fn tree_depth_dfs( + &mut self, + node_id: NodeKey, + depth: usize, + depths: &mut HashMap, + ) { + let children = self.children(&node_id); + for child_id in children { + // recursion for child node ids + self.tree_depth_dfs(child_id, depth + 1, depths); } + // setting for current node + depths.insert(node_id, depth); } -} -impl GraphEdge { - fn with_minlen(minlen: i32) -> Self { - GraphEdge { minlen: Some(minlen), weight: Some(0.0), ..GraphEdge::default() } - } + fn tree_depths(&mut self) -> HashMap { + let mut depths = HashMap::new(); - fn with_minlen_nesting(minlen: usize) -> Self { - GraphEdge { - minlen: Some(minlen as i32), - weight: Some(0.0), - nesting: true, - ..GraphEdge::default() + // processing root nodes + for node_id in self.children(&GRAPH_NODE) { + self.tree_depth_dfs(node_id, 1, &mut depths); } + depths } -} - -impl Graph { - fn tree_depths(&self) -> (HashMap, usize) { - let mut depths: HashMap = HashMap::new(); - let mut stack: Vec<(Key, usize)> = Vec::new(); - let mut max_depth: usize = 0; - - for node_id in self.children(&EMPTY_ROOT) { - stack.push((node_id, 1)); - } - while let Some((node_id, depth)) = stack.pop() { - for child_id in self.children(&node_id) { - stack.push((child_id, depth + 1)); + fn dfs( + &mut self, + root: NodeKey, + node_sep: f32, + weight: f32, + height: usize, + depths: &HashMap, + node_id: NodeKey, + ) { + let children = self.children(&node_id); + if children.is_empty() { + if node_id != root { + let mut graph_edge = GraphEdge::default(); + graph_edge.min_len = node_sep; + graph_edge.weight = 0.0; + self.set_edge(root, node_id, Some(graph_edge)); } - depths.insert(node_id, depth); - max_depth = max_depth.max(depth); + return; } - if max_depth > 0 { - max_depth -= 1; + let top = self.add_dummy_node(Dummy::Border, GraphNode::default()); + let bottom = self.add_dummy_node(Dummy::Border, GraphNode::default()); + + self.set_parent(&top, Some(node_id)); + self.set_parent(&bottom, Some(node_id)); + + if let Some(border) = self.node_borders.get_mut(&node_id) { + border.top = Some(top); + border.bottom = Some(bottom); } - (depths, max_depth) - } + for child_id in children.into_iter() { + self.dfs(root, node_sep, weight, height, depths, child_id); - fn sum_weights(&self) -> f32 { - let mut sum_weight: f32 = 0.0; + let Some(border) = self.node_borders.get(&child_id) else { continue }; - for edge in self.edge_values.values() { - if let Some(weight) = edge.weight { - sum_weight += weight; + let border_top = border.top; + let border_bottom = border.bottom; + + let mut child_top = child_id; + let mut child_bottom = child_id; + let mut this_weight: f32 = weight; + let mut minlen: usize = 1; + + if let Some(border_top) = border_top { + child_top = border_top; + this_weight = 2.0 * weight; + } + if let Some(border_bottom) = border_bottom { + child_bottom = border_bottom; } + + if child_top == child_bottom { + minlen = height - depths.get(&node_id).copied().unwrap_or(0) + 1; + } + + let ct = + GraphEdge { min_len: minlen as f32, weight: this_weight, ..GraphEdge::default() }; + self.nesting_edges.push(keyof(top, child_top)); + self.set_edge(top, child_top, Some(ct)); + + let cb = + GraphEdge { min_len: minlen as f32, weight: this_weight, ..GraphEdge::default() }; + self.nesting_edges.push(keyof(child_bottom, bottom)); + self.set_edge(child_bottom, bottom, Some(cb)); } - sum_weight + if self.parent(&node_id).is_none() { + let mut graph_edge = GraphEdge::default(); + graph_edge.min_len = (depths.get(&node_id).copied().unwrap_or(0) + height) as f32; + graph_edge.weight = 0.0; + self.nesting_edges.push(keyof(root, top)); + self.set_edge(root, top, Some(graph_edge)); + } } -} -impl Graph { - fn add_border_node(&mut self) -> Key { - self.add_dummy_node(Border, GraphNode::default()) + fn sum_weights(&self) -> f32 { + self.edges.values().map(|e| e.weight).sum() } - fn add_border_edge( - &mut self, - minlen: usize, - weight: f32, - top: Key, - bottom: Key, - child_top: Key, - child_bottom: Key, - ) { - let top_edge = GraphEdge { - minlen: Some(minlen as i32), - weight: Some(weight), - nesting: true, - ..GraphEdge::default() - }; - self.set_edge(top, child_top, Some(top_edge)); - - let bottom_edge = GraphEdge { - minlen: Some(minlen as i32), - weight: Some(weight), - nesting: true, - ..GraphEdge::default() - }; - self.set_edge(child_bottom, bottom, Some(bottom_edge)); - } -} + pub(super) fn nesting_graph_cleanup(&mut self) { + if let Some(nr) = self.config.nesting_root { + self.remove_node(&nr); + } + self.config.nesting_root = None; -impl GraphNode { - fn top_bottom(&self, key: Key, weight: f32) -> (Key, Key, f32) { - let (top, this_weight) = if let Some(border_top) = self.border_top { - (border_top, 2.0 * weight) - } else { - (key, weight) - }; + let self_ptr = self as *mut LayoutGraph; - let bottom = if let Some(border_bottom) = self.border_bottom { border_bottom } else { key }; + unsafe { + let self_mut = &mut *self_ptr; - (top, bottom, this_weight) + for ek in mem::take(&mut self.nesting_edges) { + if let Some(edge) = self.edges.get(&ek) { + self_mut.remove_edge(&ek.source, &ek.target); + } + } + } } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/normalize.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/normalize.rs index 36e598960e9a1761e006b3b6bf5e98f26093eb15..b83c043d99ff1c00e75fb3f9a54ae758fa4fe43b 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/normalize.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/normalize.rs @@ -1,96 +1,78 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use std::mem; +use ahash::{HashMap, HashMapExt}; use smallvec::smallvec; -use crate::{Dummy, Dummy::EdgeProxy, Edge, Graph, GraphEdge, GraphNode, Point, EMPTY_KEY}; +use crate::{Dummy, DummyChain, EdgeKey, GraphEdge, GraphNode, LayoutGraph}; -impl Graph { - pub(super) fn normalize(&mut self) { - self.dummy_chains = Some(vec![]); - for edge in self.edges() { - self.normalize_edge(edge); - } - } +impl LayoutGraph { + pub(super) fn normalize_run(&mut self) { + let edges: Vec = self.edges.keys().copied().collect(); - fn normalize_edge(&mut self, e: Edge) -> Option<()> { - let mut s = e.source; - let t = e.target; - let mut s_rank = self.node(&s)?.rank?; - let t_rank = self.node(&t)?.rank?; - - let edge = self.edge_mut1(e)?; - edge.points = Some(smallvec![]); - let weight = edge.weight; - let rank = edge.rank.unwrap_or(0); - - self.remove_edge1(e); - - let mut i = 0; - s_rank += 1; - while s_rank < t_rank { - let mut dummy_node = - GraphNode { edge: Some(e), rank: Some(s_rank), ..GraphNode::default() }; - if s_rank == rank { - dummy_node.dummy = Some(EdgeProxy); - } - let dummy_id = self.add_dummy_node(Dummy::Edge, dummy_node); - let dummy_edge = GraphEdge { weight, ..GraphEdge::default() }; - self.set_edge(s, dummy_id, Some(dummy_edge)); - if i == 0 { - let dummy_chains = &mut self.dummy_chains; - dummy_chains.get_or_insert(vec![]).push(dummy_id); - } - s = dummy_id; - i += 1; - s_rank += 1; - } + let dummy_chains_ptr = &mut self.dummy_chains as *mut Vec; - let graph_edge = GraphEdge { weight, ..GraphEdge::default() }; - self.set_edge(s, t, Some(graph_edge)); + unsafe { + let dummy_chains_mut = &mut *dummy_chains_ptr; + let mut idx = 0; - None - } + for ek in edges { + let mut current = ek.source; + let target = ek.target; + let mut source_rank = self.node(¤t).and_then(|n| n.rank).unwrap_or(0); + let target_rank = self.node(&target).and_then(|n| n.rank).unwrap_or(0); - pub(super) fn denormalize(&mut self) -> Option<()> { - if let Some(dummy_chains) = self.dummy_chains.clone() { - for &dummy_id in &dummy_chains { - let Some(mut node) = self.node(&dummy_id).copied() else { + if target_rank == source_rank + 1 { continue; - }; - - let edge_obj = node.edge?; - let mut prev_edge = self - .edge1(edge_obj) - .cloned() - .unwrap_or(GraphEdge { points: Some(smallvec![]), ..GraphEdge::default() }); - let mut curr_dummy = dummy_id; - while node.dummy.is_some() { - let new_dummy = - self.successors(&curr_dummy).first().copied().unwrap_or(EMPTY_KEY); - self.remove_node(&curr_dummy); - prev_edge.points.as_mut()?.push(Point::of(node.x, node.y)); - - curr_dummy = new_dummy; - node = self.node(&curr_dummy).cloned()?; } - self.set_edge1(edge_obj, Some(prev_edge)); + let mut ge = self.remove_edge(&ek.source, &ek.target).unwrap(); + ge.points = Some(smallvec!()); + let weight = ge.weight; + + dummy_chains_mut.push(DummyChain::with_capacity( + (target_rank - source_rank) as usize, + ek, + ge, + )); + let dummy_chain_mut = &mut dummy_chains_mut[idx]; + idx += 1; + + source_rank += 1; + while source_rank < target_rank { + let mut dummy_node = GraphNode::default(); + dummy_node.rank = Some(source_rank); + let dummy = self.add_dummy_node(Dummy::Edge, dummy_node); + let dummy_edge = GraphEdge::with_weight(weight); + self.add_dummy_edge(current, dummy, dummy_edge); + dummy_chain_mut.chain.push_back(dummy); + current = dummy; + source_rank += 1; + } + + let graph_edge = GraphEdge::with_weight(weight); + self.set_edge(current, target, Some(graph_edge)); } } + } + + pub(super) fn normalize_undo(&mut self) { + let dummy_chains = mem::take(&mut self.dummy_chains); + + let graph_ptr = self as *mut LayoutGraph; + + unsafe { + let graph_mut = &mut *graph_ptr; + + for DummyChain { chain, ek, mut edge } in dummy_chains { + let points = edge.points.as_mut().unwrap(); - None + for key in chain { + let point = self.remove_dummy(&key); + points.push(point); + } + + self.set_edge(ek.source, ek.target, Some(edge)); + } + } } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/add_subgraph_constraints.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/add_subgraph_constraints.rs new file mode 100644 index 0000000000000000000000000000000000000000..a1f62dec6b515ae0cd8d86ea9ccbcb4bec6df3c8 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/add_subgraph_constraints.rs @@ -0,0 +1,34 @@ +use ahash::{HashMap, HashMapExt}; +use super::{ConstraintGraph, LayerGraph}; +use crate::{NodeKey, EMPTY_NODEKEY}; + +impl LayerGraph { + pub(super) fn add_subgraph_constraints(&self, cg: &mut ConstraintGraph, keys: &[NodeKey]) { + let mut prev = HashMap::new(); + let mut _root_prev = None; + + for key in keys { + let mut child = self.parent(key).copied(); + let mut _parent = None; + let mut _prev_child = None; + while child.is_some() { + _parent = self.parent(&child.unwrap()).copied(); + if _parent.is_some() { + _prev_child = prev.get(&_parent.unwrap()).copied(); + prev.insert(_parent.unwrap(), child.unwrap()); + } else { + _prev_child = _root_prev; + _root_prev = child; + } + + let prev_child = _prev_child.unwrap_or(EMPTY_NODEKEY); + let child_ = child.unwrap_or(EMPTY_NODEKEY); + if _prev_child.is_some() && prev_child != child_ { + cg.add_edge(prev_child, child_); + return; + } + child = _parent; + } + } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/barycenter.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/barycenter.rs new file mode 100644 index 0000000000000000000000000000000000000000..3e8e2b15586b7f895a0cc06d43a497f2f63c952c --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/barycenter.rs @@ -0,0 +1,32 @@ +use super::LayerGraph; +use crate::NodeKey; + +#[derive(Debug, Clone, Copy)] +pub(super) struct Barycenter { + pub key: NodeKey, + pub barycenter: f32, + pub weight: f32, +} + +impl LayerGraph { + pub(super) fn barycenter(&self, movable: &Vec) -> Vec { + movable + .iter() + .map(|&key| { + let inedges = self.in_edges(&key); + if inedges.is_empty() { + return Barycenter { key, barycenter: 0.0, weight: 0.0 }; + } + + let (sum, weight) = + inedges.iter().fold((0.0f32, 0.0f32), |(acc_sum, acc_weight), ek| { + let order = self.nodes[&ek.source].order as f32; + let weight = self.edges[&ek].weight; + (acc_sum + weight * order, acc_weight + weight) + }); + + Barycenter { key, barycenter: sum / weight, weight } + }) + .collect() + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/build_layer_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/build_layer_graph.rs index a4e84755a111574703d25ae4913f0e31c0bad634..f04306874f107dbbe6db334c8c75d4741d0f008f 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/build_layer_graph.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/build_layer_graph.rs @@ -1,100 +1,61 @@ -//! Constructs a graph that can be used to sort a layer of nodes. -//! The graph contains all base and subgraph nodes from the request -//! layer in their original hierarchy and any edges -//! that are incident on these nodes and is of the type -//! requested by the "relationship" parameter. -//! -//! Nodes from the requested rank that don't have parents are assigned a root -//! node in the output graph, which is set in the root graph attribute. -//! This makes it easy to walk the hierarchy of movable nodes during ordering. -//! -//! Pre-conditions: -//! -//! 1. Input graph is a DAG -//! 2. Base nodes in the input graph have rank attribute -//! 3. Subgraph nodes in the input graph have minRank and maxRank attributes -//! 4. Edges have an assigned weight -//! -//! Post-conditions: -//! -//! 1. The Output graph has all nodes in the movable rank with preserved -//! hierarchy. -//! 2. Root nodes in the movable layer are made children of the node -//! indicated by the root attribute of the graph. -//! 3. Non-movable nodes incident on movable nodes, selected by the -//! relationship parameter, are included in the graph without a hierarchy. -//! 4. Edges incident on movable nodes, selected by the relationship -//! parameter, are added to the output graph. -//! 5. The weights for copied edges are aggregated as needed, since the output -//! graph isn't a multi-graph. - -use crate::{unique_key, Graph, GraphEdge, GraphNode, Key}; - -#[allow(dead_code)] -#[derive(Debug, Copy, Clone, PartialEq)] -pub(super) enum EdgeRelation { - In, - Out, -} - -impl Graph { - pub(super) fn build_layer_graph(&mut self, rank: i32, relation: EdgeRelation) -> Graph { +use ahash::HashMap; + +use super::{LayerGraph, LayerGraphEdge, LayerGraphNode}; +use crate::{EdgeKey, LayoutGraph, NodeKey, order::layer_graph::Border, unique_id}; + +impl LayoutGraph { + pub(super) fn build_layer_graph( + &self, + rank: i32, + rank_cache: &HashMap>, + in_edges: bool, + ) -> LayerGraph { let root = self.create_root_node(); - let mut lg = Graph::new(true, false); - lg.root = Some(root); + let mut lg = LayerGraph::new(); + lg.root = root; + + for (key, order, has_limit) in &rank_cache[&rank] { + let parent = self.parent(key).copied().unwrap_or(root); + lg.set_node( + *key, + Some(LayerGraphNode { order: *order, border: None }), + ); + + lg.set_parent(key, parent); - for (&key, &node) in &self.nodes { - if node.is_in_rank(Some(rank)) { - lg.set_node(key, Some(node)); - lg.set_layer_parent(key, self.parent(&key), root); + match in_edges { + true => self.process_edges(self.in_edges_iter(key), key, &mut lg), + false => self.process_edges(self.out_edges_iter(key), key, &mut lg), + }; - self.process_relations(&mut lg, key, relation); + if *has_limit { + let border = &self.node_borders[key]; + let (bl, br) = (border.left[&rank], border.right[&rank]); + let graph_node = LayerGraphNode { order: 0, border: Some(Border::new(bl, br)) }; + lg.set_node(*key, Some(graph_node)); } } lg } - fn set_layer_parent(&mut self, key: Key, parent: Option, root: Key) { - match parent { - Some(p) => self.set_parent(key, Some(p)), - _ => self.set_parent(key, Some(root)), - }; - } - - fn process_relations(&self, lg: &mut Graph, key: Key, relation: EdgeRelation) -> Option<()> { - let edges = match relation { - EdgeRelation::In => &self.in_map[&key], - EdgeRelation::Out => &self.out_map[&key], - }; - for &edge in edges { - let source = if edge.source == key { edge.target } else { edge.source }; - let weight = lg.edge(source, key).and_then(|e| e.weight).unwrap_or(0.0); - let new_weight = self.edge1(edge)?.weight.unwrap_or(0.0) + weight; - lg.set_edge( - source, - key, - Some(GraphEdge { weight: Some(new_weight), ..Default::default() }), - ); + fn process_edges(&self, iter: I, key: &NodeKey, lg: &mut LayerGraph) + where + I: Iterator, + { + for ek in iter { + let u = if ek.source == *key { ek.target } else { ek.source }; + let ge = LayerGraphEdge { weight: self.edges[&ek].weight }; + lg.set_edge(u, *key, ge); } - - None } - fn create_root_node(&self) -> Key { - loop { - let key = unique_key(); - - if !self.has_node(&key) { - return key; - } + fn create_root_node(&self) -> NodeKey { + let mut v = unique_id(); + while self.has_node(&v) { + v = unique_id(); } - } -} -impl GraphNode { - #[inline] - fn is_in_rank(&self, rank: Option) -> bool { - self.rank == rank || self.min_rank <= rank && rank <= self.max_rank + v } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/constraint_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/constraint_graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..03f1f53d6f23a1f09194124d668cae53ef559427 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/constraint_graph.rs @@ -0,0 +1,13 @@ +use crate::{keyof, EdgeKey, NodeKey}; + +#[derive(Default)] +pub(super) struct ConstraintGraph { + pub edges: Vec, +} + +impl ConstraintGraph { + pub fn add_edge(&mut self, source: NodeKey, target: NodeKey) { + let ek = keyof(source, target); + self.edges.push(ek); + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/context.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/context.rs new file mode 100644 index 0000000000000000000000000000000000000000..dc556be35abcc4ff3d0290b5f5bd387890a6a232 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/context.rs @@ -0,0 +1,12 @@ +use super::LayerGraph; + +pub(super) struct Context { + pub(super) down_layers: Vec, + pub(super) up_layers: Vec, +} + +impl Context { + pub(super) fn with_capacity(capacity: usize) -> Self { + Self { down_layers: Vec::with_capacity(capacity), up_layers: Vec::with_capacity(capacity) } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/cross_count.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/cross_count.rs index a390e804ad027ca81aeaef80d3b02caa122afc31..a396863881ac98f06ea31809f39d2772858b1575 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/cross_count.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/cross_count.rs @@ -1,77 +1,81 @@ -//! ### Algorithm derived from Barth et al., "Bilayer Cross Counting" -//! A function that takes a layering (an array of layers, each with an array of -//! ordered nodes) and a graph and returns a weighted crossing count. -//! -//! Pre-Conditions: -//! -//! 1. Input graph must be non-multigraph, directed, and include only simple edges. -//! 2. Edges in the input graph must have assigned weights. -//! -//! Post-Conditions: -//! -//! 1. The graph and layering matrix are left unchanged. +use ahash::{HashMap, HashMapExt}; -use ahash::HashMap; +use crate::{LayoutGraph, NodeKey}; -use crate::{Graph, Key}; - -impl Graph { - pub fn cross_count(&mut self, matrix: &mut [Vec]) -> usize { - let mut count = 0; +impl LayoutGraph { + pub(super) fn cross_count(&mut self, layering: &mut Vec>) -> usize { + let mut cc = 0; + let mut i = 1; + while i < layering.len() { + cc += self.two_layer_cross_count(layering, i - 1, i); + i += 1; + } - /// Sort all the edges between the north and south layers by their position - /// in the north layer and then the south. - /// Map these edges to the position of their head in the south layer. - for idx in 1..matrix.len() { - let north_idx = idx - 1; - let south_idx = idx; + cc + } - let south_layer = &matrix[south_idx]; - let south_pos: HashMap = - south_layer.iter().enumerate().map(|(idx, val)| (*val, idx)).collect(); + fn two_layer_cross_count( + &mut self, + layering: &mut Vec>, + north_idx: usize, + south_idx: usize, + ) -> usize { + let mut south_pos = HashMap::new(); + let south_layer = layering.get(south_idx).cloned().unwrap_or(vec![]); + south_layer.iter().enumerate().for_each(|(idx, &val)| { + south_pos.insert(val, idx); + }); - let mut south_entries: Vec<(usize, usize)> = matrix[north_idx] - .iter() - .flat_map(|k| { - self.out_map[k] - .iter() - .map(|&e| { - let pos = south_pos[&e.target]; - let weight = self.edge1(e).unwrap().weight.unwrap(); - (pos, weight as usize) - }) - .collect::>() - }) - .collect(); + // (pos, weight) + let south_entries: Vec<(usize, f32)> = layering + .get(north_idx) + .cloned() + .unwrap_or(vec![]) + .into_iter() + .map(|v| -> Vec<(usize, f32)> { + let mut out_edges: Vec<(usize, f32)> = self + .out_edges(&v) + .into_iter() + .map(|e| { + let pos = south_pos.get(&e.target).copied().unwrap_or(0); + (pos, self.edges[&e].weight) + }) + .collect(); + out_edges.sort_by_key(|(i, _)| *i); - south_entries.sort_by_key(|e| e.0); + out_edges + }) + .collect::>>() + .concat(); - let mut first_index = south_layer.len().next_power_of_two(); + // Build the accumulator tree + let mut first_index = 1; + while first_index < south_layer.len() { + first_index <<= 1; + } - let tree_size = 2 * first_index - 1; - first_index -= 1; + let tree_size = 2 * first_index - 1; + first_index -= 1; - let mut tree = vec![0; tree_size]; - let mut c = 0; + let mut tree: Vec = vec![0; tree_size]; - for &(f, s) in &south_entries { - let mut idx = f + first_index; - tree[idx] += s; + // Calculate the weighted crossings + let mut cc = 0; + south_entries.iter().for_each(|entry| { + let mut idx = entry.0 + first_index; + tree[idx] += entry.1 as usize; - let mut weight_sum = 0; - while idx > 0 { - if idx % 2 != 0 { - weight_sum += tree[idx + 1]; - } - idx = (idx - 1) >> 1; - tree[idx] += s; + let mut weight_sum: usize = 0; + while idx > 0 { + if idx % 2 != 0 { + weight_sum += tree[idx + 1]; } - c += s * weight_sum; + idx = (idx - 1) >> 1; + tree[idx] += entry.1 as usize; } + cc += entry.1 as usize * weight_sum; + }); - count += c - } - - count + cc } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/init_order.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/init_order.rs index 6e8411699311207956af41069accb1334782a10d..4d2ab5969aad190b701ff9f691d6921653c29d3c 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/init_order.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/init_order.rs @@ -1,47 +1,45 @@ -//! ### Gansner et al., "A Technique for Drawing Directed Graphs" -//! Assigns an initial order value for each node by performing a DFS search -//! starting from nodes in the first rank. -//! Nodes are assigned an order in their rank as they're first visited. -//! -//! Returns a layering matrix with an array per layer and each layer sorted by -//! the order of its nodes. - use ahash::{HashSet, HashSetExt}; -use crate::{Graph, Key}; +use crate::{LayoutGraph, NodeKey}; -impl Graph { - pub(super) fn init_order(&self) -> Option>> { - let mut visited: HashSet = HashSet::new(); - let mut simple_nodes: Vec = +impl LayoutGraph { + pub(super) fn init_order(&self) -> Vec> { + let mut visited = HashSet::new(); + let mut simple_nodes: Vec = self.nodes.keys().filter(|k| self.children(k).is_empty()).copied().collect(); - - let mut max_rank = 0; - for id in &simple_nodes { - if let Some(rank) = self.nodes[id].rank { - max_rank = max_rank.max(rank) + let max_rank = simple_nodes.iter().map(|k| self.nodes[k].rank.unwrap_or(0)).max().unwrap_or(0); + let mut layers: Vec> = (0..=max_rank).map(|_| vec![]).collect(); + + fn dfs( + key: NodeKey, + g: &LayoutGraph, + visited: &mut HashSet, + layers: &mut Vec>, + ) { + if visited.contains(&key) { + return; } - } - - let mut layers: Vec> = vec![Vec::new(); max_rank as usize + 1]; - - simple_nodes.sort_by_key(|id| Some(self.nodes[id].rank?)); - for id in simple_nodes { - let mut stack = vec![id]; + visited.insert(key); + let node = &g.nodes[&key]; + let node_rank = node.rank.unwrap_or(0) as usize; + if layers.get(node_rank).is_none() { + layers.insert(node_rank, vec![]); + } + let layer: &mut Vec = &mut layers[node_rank]; + layer.push(key); - while let Some(id) = stack.pop() { - if !visited.insert(id) { - continue; - } + for sv in g.successors(&key) { + dfs(sv, g, visited, layers) + } + } - let rank = self.nodes[&id].rank? as usize; - layers[rank].push(id); + simple_nodes.sort_by_key(|k| self.nodes[k].rank); - stack.extend(self.successors(&id)); - } + for v in simple_nodes { + dfs(v, self, &mut visited, &mut layers); } - Some(layers) + layers } -} +} \ No newline at end of file diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/layer_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/layer_graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..38cd4292d5feca43fb226f378f1611147cc0273a --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/layer_graph.rs @@ -0,0 +1,135 @@ +use ahash::{HashMap, HashSet, HashSetExt}; + +use crate::{keyof, EdgeKey, NodeKey, GRAPH_NODE}; + +#[derive(Default)] +pub(super) struct LayerGraph { + pub root: NodeKey, + pub nodes: HashMap, + pub edges: HashMap, + pub predecessors: HashMap>, + parent_map: HashMap, + children_map: HashMap>, +} + +impl LayerGraph { + pub fn new() -> Self { + let mut graph = Self::default(); + graph.children_map.insert(GRAPH_NODE, HashSet::new()); + graph + } + + pub fn set_node(&mut self, key: NodeKey, value: Option) { + if self.nodes.contains_key(&key) { + if value.is_some() { + self.nodes.insert(key, value.unwrap()); + } + return; + } + + self.nodes.insert(key, value.unwrap_or_default()); + + self.parent_map.insert(key, GRAPH_NODE); + self.children_map.insert(key, HashSet::new()); + self.children_map.entry(GRAPH_NODE).or_default().insert(key); + + self.predecessors.insert(key, HashSet::new()); + } + + pub fn set_parent(&mut self, key: &NodeKey, parent: NodeKey) { + self.set_node(parent, None); + self.set_node(*key, None); + self.remove_from_parents_child_list(key); + self.parent_map.insert(*key, parent); + self.children_map.entry(parent).or_default().insert(*key); + } + + pub fn remove_from_parents_child_list(&mut self, key: &NodeKey) { + if let Some(parent) = self.parent_map.get(key) { + if let Some(children) = self.children_map.get_mut(parent) { + children.remove(key); + } + } + } + + pub fn parent(&self, key: &NodeKey) -> Option<&NodeKey> { + if let Some(parent) = self.parent_map.get(key) { + if parent != &GRAPH_NODE { + return Some(parent); + } + } + + None + } + + pub fn children(&self, key: &NodeKey) -> Vec { + if let Some(children) = self.children_map.get(key) { + return children.into_iter().copied().collect(); + } + if key == &GRAPH_NODE { + return self.nodes.keys().copied().collect(); + } + vec![] + } + + pub fn predecessors(&self, key: &NodeKey) -> Vec { + self.predecessors + .get(key) + .map(|x| x.into_iter().copied().collect()) + .unwrap_or_default() + } + + pub fn set_edge(&mut self, source: NodeKey, target: NodeKey, value: LayerGraphEdge) { + let ek = keyof(source, target); + if self.edges.contains_key(&ek) { + self.edges.insert(ek, value); + return; + } + + // It didn't exist, so we need to create it. + // First ensure the nodes exist. + self.set_node(source, None); + self.set_node(target, None); + + self.edges.insert(ek, value); + + self.predecessors.get_mut(&target).unwrap().insert(source); + } + + pub fn in_edges(&self, key: &NodeKey) -> Vec { + if let Some(in_edges) = self.predecessors.get(key) { + return in_edges.iter().map(|s| keyof(*s, *key)).collect(); + } + + vec![] + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub(super) struct Border { + pub(super) left: NodeKey, + pub(super) right: NodeKey, +} + +impl Border { + pub(super) fn new(left: NodeKey, right: NodeKey) -> Self { + Self { left, right } + } +} + +#[derive(Debug, Clone, Copy, Default)] +pub(super) struct LayerGraphNode { + pub order: usize, + pub border: Option, +} + +#[derive(Debug, Clone, Copy)] +pub(super) struct LayerGraphEdge { + pub weight: f32, +} + +impl Default for LayerGraphEdge { + fn default() -> Self { + Self { weight: 1.0 } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/mod.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/mod.rs index bc7ae1435882557bfedbb3c6055e6e231904ce4b..12f239a505d8bc7a33ada3810177d32e461f13b2 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/mod.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/mod.rs @@ -1,76 +1,84 @@ -mod build_layer_graph; -mod cross_count; -mod init_order; -mod subgraph; - -use std::ops::Range; - -use build_layer_graph::EdgeRelation; - -use crate::{Graph, Key, EMPTY_KEY}; - -impl Graph { - pub fn order(&mut self) -> Option<()> { - let mut matrix = self.init_order()?; - self.assign_order(&matrix); +pub mod add_subgraph_constraints; +pub mod barycenter; +pub mod build_layer_graph; +mod constraint_graph; +pub mod context; +pub mod cross_count; +pub mod init_order; +pub mod layer_graph; +pub mod resolve_conflicts; +pub mod sort; +pub mod sort_subgraph; + +use std::{mem::size_of_val_raw, time::Instant}; +use std::collections::LinkedList; +use ahash::HashMap; +use constraint_graph::*; +use context::*; +use layer_graph::*; +use rayon::prelude::*; + +use crate::{LayoutGraph, NodeKey}; + +impl LayoutGraph { + pub fn order(&mut self) { + let (max_rank, rank_cache) = self.max_rank_with_cache(); + + let mut down_layer_graphs: LinkedList = (1..=max_rank) + .into_par_iter() + .map(|rank| self.build_layer_graph(rank, &rank_cache, true)) + .collect(); + let mut up_layer_graphs: LinkedList = (0..max_rank) + .into_par_iter() + .rev() + .map(|rank| self.build_layer_graph(rank, &rank_cache, false)) + .collect(); + + let mut layering = self.init_order(); + self.assign_order(&layering); let mut best_cc = f64::INFINITY; - let mut best: Vec> = Vec::new(); + let mut best = Vec::with_capacity(layering.len()); + let mut i = 0; let mut last_best = 0; - loop { - if last_best >= 4 { - break; - } + while last_best < 4 { + let layer_graphs = + if i % 2 != 0 { &mut down_layer_graphs } else { &mut up_layer_graphs }; - matrix = self.key_matrix(); - let cc = self.cross_count(&mut matrix) as f64; + sweep_layer_graphs(layer_graphs, i % 4 >= 2); + layering = self.build_layer_matrix(); + let cc = self.cross_count(&mut layering) as f64; if cc < best_cc { last_best = 0; - best = matrix; + best = layering; best_cc = cc; - } else { - last_best += 1; } - } - self.assign_order(&best) - } + last_best += 1; + i += 1; + } - #[allow(dead_code)] - #[inline] - fn build_layer_graphs(&mut self, ranks: Range, relationship: EdgeRelation) -> Vec { - ranks.map(|rank| self.build_layer_graph(rank, relationship)).collect() + self.assign_order(&best); } - fn assign_order(&mut self, matrix: &[Vec]) -> Option<()> { - for keys in matrix { - for (i, key) in keys.iter().enumerate() { - self.node_mut(key)?.order = Some(i); + fn assign_order(&mut self, layering: &Vec>) { + for layer in layering { + for (idx, key) in layer.iter().enumerate() { + self.nodes.get_mut(key).unwrap().order = Some(idx); } } - - None } } -#[allow(dead_code)] -fn sweep_graphs(layer_graphs: &mut [Graph], bias_right: bool) -> Option<()> { - let mut graph = Graph::new(true, false); - +fn sweep_layer_graphs(layer_graphs: &mut LinkedList, bias_right: bool) { + let mut cg = ConstraintGraph::default(); for lg in layer_graphs { - let root = lg.root.unwrap_or(EMPTY_KEY); - let sorted = lg.sort_subgraph(&graph, root, bias_right)?; - - for (i, key) in sorted.keys.iter().enumerate() { - if let Some(node) = lg.node_mut(key) { - node.order = Some(i); - } - } - - lg.add_constraints(&mut graph, &sorted.keys); + let sorted = lg.sort_subgraph(&cg, &lg.root, bias_right); + sorted.keys.iter().enumerate().for_each(|(i, key)| { + lg.nodes.get_mut(key).unwrap().order = i; + }); + lg.add_subgraph_constraints(&mut cg, &sorted.keys); } - - None } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/resolve_conflicts.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/resolve_conflicts.rs new file mode 100644 index 0000000000000000000000000000000000000000..8b51d32b7f0bf6deadfe77d0f6bac15b421d07f8 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/resolve_conflicts.rs @@ -0,0 +1,151 @@ +use ahash::{HashMap, HashMapExt}; + +use crate::{ + NodeKey, + order::{barycenter::Barycenter, constraint_graph::ConstraintGraph}, +}; + +#[derive(Debug, Clone)] +pub(super) struct ResolvedBaryEntry { + pub indegree: i32, + pub _in: Vec, + pub _out: Vec, + pub keys: Vec, + pub i: usize, + pub barycenter: f32, + pub weight: f32, + pub merged: bool, +} + +pub(super) fn resolve_conflicts( + entries: &Vec, + cg: &ConstraintGraph, +) -> Vec { + let mut mapped_entries = HashMap::new(); + for (i, entry) in entries.iter().enumerate() { + let mapped_entry = ResolvedBaryEntry { + indegree: 0, + _in: vec![], + _out: vec![], + keys: vec![entry.key], + i, + barycenter: entry.barycenter, + weight: entry.weight, + merged: false, + }; + + mapped_entries.insert(entry.key, mapped_entry); + } + + cg.edges.iter().for_each(|e| { + let entry_v_ = mapped_entries.get(&e.source); + let entry_w_ = mapped_entries.get(&e.target).cloned(); + if entry_v_.is_some() && entry_w_.is_some() { + if let Some(entry_w) = mapped_entries.get_mut(&e.target) { + entry_w.indegree += 1; + } + if let Some(entry_v) = mapped_entries.get_mut(&e.source) { + entry_v._out.push(entry_w_.unwrap().i); + } + } + }); + + let mut source_set: Vec = + mapped_entries.values().filter(|entry| entry.indegree == 0).cloned().collect(); + + do_resolve_conflicts(&mut source_set) +} + +fn do_resolve_conflicts(source_set: &mut Vec) -> Vec { + let mut entries: Vec = vec![]; + let mut source_hash = HashMap::new(); + + source_set.iter().for_each(|entry| { + source_hash.insert(entry.i, entry.clone()); + }); + + fn handle_in(v_idx: usize, u_idx: usize, source_hash: &mut HashMap) { + let v_entry = source_hash.get(&v_idx).unwrap(); + let u_entry = source_hash.get(&u_idx).unwrap(); + if u_entry.merged { + return; + } + + if u_entry.barycenter == 0.0 + || v_entry.barycenter == 0.0 + || u_entry.barycenter >= v_entry.barycenter + { + merge_entries(v_idx, u_idx, source_hash); + } + } + + fn handle_out( + v_idx: usize, + w_idx: usize, + source_hash: &mut HashMap, + source_set: &mut Vec, + ) { + let v_entry = source_hash.get(&v_idx).cloned().unwrap(); + let w_entry = source_hash.get_mut(&w_idx).unwrap(); + w_entry._in.push(v_entry.i); + w_entry.indegree -= 1; + if w_entry.indegree == 0 { + source_set.push(w_entry.clone()); + } + } + + while source_set.len() > 0 { + let entry_ = source_set.pop().unwrap(); + let entry = source_hash.get(&entry_.i).unwrap(); + + let _in = entry._in.clone(); + let out = entry_._out.clone(); + _in.iter().rev().for_each(|u_entry| { + handle_in(entry_.i, *u_entry, &mut source_hash); + }); + out.iter().for_each(|w_entry| { + handle_out(entry_.i, *w_entry, &mut source_hash, source_set); + }); + + entries.push(source_hash.get(&entry_.i).cloned().unwrap()); + } + + entries.into_iter().filter(|entry| !entry.merged).collect() +} + +fn merge_entries( + target_idx: usize, + source_idx: usize, + source_hash: &mut HashMap, +) { + let mut sum = 0.0; + let mut weight = 0.0; + + let target_ = source_hash.get(&target_idx).cloned().unwrap(); + if target_.weight != 0.0 { + let target_weight = target_.weight; + let target_barycenter = target_.barycenter; + sum += target_barycenter * target_weight; + weight += target_weight; + } + + let source_ = source_hash.get(&source_idx).cloned().unwrap(); + if source_.weight != 0.0 { + let source_weight = source_.weight; + let source_barycenter = source_.barycenter; + sum += source_barycenter * source_weight; + weight += source_weight; + } + + let mut target_vs = source_.keys; + target_vs.extend(target_.keys); + + let target = source_hash.get_mut(&target_idx).unwrap(); + target.keys = target_vs; + target.barycenter = sum / weight; + target.weight = weight; + target.i = std::cmp::min(source_.i, target_.i); + + let source = source_hash.get_mut(&source_idx).unwrap(); + source.merged = true; +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/sort.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/sort.rs new file mode 100644 index 0000000000000000000000000000000000000000..23d3f7fb5d83101c34a131b5957609da0cf4995f --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/sort.rs @@ -0,0 +1,71 @@ +use std::cmp::Ordering; + +use crate::{ + NodeKey, + order::{resolve_conflicts::ResolvedBaryEntry, sort_subgraph::Subgraph}, +}; + +pub(super) fn sort(entries: &Vec, bias_right: bool) -> Subgraph { + let (mut sortable, mut unsortable): (Vec<_>, Vec<_>) = + entries.iter().map(|ent| ent.clone()).partition(|ent| ent.barycenter != 0.0); + + sortable.sort_by(|e1, e2| compare_with_bias(e1, e2, bias_right)); + unsortable.sort_by_key(|e| e.i); + + let mut vs = vec![]; + let mut sum = 0.0; + let mut weight = 0.0; + let mut vs_index: usize = 0; + + vs_index = consume_unsortable(&mut vs, &mut sortable, &mut vs_index); + sortable.iter().for_each(|entry| { + vs_index += entry.keys.len(); + vs.append(entry.keys.clone().as_mut()); + let entry_weight = entry.weight; + sum += entry.barycenter * entry_weight; + weight += entry_weight; + vs_index = consume_unsortable(&mut vs, &mut unsortable, &mut vs_index); + }); + + let mut result = Subgraph::default(); + result.keys = vs; + if weight > 0.0 { + result.barycenter = sum / weight; + result.weight = weight; + } + + result +} + +fn consume_unsortable( + vs: &mut Vec, + unsortable: &mut Vec, + index: &mut usize, +) -> usize { + if unsortable.is_empty() { + return *index; + } + let mut last: ResolvedBaryEntry = unsortable.get(unsortable.len() - 1).cloned().unwrap(); + while unsortable.len() > 0 && &last.i <= index { + vs.append(last.keys.clone().as_mut()); + last = unsortable.pop().unwrap(); + *index += 1; + } + *index +} + +fn compare_with_bias( + entry_v: &ResolvedBaryEntry, + entry_w: &ResolvedBaryEntry, + bias: bool, +) -> Ordering { + let barycenter_v = entry_v.barycenter; + let barycenter_w = entry_w.barycenter; + if barycenter_v < barycenter_w { + return Ordering::Greater; + } else if barycenter_v > barycenter_w { + return Ordering::Less; + } + + if !bias { entry_v.i.cmp(&entry_w.i) } else { entry_w.i.cmp(&entry_v.i) } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/sort_subgraph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/sort_subgraph.rs new file mode 100644 index 0000000000000000000000000000000000000000..428f8c46f8a8fb527769111386abbb4792f1ddbd --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/sort_subgraph.rs @@ -0,0 +1,99 @@ +use ahash::{HashMap, HashMapExt}; + +use super::{ + Border, ConstraintGraph, LayerGraph, + barycenter::Barycenter, + resolve_conflicts::{ResolvedBaryEntry, resolve_conflicts}, + sort::sort, +}; +use crate::NodeKey; + +#[derive(Debug, Clone, Default)] +pub(super) struct Subgraph { + pub keys: Vec, + pub barycenter: f32, + pub weight: f32, +} + +impl LayerGraph { + pub(super) fn sort_subgraph( + &self, + cg: &ConstraintGraph, + key: &NodeKey, + bias_right: bool, + ) -> Subgraph { + let mut movable = self.children(key); + let node = self.nodes.get(key); + let border = node.and_then(|n| n.border); + let mut subgraphs = HashMap::new(); + + if let Some(border) = border { + movable = + movable.into_iter().filter(|&k| k != border.left && k != border.right).collect(); + } + + let mut barycenters = self.barycenter(&movable); + barycenters.iter_mut().for_each(|entry| { + if self.children(&entry.key).len() > 0 { + let subgraph_result = self.sort_subgraph(cg, &entry.key, bias_right); + subgraphs.insert(entry.key, subgraph_result.clone()); + merge_barycenters(entry, &subgraph_result); + } + }); + + let mut entries = resolve_conflicts(&barycenters, cg); + expand_subgraphs(&mut entries, &subgraphs); + + let mut result = sort(&entries, bias_right); + if let Some(Border { left, right }) = border { + result.keys.insert(0, left); + result.keys.push(right); + let bl_preds = self.predecessors(&left); + if bl_preds.len() > 0 { + let bl_pred = self.nodes[&bl_preds[0]]; + let br_pred = self.nodes[&self.predecessors(&right)[0]]; + + let bl_pred_order = bl_pred.order as f32; + let br_pred_order = br_pred.order as f32; + result.barycenter = + (result.barycenter * result.weight + bl_pred_order + br_pred_order) + / (result.weight + 2.0); + result.weight += 2.0; + } + } + + result + } +} + +fn expand_subgraphs(entries: &mut Vec, subgraphs: &HashMap) { + entries.iter_mut().for_each(|entry| { + let mut vs = vec![]; + entry.keys.iter().for_each(|v| { + if subgraphs.contains_key(v) { + let subgraph = subgraphs.get(v).unwrap(); + subgraph.keys.iter().for_each(|&v_| { + vs.push(v_); + }); + return; + } + vs.push(*v); + }); + + entry.keys = vs; + }); +} + +fn merge_barycenters(target: &mut Barycenter, other: &Subgraph) { + let target_barycenter = target.barycenter; + if target_barycenter > 0.0 { + let target_weight = target.weight; + + target.barycenter = (target_barycenter * target_weight + other.barycenter * other.weight) + / (target_weight + other.weight); + target.weight = target_weight + other.weight; + } else { + target.barycenter = other.barycenter; + target.weight = other.weight; + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/add_constraints.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/add_constraints.rs deleted file mode 100644 index 0450567e03eb6dc8833d74f65fd7c08a21429475..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/add_constraints.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::{HashMap, HashMapExt}; - -use crate::{Graph, Key}; - -impl Graph { - pub(crate) fn add_constraints(&self, cg: &mut Graph, keys: &[Key]) { - let mut prev_map: HashMap = HashMap::new(); - let mut prev_root: Option = None; - - for key in keys { - let mut current = self.parent(key); - - while let Some(child) = current { - match self.parent(&child) { - Some(parent) => { - if let Some(&prev_child) = prev_map.get(&parent) { - if prev_child != child { - cg.set_edge(prev_child, child, None); - return; - } - } - prev_map.insert(parent, child); - } - None => { - if let Some(prev_child) = prev_root { - if prev_child != child { - cg.set_edge(prev_child, child, None); - return; - } - } - prev_root = Some(child); - } - } - current = self.parent(&child); - } - } - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/barycenters.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/barycenters.rs deleted file mode 100644 index 60cd153eef8577c8b771f1803a39ec0737ff5c32..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/barycenters.rs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use super::Barycenter; -use crate::{Graph, Key}; - -impl Graph { - pub(super) fn barycenters(&self, movable: &[Key]) -> Vec { - movable - .iter() - .map(|&key| { - if self.edge_values.is_empty() { - return Barycenter { key, barycenter: None, weight: None }; - } - - let (sum, weight) = self - .edge_values - .values() - .try_fold((0.0, 0.0), |(sum, weight), edge| { - let w = edge.weight?; - let order = self.node(&edge.source)?.order? as f32; - Some((sum + w * order, weight + w)) - }) - .unwrap(); - - return Barycenter { key, barycenter: Some(sum / weight), weight: Some(weight) }; - }) - .collect() - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/context.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/context.rs deleted file mode 100644 index 3491ec7a22b752f0fb5532517639f98686bbd504..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/context.rs +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::HashMap; - -use crate::Key; - -#[derive(Copy, Clone)] -pub(super) struct Barycenter { - pub key: Key, - pub barycenter: Option, - pub weight: Option, -} - -#[derive(Copy, Clone)] -pub struct ResolvedEntry { - /// ## Layout - /// This struct has 3 bytes of padding. If set either [`idx`] - /// or [`indegree`] to u8, the size of the struct be reduced - /// by 4 bytes, but *2^8-1* may not be enough. - /// - /// ## Another Solution - /// Stores these three fields into u32. [`merged`] occupies - /// 1 byte, [`idx`] and [`degree`] each occupies 15 bytes. - pub idx: u16, - pub indegree: u16, - pub merged: bool, - pub barycenter: Option, - pub weight: Option, -} - -impl ResolvedEntry { - #[inline] - pub(super) fn of(idx: u16) -> Self { - Self { idx, indegree: 0, merged: false, barycenter: None, weight: None } - } -} - -/// A context for managing [`ResolvedEntry`] sources, sinks, and keys. -/// -/// Storing these fields directly within `ResolvedEntry` would prevent -/// it from deriving the [`Copy`] trait. Additionally, it'd lead to -/// excessive cloning when accessing these fields. -/// -/// ## Performance -/// There are three more solutions, Raw Pointer preferred. -/// - **Reference** Safe Rust, Manual lifecycle management, High Performance -/// - **RC/RefCell** Safe Rust, with extremely cumbersome boilerplate code. -/// - **NonNull** The best Performance, but Unsafe Rust, -#[derive(Default)] -pub(super) struct Context { - pub entries: HashMap, - pub sources_map: HashMap>, - pub sinks_map: HashMap>, - pub keys_map: HashMap>, - pub keys: Vec, - pub index: usize, -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/mod.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/mod.rs deleted file mode 100644 index 77b545d9859be41efb9e922ac6b2a26850140342..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/mod.rs +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -pub mod add_constraints; -mod barycenters; -mod context; -mod resolve_conflict; -mod sort; - -use ahash::{HashMap, HashMapExt}; -pub use context::*; - -use crate::{Graph, Key}; - -#[derive(Debug, Clone, Default)] -pub struct Subgraph { - pub keys: Vec, - pub barycenter: f32, - pub weight: f32, -} - -impl Graph { - pub fn sort_subgraph(&self, cg: &Graph, start_key: Key, bias_right: bool) -> Option { - let mut stack: Vec<(Key, Subgraph)> = vec![(start_key, Subgraph::default())]; - let mut subgraphs: HashMap = HashMap::new(); - - while let Some((curr_key, mut subgraph)) = stack.pop() { - let movable = self.children(&curr_key); - - let mut barycenters = self.barycenters(&movable); - for entry in &mut barycenters { - if self.children(&entry.key).len() > 0 { - stack.push((entry.key, Subgraph::default())); - } - } - - let mut ctx = Context::default(); - ctx.resolve(cg, &barycenters); - ctx.expand_subgraph(&subgraphs); - - subgraph = ctx.next_subgraph(bias_right)?; - - if stack.is_empty() { - return Some(subgraph); - } - - subgraphs.insert(curr_key, subgraph); - } - - None - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/resolve_conflict.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/resolve_conflict.rs deleted file mode 100644 index dc7cb9151141e8ef6b566538cb63debf573bf05d..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/resolve_conflict.rs +++ /dev/null @@ -1,161 +0,0 @@ -//! ### Implementation Based on the description in Forster. -//! ### "A Fast and Simple Heuristic for Constrained Two-Level Crossing Reduction" -//! Thought differs in some specific details. -//! -//! Given a list of entries with the form {key, barycenter, weight} and a -//! constraint graph, this function resolves any conflicts between the -//! constraint graph and the barycenters for the entries. -//! If the barycenters for an entry violate a constraint in the constraint graph, -//! then coalesce the nodes in the conflict into a new node that respects the -//! constraint and aggregates barycenter and weight information. -//! -//! Pre-Conditions: -//! -//! 1. Each entry has the form {key, barycenter, weight}, or if the node has -//! no barycenter, then {key}. -//! -//! Returns: -//! -//! A new list of entries with the form {keys, idx, barycenter, weight}. -//! The list `keys` may either be a singleton or it may be aggregation of nodes -//! ordered such that they don't violate constraints from the constraint graph. -//! The property `idx` is the lowest original index of the elements in `keys`. - -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::HashMap; - -use super::{Barycenter, Context, ResolvedEntry, Subgraph}; -use crate::{Edge, Graph, Key}; - -impl Context { - pub(super) fn resolve(&mut self, cg: &Graph, entries: &[Barycenter]) -> Option<()> { - for (idx, entry) in entries.iter().enumerate() { - let mut tmp = ResolvedEntry::of(idx as u16); - if entry.barycenter.is_some() { - tmp.barycenter = entry.barycenter; - tmp.weight = entry.weight; - } - self.entries.insert(entry.key, tmp); - self.keys_map.insert(entry.key, vec![entry.key]); - } - - for &Edge { source, target } in cg.edges.values() { - let flag = self.entries.get(&source).is_some(); - let te = self.entries.get_mut(&target); - if flag && let Some(te) = te { - te.indegree += 1; - self.sinks_map.entry(source).or_default().push(target); - } - } - - let mut source_array: Vec = self - .entries - .iter() - .filter(|(k, _)| self.sources_map.get(k).is_none()) - .map(|(&k, _)| k) - .collect(); - - self.do_resolve(&mut source_array) - } - - fn handle_in(&mut self, source: Key, target: Key) -> Option<()> { - let source_ent = self.entries[&target]; - if source_ent.merged { - return None; - } - - let target_ent = self.entries[&source]; - - if source_ent.barycenter.is_none() - || target_ent.barycenter.is_none() - || source_ent.barycenter >= target_ent.barycenter - { - self.merge_entries(target, source); - } - - None - } - - fn handle_out(&mut self, source: Key, target: Key) -> Option<()> { - self.sources_map.get_mut(&target)?.push(source); - - let target_ent = self.entries.get_mut(&target)?; - target_ent.indegree -= 1; - - if target_ent.indegree == 0 { - self.sinks_map.get_mut(&source)?.push(target); - } - - None - } - - fn do_resolve(&mut self, keys: &mut Vec) -> Option<()> { - while let Some(key) = keys.pop() { - for &source_key in self.sources_map[&key].clone().iter().rev() { - self.handle_in(source_key, key); - } - - for sink_key in self.sinks_map.get(&key).cloned()? { - self.handle_out(key, sink_key); - } - } - - None - } - - fn merge_entries(&mut self, source_key: Key, target_key: Key) -> Option<()> { - let mut sum = 0.0; - let mut weight = 0.0; - - let source = &self.entries[&source_key]; - if let Some(w) = source.weight { - let source_barycenter = source.barycenter?; - sum += source_barycenter * w; - weight += w; - } - let source_idx = source.idx; - - let target = &self.entries[&target_key]; - if let Some(w) = target.weight { - let target_barycenter = target.barycenter?; - sum += target_barycenter * w; - weight += w; - } - - let source_keys = self.keys_map[&target_key].clone(); - let target_keys = self.keys_map.get_mut(&source_key)?; - target_keys.extend(source_keys); - - let target = self.entries.get_mut(&target_key)?; - target.barycenter = Some(sum / weight); - target.weight = Some(weight); - target.idx = source_idx.min(target.idx); - - self.entries.get_mut(&source_key)?.merged = true; - - None - } - - #[inline] - pub(super) fn expand_subgraph(&mut self, subgraphs: &HashMap) { - for (key, keys) in &mut self.keys_map { - if let Some(subgraph) = subgraphs.get(key) { - keys.extend(&subgraph.keys); - } - } - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/sort.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/sort.rs deleted file mode 100644 index 6ccdbf796fb39502719de8c510aa961a7267eb52..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/order/subgraph/sort.rs +++ /dev/null @@ -1,92 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::{cmp, cmp::Ordering}; - -use super::{Context, ResolvedEntry, Subgraph}; -use crate::Key; - -impl Context { - pub fn next_subgraph(&mut self, bias_right: bool) -> Option { - let (mut sortable, mut unsortable) = self - .entries - .iter() - .map(|(&k, &e)| (k, e)) - .partition::, _>(|(_, ent)| ent.barycenter.is_some()); - - sortable.sort_by(|(_, lhs), (_, rhs)| cmp_with_bias(lhs, rhs, bias_right)); - unsortable.sort_by_key(|(_, entry)| cmp::Reverse(entry.idx)); - - let mut sum = 0.0; - let mut weight = 0.0; - - self.consume(&mut sortable); - - for (k, entry) in sortable { - let keys = &self.keys_map[&k]; - self.index += keys.len(); - self.keys.extend(keys); - let entry_weight = entry.weight?; - sum += entry.barycenter? * entry_weight; - weight += entry_weight; - self.consume(&mut unsortable); - } - - let mut subgraph = Subgraph::default(); - let mut keys = vec![]; - keys.extend(&self.keys); - subgraph.keys = keys; - if weight > 0.0 { - subgraph.barycenter = sum / weight; - subgraph.weight = weight; - } - - Some(subgraph) - } - - fn consume(&mut self, consumable: &mut Vec<(Key, ResolvedEntry)>) -> Option<()> { - if consumable.is_empty() { - return None; - } - - while let Some((last, _)) = consumable.last() { - let idx = self.entries[last].idx; - if idx > self.index as u16 { - break; - } - - let keys = &self.keys_map[last]; - - self.keys.extend(keys); - - consumable.pop(); - self.index += 1; - } - - None - } -} - -fn cmp_with_bias(lhs: &ResolvedEntry, rhs: &ResolvedEntry, bias: bool) -> Ordering { - let lb = lhs.barycenter; - let rb = rhs.barycenter; - if lb < rb { - return Ordering::Greater; - } else if lb > rb { - return Ordering::Less; - } - - if !bias { lhs.idx.cmp(&rhs.idx) } else { rhs.idx.cmp(&lhs.idx) } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/parent_dummy_chains.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/parent_dummy_chains.rs index 9d11943941a77ab5af9d3b7027ff1e676e28f0ee..58b9ad590dc74c88816052871ae98bdcc45d1476 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/parent_dummy_chains.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/parent_dummy_chains.rs @@ -1,147 +1,130 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. use ahash::{HashMap, HashMapExt}; -use crate::{Edge, Graph, Key, EMPTY_KEY, EMPTY_ROOT}; - -impl Graph { - /// Processes dummy chains in the graph - /// to establish proper parent-child relationships. - /// - /// This method performs a post-order traversal to determine node limits, - /// then processes each - /// dummy chain to find the lowest common ancestor - /// (LCA) and adjust parent relationships along - /// the path between edge endpoints. - /// - /// # Returns - /// - `Option<()>`: always returns None, - pub(super) fn parent_dummy_chains(&mut self) -> Option<()> { - let lims = postorder(self); - let dummy_chains = self.dummy_chains.clone().unwrap_or(vec![]); - - for mut dummy_id in dummy_chains { - let edge = self.node(&dummy_id)?.edge?; - let (path, ref lca) = self.find_lca(&lims, edge.source, edge.target); - - if !path.is_empty() { - self.traverse_path(&mut dummy_id, &path, lca, edge)?; - } - } +use crate::{DummyChain, EMPTY_NODEKEY, EdgeKey, GRAPH_NODE, LayoutGraph, NodeKey}; - None - } +impl LayoutGraph { + pub(super) fn parent_dummy_chains(&mut self) { + let low_lims = self.collect_low_lims(); + let dummy_chains = self.dummy_chains.clone(); - /// Traverses a path between nodes - /// while setting parent relationships for dummy nodes. - /// - /// # Arguments - /// * `dummy_id` - Mutable reference to the current dummy node key - /// * `path` - Path through which to establish relationships - /// * `lca` - Lowest common ancestor of the edge endpoints - /// * `edge` - Original edge being processed - fn traverse_path( - &mut self, - dummy_id: &mut Key, - path: &[Key], - lca: &Key, - edge: Edge, - ) -> Option<()> { - let mut path_iter = path.iter().peekable(); - let mut ascending = true; - - while *dummy_id != edge.target { - let node = self.node(dummy_id)?; - let mut current = (if ascending { - path_iter.find(|k| self.node(k).unwrap().max_rank > node.rank) - } else { - path_iter.rfind(|k| self.node(k).unwrap().min_rank < node.rank) - }) - .unwrap_or(lca); - - if ascending && current == lca { - ascending = false; - path_iter = path.iter().peekable(); - current = path_iter.next_back()?; + for DummyChain { chain, ek, .. } in dummy_chains { + let mut curr = *chain.front().unwrap(); + + let (path, lca) = self.find_path(&low_lims, ek); + if path.is_empty() { + continue; } - self.set_parent(*dummy_id, Some(*current)); - *dummy_id = self.successors(dummy_id).first().copied().unwrap_or(EMPTY_KEY) + let mut path_idx = 0; + let plen = path.len(); + let mut ascending = true; + + while curr != ek.target { + let node_rank = self.nodes[&curr].rank; + + let parent_key = if ascending { + while path_idx < plen { + let pk = path[path_idx]; + if pk == lca { + ascending = false; + break; + } + + if self.node(&pk).and_then(|n| n.rank_limit.map(|l|l.max)) >= node_rank { + break; + } + + path_idx += 1; + } + path.get(path_idx).copied().unwrap_or(lca) + } else { + let next_idx = path_idx + 1; + match next_idx < plen { + true => { + let pk = path[next_idx]; + match self.nodes[&pk].rank_limit.map(|l| l.max) > node_rank { + true => { + path_idx = next_idx; + pk + } + false => EMPTY_NODEKEY, + } + } + false => EMPTY_NODEKEY, + } + }; + + self.set_parent(&curr, Some(parent_key)); + curr = self.next_successor(&curr); + } } - - None } - - /// Finds the lowest common ancestor (LCA) and constructs a path between two nodes. - /// - /// # Arguments - /// * `lims` - Post-order traversal limits map - /// * `source` - Source node key - /// * `target` - Target node key - /// - /// # Returns - /// - `(Vec, Key)`: tuple containing: - /// - Full path from source to target through LCA - /// - Lowest common ancestor key - fn find_lca(&self, lims: &HashMap, source: Key, target: Key) -> (Vec, Key) { - let mut s_path: Vec = vec![]; - let mut t_path: Vec = vec![]; - - let lim = lims[&source].min(lims[&target]); - - let mut lca = source; - while let Some(parent) = self.parent(&lca) { - lca = parent; - s_path.push(parent); - if lims.get(&parent).map_or(false, |&l| lim == l) { - break; + fn find_path( + &self, + low_lims: &HashMap, + ek: EdgeKey, + ) -> (Vec, NodeKey) { + let EdgeKey { source, target } = ek; + let mut source_path = vec![]; + let mut target_path = vec![]; + + let (s_low, s_lim) = low_lims[&source]; + let (t_low, t_lim) = low_lims[&target]; + let (low, lim) = (s_low.min(t_low), s_lim.min(t_lim)); + + let mut current = source; + while let Some(&parent) = self.parent(¤t) { + source_path.push(parent); + if let Some(&(cur_low, cur_lim)) = low_lims.get(&parent) { + if cur_low <= low && lim <= cur_lim { + break; + } } + current = parent; } - let mut parent = self.parent(&target).unwrap_or(lca); - while parent != lca { - t_path.push(parent); - parent = self.parent(&parent).unwrap_or(lca); + let lca = current; + + loop { + match self.parent(¤t) { + Some(&p) => match p == lca { + true => break, + false => { + current = p; + target_path.push(p) + } + }, + None => break, + } } - t_path.reverse(); - s_path.extend(t_path); - - (s_path, lca) + target_path.reverse(); + source_path.extend(target_path); + (source_path, lca) } -} -/// Generates post-order traversal limits for graph nodes. -fn postorder(g: &Graph) -> HashMap { - let mut ret: HashMap = HashMap::new(); - let mut lim = 0; + fn collect_low_lims(&self) -> HashMap { + let mut ret = HashMap::new(); + let mut lim = 0; + let mut stack = Vec::new(); - for child in g.children(&EMPTY_ROOT) { - let mut stack = vec![child]; + for child in self.children(&GRAPH_NODE) { + stack.push((child, None)); + } - while let Some(node) = stack.pop() { - if ret.contains_key(&node) { - continue; + while let Some((key, low)) = stack.pop() { + if let Some(x) = low { + ret.insert(key, (x, lim)); + lim += 1 + } else { + stack.push((key, Some(lim))); + for &child in self.children(&key).iter().rev() { + stack.push((child, None)) + } } - - ret.insert(node, lim); - lim += 1; - stack.extend(g.children(&node)) } - } - ret + ret + } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/align.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/align.rs index f45dc79abfff1f7cc1f3c2edc7e75609c05a989d..a24cb9aa76f18321238e08de1517aa7e69f9a2ad 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/align.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/align.rs @@ -1,107 +1,79 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; - -use super::{BlockGraph, Context, Vertical, Vertical::*}; -use crate::{Graph, Key, KeyCodecExt}; - -impl Graph { - pub(super) fn vertical_alignment( - &self, - ctx: &mut Context, - matrix: &[Vec], - vertical: Vertical, - ) { - let mut pos: HashMap = HashMap::new(); - - for keys in matrix { - for (order, &key) in keys.iter().enumerate() { - ctx.root.insert(key, key); - ctx.align.insert(key, key); - pos.insert(key, order); - } - } +use super::{Context, Direction, Direction::*, DirectionAlignment, Horizontal::*}; +use crate::{LayoutGraph, PositionGraph}; - for keys in matrix { - let mut prev_idx: i32 = -1; - for &key in keys { - let mut neighbors: Vec = match vertical { - Top => self.predecessors(&key), - Bottom => self.successors(&key), - }; +fn extent(map: &DirectionAlignment) -> (f32, f32) { + let mut min = f32::INFINITY; + let mut max = f32::NEG_INFINITY; - if neighbors.is_empty() { - continue; + for &value in map.values() { + min = min.min(value); + max = max.max(value); + } + + (min, max) +} + +impl Context { + pub(super) fn min_direction(&self, graph: &PositionGraph) -> (Direction, f32, f32) { + let (dir, alignment) = self + .alignments + .iter() + .enumerate() + .min_by_key(|(_, a)| { + let mut min = f32::INFINITY; + let mut max = f32::NEG_INFINITY; + + for (key, x) in a.iter() { + let half_width = graph.nodes[&key].width * 0.5; + min = min.min(x - half_width); + max = max.max(x + half_width); } - /// Here we can improve performance a little by **unwrap** - neighbors.sort_by_key(|id| pos.get(id)); - let mid = (neighbors.len() as f32 - 1.0) / 2.0000001; - let start = mid.floor() as usize; - let end = mid.ceil() as usize; - for idx in start..=end { - let neighbor = neighbors[idx]; - if ctx.align[&key] == key - && prev_idx < (pos[&neighbor] as i32) - && !ctx.has_conflict(key, neighbor) - { - let x = ctx.root[&neighbor]; - ctx.align.insert(neighbor, key); - ctx.align.insert(key, x); - ctx.root.insert(key, x); - - prev_idx = pos[&neighbor] as i32; - } + (max - min).to_bits() + }) + .unwrap(); + + let (min, max) = extent(alignment); + + (Direction::from(dir as u32), min, max) + } + + pub(super) fn align_coordinates(&mut self, graph: &PositionGraph) { + let (direction, min, max) = self.min_direction(graph); + + for dir in [TopLeft, TopRight, BottomLeft, BottomRight] { + if dir != direction { + let alignment = &mut self.alignments[dir]; + let (vals_min, vals_max) = extent(alignment); + + let delta = match direction.horizon() { + Left => min - vals_min, + Right => max - vals_max, + }; + + if delta != 0.0 { + alignment.values_mut().for_each(|x| *x += delta) } } } } - pub(super) fn horizontal_compaction( - &self, - matrix: &[Vec], - ctx: &Context, - ) -> HashMap { - let mut compact: HashMap = HashMap::new(); - let block: BlockGraph = self.build_block_graph(matrix, &ctx.root); - - let mut stack = block.nodes(); - let mut visited: HashSet = HashSet::new(); - while let Some(k) = stack.pop() { - if visited.contains(&k) { - let in_edges = &block.in_edges[&k]; - let mut val: f32 = 0.0; - for key in in_edges { - let source = key.source(); - let ev: f32 = compact[&source] + block.edges[key]; - val = val.max(ev) - } + pub(super) fn balance(&self, graph: &mut PositionGraph, src: &mut LayoutGraph) { + let vals = &self.alignments[TopLeft]; - compact.insert(k, val); - } else { - visited.insert(k); - stack.push(k); - stack.extend(block.predecessors(&k)); - } - } + for key in vals.keys() { + let mut coord = [ + self.alignments[TopLeft][key], + self.alignments[TopRight][key], + self.alignments[BottomLeft][key], + self.alignments[BottomRight][key], + ]; - for &k in ctx.align.values() { - compact.insert(k, compact[&ctx.root[&k]]); - } + coord.sort_by_key(|a| a.to_bits()); - compact + let tgt = src.nodes.get_mut(key).unwrap(); + tgt.x = (coord[1] + coord[2]) * 0.5; + tgt.y = graph.nodes[key].y; + } } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/bk.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/bk.rs new file mode 100644 index 0000000000000000000000000000000000000000..2fb85fee40b757c795df906d36295fc5648f6233 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/bk.rs @@ -0,0 +1,150 @@ +use ahash::{HashMap, HashMapExt, HashSet, HashSetExt}; + +use super::{ + Context, Vertical, + block_graph::BlockGraph, + context::{DirectionAlignment, Vertical::Top}, +}; +use crate::{NodeKey, PositionGraph, iter_ext::RevIfExt}; + +impl PositionGraph { + pub(super) fn vertical_alignment(&self, ctx: &mut Context, vertical: Vertical) { + let mut pos = HashMap::new(); + + for layer in ctx.layering.iter().rev_if(ctx.rev_outer) { + for (order, &key) in layer.iter().rev_if(ctx.rev_inner).enumerate() { + ctx.root.insert(key, key); + pos.insert(key, order); + } + } + ctx.align = ctx.root.clone(); + + for layer in ctx.layering.iter().rev_if(ctx.rev_outer) { + let mut prev_idx: i32 = -1; + for key in layer.iter().rev_if(ctx.rev_inner) { + let mut neighbors: Vec = + if vertical == Top { self.predecessors(key) } else { self.successors(key) } + .iter() + .copied() + .collect(); + if neighbors.len() > 0 { + neighbors.sort_by_key(|k| pos[k]); + let mp = (neighbors.len() as f32 - 1.0) * 0.5; + let mut i = mp as usize; + let il = mp.ceil() as usize; + while i <= il { + let w = neighbors[i]; + if ctx.align[key] == *key + && prev_idx < (pos[&w] as i32) + && !ctx.has_conflict(*key, w) + { + ctx.align.insert(w, *key); + + ctx.root.insert(*key, ctx.root[&w]); + ctx.align.insert(*key, ctx.root[&w]); + + prev_idx = pos[&w] as i32; + } + + i += 1; + } + } + } + } + } + + pub(super) fn horizontal_compaction( + &self, + ctx: &Context, + reverse_sep: bool, + ) -> DirectionAlignment { + let mut alignment = DirectionAlignment::new(); + let bg: BlockGraph = self.build_block_graph(ctx); + + let mut stack = bg.nodes(); + let mut visited = HashSet::new(); + while let Some(curr) = stack.pop() { + if visited.insert(curr) { + stack.push(curr); + stack.extend(bg.predecessors(&curr)); + } else { + let val: f32 = bg.predecessors(&curr).into_iter().fold(0.0, |acc, &source| { + let ev = alignment[&source] + (bg.edge(source, curr).copied().unwrap_or(0.0)); + acc.max(ev) + }); + alignment.insert(curr, val); + } + } + + let mut stack = bg.nodes(); + let mut visited = HashSet::new(); + while let Some(curr) = stack.pop() { + if visited.insert(curr) { + stack.push(curr); + stack.extend(bg.successors(&curr)); + } else { + let min = bg.successors(&curr).into_iter().fold(f32::INFINITY, |acc, &sink| { + let ev = alignment[&sink] - (bg.edge(curr, sink).copied().unwrap_or(0.0)); + + acc.min(ev) + }); + + let node = self.nodes[&curr]; + if min != f32::INFINITY && node.border_at_left != reverse_sep { + alignment.insert(curr, alignment[&curr].max(min as f32)); + } + } + } + + ctx.align.values().for_each(|key| { + alignment.insert(*key, alignment[&ctx.root[key]]); + }); + + if reverse_sep { + for x in alignment.values_mut() { + *x = -*x + } + } + + alignment + } + + pub(super) fn build_block_graph(&self, ctx: &Context) -> BlockGraph { + let mut block_graph = BlockGraph::default(); + + for layer in ctx.layering.iter().rev_if(ctx.rev_outer) { + let key_roots: Vec<_> = + layer.iter().rev_if(ctx.rev_inner).map(|&key| (key, ctx.root[&key])).collect(); + + key_roots.iter().for_each(|(_, root)| block_graph.set_node(*root)); + + for window in key_roots.windows(2) { + let (u_key, u_root) = window[0]; + let (v_key, v_root) = window[1]; + + let prev_max = block_graph.edge(u_root, v_root).copied().unwrap_or(0.0); + block_graph.set_edge(u_root, v_root, self.sep(v_key, u_key).max(prev_max)); + } + } + + block_graph + } + + fn sep(&self, source: NodeKey, target: NodeKey) -> f32 { + let node_sep = self.node_sep; + let edge_sep = self.edge_sep; + + let sn = &self.nodes[&source]; + let tn = &self.nodes[&target]; + let mut sum: f32 = 0.0; + + sum += sn.width / 2.0; + + sum += if sn.dummy.is_some() { edge_sep } else { node_sep } / 2.0; + sum += if tn.dummy.is_some() { edge_sep } else { node_sep } / 2.0; + + sum += tn.width / 2.0; + + sum + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/block_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/block_graph.rs index 61c1c798603f5b8ecf953e06488f399bbafcbb74..b89e8e9d7dabcc8325dd13eef9b96703077a3add 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/block_graph.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/block_graph.rs @@ -1,117 +1,61 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use std::hash::Hash; -use ahash::{HashMap, HashMapExt}; +use ahash::{HashMap, HashSet}; -use crate::{Graph, Key, KeyCodecExt, EMPTY_KEY}; +use crate::{EdgeKey, NodeKey, keyof}; #[derive(Default)] pub struct BlockGraph { - nodes: HashMap, - pub(super) in_edges: HashMap>, - predecessors: HashMap>, - pub(super) edges: HashMap, + pub edges: HashMap, + pub predecessors: HashMap>, + pub successors: HashMap>, } impl BlockGraph { - pub fn set_node(&mut self, key: Key) -> &mut Self { - if self.nodes.contains_key(&key) { - return self; - } - - self.nodes.insert(key, EMPTY_KEY); - self.in_edges.insert(key, vec![]); - self.predecessors.insert(key, HashMap::new()); - - self + pub fn nodes(&self) -> Vec { + self.predecessors.keys().copied().collect() } - #[inline] - pub fn nodes(&self) -> Vec { - self.nodes.keys().copied().collect() + pub fn set_node(&mut self, key: NodeKey) { + if !self.predecessors.contains_key(&key) { + self.predecessors.insert(key, vec![]); + self.successors.insert(key, vec![]); + } } - #[inline] - pub fn predecessors(&self, k: &Key) -> Vec { - self.predecessors.get(k).map_or(vec![], |p| p.keys().copied().collect()) + pub fn predecessors(&self, key: &NodeKey) -> &[NodeKey] { + match self.predecessors.get(key) { + Some(vals) => vals.as_slice(), + _ => &[], + } } - pub fn set_edge(&mut self, source: Key, target: Key, val: f32) -> &mut Self { - let key = Key::of(source, target); - if self.edges.contains_key(&key) { - self.edges.insert(key, val); - return self; + pub fn successors(&self, key: &NodeKey) -> &[NodeKey] { + match self.successors.get(key) { + Some(vals) => vals.as_slice(), + _ => &[], } - - self.set_node(source); - self.set_node(target); - - self.edges.insert(key, val); - - self.predecessors - .get_mut(&target) - .map(|preds| preds.entry(source).and_modify(|c| *c += 1).or_insert(1)); - - self.in_edges.entry(target).or_default().push(key); - - self } -} -impl Graph { - pub(super) fn build_block_graph( - &self, - matrix: &[Vec], - root: &HashMap, - ) -> BlockGraph { - let mut block_graph: BlockGraph = BlockGraph::default(); + pub fn set_edge(&mut self, source: NodeKey, target: NodeKey, val: f32) { + let e = keyof(source, target); + if self.edges.contains_key(&e) { + self.edges.insert(e, val); + return; + } - for keys in matrix { - let mut target: Option = None; - for &key in keys { - let source = root[&key]; - block_graph.set_node(source); - if let Some(t) = target { - let target = root[&t]; - let prev_max = match block_graph.edges.get(&Key::of(target, source)) { - Some(&x) => x, - None => 0.0, - }; + self.edges.insert(e, val); - let max = self.sep(key, t).max(prev_max); - block_graph.set_edge(target, source, max); - } - target = Some(key); - } + if let Some(preds) = self.predecessors.get_mut(&target) { + preds.push(source); + } + if let Some(sucs) = self.successors.get_mut(&source) { + sucs.push(target); } - - block_graph } - fn sep(&self, source: Key, target: Key) -> f32 { - let nodesep = self.config.nodesep; - let edgesep = self.config.edgesep; - - let source_node = &self.nodes[&source]; - let target_node = &self.nodes[&target]; - - let mut sum = source_node.width / 2.0; - sum += if source_node.dummy.is_some() { edgesep } else { nodesep } / 2.0; - sum += if target_node.dummy.is_some() { edgesep } else { nodesep } / 2.0; - sum += target_node.width / 2.0; - - sum + pub fn edge(&self, v: NodeKey, w: NodeKey) -> Option<&f32> { + let e = keyof(v, w); + self.edges.get(&e) } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/conflict.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/conflict.rs index 93823fa1f43067b95bd948963c5552a539f88263..bbf22ba186f90bf5051efcd04c406e00818222ef 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/conflict.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/conflict.rs @@ -1,173 +1,147 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use ahash::{HashSet, HashSetExt}; use super::Context; -use crate::{normalize_st, Dummy::Border, Graph, Key}; - -#[derive(Copy, Clone)] -struct ScanCtx { - south_start: usize, - south_end: usize, - prev_north: i32, - next_north: i32, -} +use crate::{Dummy, NodeKey, PositionGraph}; -impl ScanCtx { - fn of(south_start: usize, south_end: usize, prev_north: i32, next_north: i32) -> Self { - Self { south_start, south_end, prev_north, next_north } - } -} - -impl Graph { - pub(super) fn find_conflict(&mut self, ctx: &mut Context, matrix: &[Vec]) { - if !matrix.is_empty() { - self.find_type1_conflict(ctx, matrix); - self.find_type2_conflict(ctx, matrix); +impl Context { + pub(super) fn add_conflict(&mut self, mut source: NodeKey, mut target: NodeKey) { + if source > target { + (source, target) = (target, source) } + + self.conflicts.entry(source).or_insert(HashSet::new()).insert(target); } - - fn find_type1_conflict(&mut self, ctx: &mut Context, matrix: &[Vec]) -> Option<()> { - let mut prev_len = matrix.first()?.len(); - - for layer in matrix.iter().skip(1) { - let mut k0 = 0; - let mut scan_pos = 0; - let last_key = layer.last()?; - - for (i, key) in layer.iter().enumerate() { - let w = self.other_inner_segment(key); - - let k1 = match (w, key == last_key) { - (None, false) => continue, - (Some(w), _) => self.node(&w)?.order?, - (None, true) => prev_len, - }; - - for scan_key in &layer[scan_pos..i + 1] { - let scan_node = self.node(scan_key)?; - for pre_key in &self.predecessors(scan_key) { - let pre_node = self.node(pre_key)?; - let pos = pre_node.order?; - let both_dummy = pre_node.dummy.is_some() && scan_node.dummy.is_some(); - if (pos < k0 || k1 < pos) && !both_dummy { - ctx.add_conflict(*pre_key,*scan_key) - } - } - } - scan_pos = i + 1; - k0 = k1; - prev_len = layer.len(); - } + pub(super) fn has_conflict(&self, mut source: NodeKey, mut target: NodeKey) -> bool { + if source > target { + (source, target) = (target, source) } - None + self.conflicts.get(&source).map_or(false, |set| set.contains(&target)) + } +} + +impl PositionGraph { + pub(super) fn collect_conflicts(&mut self, ctx: &mut Context) { + self.find_type_1_conflicts(ctx); + self.find_type_2_conflicts(ctx) } - fn find_type2_conflict(&mut self, ctx: &mut Context, matrix: &[Vec]) -> Option<()> { - let mut north_len = matrix.first()?.len(); - - for south in matrix.iter().skip(1) { - let mut prev_north_pos = -1; - let mut next_north_pos = 0; - let mut south_pos = 0; - let south_len = south.len(); - - for (south_ahead, key) in south.iter().enumerate() { - if self.node(key)?.dummy == Some(Border) { - let preds = self.predecessors(key); - if let [pk] = preds[..] { - next_north_pos = self.node(&pk)?.order? as i32; - let scan = - ScanCtx::of(south_pos, south_ahead, prev_north_pos, next_north_pos); - self.scan(ctx, south, scan); - south_pos = south_ahead; - prev_north_pos = next_north_pos; + fn find_type_1_conflicts(&mut self, ctx: &mut Context) { + let layering_ptr = &ctx.layering as *const Vec>; + + unsafe { + let layering_ref = &*layering_ptr; + + layering_ref.iter().reduce(|prev_layer, layer| { + let mut k0 = 0; + + let mut scan_pos = 0; + let prev_layer_length = prev_layer.len(); + let last_node = layer.last().unwrap(); + + for (i, v) in layer.iter().enumerate() { + let w = self.find_other_inner_segment_node(v); + let k1 = if let Some(w) = w { + self.nodes[&w].order.unwrap_or(0) + } else { + prev_layer_length + }; + + if w.is_some() || v == last_node { + for scan_node in &layer[scan_pos..=i] { + for k in self.predecessors(scan_node) { + let node = self.nodes[&k]; + let pos = node.order.unwrap_or(0); + if (pos < k0 || k1 < pos) + && !(node.dummy.is_some() + && self.nodes[scan_node].dummy.is_some()) + { + ctx.add_conflict(*k, *scan_node); + } + } + } + scan_pos = i + 1; + k0 = k1; } } - let scan = ScanCtx::of(south_pos, south_len, next_north_pos, north_len as i32); - self.scan(ctx, south, scan); - } - - north_len = south_len; + layer + }); } - - None } - fn scan(&mut self, ctx: &mut Context, south: &[Key], scan_ctx: ScanCtx) -> Option<()> { - let ScanCtx { - south_start, - south_end, - prev_north: prev_north_border, - next_north: next_north_border, - } = scan_ctx; - for sid in &south[south_start..south_end] { - if self.node(sid)?.dummy.is_none() { - continue; - } - - for id in self.predecessors(sid) { - let Some(node) = self.node(&id) else { continue }; - - let order = node.order.unwrap_or(0) as i32; - let has_conflict = order < prev_north_border || order > next_north_border; - if node.dummy.is_some() && has_conflict { - ctx.add_conflict(id,*sid); - } + fn scan( + &mut self, + ctx: &mut Context, + south: &Vec, + south_pos: usize, + south_end: usize, + prev_north_border: i32, + next_north_border: i32, + ) { + for key in &south[south_pos..south_end] { + if self.node(key).and_then(|n| n.dummy).is_some() { + self.predecessors(key).into_iter().for_each(|u| { + if let Some(node) = self.node(u) { + let order = node.order.unwrap_or(0) as i32; + if node.dummy.is_some() + && (order < prev_north_border || order > next_north_border) + { + ctx.add_conflict(*u, *key); + } + } + }); } } - - None } - fn other_inner_segment(&mut self, key: &Key) -> Option { - match self.node(key)?.dummy { - Some(_) => { - /// ### Functional style - /// ```asm - /// .iter().find(|u| g.node(u).unwrap().dummy.is_some()).copied() - /// ``` - /// However, there's [`unwrap`] and [`copied`] - for k in self.predecessors(key) { - if self.node(&k)?.dummy.is_some() { - return Some(k); + fn find_type_2_conflicts(&mut self, ctx: &mut Context) { + let layering_ptr = &ctx.layering as *const Vec>; + + unsafe { + let layering_ref = &*layering_ptr; + + for layers in layering_ref.windows(2) { + let north = &layers[0]; + let south = &layers[1]; + + let mut next_north_pos: i32 = -1; + let mut south_pos = 0; + + for (lookahead, &key) in south.iter().enumerate() { + if let Some(node) = self.nodes.get(&key) { + if node.dummy == Some(Dummy::Border) + && let Some(pred) = self.predecessors(&key).first() + { + next_north_pos = self.nodes[pred].order.unwrap_or(0) as i32; + self.scan( + ctx, + south, + south_pos, + south.len(), + next_north_pos, + north.len() as i32, + ); + south_pos = lookahead; + } + + self.scan( + ctx, + south, + south_pos, + south.len(), + next_north_pos, + north.len() as i32, + ); } } - - None } - None => None, } } -} - -impl Context { - fn add_conflict(&mut self, source: Key, target: Key) { - let (s, t) = normalize_st(source, target); - - self.conflicts.entry(s).or_default().push(t); - } - pub(super) fn has_conflict(&self, source: Key, target: Key) -> bool { - let (s, t) = normalize_st(source, target); - - match self.conflicts.get(&s) { - Some(set) => set.contains(&t), - _ => false, - } + fn find_other_inner_segment_node(&mut self, key: &NodeKey) -> Option { + self.nodes[key].dummy.as_ref()?; + self.predecessors(key).into_iter().find(|k| self.nodes[k].dummy.is_some()).copied() } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/context.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/context.rs index 1e01243b016bc801d64f1ff89515ea638d7803b7..2deb8b08d9258078e6b0dba16801c32a1bdbf75e 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/context.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/context.rs @@ -1,27 +1,11 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - +use ahash::{HashMap, HashSet}; use std::{ ops, ops::{Index, IndexMut}, }; -use ahash::HashMap; - use self::{Direction::*, Horizontal::*, Vertical::*}; -use crate::Key; +use crate::NodeKey; #[derive(Copy, Clone, PartialEq)] pub(super) enum Horizontal { @@ -43,8 +27,8 @@ pub(super) enum Direction { BottomRight, } -impl From for Direction { - fn from(value: usize) -> Self { +impl From for Direction { + fn from(value: u32) -> Self { match value { 0 => TopLeft, 1 => TopRight, @@ -71,16 +55,17 @@ impl ops::Add for Vertical { impl Direction { pub(super) fn horizon(self) -> Horizontal { match self { - TopLeft | TopRight => Left, - BottomLeft | BottomRight => Right, + TopLeft | BottomLeft => Left, + TopRight | BottomRight => Right, } } } -type DirectionMap = [HashMap; 4]; +pub(super) type DirectionAlignment = HashMap; +pub(super) type Alignments = [DirectionAlignment; 4]; -impl Index for DirectionMap { - type Output = HashMap; +impl Index for Alignments { + type Output = DirectionAlignment; fn index(&self, index: Direction) -> &Self::Output { match index { @@ -92,7 +77,7 @@ impl Index for DirectionMap { } } -impl IndexMut for DirectionMap { +impl IndexMut for Alignments { fn index_mut(&mut self, index: Direction) -> &mut Self::Output { match index { TopLeft => &mut self[0], @@ -103,11 +88,13 @@ impl IndexMut for DirectionMap { } } -#[derive(Default)] +#[derive(Debug, Default)] pub(super) struct Context { - pub conflicts: HashMap>, - pub direction_map: DirectionMap, - pub root: HashMap, - pub align: HashMap, - pub balanced: HashMap + pub conflicts: HashMap>, + pub alignments: Alignments, + pub root: HashMap, + pub align: HashMap, + pub layering: Vec>, + pub rev_outer: bool, + pub rev_inner: bool, } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/mod.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/mod.rs index bab053a421484e00f79e3a7361d30c94b23292d9..708403dffaac50eb7651f331ee9bacb9495533be 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/mod.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/mod.rs @@ -1,43 +1,82 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - mod align; +pub mod bk; mod block_graph; mod conflict; mod context; -mod xy; -use block_graph::*; -use context::*; +use ahash::HashMapExt; +use context::{Horizontal::*, Vertical::*, *}; -use crate::Graph; +use crate::{LayoutGraph, NodeKey, PositionGraph}; -impl Graph { - pub(super) fn position(&mut self) -> Option<()> { - let mut ncg: Graph = self.as_non_compound(); +impl LayoutGraph { + pub fn position(&mut self) { + let mut ncg = self.as_position_graph(); let mut ctx = Context::default(); + ncg.position_y(); - ncg.position_x(&mut ctx); + ncg.position_x(&mut ctx, self); + } +} + +impl PositionGraph { + fn position_x(&mut self, ctx: &mut Context, src: &mut LayoutGraph) { + ctx.layering = self.build_layer_matrix(); + self.collect_conflicts(ctx); - for (key, &x) in &ctx.balanced { - let node = self.node_mut(key)?; - node.x = x; - node.y = ncg.nodes[key].y; + for vert in [Top, Bottom] { + ctx.rev_outer = vert == Bottom; + for horiz in [Left, Right] { + ctx.rev_inner = horiz == Right; + + self.vertical_alignment(ctx, vert); + ctx.alignments[vert + horiz] = self.horizontal_compaction(ctx, horiz == Right); + } } - None + ctx.align_coordinates(self); + ctx.balance(self, src); + } + + fn position_y(&mut self) { + let layering = self.build_layer_matrix(); + let rank_sep = self.rank_sep; + + let mut prev_y = 0.0; + layering.iter().for_each(|layer| { + let max_height: f32 = + layer.iter().map(|key| self.nodes[key].height as i32).max().unwrap_or(0) as f32; + + layer.iter().for_each(|key| { + let node = self.nodes.get_mut(key).unwrap(); + node.y = prev_y + max_height * 0.5; + }); + + prev_y += max_height + rank_sep; + }); + } + + fn build_layer_matrix(&self) -> Vec> { + let mut layering: Vec> = + (0..=self.max_rank()).map(|_| vec![]).collect(); + + self.nodes.iter().for_each(|(&key, node)| { + let rank = node.rank as usize; + let layer = &mut layering[rank]; + layer.push((node.order.unwrap_or(0), key)); + }); + + layering + .into_iter() + .map(|mut layer| { + layer.sort_by_key(|(order, _)| *order); + layer.into_iter().map(|(_, key)| key).collect() + }) + .collect() + } + + fn max_rank(&self) -> i32 { + self.nodes.values().map(|node| node.rank).max().unwrap_or(0) } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/xy.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/xy.rs deleted file mode 100644 index edda8fc6dd8db562fc1e9797bcfaed5d8f4f5fbd..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/position/xy.rs +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::HashMap; - -use super::{Context, Direction, Direction::*, Horizontal::*, Vertical::*}; -use crate::{Graph, Key}; - -trait ExtentExt { - fn extent(&self) -> (f32, f32); -} - -impl ExtentExt for HashMap { - fn extent(&self) -> (f32, f32) { - let iter = self.values().copied(); - - let (mut min, mut max) = (f32::INFINITY, f32::NEG_INFINITY); - - for value in iter { - min = min.min(value); - max = max.max(value); - } - - (min, max) - } -} - -impl Context { - fn balance(&mut self) -> Option<()> { - self.balanced = self.direction_map[TopLeft].clone(); - let keys: Vec = self.balanced.keys().copied().collect(); - - for key in &keys { - let mut vals: Vec = - self.direction_map.iter().map(|dirs| dirs.get(key).copied().unwrap()).collect(); - vals.sort_by_key(|f| f.to_bits()); - let x1 = vals[1]; - let x2 = vals[2]; - let mid = self.balanced.get_mut(key)?; - *mid = (x1 + x2) / 2.0; - } - - None - } - - fn min_alignment(&self, graph: &Graph) -> Option<(Direction, f32, f32)> { - let (idx, align) = self.direction_map.iter().enumerate().min_by_key(|(_, keys)| { - let mut max = f32::NEG_INFINITY; - let mut min = f32::INFINITY; - - for (key, x) in keys.iter() { - let half_width = graph.nodes[key].width / 2.0; - max = max.max(x + half_width); - min = min.min(x - half_width); - } - - (max - min).to_bits() - })?; - - let (min, max) = align.extent(); - - Some((Direction::from(idx), min, max)) - } - - fn align_coordinates(&mut self, graph: &Graph) -> Option<()> { - let (min_direction, min, max) = self.min_alignment(graph)?; - - for direction in [TopLeft, TopRight, BottomLeft, BottomRight] { - if direction != min_direction { - let vals = &mut self.direction_map[direction]; - let (vals_min, vals_max) = vals.extent(); - - let delta = match direction.horizon() { - Left => min - vals_min, - Right => max - vals_max, - }; - - if delta != 0.0 { - for x in vals.values_mut() { - *x += delta; - } - } - } - } - - None - } -} - -impl Graph { - pub(super) fn position_x(&mut self, ctx: &mut Context) -> Option<()> { - let matrix = self.key_matrix(); - - self.find_conflict(ctx, &matrix); - let mut matrix = matrix; - - for vertical in [Top, Bottom] { - if vertical == Bottom { - matrix = self.key_matrix(); - matrix.reverse() - } - - for horizontal in [Left, Right] { - if horizontal == Right { - matrix.iter_mut().for_each(|inner| inner.reverse()); - } - - self.vertical_alignment(ctx, &matrix, vertical); - let mut compact = self.horizontal_compaction(&matrix, ctx); - - if horizontal == Right { - compact.values_mut().for_each(|x| *x = -*x); - } - - ctx.direction_map[vertical + horizontal] = compact; - } - } - - ctx.align_coordinates(self); - ctx.balance() - } - - pub(super) fn position_y(&mut self) -> Option<()> { - let matrix = self.key_matrix(); - let rank_sep = self.config.ranksep; - let mut y = 0.0; - for keys in matrix { - let mut max_height = f32::NEG_INFINITY; - - for key in &keys { - max_height = max_height.max(self.node(key)?.height) - } - - for key in &keys { - let node = self.node_mut(key)?; - node.y = y + max_height / 2.0; - } - - y += max_height + rank_sep; - } - - None - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/feasible_tree.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/feasible_tree.rs index 78921409fc97caf7ac914f82443a33001e7adb16..8b15cd7cf8c9a24ea8c5fd84adbee9e404f542b3 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/feasible_tree.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/feasible_tree.rs @@ -1,70 +1,83 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +use crate::{EdgeKey, NodeKey, RankGraph}; +use crate::rank::tight_graph::{TightGraphEdge, TightGraphNode}; +use super::TightGraph; -use crate::{Edge, Graph, GraphEdge, GraphNode, Key, EMPTY_KEY}; +pub(super) fn feasible_tree(g: &mut RankGraph) -> TightGraph { + let mut t = TightGraph::default(); -impl Graph { - pub(super) fn feasible_tree(&mut self) -> Graph { - let mut t: Graph = Graph::new(false, false); + let start = g.next_node(); + let size = g.nodes.len(); + t.set_node(start, Some(TightGraphNode::default())); - let start = self.nodes().first().copied().unwrap_or(EMPTY_KEY); - let size = self.nodes.len(); - t.set_node(start, Some(GraphNode::default())); - - while tight_tree(&mut t, self) < size { - if let Some(edge) = find_min_stack_edge(&t, self) { - let delta = - if t.has_node(&edge.source) { self.slack(edge) } else { -self.slack(edge) }; - shift_ranks(&t, self, delta); + let mut edge: Option = None; + let mut delta: Option = None; + while tight_tree(&mut t, g) < size { + edge = find_min_stack_edge(&t, g); + if let Some(edge_) = edge { + if t.has_node(&edge_.source) { + delta = Some(g.slack(&edge_)); + } else { + delta = Some(-1 * g.slack(&edge_)); } + shift_ranks(&t, g, delta.unwrap_or(0)); } - - t } -} -fn tight_tree(t: &mut Graph, g: &Graph) -> usize { - let mut stack: Vec = t.nodes(); + t +} - while let Some(curr) = stack.pop() { - for edge in g.node_edges(&curr) { - let source = edge.source; - let key = if curr == source { edge.target } else { edge.source }; - if !t.has_node(&key) && g.slack(edge) == 0 { - t.set_node(key, Some(GraphNode::default())); - t.set_edge_undirected(curr, key, Some(GraphEdge::default())); - stack.push(key); +fn tight_tree(t: &mut TightGraph, g: &RankGraph) -> usize { + fn dfs(v: &NodeKey, t: &mut TightGraph, g: &RankGraph) { + let node_edges = g.node_edges(v); + for node_edge in node_edges { + let edge_v = node_edge.source; + let mut _w: Option<&NodeKey> = None; + if v == &edge_v { + _w = Some(&node_edge.target); + } else { + _w = Some(&edge_v); + } + let w = _w.unwrap().clone(); + if !t.has_node(&w) && g.slack(&node_edge) == 0 { + t.set_node(w, Some(TightGraphNode::default())); + t.set_edge(*v, w, TightGraphEdge::default()); + dfs(&w, t, g); } } } + let nodes = t.nodes(); + for node_id in nodes.into_iter() { + dfs(&node_id, t, g); + } t.nodes.len() } -fn find_min_stack_edge(t: &Graph, g: &Graph) -> Option { - g.edges - .values() - .filter_map(|&e| (t.has_node(&e.source) != t.has_node(&e.target)).then(|| (e, g.slack(e)))) - .min_by_key(|(_, slack)| *slack) - .map(|(e, _)| e) +fn find_min_stack_edge(t: &TightGraph, g: &RankGraph) -> Option { + let edges = g.edges(); + let result = edges + .iter() + .map(|e| { + let mut e_: Option = None; + if t.has_node(&e.source) != t.has_node(&e.target) { + e_ = Some(g.slack( e)); + } + (e, e_) + }) + .filter(|(e, e_)| e_.is_some()) + .min_by(|(e1, e1_), (e2, e2_)| { + return e1_.unwrap().cmp(&e2_.unwrap()); + }); + + if result.is_some() { Some(result.unwrap().0.clone()) } else { None } } -fn shift_ranks(t: &Graph, g: &mut Graph, delta: i32) { - for node_id in t.nodes.keys() { - if let Some(node) = g.node_mut(node_id) { - node.rank = Some(node.rank.unwrap_or(0).wrapping_add(delta)); +fn shift_ranks(t: &TightGraph, g: &mut RankGraph, delta: i32) { + let nodes = t.nodes(); + for node_id in nodes { + let node_ = g.node_mut(&node_id); + if let Some(node) = node_ { + node.rank = Some(node.rank.unwrap_or(0) + delta); } } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/longest_path.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/longest_path.rs deleted file mode 100644 index a5a13334e8ea95902607e01cf38de4afd9311261..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/longest_path.rs +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::{HashSet, HashSetExt}; - -use self::Variant::*; -use crate::{Graph, Key}; - -enum Variant { - Array(Vec), - Single(Key), -} - -impl Graph { - pub(super) fn longest_path(&mut self) { - let mut visited = HashSet::new(); - let init = self.in_map.iter().filter(|(_, vec)| vec.is_empty()).map(|(&k, _)| k).collect(); - let mut stack = vec![Array(init)]; - - while stack.len() > 0 { - let curr = stack.last_mut().unwrap(); - - match curr { - Array(arr) => { - let k = arr.pop().unwrap(); - if arr.is_empty() { - stack.pop(); - } - - if !visited.contains(&k) { - visited.insert(k); - let children: Vec = - self.out_edges(&k).iter().map(|e| e.target).rev().collect(); - if children.len() > 0 { - stack.push(Single(k)); - stack.push(Array(children)) - } else { - self.node_mut(&k).unwrap().rank = Some(0); - } - } - } - Single(k) => { - let k = k.clone(); - stack.pop(); - let mut rank = i32::MAX; - - for &edge in &self.out_map[&k] { - let minlen = self.edge1(edge).unwrap().minlen.unwrap(); - let target_rank = self.nodes[&edge.target].rank.unwrap(); - - rank = rank.min(target_rank - minlen); - } - - self.node_mut(&k).unwrap().rank = Some(rank); - } - } - } - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/mod.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/mod.rs index f91afa087ecdf9128d6837e46c72f48d4b5864b3..47942ae614186b09e6da86798d8532cbfc73489c 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/mod.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/mod.rs @@ -1,38 +1,18 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. +pub mod feasible_tree; +pub mod network_simplex; +pub mod util; +mod tight_graph; -mod feasible_tree; -mod longest_path; -mod network_simplex; -mod slack; +use tight_graph::*; -use crate::{Graph, Ranker::*}; +use crate::{rank::feasible_tree::feasible_tree, LayoutGraph}; -impl Graph { - #[inline] +impl LayoutGraph { pub(super) fn rank(&mut self) { - match self.config.ranker { - NetworkSimplex => self.network_simplex(), - TightTree => self.tight_tree(), - LongestPath => self.longest_path(), - } - } - - #[inline] - fn tight_tree(&mut self) { - self.longest_path(); - self.feasible_tree(); + let mut rg = self.as_rank_graph(); + rg.longest_path(); + feasible_tree(&mut rg); + + rg.transfer_node_edge(self) } } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/network_simplex.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/network_simplex.rs index 384528283503c183c9ee74c4638b21d529b381dc..cdef40ea43956b02c74e168a28a0da204e632b44 100644 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/network_simplex.rs +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/network_simplex.rs @@ -1,224 +1,274 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use std::mem; - use ahash::{HashSet, HashSetExt}; -use crate::{Edge, Graph, GraphEdge, GraphNode, Key, EMPTY_KEY}; - -impl Graph { - pub(super) fn network_simplex(&mut self) { - self.simplify_ref(); - self.longest_path(); - - let mut t: Graph = self.feasible_tree(); - init_low_lim_values(&mut t); - init_cut_values(&mut t, self); - - while let Some(e) = leave_edge(&t) { - if let Some(f) = enter_edge(&t, &self, e) { - exchange_edges(&mut t, self, e, f); - } +use super::{TightGraph, TightGraphEdge, TightGraphNode, feasible_tree}; +use crate::{EMPTY_NODEKEY, EdgeKey, NodeKey, RankGraph}; + +pub fn network_simplex(g: &mut RankGraph) { + g.longest_path(); + + let mut t: TightGraph = feasible_tree(g); + init_low_lim_values(&mut t, None); + init_cut_values(&mut t, g); + + let mut e; + let mut f; + while { + e = leave_edge(&t); + e.is_some() + } { + f = enter_edge(&t, &g, &e.unwrap()); + if f.is_some() { + exchange_edges(&mut t, g, &e.unwrap(), f.unwrap()); } } - - fn simplify_ref(&mut self) { - for edge in self.edge_values.values_mut() { - edge.weight.get_or_insert(0.0); - edge.minlen.get_or_insert(1); - } - } -} - -fn init_cut_values(t: &mut Graph, g: &mut Graph) { - let keys = g.postorder(&g.nodes()); - for &key in keys.iter().skip(1) { - assign_cut_value(t, g, key); - } } -fn assign_cut_value(t: &mut Graph, g: &mut Graph, child: Key) { - let cutvalue = calc_cut_value(t, g, child); - if let Some(node) = t.node_mut(&child) { - let parent = node.parent.unwrap_or(EMPTY_KEY); - if let Some(edge) = t.edge_mut(child, parent) { - edge.cutvalue = Some(cutvalue); - } +fn init_cut_values(t: &mut TightGraph, g: &mut RankGraph) { + let node_ids = g.nodes(); + let vs = postorder(g, &node_ids); + for node_id in vs.iter().skip(1) { + assign_cut_value(t, g, node_id); } } -fn calc_cut_value(t: &mut Graph, g: &mut Graph, child: Key) -> f32 { - let Some(node) = t.node_mut(&child) else { return 0.0 }; +fn assign_cut_value(t: &mut TightGraph, g: &mut RankGraph, child: &NodeKey) { + let cut_value = calc_cut_value(t, g, child); - let parent = node.parent.unwrap_or(EMPTY_KEY); - let mut child_is_tail = true; - let mut graph_edge = g.edge_mut(child, parent); + if let Some(node) = t.node_mut(child) { + let parent = node.parent.unwrap_or(EMPTY_NODEKEY); - if graph_edge.is_none() { - child_is_tail = false; - graph_edge = g.edge_mut(parent, child); + if let Some(edge) = t.edge_mut(&child, &parent) { + edge.cut_value = Some(cut_value); + } } +} - let mut cut_value = graph_edge.and_then(|e| e.weight).unwrap_or(0.0); - - for edge in g.node_edges(&child) { - let is_out_edge = edge.source == child; - let other = if is_out_edge { edge.target } else { edge.source }; - - if other == parent { - continue; +fn calc_cut_value(t: &mut TightGraph, g: &mut RankGraph, child: &NodeKey) -> f32 { + // The accumulated cut value for the edge between this node and its parent + let mut cut_value = 0.0; + let child_lab_ = t.node_mut(child); + if let Some(child_lab) = child_lab_ { + let parent = child_lab.parent.unwrap_or(EMPTY_NODEKEY); + // True if the child is on the tail end of the edge in the directed graph + let mut child_is_tail = true; + // The graph's view of the tree edge we're inspecting + let mut graph_edge = g.edge_mut(child, &parent); + + if graph_edge.is_none() { + child_is_tail = false; + graph_edge = g.edge_mut(&parent, &child); } - let points_to_head = is_out_edge == child_is_tail; - let other_weight = g.edge1(edge).and_then(|e| e.weight).unwrap_or(0.0); + cut_value = graph_edge.map(|e| e.weight).unwrap_or(0.0); + let edge_objs = g.node_edges(child); + for e in edge_objs { + let is_out_edge = &e.source == child; + let other = if is_out_edge { e.target } else { e.source }; + + if other != parent { + let points_to_head = is_out_edge == child_is_tail; + let other_weight = g.edge1(&e).map(|e| e.weight).unwrap_or(0.0); - cut_value += if points_to_head { other_weight } else { -other_weight }; + cut_value += if points_to_head { other_weight } else { -other_weight }; - if is_tree_edge(t, child, other) { - let out_cut_value = t.edge(child, other).and_then(|e| e.cutvalue).unwrap_or(0.0); - cut_value += if points_to_head { -out_cut_value } else { out_cut_value } + if t.has_edge(child, &other) { + let out_cut_value = + t.edge(&child, &other).and_then(|e| e.cut_value).unwrap_or(0.0); + cut_value += if points_to_head { -out_cut_value } else { out_cut_value } + } + } } } cut_value } -fn init_low_lim_values(tree: &mut Graph) { - let root = tree.nodes().first().copied().unwrap_or(EMPTY_KEY); - let mut visited: HashSet = HashSet::new(); - assign_low_lim(tree, &mut visited, 1, root); +fn init_low_lim_values(tree: &mut TightGraph, root_: Option) { + let root = match root_ { + Some(r) => r, + None => tree.next_node(), + }; + + let mut visited = HashSet::new(); + dfs_assign_low_lim(tree, &mut visited, 1, &root, None); } -fn assign_low_lim( - tree: &mut Graph, - visited: &mut HashSet, - mut next_lim: usize, - start_key: Key, +fn dfs_assign_low_lim( + tree: &mut TightGraph, + visited: &mut HashSet, + next_lim_: usize, + v: &NodeKey, + parent: Option<&NodeKey>, ) -> usize { - let mut stack: Vec<(Key, usize, Option)> = vec![(start_key, next_lim, None)]; + let low = next_lim_; + let mut next_lim = next_lim_; - while let Some((k, low, parent)) = stack.pop() { - if !visited.insert(k) { - continue; - } + visited.insert(*v); + let neighbors = tree.neighbors(v); - let neighbors = tree.neighbors(&k); - let unvisited_neighbors = - neighbors.into_iter().filter(|w| !visited.contains(w)).collect::>(); + for w in neighbors { + if !visited.contains(&w) { + next_lim = dfs_assign_low_lim(tree, visited, next_lim, &w, Some(v)); + } + } - if !unvisited_neighbors.is_empty() { - stack.push((k, low, parent)); + if let Some(node) = tree.node_mut(v) { + node.low = Some(low); + node.lim = Some(next_lim); + next_lim += 1; - for t in unvisited_neighbors { - stack.push((t, next_lim, Some(k))); - } + if parent.is_some() { + node.parent = Some(parent.cloned().unwrap()); } else { - if let Some(node) = tree.node_mut(&k) { - node.low = Some(low); - node.lim = Some(next_lim); - next_lim += 1; - - node.parent = parent; - } + node.parent = None; } } next_lim } -fn leave_edge(tree: &Graph) -> Option { +fn leave_edge(tree: &TightGraph) -> Option { tree.edges - .values() - .find(|&&edge_obj| tree.edge1(edge_obj).map(|e| e.cutvalue) < Some(Some(0.0))) - .copied() + .iter() + .find(|(ek, edge)| edge.cut_value.unwrap_or(0.0) < 0.0) + .map(|(&ek, _)| ek) } -fn enter_edge(t: &Graph, g: &Graph, edge: Edge) -> Option { - let mut source = edge.source; - let mut target = edge.target; +fn enter_edge(t: &TightGraph, g: &RankGraph, edge: &EdgeKey) -> Option { + let mut v = edge.source; + let mut w = edge.target; - if !g.has_edge(source, target) { - mem::swap(&mut source, &mut target); + if !g.has_edge(&v, &w) { + v = edge.target; + w = edge.source; } - let source_node = t.node(&source); - let target_node = t.node(&target); - let mut tail_node = source_node; + let v_label = t.node(&v).copied().unwrap_or_default(); + let w_label = t.node(&w).copied().unwrap_or_default(); + let mut tail_label = &v_label; let mut flip = false; - if source_node?.lim > target_node?.lim { - tail_node = target_node; + if v_label.lim > w_label.lim { + tail_label = &w_label; flip = true; } - g.edges - .values() - .filter(|edge_obj| { - let v_node = t.node(&edge_obj.source); - let w_node = t.node(&edge_obj.target); - flip == is_descendant(v_node, tail_node) && flip != is_descendant(w_node, tail_node) - }) - .min_by_key(|&&e| g.slack(e)) - .copied() + let edge_objs = g.edges(); + let candidates = edge_objs.iter().filter(|edge_obj| { + let v_node = t.node(&edge_obj.source).copied().unwrap_or_default(); + let w_node = t.node(&edge_obj.target).copied().unwrap_or_default(); + flip == is_descendant(&v_node, tail_label) && flip != is_descendant(&w_node, tail_label) + }); + + candidates.min_by_key(|e| g.slack(e)).copied() } -fn exchange_edges(t: &mut Graph, g: &mut Graph, e: Edge, f: Edge) { - t.remove_edge(e.source, e.target); - t.set_edge(f.source, f.target, Some(GraphEdge::default())); - init_low_lim_values(t); +fn exchange_edges(t: &mut TightGraph, g: &mut RankGraph, e: &EdgeKey, f: EdgeKey) { + let v = e.source; + let w = e.target; + t.remove_edge(&v, &w); + t.set_edge(f.source, f.target, TightGraphEdge::default()); + init_low_lim_values(t, None); init_cut_values(t, g); update_ranks(t, g); } -fn update_ranks(t: &mut Graph, g: &mut Graph) { +fn update_ranks(t: &mut TightGraph, g: &mut RankGraph) { let root = t .nodes .keys() - .find(|k| !g.node(k).map_or(true, |n| n.parent.is_none())) + .find(|k| g.node(k).and_then(|n| n.parent).is_none()) .copied() - .unwrap_or(EMPTY_KEY); - let keys = t.preorder(&vec![root]); - for &k in keys.iter().skip(1) { - let parent = t.node(&k).and_then(|n| n.parent).unwrap_or(EMPTY_KEY); - let mut edge = g.edge(k, parent); + .unwrap_or(EMPTY_NODEKEY); + let keys = preorder(t, &vec![root]); + + for key in keys.iter().skip(1) { + let parent = t.node(&key).and_then(|n| n.parent).unwrap_or(EMPTY_NODEKEY); + let mut edge = g.edge(&key, &parent); let mut flipped = false; if edge.is_none() { - edge = g.edge(parent, k); + edge = g.edge(&parent, &key); flipped = true; } - let mut minlen = edge.and_then(|e| e.minlen).unwrap_or(0); - if !flipped { - minlen = -minlen - } + let minlen = if flipped { + edge.map(|e| e.min_len).unwrap_or(0.0) + } else { + -edge.map(|e| e.min_len).unwrap_or(0.0) + }; let parent_rank = g.node(&parent).and_then(|n| n.rank).unwrap_or(0); - if let Some(node) = g.node_mut(&k) { - node.rank = Some(parent_rank + (minlen)); + + if let Some(node) = g.node_mut(&key) { + node.rank = Some(parent_rank + (minlen as i32)); } } } -#[inline] -fn is_tree_edge(tree: &Graph, source: Key, target: Key) -> bool { - tree.has_edge(source, target) +fn is_descendant(v_label: &TightGraphNode, root_label: &TightGraphNode) -> bool { + let low = root_label.low; + let v_lim = v_label.lim; + let root_lim = root_label.lim; + low <= v_lim && v_lim <= root_lim +} + +fn postorder(g: &mut RankGraph, vs: &Vec) -> Vec { + let mut acc = vec![]; + let mut visited = HashSet::new(); + for v in vs.iter() { + let mut stack = vec![(*v, false)]; + while stack.len() > 0 { + let curr_ = stack.pop(); + if curr_.is_none() { + continue; + } + let curr = curr_.unwrap(); + if curr.1 { + acc.push(curr.0); + } else { + if !visited.contains(&curr.0) { + visited.insert(curr.0); + stack.push((curr.0, true)); + + let _navigation_nodes = g.neighbors(&curr.0); + let mut idx = _navigation_nodes.len(); + while idx > 0 { + let nav_node = _navigation_nodes[idx - 1]; + stack.push((nav_node, false)); + idx -= 1; + } + } + } + } + } + + acc } -fn is_descendant(node: Option<&GraphNode>, root_node: Option<&GraphNode>) -> bool { - let root_node = root_node.unwrap(); - let lim = node.unwrap().lim; - root_node.low <= lim && lim <= root_node.lim +fn preorder(g: &mut TightGraph, vs: &Vec) -> Vec { + let mut acc = vec![]; + let mut visited = HashSet::new(); + for &v in vs { + let mut stack = vec![v]; + while stack.len() > 0 { + let curr_ = stack.pop(); + if curr_.is_none() { + continue; + } + let curr = curr_.unwrap(); + if !visited.contains(&curr) { + visited.insert(curr); + acc.push(curr); + + let _navigation_nodes = g.neighbors(&curr); + let mut idx = _navigation_nodes.len() as i32; + while idx >= 0 { + stack.push(_navigation_nodes[idx as usize]); + idx -= 1; + } + } + } + } + + acc } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/slack.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/slack.rs deleted file mode 100644 index c89790255807b4983d7bd68b020b8a56851b0cb8..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/slack.rs +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{Edge, Graph}; - -/// The slack is defined as the -/// difference between the length of the edge and its minlen. -impl Graph { - pub(super) fn slack(&self, edge: Edge) -> i32 { - let source_rank = self.nodes[&edge.source].rank.unwrap(); - let target_rank = self.nodes[&edge.target].rank.unwrap(); - let minlen = self.edge_values[&edge.to_key()].minlen.unwrap(); - target_rank - source_rank - minlen - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/tight_graph.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/tight_graph.rs new file mode 100644 index 0000000000000000000000000000000000000000..dffac9fda5122ca8ca262ebaa36988da2834b7bb --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/tight_graph.rs @@ -0,0 +1,151 @@ +use std::hash::Hash; + +use ahash::{HashMap, HashSet, HashSetExt}; + +use crate::{EMPTY_NODEKEY, EdgeKey, NodeKey}; + +#[derive(Debug, Default)] +pub(super) struct TightGraph { + pub nodes: HashMap, + pub edges: HashMap, + pub predecessors: HashMap>, + pub successors: HashMap>, +} + +impl TightGraph { + pub fn nodes(&self) -> Vec { + self.nodes.keys().copied().collect() + } + + pub fn next_node(&self) -> NodeKey { + self.nodes.keys().next().copied().unwrap_or(EMPTY_NODEKEY) + } + + pub fn set_node(&mut self, key: NodeKey, value: Option) { + if self.nodes.contains_key(&key) { + if value.is_some() { + self.nodes.insert(key, value.unwrap()); + } + return; + } + + self.nodes.insert(key, value.unwrap_or_default()); + + self.predecessors.insert(key, HashSet::new()); + self.successors.insert(key, HashSet::new()); + } + + pub fn node(&self, v: &NodeKey) -> Option<&TightGraphNode> { + self.nodes.get(v) + } + + pub fn node_mut(&mut self, v: &NodeKey) -> Option<&mut TightGraphNode> { + self.nodes.get_mut(v) + } + + pub fn has_node(&self, v: &NodeKey) -> bool { + self.nodes.contains_key(v) + } + + pub fn predecessors(&self, key: &NodeKey) -> Vec { + self.predecessors + .get(key) + .map(|x| x.into_iter().copied().collect()) + .unwrap_or_default() + } + + pub fn successors(&self, key: &NodeKey) -> Vec { + self.successors + .get(key) + .map(|x| x.into_iter().copied().collect()) + .unwrap_or_default() + } + + pub fn neighbors(&self, key: &NodeKey) -> Vec { + let mut union: HashSet = HashSet::new(); + union.extend(self.predecessors(key)); + union.extend(self.successors(key)); + + union.into_iter().collect() + } + + pub fn set_edge(&mut self, source: NodeKey, target: NodeKey, value: TightGraphEdge) { + let ek = edge_args_to_obj(source, target); + if self.edges.contains_key(&ek) { + self.edges.insert(ek, value); + return; + } + + // It didn't exist, so we need to create it. + // First ensure the nodes exist. + self.set_node(source, None); + self.set_node(target, None); + + self.edges.insert(ek, value); + + if let Some(preds) = self.predecessors.get_mut(&target) { + preds.insert(source); + } + if let Some(sucs) = self.successors.get_mut(&source) { + sucs.insert(target); + } + } + + pub fn edge(&self, source: &NodeKey, target: &NodeKey) -> Option<&TightGraphEdge> { + let ek = edge_args_to_obj(*source, *target); + self.edges.get(&ek) + } + + pub fn edge1(&self, ek: &EdgeKey) -> Option<&TightGraphEdge> { + self.edges.get(ek) + } + + pub fn edge_mut(&mut self, source: &NodeKey, target: &NodeKey) -> Option<&mut TightGraphEdge> { + let ek = edge_args_to_obj(*source, *target); + self.edges.get_mut(&ek) + } + + pub fn has_edge(&self, source: &NodeKey, target: &NodeKey) -> bool { + let ek = edge_args_to_obj(*source, *target); + self.edges.contains_key(&ek) + } + + pub fn remove_edge(&mut self, source: &NodeKey, target: &NodeKey) -> Option { + let ek = edge_args_to_obj(*source, *target); + if let Some(edge) = self.edges.get_mut(&ek) { + let v = ek.source; + let w = ek.target; + let val = self.edges.remove(&ek); + if let Some(preds) = self.predecessors.get_mut(&w) { + preds.remove(&v); + } + if let Some(sucs) = self.successors.get_mut(&v) { + sucs.remove(&w); + } + + return val; + } + + None + } +} + +fn edge_args_to_obj(mut source: NodeKey, mut target: NodeKey) -> EdgeKey { + if source > target { + (source, target) = (target, source) + } + + EdgeKey { source, target } +} + +#[derive(Debug, Clone, Copy, Default)] +pub(super) struct TightGraphNode { + pub low: Option, + pub lim: Option, + pub parent: Option, +} + +#[derive(Debug, Clone, Copy, Default)] +pub(super) struct TightGraphEdge { + pub cut_value: Option, +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/util.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..220f4b146bfbcbd018636ff95b72962398c75c45 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/rank/util.rs @@ -0,0 +1,44 @@ +use ahash::{HashSet, HashSetExt}; + +use crate::{EdgeKey, NodeKey, RankGraph}; + +impl RankGraph { + pub(super) fn longest_path(&mut self) { + let mut visited = HashSet::new(); + + fn dfs(key: &NodeKey, g: &mut RankGraph, visited: &mut HashSet) -> i32 { + let node_label = g.node(key); + if visited.contains(key) { + return node_label.and_then(|n| n.rank).unwrap_or(0); + } + visited.insert(*key); + + let ranks: Vec = g + .out_edges(key) + .iter() + .map(|e| { + dfs(&e.target, g, visited) + - (g.edge1(&e).map(|e| e.min_len).unwrap_or(0.0).round() as i32) + }) + .collect(); + let rank: i32 = ranks.iter().min().copied().unwrap_or(0); + { + if let Some(node_label) = g.node_mut(key) { + node_label.rank = Some(rank); + } + } + rank + } + + for node_id in self.sources() { + dfs(&node_id, self, &mut visited); + } + } + + pub(super) fn slack(&self, ek: &EdgeKey) -> i32 { + let tr = self.node(&ek.target).and_then(|n| n.rank).unwrap_or(0); + let sr = self.node(&ek.source).and_then(|n| n.rank).unwrap_or(0); + let min_len = self.edge1(ek).map(|e| e.min_len).unwrap_or(10.0).round() as i32; + tr - sr - min_len + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/self_edge.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/self_edge.rs new file mode 100644 index 0000000000000000000000000000000000000000..d9bca2756db1482f2bdbaea1f1677e539a293cee --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/self_edge.rs @@ -0,0 +1,163 @@ +use std::mem; + +use ahash::HashMap; +use smallvec::{smallvec, SmallVec}; +use crate::{ + Dummy, EdgeKey, GraphEdge, GraphNode, LayoutGraph, NodeKey, Point, SelfEdge, +}; + +impl LayoutGraph { + pub(super) fn make_space_for_edge_labels(&mut self) { + self.config.rank_sep = self.config.rank_sep * 0.5; + + for edge in self.edges.values_mut() { + edge.min_len *= 2.0; + } + } + + pub(super) fn assign_node_intersects(&mut self) { + let nodes_ptr = &self.nodes as *const HashMap; + + unsafe { + let nodes_ref = &*nodes_ptr; + + for (ek, edge) in &mut self.edges { + let source_node = &nodes_ref[&ek.source]; + let target_node = &nodes_ref[&ek.target]; + if let Some(points) = &mut edge.points { + let points_ptr = points as *mut SmallVec; + let points_ref = &*points_ptr; + + let p1 = points_ref.first().unwrap(); + let p2 = points_ref.last().unwrap(); + + points.insert(0, source_node.intersect(p1)); + points.push(target_node.intersect(p2)); + } else { + let p1 = Point { x: target_node.x, y: target_node.y }; + let p2 = Point { x: source_node.x, y: source_node.y }; + edge.points = Some(smallvec![source_node.intersect(&p1), target_node.intersect(&p2)]) + } + } + } + } + + pub(super) fn remove_edge_label_proxies(&mut self) { + let nodes_ptr = &self.nodes as *const HashMap; + + unsafe { + let nodes_ref = &*nodes_ptr; + + for (key, node) in nodes_ref { + if node.dummy == Some(Dummy::EdgeProxy) { + self.remove_node(key); + } + } + } + } + + pub(super) fn remove_border_nodes(&mut self) { + let nodes_ptr = &mut self.nodes as *mut HashMap; + + unsafe { + let nodes_mut = &mut *nodes_ptr; + let nodes_ref = &*nodes_ptr; + + for (key, border) in &self.node_borders { + if self.children(key).is_empty() { + continue; + } + + let node = nodes_mut.get_mut(key).unwrap(); + + let t = &nodes_ref[&border.top.unwrap()]; + let b = &nodes_ref[&border.bottom.unwrap()]; + let l = { + let border_left = &border.left; + let l_key = border_left.keys().max().unwrap(); + &nodes_ref[&border_left[l_key]] + }; + let r = { + let border_right = &border.right; + let r_key = border_right.keys().max().unwrap(); + &nodes_ref[&border_right[r_key]] + }; + + node.width = (r.x - l.x).abs(); + node.height = (b.y - t.y).abs(); + node.x = l.x + node.width / 2.0; + node.y = l.y + node.height / 2.0; + } + + for (key, _) in mem::take(&mut self.node_borders) { + self.remove_node(&key); + } + } + } + + pub(super) fn remove_self_edges(&mut self) { + let edges_ptr = &self.edges as *const HashMap; + + unsafe { + let edges_ref = &*edges_ptr; + + for ek in edges_ref.keys() { + if ek.source == ek.target { + let Some(edge) = self.remove_edge(&ek.source, &ek.target) else { continue }; + + self.self_edges.entry(ek.source).or_default().push(SelfEdge::new(*ek, edge)); + } + } + } + } + + pub(super) fn insert_self_edges(&mut self) { + let layers = self.build_layer_matrix(); + for layer in layers { + let mut order_shift = 0; + for (i, key) in layer.iter().enumerate() { + let node = self.nodes.get_mut(key).unwrap(); + node.order = Some(i + order_shift); + let rank = node.rank; + + for _ in mem::take(&mut self.self_edges) { + let mut gn = GraphNode::default(); + gn.rank = rank; + order_shift += 1; + gn.order = Some(i + order_shift); + self.add_dummy_node(Dummy::SelfEdge, gn); + } + } + } + } + + pub fn position_self_edges(&mut self) { + let self_edges_ptr = &mut self.self_edges as *mut HashMap>; + + unsafe { + let self_edges_mut = &mut *self_edges_ptr; + + for (key, self_edges) in mem::take(self_edges_mut) { + for SelfEdge { ek, mut edge } in self_edges { + let node = self.remove_node(&key).unwrap(); + let self_node = &self.nodes[&ek.source]; + let x = self_node.x + self_node.width / 2.0; + let y = self_node.y; + let dx = node.x - x; + let dy = self_node.height / 2.0; + + edge.points = Some(smallvec![ + Point { x: x + 2.0 * dx / 3.0, y: y - dy }, + Point { x: x + 2.0 * dx / 3.0, y: y - dy }, + Point { x: x + 5.0 * dx / 6.0, y: y - dy }, + Point { x: x + dx, y }, + Point { x: x + 5.0 * dx / 6.0, y: y + dy }, + Point { x: x + 2.0 * dx / 3.0, y: y + dy }, + ]); + + self.set_edge(ek.source, ek.target, Some(edge)); + } + } + } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/selfedge.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/selfedge.rs deleted file mode 100644 index 280b5e1a0501a7112d93178032939227e647d080..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/selfedge.rs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use smallvec::smallvec; - -use crate::{Dummy::SelfEdge, Graph, GraphNode, Point}; - -/// Self-edge Processing Module -/// -/// ## Overview -/// This module implements a three-phase process to layout -/// self-edges properly in hierarchical graph diagrams. -/// Self-edge is edge where source == target -/// -/// The implementation handles: -/// - Temporary removal of original edges -/// - Insertion of layout markers -/// - Final path calculation and cleanup -/// -/// ## Operation Phases -/// 1. **Edge Removal** (`remove_self_edges`): -/// - Detects and removes self-edges from the main graph structure -/// - Stores original edges in a temporary map for later processing -/// -/// 2. **Marker Insertion** (`insert_self_edges`): -/// - Creates dummy nodes in the layout matrix to reserve space -/// - Maintains proper node ordering through order shifting -/// - Preserves edge metadata for final rendering -/// -/// 3. **Path Positioning** (`position_self_edges`): -/// - Calculates smooth Bézier curve control points -/// - Creates an elliptical path around source node -/// - Removes temporary dummy nodes after path generation -/// -/// ## Key Characteristics -/// - Maintains layout integrity through temporary dummy nodes -/// - Generates consistent elliptical paths for visual clarity -/// - Preserves original-edge data while modifying visual representation -impl Graph { - pub(super) fn remove_self_edges(&mut self) { - for edge in self.edges() { - if edge.source == edge.target { - self.selfedge_map.entry(edge.source).or_default().push(edge); - self.remove_edge1(edge); - } - } - } - - pub(super) fn insert_self_edges(&mut self) -> Option<()> { - let matrix = self.key_matrix(); - for layer in matrix { - let mut order_shift = 0; - for (i, id) in layer.iter().enumerate() { - let node = self.node_mut(id)?; - node.order = Some(i + order_shift); - let rank = node.rank; - - let self_edges = self.selfedge_map.get(id)?.clone(); - for edge in self_edges { - order_shift += 1; - let graph_node = GraphNode { - rank, - order: Some(i + order_shift), - edge: Some(edge), - ..GraphNode::default() - }; - self.add_dummy_node(SelfEdge, graph_node); - } - } - } - - None - } - - pub(super) fn position_self_edges(&mut self) -> Option<()> { - for key in &mut self.nodes() { - let node = &mut self.node(&key)?; - if node.dummy == Some(SelfEdge) { - let self_node = &mut self.node(&node.edge?.source)?; - let x = self_node.x + self_node.width / 2.0; - let y = self_node.y; - let dx = node.x - x; - let dy = self_node.height / 2.0; - let graph_edge = &mut self.edge_mut1(node.edge?)?; - graph_edge.points = Some(smallvec![ - Point::of(x + 2.0 * dx / 3.0, y - dy), - Point::of(x + 2.0 * dx / 3.0, y - dy), - Point::of(x + 5.0 * dx / 6.0, y - dy), - Point::of(x + dx, y), - Point::of(x + 5.0 * dx / 6.0, y + dy), - Point::of(x + 2.0 * dx / 3.0, y + dy), - ]); - self.remove_node(&key); - } - } - - None - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/util.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d57e69647b00867d56115ddc3a1a394ff149b69 --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/util.rs @@ -0,0 +1,152 @@ +use std::collections::LinkedList; +use ahash::{HashMap, HashMapExt}; + +use crate::{unique_id, Dummy, GraphNode, LayoutGraph, NodeKey, Point, RankLimit}; + +impl GraphNode { + pub(super) fn intersect(&self, point: &Point) -> Point { + let x = self.x; + let y = self.y; + + let dx = point.x - x; + let dy = point.y - y; + let w = self.width / 2.0; + let h = self.height / 2.0; + + if dx == 0.0 && dy == 0.0 { + panic!("Not possible to find intersection inside of the rectangle"); + } + + let (sx, sy) = if (dy.abs() * w) > (dx.abs() * h) { + if dy < 0.0 { (-h * dx / dy, -h) } else { (h * dx / dy, h) } + } else { + if dx < 0.0 { (-w, -w * dy / dx) } else { (w, w * dy / dx) } + }; + + Point { x: x + sx, y: y + sy } + } +} + +impl LayoutGraph { + pub(crate) fn build_layer_matrix(&self) -> Vec> { + let mut layering: Vec> = + (0..=self.max_rank()).map(|_| vec![]).collect(); + + self.nodes.iter().for_each(|(&key, node)| { + let rank = node.rank.unwrap_or(0) as usize; + let layer = &mut layering[rank]; + layer.push((node.order.unwrap_or(0), key)); + }); + + layering + .into_iter() + .map(|mut layer| { + layer.sort_by_key(|(order, _)| *order); + layer.into_iter().map(|(_, key)| key).collect() + }) + .collect() + } + + pub(crate) fn normalize_ranks(&mut self) { + let min = self.nodes.values().map(|node| node.rank.unwrap_or(0)).min().unwrap_or(0); + for node in self.nodes.values_mut() { + if let Some(rank) = node.rank { + node.rank = Some(rank - min); + } + } + } + + pub(crate) fn remove_empty_ranks(&mut self) { + let offset: i32 = self.nodes.values().map(|node| node.rank.unwrap_or(0)).min().unwrap_or(0); + + let mut layers = HashMap::new(); + for (key, node) in &self.nodes { + let rank = node.rank.unwrap_or(0) - offset; + layers.entry(rank).or_insert(vec![]).push(*key); + } + + let mut delta = 0; + let node_rank_factor = self.config.node_rank_factor.unwrap_or(0.0) as i32; + for (i, keys) in &layers { + if keys.is_empty() && i % node_rank_factor != 0 { + delta -= 1; + } else if delta != 0 { + for key in keys.iter() { + if let Some(node) = self.node_mut(key) { + node.rank = Some(node.rank.unwrap_or(0) + delta) + } + } + } + } + } + + pub(crate) fn max_rank(&self) -> i32 { + self.nodes.values().map(|node| node.rank.unwrap()).max().unwrap() + } + + pub(crate) fn max_rank_with_cache(&self) -> (i32, HashMap>) { + let mut max_rank = 0; + let mut rank_cache: HashMap> = HashMap::new(); + for (&key, node) in &self.nodes { + if let Some(limit) = node.rank_limit { + for rank in limit.min..=limit.max { + rank_cache.entry(rank).or_default().push((key, node.order.unwrap_or(0), true)) + } + } + + let node_rank = node.rank.unwrap(); + rank_cache + .entry(node_rank) + .or_default() + .push((key, node.order.unwrap_or(0), false)); + max_rank = max_rank.max(node_rank) + } + + (max_rank, rank_cache) + } + + pub(crate) fn assign_rank_min_max(&mut self) { + let nodes_ptr = &mut self.nodes as *mut HashMap; + + unsafe { + let nodes_mut = &mut *nodes_ptr; + + for (key, border) in &self.node_borders { + let node = nodes_mut.get_mut(key).unwrap(); + + let min_rank = self.node(&border.top.unwrap()).and_then(|n| n.rank).unwrap_or(0); + let max_rank = self.node(&border.bottom.unwrap()).and_then(|n| n.rank).unwrap_or(0); + + node.rank_limit = Some(RankLimit { min: min_rank, max: max_rank }); + } + } + } + + pub(crate) fn translate_graph(&mut self) { + let mut min_x = f32::INFINITY; + let mut max_x: f32 = 0.0; + let mut min_y = f32::INFINITY; + let mut max_y: f32 = 0.0; + + for &GraphNode { x, y, width, height, .. } in self.nodes.values() { + min_x = min_x.min(x - width / 2.0); + max_x = max_x.max(x + width / 2.0); + min_y = min_y.min(y - height / 2.0); + max_y = max_y.max(y + height / 2.0); + } + + for node in self.nodes.values_mut() { + node.x -= min_x; + node.y -= min_y; + } + + for edge in self.edges.values_mut() { + if let Some(points) = &mut edge.points { + for p in points { + p.x -= min_x; + p.y -= min_y; + } + } + } + } +} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/utils.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/utils.rs deleted file mode 100644 index 465fd7e9581424fc01ddf52ace4b93e702ffefaf..0000000000000000000000000000000000000000 --- a/plugins/mindstudio-insight-plugins/ModelVis/rust/layout/src/utils.rs +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright (c) 2025, Huawei Technologies Co., Ltd. -// All rights reserved. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use ahash::{HashMap, HashMapExt}; -use smallvec::smallvec; - -use crate::{Dummy, Dummy::EdgeProxy, Graph, GraphNode, Key, Point}; - -static mut KEY_COUNTER: Key = 2; - -#[inline] -pub(crate) fn unique_key() -> Key { - unsafe { - KEY_COUNTER += 1; - KEY_COUNTER - } -} - -impl Graph { - fn max_rank(&self) -> usize { - self.nodes.values().map(|n| n.rank.unwrap()).max().unwrap_or(0) as usize - } - - pub(super) fn add_dummy_node(&mut self, dummy: Dummy, mut node: GraphNode) -> Key { - let mut node_id = unique_key(); - while self.has_node(&node_id) { - node_id = unique_key(); - } - - node.dummy = Some(dummy); - self.set_node(node_id, Some(node)); - node_id - } - - pub(super) fn as_non_compound(&mut self) -> Graph { - let mut simplified: Graph = Graph::new(true, false); - simplified.config = self.config; - - self.transfer_node_edges(&mut simplified); - - simplified - } - - pub(super) fn transfer_node_edges(&mut self, dst: &mut Graph) { - for (&node_id, &node) in &self.nodes { - if self.children(&node_id).is_empty() { - dst.set_node(node_id, Some(node)); - } - } - - for &edge in self.edges.values() { - dst.set_edge1(edge, self.edge1(edge).cloned()); - } - } - - /// Adjusts rank for all nodes, then all node sources have - /// **rank(source) >= 0** - /// and at least one node target has **rank(target) = 0**. - pub(super) fn normalize_ranks(&mut self) { - let min = self.nodes.values().map(|n| n.rank.unwrap_or(0)).min().unwrap_or(0); - - for node in &mut self.nodes.values_mut() { - if let Some(rank) = node.rank { - node.rank = Some(rank - min) - } - } - } - - pub(super) fn remove_empty_ranks(&mut self) { - /// Ranks may not start at 0, so we need to offset them - let offset = self.nodes.values().map(|n| n.rank.unwrap_or(0)).min().unwrap_or(0); - - let mut layers: HashMap> = HashMap::new(); - for (&node_id, node) in &self.nodes { - let rank = node.rank.unwrap_or(0).wrapping_sub(offset); - layers.entry(rank).or_default().push(node_id) - } - - let mut delta = 0; - let node_rank_factor = self.config.node_rank_factor as i32; - for (rank, keys) in &layers { - if keys.is_empty() && rank % node_rank_factor != 0 { - delta -= 1; - } else if delta != 0 { - for k in keys { - self.node_mut(k).map(|n| n.rank = Some(n.rank.unwrap_or(0) + delta)); - } - } - } - } - - /// Given a DAG with each node assigned "rank" and "order" properties, this - /// function produces a matrix with the keys of each node. - pub(super) fn key_matrix(&self) -> Vec> { - let mut matrix: Vec> = vec![Vec::new(); self.max_rank() + 1]; - - for (&key, node) in &self.nodes { - if let Some(rank) = node.rank { - matrix[rank as usize].push((node.order.unwrap(), key)); - } - } - - matrix - .iter_mut() - .map(|layer| { - layer.sort_by_key(|&(order, _)| order); - layer.iter().map(|&(_, key)| key).collect() - }) - .collect() - } - - pub(super) fn make_space_for_edge_labels(&mut self) { - let config = &mut self.config; - config.ranksep /= 2.0; - - for edge in self.edge_values.values_mut() { - // In fact, minlen is usually Some(x)! - if let Some(minlen) = &mut edge.minlen { - *minlen *= 2; - } - } - } - - pub(super) fn assign_rank_min_max(&mut self) -> Option<()> { - for key in self.nodes().iter() { - let node = self.node(key)?; - if let (Some(border_top), Some(border_bottom)) = (&node.border_top, &node.border_bottom) - { - let min_rank = self.node(border_top).and_then(|n| n.rank).unwrap_or(0); - let max_rank = self.node(border_bottom).and_then(|n| n.rank).unwrap_or(0); - - let node = self.node_mut(key)?; - node.min_rank = Some(min_rank); - node.max_rank = Some(max_rank); - } - } - - None - } - - pub(super) fn translate_graph(&mut self) { - let mut min_x = f64::INFINITY as f32; - let mut max_x: f32 = 0.0; - let mut min_y = f64::INFINITY as f32; - let mut max_y: f32 = 0.0; - - for GraphNode { x, y, width, height, .. } in self.nodes.values() { - min_x = min_x.min(x - width / 2.0); - max_x = max_x.max(x + width / 2.0); - min_y = min_y.min(y - height / 2.0); - max_y = max_y.max(y + height / 2.0); - } - - for node in self.nodes.values_mut() { - node.x -= min_x; - node.y -= min_y; - } - - for edge in self.edge_values.values_mut() { - if let Some(points) = &mut edge.points { - for point in points { - point.x -= min_x; - point.y -= min_y; - } - } - } - - self.width = max_x - min_x; - self.height = max_y - min_y; - } - - pub(super) fn assign_node_intersects(&mut self) -> Option<()> { - for e in self.edges() { - let ((source_p, source_bbox), (target_p, target_bbox)) = { - let source_node = self.node(&e.source)?; - let target_node = self.node(&e.target)?; - (source_node.coord_bbox(), target_node.coord_bbox()) - }; - - let edge = self.edge_mut1(e)?; - - if let Some(points) = &mut edge.points { - let p1 = source_bbox.intersect_point(Point::of(points[0].x, points[0].y)); - points.insert(0, p1); - let p2 = target_bbox.intersect_point(Point::of( - points[points.len() - 1].x, - points[points.len() - 1].y, - )); - points.push(p2); - } else { - let p1 = source_bbox.intersect_point(target_p); - let p2 = target_bbox.intersect_point(source_p); - edge.points = Some(smallvec![p1, p2]); - }; - } - - None - } - - pub(super) fn remove_edge_proxies(&mut self) -> Option<()> { - for key in &self.nodes() { - let node = self.node(key)?; - if node.dummy == Some(EdgeProxy) { - let rank = node.rank.unwrap_or(0); - if let Some(graph_edge) = self.edge_mut1(node.edge?) { - graph_edge.rank = Some(rank); - } - self.remove_node(key); - } - } - - None - } - - pub(super) fn reverse_points_for_reversed_edges(&mut self) { - for edge in self.edge_values.values_mut() { - if edge.reversed - && let Some(points) = &mut edge.points - { - points.reverse(); - } - } - } -} - -#[derive(Copy, Clone)] -pub(crate) struct Rect { - pub x: f32, - pub y: f32, - pub width: f32, - pub height: f32, -} - -impl GraphNode { - #[inline] - fn bbox(&self) -> Rect { - Rect { x: self.x, y: self.y, width: self.width, height: self.height } - } - - #[inline] - fn coord_bbox(&self) -> (Point, Rect) { - (Point::of(self.x, self.y), self.bbox()) - } -} - -impl Rect { - /// Finds where a line starting at point {x, y} would intersect a rectangle - /// {x, y, width, height} if it were pointing at the rectangle's center. - pub(crate) fn intersect_point(self, point: Point) -> Point { - let x = self.x; - let y = self.y; - - /// Rectangle intersection algorithm - /// [math.stackoverflow](http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes): - let dx = point.x - x; - let dy = point.y - y; - let w = self.width / 2.0; - let h = self.height / 2.0; - - let (sx, sy) = if (dy.abs() * w) > (dx.abs() * h) { - if dy < 0.0 { (-h * dx / dy, -h) } else { (h * dx / dy, h) } - } else { - if dx < 0.0 { (-w, -w * dy / dx) } else { (w, w * dy / dx) } - }; - - Point::of(x + sx, y + sy) - } -} diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/perf/Cargo.toml b/plugins/mindstudio-insight-plugins/ModelVis/rust/perf/Cargo.toml new file mode 100644 index 0000000000000000000000000000000000000000..a4f62f8c15f80279f94ccd00bb002f09ba946c1c --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/perf/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "perf" +version = "0.0.1" +authors = ["空白 "] +edition = "2024" + +[lib] +proc-macro = true + +[dependencies] +proc-macro2 = "1.0" +quote = "1.0.38" +syn = { version = "2.0.98", features = ["full"] } diff --git a/plugins/mindstudio-insight-plugins/ModelVis/rust/perf/src/lib.rs b/plugins/mindstudio-insight-plugins/ModelVis/rust/perf/src/lib.rs new file mode 100644 index 0000000000000000000000000000000000000000..9b312d0d7522241c24444593244d7b25ebe9a9af --- /dev/null +++ b/plugins/mindstudio-insight-plugins/ModelVis/rust/perf/src/lib.rs @@ -0,0 +1,82 @@ +use proc_macro::TokenStream; +use quote::{ToTokens, quote}; +use syn::{Ident, ItemFn, parse_macro_input, spanned::Spanned}; + +/// # Profile Lines with Memory Tracking +/// +/// Adds performance and memory allocation profiling to each statement in a function. +/// Tracks both execution time and heap memory allocations. +#[proc_macro_attribute] +pub fn profile_lines(_attr: TokenStream, item: TokenStream) -> TokenStream { + let item: ItemFn = parse_macro_input!(item); + let name = &item.sig.ident; + let params = &item.sig.inputs; + let return_type = &item.sig.output; + let vis = &item.vis; + + let new_body: Vec<_> = item + .block + .stmts + .iter() + .enumerate() + .flat_map(|(i, stmt)| { + let stmt_str = stmt.to_token_stream().to_string(); + let stmt_clone = stmt.to_token_stream(); + let start_time_ident = Ident::new(&format!("start_time_{}", i), stmt.span()); + let start_mem_ident = Ident::new(&format!("start_mem_{}", i), stmt.span()); + + let identifier = extract_stmt_identifier(&stmt_str); + + vec![ + quote! { + let #start_mem_ident = crate::memory_profiler::get_memory_stats(); + }, + quote! { + let #start_time_ident = std::time::Instant::now(); + }, + stmt_clone, + quote! { + let end_mem = crate::memory_profiler::get_memory_stats(); + let mem_diff = end_mem - #start_mem_ident; + let elapsed = #start_time_ident.elapsed(); + + crate::memory_profiler::print_colored_profile( + #identifier, + elapsed, + mem_diff.allocated, + mem_diff.deallocated, + mem_diff.net_allocated() + ); + }, + ] + }) + .collect(); + + let expanded = quote! { + #[inline(never)] + #vis fn #name(#params) #return_type { + #(#new_body)* + } + }; + + expanded.into() +} + +fn extract_stmt_identifier(stmt_str: &str) -> String { + if let Some(dot_pos) = stmt_str.find('.') { + if let Some(paren_pos) = stmt_str[dot_pos..].find('(') { + let method_name = &stmt_str[dot_pos + 1..dot_pos + paren_pos]; + return method_name.trim().to_string(); + } + } + + if let Some(eq_pos) = stmt_str.find('=') { + let var_name = stmt_str[..eq_pos].trim(); + if let Some(let_pos) = var_name.find("let ") { + return var_name[let_pos + 4..].trim().to_string(); + } + return var_name.to_string(); + } + + stmt_str.chars().take(20).collect::() +}