Skip to main content

problemreductions/models/misc/
register_sufficiency.rs

1//! Register Sufficiency problem implementation.
2//!
3//! Given a directed acyclic graph G = (V, A) representing a computation and a
4//! bound K, determine whether the computation can be performed using at most K
5//! registers. NP-complete even for out-degree <= 2 [Sethi, 1975].
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use serde::{Deserialize, Serialize};
10
11inventory::submit! {
12    ProblemSchemaEntry {
13        name: "RegisterSufficiency",
14        display_name: "Register Sufficiency",
15        aliases: &[],
16        dimensions: &[],
17        module_path: module_path!(),
18        description: "Determine whether a DAG computation can be performed using K or fewer registers",
19        fields: &[
20            FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices n = |V|" },
21            FieldInfo { name: "arcs", type_name: "Vec<(usize, usize)>", description: "Directed arcs (v, u) meaning v depends on u" },
22            FieldInfo { name: "bound", type_name: "usize", description: "Register bound K" },
23        ],
24    }
25}
26
27/// The Register Sufficiency problem.
28///
29/// Given a directed acyclic graph G = (V, A) where arcs represent data
30/// dependencies, and a positive integer K, determine whether there is an
31/// evaluation ordering of all vertices such that at most K registers are
32/// needed at any point during the computation.
33///
34/// # Representation
35///
36/// An arc `(v, u)` means vertex `v` depends on vertex `u` (i.e., `u` must be
37/// in a register when `v` is evaluated). Each variable represents a vertex,
38/// with domain `{0, ..., n-1}` giving its evaluation position (the config
39/// must be a valid permutation).
40///
41/// # Example
42///
43/// ```
44/// use problemreductions::models::misc::RegisterSufficiency;
45/// use problemreductions::{Problem, Solver, BruteForce};
46///
47/// // 4 vertices: v2 depends on v0, v3 depends on v0 and v1
48/// let problem = RegisterSufficiency::new(
49///     4,
50///     vec![(2, 0), (3, 0), (3, 1)],
51///     2,
52/// );
53/// let solver = BruteForce::new();
54/// let solution = solver.find_witness(&problem);
55/// assert!(solution.is_some());
56/// ```
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct RegisterSufficiency {
59    /// Number of vertices.
60    num_vertices: usize,
61    /// Directed arcs (v, u) meaning v depends on u.
62    arcs: Vec<(usize, usize)>,
63    /// Register bound K.
64    bound: usize,
65}
66
67impl RegisterSufficiency {
68    /// Create a new Register Sufficiency instance.
69    ///
70    /// # Panics
71    ///
72    /// Panics if any arc index is out of bounds (>= num_vertices),
73    /// or if any arc is a self-loop.
74    pub fn new(num_vertices: usize, arcs: Vec<(usize, usize)>, bound: usize) -> Self {
75        for &(v, u) in &arcs {
76            assert!(
77                v < num_vertices && u < num_vertices,
78                "Arc ({}, {}) out of bounds for {} vertices",
79                v,
80                u,
81                num_vertices
82            );
83            assert!(v != u, "Self-loop ({}, {}) not allowed in a DAG", v, u);
84        }
85        Self {
86            num_vertices,
87            arcs,
88            bound,
89        }
90    }
91
92    /// Get the number of vertices.
93    pub fn num_vertices(&self) -> usize {
94        self.num_vertices
95    }
96
97    /// Get the number of arcs.
98    pub fn num_arcs(&self) -> usize {
99        self.arcs.len()
100    }
101
102    /// Count vertices with no dependents.
103    pub fn num_sinks(&self) -> usize {
104        let mut has_dependent = vec![false; self.num_vertices];
105        for &(_, dependency) in &self.arcs {
106            has_dependent[dependency] = true;
107        }
108        has_dependent.into_iter().filter(|&flag| !flag).count()
109    }
110
111    /// Get the register bound K.
112    pub fn bound(&self) -> usize {
113        self.bound
114    }
115
116    /// Get the arcs.
117    pub fn arcs(&self) -> &[(usize, usize)] {
118        &self.arcs
119    }
120
121    /// Simulate register usage for a given evaluation ordering and return the
122    /// maximum number of registers used, or `None` if the ordering is invalid
123    /// (not a permutation or violates dependencies).
124    pub fn simulate_registers(&self, config: &[usize]) -> Option<usize> {
125        let n = self.num_vertices;
126        if config.len() != n {
127            return None;
128        }
129
130        // Check valid permutation: each position 0..n-1 used exactly once
131        let mut order = vec![0usize; n]; // order[position] = vertex
132        let mut used = vec![false; n];
133        for (vertex, &position) in config.iter().enumerate() {
134            if position >= n {
135                return None;
136            }
137            if used[position] {
138                return None;
139            }
140            used[position] = true;
141            order[position] = vertex;
142        }
143
144        // Build dependency info:
145        // dependents[u] = list of vertices that depend on u (i.e., arcs (v, u))
146        // dependencies[v] = list of vertices that v depends on (i.e., arcs (v, u))
147        let mut dependencies: Vec<Vec<usize>> = vec![vec![]; n];
148        let mut dependents: Vec<Vec<usize>> = vec![vec![]; n];
149        for &(v, u) in &self.arcs {
150            dependencies[v].push(u);
151            dependents[u].push(v);
152        }
153
154        // For each vertex u, compute the latest position among its dependents.
155        // A vertex u must stay in registers until all its dependents have been evaluated.
156        let mut last_use = vec![0usize; n];
157        for u in 0..n {
158            if dependents[u].is_empty() {
159                // Vertex u has no dependents. It stays in registers from its
160                // evaluation step until the end (final outputs must be in S_n).
161                last_use[u] = n; // stays until the end
162            } else {
163                let mut latest = 0;
164                for &v in &dependents[u] {
165                    latest = latest.max(config[v]);
166                }
167                last_use[u] = latest;
168            }
169        }
170
171        let mut max_registers = 0;
172
173        // Simulate: process vertices in evaluation order
174        for step in 0..n {
175            let vertex = order[step];
176
177            // Check dependencies: all dependencies of this vertex must have
178            // been evaluated before this step
179            for &dep in &dependencies[vertex] {
180                if config[dep] >= step {
181                    // Dependency not yet evaluated
182                    return None;
183                }
184            }
185
186            // Count registers at this step:
187            // A vertex v is in registers if:
188            // - v has been evaluated (config[v] <= step)
189            // - v is still needed (last_use[v] > step, or v is the current vertex)
190            // Actually, more precisely: after evaluating vertex at position `step`,
191            // the register set contains all vertices evaluated so far whose last
192            // use is > step (they're still needed later), plus the current vertex.
193            let reg_count = order[..=step]
194                .iter()
195                .filter(|&&v| last_use[v] > step)
196                .count();
197
198            max_registers = max_registers.max(reg_count);
199        }
200
201        Some(max_registers)
202    }
203
204    /// Exact branch-and-bound solver: finds a topological ordering using at
205    /// most `self.bound` registers, or returns `None` if no such ordering
206    /// exists.  Uses heuristic candidate ordering (prefer vertices that free
207    /// the most registers) so that YES instances typically resolve on the
208    /// first greedy path without backtracking.  For NO instances the full
209    /// search tree must be explored, so prefer the ILP solver path for
210    /// infeasibility proofs.
211    ///
212    /// NOTE: a greedy topological sort is *not* exact — it can miss valid
213    /// orderings.  This method is exact because it backtracks when the
214    /// greedy choice fails.  Do not replace it with a pure greedy solver.
215    pub fn solve_exact(&self) -> Option<Vec<usize>> {
216        let n = self.num_vertices;
217        if n == 0 {
218            return Some(vec![]);
219        }
220
221        let mut dependents: Vec<Vec<usize>> = vec![vec![]; n];
222        let mut dependencies: Vec<Vec<usize>> = vec![vec![]; n];
223        let mut in_degree = vec![0u32; n];
224        for &(v, u) in &self.arcs {
225            in_degree[v] += 1;
226            dependents[u].push(v);
227            dependencies[v].push(u);
228        }
229
230        let mut state = BnBState {
231            n,
232            bound: self.bound,
233            config: vec![0usize; n],
234            live: vec![false; n],
235            live_count: 0,
236            remaining_in_degree: in_degree.clone(),
237            remaining_deps: dependents.iter().map(|d| d.len()).collect(),
238            ready: (0..n).filter(|&v| in_degree[v] == 0).collect(),
239            dependents,
240            dependencies,
241        };
242        state.ready.sort_unstable();
243
244        if state.backtrack(0) {
245            Some(state.config)
246        } else {
247            None
248        }
249    }
250}
251
252struct BnBState {
253    n: usize,
254    bound: usize,
255    config: Vec<usize>,
256    live: Vec<bool>,
257    live_count: usize,
258    remaining_in_degree: Vec<u32>,
259    remaining_deps: Vec<usize>,
260    ready: Vec<usize>,
261    dependents: Vec<Vec<usize>>,
262    dependencies: Vec<Vec<usize>>,
263}
264
265impl BnBState {
266    fn backtrack(&mut self, step: usize) -> bool {
267        if step == self.n {
268            return true;
269        }
270
271        // Heuristic: prefer vertices that free the most registers.
272        let mut candidates = self.ready.clone();
273        candidates.sort_by_key(|&v| {
274            let frees = self.dependencies[v]
275                .iter()
276                .filter(|&&dep| self.remaining_deps[dep] == 1 && self.live[dep])
277                .count();
278            std::cmp::Reverse(frees)
279        });
280
281        for &vertex in &candidates {
282            self.config[vertex] = step;
283
284            let was_live = self.live[vertex];
285            if !was_live {
286                self.live[vertex] = true;
287                self.live_count += 1;
288            }
289
290            let mut freed = Vec::new();
291            for &dep in &self.dependencies[vertex] {
292                self.remaining_deps[dep] -= 1;
293                if self.remaining_deps[dep] == 0 && self.live[dep] {
294                    self.live[dep] = false;
295                    self.live_count -= 1;
296                    freed.push(dep);
297                }
298            }
299
300            if self.live_count <= self.bound {
301                self.ready.retain(|&v| v != vertex);
302                let mut newly_ready = Vec::new();
303                for &dep in &self.dependents[vertex] {
304                    self.remaining_in_degree[dep] -= 1;
305                    if self.remaining_in_degree[dep] == 0 {
306                        self.ready.push(dep);
307                        newly_ready.push(dep);
308                    }
309                }
310
311                if self.backtrack(step + 1) {
312                    return true;
313                }
314
315                for &dep in &newly_ready {
316                    self.ready.retain(|&v| v != dep);
317                }
318                for &dep in &self.dependents[vertex] {
319                    self.remaining_in_degree[dep] += 1;
320                }
321                self.ready.push(vertex);
322                self.ready.sort_unstable();
323            }
324
325            for &dep in &freed {
326                self.live[dep] = true;
327                self.live_count += 1;
328            }
329            for &dep in &self.dependencies[vertex] {
330                self.remaining_deps[dep] += 1;
331            }
332
333            if !was_live {
334                self.live[vertex] = false;
335                self.live_count -= 1;
336            }
337        }
338
339        false
340    }
341}
342
343impl Problem for RegisterSufficiency {
344    const NAME: &'static str = "RegisterSufficiency";
345    type Value = crate::types::Or;
346
347    fn variant() -> Vec<(&'static str, &'static str)> {
348        crate::variant_params![]
349    }
350
351    fn dims(&self) -> Vec<usize> {
352        vec![self.num_vertices; self.num_vertices]
353    }
354
355    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
356        crate::types::Or(
357            self.simulate_registers(config)
358                .is_some_and(|max_reg| max_reg <= self.bound),
359        )
360    }
361}
362
363crate::declare_variants! {
364    default RegisterSufficiency => "num_vertices ^ 2 * 2 ^ num_vertices",
365}
366
367#[cfg(feature = "example-db")]
368pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
369    vec![crate::example_db::specs::ModelExampleSpec {
370        id: "register_sufficiency",
371        // Issue #515 example: 7 vertices, 8 arcs, K=3
372        // Arcs (0-indexed): (2,0), (2,1), (3,1), (4,2), (4,3), (5,0), (6,4), (6,5)
373        // Order: v0,v1,v2,v3,v5,v4,v6 -> positions [0,1,2,3,5,4,6]
374        instance: Box::new(RegisterSufficiency::new(
375            7,
376            vec![
377                (2, 0),
378                (2, 1),
379                (3, 1),
380                (4, 2),
381                (4, 3),
382                (5, 0),
383                (6, 4),
384                (6, 5),
385            ],
386            3,
387        )),
388        // Order: v1,v2,v3,v4,v6,v5,v7 (1-indexed) = v0,v1,v2,v3,v5,v4,v6 (0-indexed)
389        // Positions: v0->0, v1->1, v2->2, v3->3, v4->5, v5->4, v6->6
390        optimal_config: vec![0, 1, 2, 3, 5, 4, 6],
391        optimal_value: serde_json::json!(true),
392    }]
393}
394
395#[cfg(test)]
396#[path = "../../unit_tests/models/misc/register_sufficiency.rs"]
397mod tests;