Skip to main content

problemreductions/rules/
minimumfeedbackvertexset_minimumcodegenerationunlimitedregisters.rs

1//! Reduction from MinimumFeedbackVertexSet to MinimumCodeGenerationUnlimitedRegisters.
2//!
3//! The Aho-Johnson-Ullman chain gadget construction: for each source vertex x
4//! with outgoing edges (x,y₁),...,(x,y_d), create a chain of internal nodes
5//! x¹,...,x^d where xⁱ has left child x^{i-1} and right child y_i⁰ (the leaf).
6//! Copies in an optimal program correspond exactly to a minimum feedback vertex set.
7
8use crate::models::graph::MinimumFeedbackVertexSet;
9use crate::models::misc::MinimumCodeGenerationUnlimitedRegisters;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12
13/// Result of reducing MinimumFeedbackVertexSet to MinimumCodeGenerationUnlimitedRegisters.
14#[derive(Debug, Clone)]
15pub struct ReductionFVSToCodeGen {
16    target: MinimumCodeGenerationUnlimitedRegisters,
17    /// Number of source vertices (= number of leaves in the target DAG).
18    num_source_vertices: usize,
19    /// For each source vertex x, the target index of x¹ (first chain node).
20    /// `None` if vertex x has out-degree 0 (no chain, leaf only).
21    chain_start: Vec<Option<usize>>,
22    /// For each leaf x⁰ (source vertex x), the list of internal node target
23    /// indices that use x⁰ as a right child.
24    right_child_users: Vec<Vec<usize>>,
25}
26
27impl ReductionResult for ReductionFVSToCodeGen {
28    type Source = MinimumFeedbackVertexSet<i32>;
29    type Target = MinimumCodeGenerationUnlimitedRegisters;
30
31    fn target_problem(&self) -> &Self::Target {
32        &self.target
33    }
34
35    /// Extract a feedback vertex set from a target evaluation-order solution.
36    ///
37    /// A leaf register R_x is destroyed when x¹ executes (left operand).
38    /// If any right-child user of x⁰ is evaluated after x¹, a LOAD was needed,
39    /// meaning x is in the feedback vertex set.
40    fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
41        let n = self.num_source_vertices;
42        let mut source_config = vec![0usize; n];
43
44        // target_solution[i] = evaluation position for the i-th internal node
45        // Internal nodes are indices n, n+1, ..., n+m-1 (sorted), so
46        // target_solution[j] = position for internal node (n + j).
47
48        // eval_pos[j] = evaluation position for internal node (n + j)
49        let eval_pos = target_solution;
50
51        for (x, cfg) in source_config.iter_mut().enumerate() {
52            if let Some(chain_start_idx) = self.chain_start[x] {
53                let start_j = chain_start_idx - n;
54                let start_pos = eval_pos[start_j];
55
56                for &user_idx in &self.right_child_users[x] {
57                    let user_j = user_idx - n;
58                    if eval_pos[user_j] > start_pos {
59                        *cfg = 1;
60                        break;
61                    }
62                }
63            }
64        }
65
66        source_config
67    }
68}
69
70#[reduction(
71    overhead = {
72        num_vertices = "num_vertices + num_arcs",
73    }
74)]
75impl ReduceTo<MinimumCodeGenerationUnlimitedRegisters> for MinimumFeedbackVertexSet<i32> {
76    type Result = ReductionFVSToCodeGen;
77
78    fn reduce_to(&self) -> Self::Result {
79        let n = self.graph().num_vertices();
80        let m = self.graph().num_arcs();
81
82        // Build outgoing adjacency list
83        let mut out_neighbors: Vec<Vec<usize>> = vec![vec![]; n];
84        for (u, v) in self.graph().arcs() {
85            out_neighbors[u].push(v);
86        }
87
88        // Assign internal node indices: leaves are 0..n, chain nodes are n..n+m
89        let mut left_arcs = Vec::with_capacity(m);
90        let mut right_arcs = Vec::with_capacity(m);
91        let mut chain_start = vec![None; n];
92        let mut right_child_users: Vec<Vec<usize>> = vec![vec![]; n];
93
94        let mut next_internal = n; // first internal node index
95        for x in 0..n {
96            let neighbors = &out_neighbors[x];
97            let d = neighbors.len();
98            if d == 0 {
99                continue;
100            }
101
102            chain_start[x] = Some(next_internal);
103
104            for (i, &neighbor) in neighbors.iter().enumerate() {
105                let node_idx = next_internal + i;
106                // Left child: predecessor in chain
107                let left_child = if i == 0 {
108                    x // leaf x⁰
109                } else {
110                    next_internal + i - 1 // previous chain node
111                };
112                // Right child: leaf y_i⁰
113                let right_child = neighbor; // leaf index = source vertex index
114
115                left_arcs.push((node_idx, left_child));
116                right_arcs.push((node_idx, right_child));
117                right_child_users[right_child].push(node_idx);
118            }
119
120            next_internal += d;
121        }
122
123        debug_assert_eq!(next_internal, n + m);
124
125        let target = MinimumCodeGenerationUnlimitedRegisters::new(n + m, left_arcs, right_arcs);
126
127        ReductionFVSToCodeGen {
128            target,
129            num_source_vertices: n,
130            chain_start,
131            right_child_users,
132        }
133    }
134}
135
136#[cfg(any(test, feature = "example-db"))]
137fn issue_example_source() -> MinimumFeedbackVertexSet<i32> {
138    use crate::topology::DirectedGraph;
139    // 3-cycle: a→b→c→a (vertices 0,1,2)
140    MinimumFeedbackVertexSet::new(
141        DirectedGraph::new(3, vec![(0, 1), (1, 2), (2, 0)]),
142        vec![1i32; 3],
143    )
144}
145
146#[cfg(feature = "example-db")]
147pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
148    use crate::export::SolutionPair;
149    use crate::solvers::BruteForce;
150
151    vec![crate::example_db::specs::RuleExampleSpec {
152        id: "minimumfeedbackvertexset_to_minimumcodegenerationunlimitedregisters",
153        build: || {
154            let source = issue_example_source();
155            let reduction = ReduceTo::<MinimumCodeGenerationUnlimitedRegisters>::reduce_to(&source);
156
157            // Find a target witness whose extracted source solution matches an optimal FVS
158            let solver = BruteForce::new();
159            let source_witnesses = solver.find_all_witnesses(&source);
160            let target_witnesses = solver.find_all_witnesses(reduction.target_problem());
161
162            let (source_config, target_config) = target_witnesses
163                .iter()
164                .find_map(|tw| {
165                    let extracted = reduction.extract_solution(tw);
166                    if source_witnesses.contains(&extracted) {
167                        Some((extracted, tw.clone()))
168                    } else {
169                        None
170                    }
171                })
172                .expect("canonical FVS -> CodeGen example must have matching witness");
173
174            crate::example_db::specs::assemble_rule_example(
175                &source,
176                reduction.target_problem(),
177                vec![SolutionPair {
178                    source_config,
179                    target_config,
180                }],
181            )
182        },
183    }]
184}
185
186#[cfg(test)]
187#[path = "../unit_tests/rules/minimumfeedbackvertexset_minimumcodegenerationunlimitedregisters.rs"]
188mod tests;