impl FixedGraphBuilder {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new(config: GraphConfig) -> Self {
Self {
max_nodes: config.max_nodes,
max_edges: config.max_edges,
namer: SemanticNamer::new(),
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_max_nodes(mut self, max_nodes: usize) -> Self {
self.max_nodes = max_nodes;
self
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn with_max_edges(mut self, max_edges: usize) -> Self {
self.max_edges = max_edges;
self
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn build(&self, graph: &DependencyGraph) -> Result<FixedGraph> {
let groups = self.group_by_module(graph);
let scores = self.calculate_pagerank(graph, &groups);
let selected_nodes = self.select_top_nodes(scores, &groups);
self.build_with_budget(selected_nodes, graph)
}
fn group_by_module(&self, graph: &DependencyGraph) -> HashMap<String, Vec<String>> {
let mut groups: HashMap<String, Vec<String>> = HashMap::new();
for (node_id, node) in &graph.nodes {
let module_name = self.get_module_name(node);
groups.entry(module_name).or_default().push(node_id.clone());
}
groups
}
fn get_module_name(&self, node: &NodeInfo) -> String {
if !node.file_path.is_empty() {
let parts: Vec<&str> = node.file_path.split('/').collect();
if parts.len() > 1 {
if let Some(module) = parts.get(1) {
return (*module).to_string();
}
}
}
match node.node_type {
NodeType::Module => "modules".to_string(),
NodeType::Function => "functions".to_string(),
NodeType::Class => "classes".to_string(),
NodeType::Trait => "traits".to_string(),
NodeType::Interface => "interfaces".to_string(),
}
}
fn calculate_pagerank(
&self,
graph: &DependencyGraph,
groups: &HashMap<String, Vec<String>>,
) -> HashMap<String, f64> {
let damping_factor = 0.85;
let iterations = 10;
let num_nodes = graph.nodes.len() as f64;
let mut scores: HashMap<String, f64> = HashMap::new();
for node_id in graph.nodes.keys() {
scores.insert(node_id.clone(), 1.0 / num_nodes);
}
let mut incoming: HashMap<String, Vec<String>> = HashMap::new();
let mut outgoing_count: HashMap<String, usize> = HashMap::new();
for edge in &graph.edges {
incoming
.entry(edge.to.clone())
.or_default()
.push(edge.from.clone());
*outgoing_count.entry(edge.from.clone()).or_default() += 1;
}
for _ in 0..iterations {
let mut new_scores = HashMap::new();
for node_id in graph.nodes.keys() {
let mut rank = (1.0 - damping_factor) / num_nodes;
if let Some(incoming_nodes) = incoming.get(node_id) {
for incoming_node in incoming_nodes {
if let (Some(&score), Some(&count)) =
(scores.get(incoming_node), outgoing_count.get(incoming_node))
{
rank += damping_factor * score / count as f64;
}
}
}
new_scores.insert(node_id.clone(), rank);
}
scores = new_scores;
}
let mut group_scores: HashMap<String, f64> = HashMap::new();
for (group_name, node_ids) in groups {
let group_score: f64 = node_ids.iter().filter_map(|id| scores.get(id)).sum();
group_scores.insert(group_name.clone(), group_score);
}
group_scores
}
fn select_top_nodes(
&self,
scores: HashMap<String, f64>,
groups: &HashMap<String, Vec<String>>,
) -> Vec<String> {
let mut sorted_groups: Vec<(String, f64)> = scores.into_iter().collect();
sorted_groups.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
let mut selected_nodes = Vec::new();
let mut node_count = 0;
for (group_name, _score) in sorted_groups {
if let Some(node_ids) = groups.get(&group_name) {
if node_count + node_ids.len() <= self.max_nodes {
selected_nodes.extend(node_ids.clone());
node_count += node_ids.len();
} else {
let remaining = self.max_nodes - node_count;
selected_nodes.extend(node_ids.iter().take(remaining).cloned());
break;
}
}
}
selected_nodes
}
fn build_with_budget(
&self,
selected_nodes: Vec<String>,
original_graph: &DependencyGraph,
) -> Result<FixedGraph> {
let selected_set: HashSet<_> = selected_nodes.iter().cloned().collect();
let mut nodes = BTreeMap::new();
let mut edges = Vec::new();
for node_id in &selected_nodes {
if let Some(node) = original_graph.nodes.get(node_id) {
let semantic_name = self.namer.get_semantic_name(node_id, node);
let fixed_node = FixedNode {
id: node_id.clone(),
display_name: semantic_name.clone(),
node_type: node.node_type.clone(),
complexity: u64::from(node.complexity),
};
nodes.insert(semantic_name, fixed_node);
}
}
let mut edge_count = 0;
for edge in &original_graph.edges {
if selected_set.contains(&edge.from) && selected_set.contains(&edge.to) {
edges.push(edge.clone());
edge_count += 1;
if edge_count >= self.max_edges {
break;
}
}
}
Ok(FixedGraph { nodes, edges })
}
}