Skip to content

Commit c2c6fde

Browse files
committed
improve graph sorting to reduce edge span
1 parent 43e72ad commit c2c6fde

File tree

1 file changed

+202
-29
lines changed

1 file changed

+202
-29
lines changed

src/graph_ops.rs

Lines changed: 202 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -327,11 +327,89 @@ impl Graph {
327327
});
328328
}
329329

330+
/// Compute strongly connected components using Tarjan's algorithm
331+
#[allow(dead_code)]
332+
fn compute_sccs(&self, adj_list: &HashMap<usize, Vec<usize>>) -> Vec<Vec<usize>> {
333+
let mut index_counter = 0;
334+
let mut stack = Vec::new();
335+
let mut indices: HashMap<usize, usize> = HashMap::new();
336+
let mut lowlinks: HashMap<usize, usize> = HashMap::new();
337+
let mut on_stack: HashSet<usize> = HashSet::new();
338+
let mut sccs = Vec::new();
339+
340+
fn strongconnect(
341+
v: usize,
342+
adj_list: &HashMap<usize, Vec<usize>>,
343+
index_counter: &mut usize,
344+
stack: &mut Vec<usize>,
345+
indices: &mut HashMap<usize, usize>,
346+
lowlinks: &mut HashMap<usize, usize>,
347+
on_stack: &mut HashSet<usize>,
348+
sccs: &mut Vec<Vec<usize>>,
349+
) {
350+
indices.insert(v, *index_counter);
351+
lowlinks.insert(v, *index_counter);
352+
*index_counter += 1;
353+
stack.push(v);
354+
on_stack.insert(v);
355+
356+
if let Some(neighbors) = adj_list.get(&v) {
357+
for &w in neighbors {
358+
if !indices.contains_key(&w) {
359+
strongconnect(w, adj_list, index_counter, stack, indices, lowlinks, on_stack, sccs);
360+
let w_lowlink = lowlinks[&w];
361+
let v_lowlink = lowlinks[&v];
362+
lowlinks.insert(v, v_lowlink.min(w_lowlink));
363+
} else if on_stack.contains(&w) {
364+
let w_index = indices[&w];
365+
let v_lowlink = lowlinks[&v];
366+
lowlinks.insert(v, v_lowlink.min(w_index));
367+
}
368+
}
369+
}
370+
371+
if lowlinks[&v] == indices[&v] {
372+
let mut component = Vec::new();
373+
loop {
374+
let w = stack.pop().unwrap();
375+
on_stack.remove(&w);
376+
component.push(w);
377+
if w == v {
378+
break;
379+
}
380+
}
381+
sccs.push(component);
382+
}
383+
}
384+
385+
let mut nodes: Vec<_> = self.nodes.keys().cloned().collect();
386+
nodes.sort(); // Ensure deterministic order
387+
388+
for node in nodes {
389+
if !indices.contains_key(&node) {
390+
strongconnect(
391+
node,
392+
adj_list,
393+
&mut index_counter,
394+
&mut stack,
395+
&mut indices,
396+
&mut lowlinks,
397+
&mut on_stack,
398+
&mut sccs,
399+
);
400+
}
401+
}
402+
403+
sccs
404+
}
405+
330406
/// Perform topological sort on the graph
331407
pub fn topological_sort(&mut self) {
332-
// Use an improved algorithm that handles cycles and optimizes for visualization
408+
// Use a multi-pass algorithm that minimizes edge spans
409+
410+
// First, do a basic topological sort
333411
let mut visited = HashSet::new();
334-
let mut stack = Vec::new();
412+
let mut initial_order = Vec::new();
335413

336414
// Build adjacency lists
337415
let mut adj_list: HashMap<usize, Vec<usize>> = HashMap::new();
@@ -349,33 +427,36 @@ impl Graph {
349427
*in_degree.get_mut(&edge.to).unwrap() += 1;
350428
}
351429

352-
// First, try to find nodes that appear at the beginning of paths
353-
let mut path_starts: HashSet<usize> = HashSet::new();
430+
// Find path information
431+
let mut path_positions: HashMap<usize, Vec<usize>> = HashMap::new();
354432
for (_, path) in &self.paths {
355-
if let Some(&first) = path.first() {
356-
path_starts.insert(first);
433+
for (pos, &node) in path.iter().enumerate() {
434+
path_positions.entry(node).or_insert_with(Vec::new).push(pos);
357435
}
358436
}
359437

360-
// Use modified Kahn's algorithm with path-aware ordering
438+
// Modified Kahn's algorithm
361439
let mut queue: Vec<usize> = Vec::new();
362440

363-
// Start with nodes that have no incoming edges and appear at path starts
441+
// Start with nodes that have no incoming edges
364442
for (&node_id, &degree) in &in_degree {
365443
if degree == 0 {
366444
queue.push(node_id);
367445
}
368446
}
369447

370-
// Sort queue to prefer path starts
371-
queue.sort_by_key(|&node| (!path_starts.contains(&node), node));
448+
// Sort by average position in paths
449+
queue.sort_by_key(|&node| {
450+
path_positions.get(&node)
451+
.map(|positions| positions.iter().sum::<usize>() / positions.len())
452+
.unwrap_or(usize::MAX)
453+
});
372454

373-
// Process nodes in topological order
455+
// Process nodes
374456
while let Some(node) = queue.pop() {
375-
stack.push(node);
457+
initial_order.push(node);
376458
visited.insert(node);
377459

378-
// Process neighbors
379460
if let Some(neighbors) = adj_list.get(&node) {
380461
let mut next_nodes = Vec::new();
381462

@@ -388,27 +469,29 @@ impl Graph {
388469
}
389470
}
390471

391-
// Sort next nodes to maintain consistency
392-
next_nodes.sort_by_key(|&node| (!path_starts.contains(&node), node));
472+
// Sort by path position
473+
next_nodes.sort_by_key(|&node| {
474+
path_positions.get(&node)
475+
.map(|positions| positions.iter().sum::<usize>() / positions.len())
476+
.unwrap_or(usize::MAX)
477+
});
393478
queue.extend(next_nodes.into_iter().rev());
394479
}
395480
}
396481

397-
// Handle remaining nodes (in cycles) using DFS
482+
// Handle remaining nodes (in cycles)
398483
let mut remaining: Vec<_> = self.nodes.keys()
399484
.filter(|&&id| !visited.contains(&id))
400485
.cloned()
401486
.collect();
402-
remaining.sort(); // Ensure deterministic order
487+
remaining.sort();
403488

404-
fn dfs_cycle(node: usize, adj_list: &HashMap<usize, Vec<usize>>,
405-
visited: &mut HashSet<usize>, stack: &mut Vec<usize>,
406-
visiting: &mut HashSet<usize>) {
407-
if visiting.contains(&node) || visited.contains(&node) {
489+
fn dfs_visit(node: usize, adj_list: &HashMap<usize, Vec<usize>>,
490+
visited: &mut HashSet<usize>, stack: &mut Vec<usize>) {
491+
if visited.contains(&node) {
408492
return;
409493
}
410-
411-
visiting.insert(node);
494+
visited.insert(node);
412495

413496
if let Some(neighbors) = adj_list.get(&node) {
414497
let mut sorted_neighbors: Vec<_> = neighbors.iter()
@@ -418,25 +501,115 @@ impl Graph {
418501
sorted_neighbors.sort();
419502

420503
for neighbor in sorted_neighbors {
421-
dfs_cycle(neighbor, adj_list, visited, stack, visiting);
504+
dfs_visit(neighbor, adj_list, visited, stack);
422505
}
423506
}
424507

425-
visiting.remove(&node);
426-
visited.insert(node);
427508
stack.push(node);
428509
}
429510

430-
let mut visiting = HashSet::new();
431511
for node_id in remaining {
432512
if !visited.contains(&node_id) {
433-
dfs_cycle(node_id, &adj_list, &mut visited, &mut stack, &mut visiting);
513+
dfs_visit(node_id, &adj_list, &mut visited, &mut initial_order);
514+
}
515+
}
516+
517+
// Now optimize the ordering to minimize edge spans
518+
// Create position map
519+
let mut position: HashMap<usize, usize> = HashMap::new();
520+
for (pos, &node) in initial_order.iter().enumerate() {
521+
position.insert(node, pos);
522+
}
523+
524+
// Calculate edge spans and identify problematic nodes
525+
let mut node_span_scores: HashMap<usize, f64> = HashMap::new();
526+
527+
for edge in &self.edges {
528+
if let (Some(&from_pos), Some(&to_pos)) =
529+
(position.get(&edge.from), position.get(&edge.to)) {
530+
let span = if to_pos > from_pos {
531+
to_pos - from_pos
532+
} else {
533+
from_pos - to_pos
534+
};
535+
536+
// Accumulate span scores for nodes
537+
*node_span_scores.entry(edge.from).or_insert(0.0) += span as f64;
538+
*node_span_scores.entry(edge.to).or_insert(0.0) += span as f64;
539+
}
540+
}
541+
542+
// Identify nodes with high span scores
543+
let mut problematic_nodes: Vec<(usize, f64)> = node_span_scores.iter()
544+
.map(|(&node, &score)| (node, score))
545+
.filter(|(_, score)| *score > 100.0) // Threshold for problematic nodes
546+
.collect();
547+
problematic_nodes.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
548+
549+
// Try to reposition problematic nodes
550+
let mut final_order = initial_order.clone();
551+
552+
for (prob_node, _) in problematic_nodes.iter().take(50) { // Limit iterations
553+
// Find optimal position for this node
554+
let current_position = position[prob_node];
555+
let mut best_position = current_position;
556+
let mut best_score = f64::MAX;
557+
558+
// Calculate connected nodes' positions
559+
let mut connected_positions = Vec::new();
560+
561+
if let Some(neighbors) = adj_list.get(prob_node) {
562+
for &neighbor in neighbors {
563+
if let Some(&pos) = position.get(&neighbor) {
564+
connected_positions.push(pos);
565+
}
566+
}
567+
}
568+
569+
// Check incoming edges too
570+
for edge in &self.edges {
571+
if edge.to == *prob_node {
572+
if let Some(&pos) = position.get(&edge.from) {
573+
connected_positions.push(pos);
574+
}
575+
}
576+
}
577+
578+
if !connected_positions.is_empty() {
579+
// Try median position
580+
connected_positions.sort();
581+
let median_pos = connected_positions[connected_positions.len() / 2];
582+
583+
// Calculate score at median position
584+
let score: f64 = connected_positions.iter()
585+
.map(|&pos| (median_pos as i32 - pos as i32).abs() as f64)
586+
.sum();
587+
588+
if score < best_score {
589+
best_score = score;
590+
best_position = median_pos;
591+
}
592+
}
593+
594+
// Update position if it improves things
595+
if best_position != position[prob_node] {
596+
// Remove from current position
597+
final_order.retain(|&n| n != *prob_node);
598+
599+
// Insert at new position
600+
let insert_pos = best_position.min(final_order.len());
601+
final_order.insert(insert_pos, *prob_node);
602+
603+
// Update position map
604+
for (pos, &node) in final_order.iter().enumerate() {
605+
position.insert(node, pos);
606+
}
434607
}
435608
}
436609

437610
// Create mapping from old to new IDs
438611
let mut old_to_new = HashMap::new();
439-
for (new_id, &old_id) in stack.iter().enumerate() {
612+
for (new_id, &old_id) in final_order.iter().enumerate() {
440613
old_to_new.insert(old_id, new_id + 1); // 1-based IDs
441614
}
442615

0 commit comments

Comments
 (0)