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        category: crate::registry::ProblemCategory::Misc,
18        module_path: module_path!(),
19        description: "Determine whether a DAG computation can be performed using K or fewer registers",
20        fields: &[
21            FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices n = |V|" },
22            FieldInfo { name: "arcs", type_name: "Vec<(usize, usize)>", description: "Directed arcs (v, u) meaning v depends on u" },
23            FieldInfo { name: "bound", type_name: "usize", description: "Register bound K" },
24        ],
25    }
26}
27
28/// The Register Sufficiency problem.
29///
30/// Given a directed acyclic graph G = (V, A) where arcs represent data
31/// dependencies, and a positive integer K, determine whether there is an
32/// evaluation ordering of all vertices such that at most K registers are
33/// needed at any point during the computation.
34///
35/// # Representation
36///
37/// An arc `(v, u)` means vertex `v` depends on vertex `u` (i.e., `u` must be
38/// in a register when `v` is evaluated). Each variable represents a vertex,
39/// with domain `{0, ..., n-1}` giving its evaluation position (the config
40/// must be a valid permutation).
41///
42/// # Example
43///
44/// ```
45/// use problemreductions::models::misc::RegisterSufficiency;
46/// use problemreductions::{Problem, BruteForce};
47///
48/// // 4 vertices: v2 depends on v0, v3 depends on v0 and v1
49/// let problem = RegisterSufficiency::new(
50///     4,
51///     vec![(2, 0), (3, 0), (3, 1)],
52///     2,
53/// );
54/// let solver = BruteForce::new();
55/// let solution = solver.solve(&problem).unwrap();
56/// assert!(solution.is_some());
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59#[serde(try_from = "RegisterSufficiencyData")]
60pub struct RegisterSufficiency {
61    /// Number of vertices.
62    num_vertices: usize,
63    /// Directed arcs (v, u) meaning v depends on u.
64    arcs: Vec<(usize, usize)>,
65    /// Register bound K.
66    bound: usize,
67}
68
69#[derive(Deserialize)]
70struct RegisterSufficiencyData {
71    num_vertices: usize,
72    arcs: Vec<(usize, usize)>,
73    bound: usize,
74}
75
76impl TryFrom<RegisterSufficiencyData> for RegisterSufficiency {
77    type Error = crate::registry::ConstructionError;
78
79    fn try_from(data: RegisterSufficiencyData) -> Result<Self, Self::Error> {
80        Self::try_new(data.num_vertices, data.arcs, data.bound)
81    }
82}
83
84impl RegisterSufficiency {
85    /// Create a new Register Sufficiency instance.
86    ///
87    /// # Panics
88    ///
89    /// Panics if any arc index is out of bounds (>= num_vertices),
90    /// or if any arc is a self-loop.
91    pub fn new(num_vertices: usize, arcs: Vec<(usize, usize)>, bound: usize) -> Self {
92        Self::try_new(num_vertices, arcs, bound).unwrap_or_else(|error| panic!("{error}"))
93    }
94
95    fn try_new(
96        num_vertices: usize,
97        arcs: Vec<(usize, usize)>,
98        bound: usize,
99    ) -> Result<Self, crate::registry::ConstructionError> {
100        for &(v, u) in &arcs {
101            if v >= num_vertices || u >= num_vertices {
102                return Err(format!(
103                    "Arc ({}, {}) out of bounds for {} vertices",
104                    v, u, num_vertices
105                )
106                .into());
107            }
108            if v == u {
109                return Err(format!("Self-loop ({}, {}) not allowed in a DAG", v, u).into());
110            }
111        }
112        Ok(Self {
113            num_vertices,
114            arcs,
115            bound,
116        })
117    }
118
119    /// Get the number of vertices.
120    pub fn num_vertices(&self) -> usize {
121        self.num_vertices
122    }
123
124    /// Get the number of arcs.
125    pub fn num_arcs(&self) -> usize {
126        self.arcs.len()
127    }
128
129    /// Count vertices with no dependents.
130    pub fn num_sinks(&self) -> usize {
131        let mut has_dependent = vec![false; self.num_vertices];
132        for &(_, dependency) in &self.arcs {
133            has_dependent[dependency] = true;
134        }
135        has_dependent.into_iter().filter(|&flag| !flag).count()
136    }
137
138    /// Get the register bound K.
139    pub fn bound(&self) -> usize {
140        self.bound
141    }
142
143    /// Get the arcs.
144    pub fn arcs(&self) -> &[(usize, usize)] {
145        &self.arcs
146    }
147
148    /// Simulate register usage for a given evaluation ordering and return the
149    /// maximum number of registers used, or `None` if the ordering is invalid
150    /// (not a permutation or violates dependencies).
151    pub fn simulate_registers(
152        &self,
153        config: &[usize],
154    ) -> Result<Option<i64>, crate::traits::EvaluationError> {
155        let n = self.num_vertices;
156        if config.len() != n {
157            return Ok(None);
158        }
159
160        // Check valid permutation: each position 0..n-1 used exactly once
161        let mut order = vec![0usize; n]; // order[position] = vertex
162        let mut used = vec![false; n];
163        for (vertex, &position) in config.iter().enumerate() {
164            if position >= n {
165                return Ok(None);
166            }
167            if used[position] {
168                return Ok(None);
169            }
170            used[position] = true;
171            order[position] = vertex;
172        }
173
174        // Build dependency info:
175        // dependents[u] = list of vertices that depend on u (i.e., arcs (v, u))
176        // dependencies[v] = list of vertices that v depends on (i.e., arcs (v, u))
177        let mut dependencies: Vec<Vec<usize>> = vec![vec![]; n];
178        let mut dependents: Vec<Vec<usize>> = vec![vec![]; n];
179        for &(v, u) in &self.arcs {
180            dependencies[v].push(u);
181            dependents[u].push(v);
182        }
183
184        // For each vertex u, compute the latest position among its dependents.
185        // A vertex u must stay in registers until all its dependents have been evaluated.
186        let mut last_use = vec![0usize; n];
187        for u in 0..n {
188            if dependents[u].is_empty() {
189                // Vertex u has no dependents. It stays in registers from its
190                // evaluation step until the end (final outputs must be in S_n).
191                last_use[u] = n; // stays until the end
192            } else {
193                let mut latest = 0;
194                for &v in &dependents[u] {
195                    latest = latest.max(config[v]);
196                }
197                last_use[u] = latest;
198            }
199        }
200
201        let mut max_registers = 0;
202
203        // Simulate: process vertices in evaluation order
204        for step in 0..n {
205            let vertex = order[step];
206
207            // Check dependencies: all dependencies of this vertex must have
208            // been evaluated before this step
209            for &dep in &dependencies[vertex] {
210                if config[dep] >= step {
211                    // Dependency not yet evaluated
212                    return Ok(None);
213                }
214            }
215
216            // Count registers at this step:
217            // A vertex v is in registers if:
218            // - v has been evaluated (config[v] <= step)
219            // - v is still needed (last_use[v] > step, or v is the current vertex)
220            // Actually, more precisely: after evaluating vertex at position `step`,
221            // the register set contains all vertices evaluated so far whose last
222            // use is > step (they're still needed later), plus the current vertex.
223            let reg_count = order[..=step]
224                .iter()
225                .filter(|&&v| last_use[v] > step)
226                .count();
227
228            max_registers = max_registers.max(reg_count);
229        }
230
231        Ok(Some(i64::try_from(max_registers).map_err(|_| {
232            crate::traits::EvaluationError::IntegerOverflow(
233                "converting register-usage count to i64".into(),
234            )
235        })?))
236    }
237
238    /// Exact branch-and-bound solver: finds a topological ordering using at
239    /// most `self.bound` registers, or returns `None` if no such ordering
240    /// exists.  Uses heuristic candidate ordering (prefer vertices that free
241    /// the most registers) so that YES instances typically resolve on the
242    /// first greedy path without backtracking. Identical dependency/consumer
243    /// groups are scheduled consecutively, and ready operations that do not
244    /// increase the live set can be performed immediately.
245    ///
246    /// NOTE: a greedy topological sort is *not* exact — it can miss valid
247    /// orderings.  This method is exact because it backtracks when the
248    /// greedy choice fails.  Do not replace it with a pure greedy solver.
249    pub fn solve_exact(&self) -> Option<Vec<usize>> {
250        let n = self.num_vertices;
251        if n == 0 {
252            return Some(vec![]);
253        }
254
255        let mut dependents: Vec<Vec<usize>> = vec![vec![]; n];
256        let mut dependencies: Vec<Vec<usize>> = vec![vec![]; n];
257        let mut in_degree = vec![0usize; n];
258        for &(v, u) in &self.arcs {
259            in_degree[v] += 1;
260            dependents[u].push(v);
261            dependencies[v].push(u);
262        }
263        for neighbors in dependents.iter_mut().chain(dependencies.iter_mut()) {
264            neighbors.sort_unstable();
265        }
266
267        let mut state = BnBState {
268            n,
269            bound: self.bound,
270            config: vec![0usize; n],
271            live: vec![false; n],
272            live_count: 0,
273            computed: vec![false; n],
274            failed: std::collections::HashSet::new(),
275            remaining_in_degree: in_degree.clone(),
276            remaining_deps: dependents.iter().map(|d| d.len()).collect(),
277            ready: (0..n).filter(|&v| in_degree[v] == 0).collect(),
278            dependents,
279            dependencies,
280        };
281        state.ready.sort_unstable();
282
283        // If every next operation requires all input vertices, their order
284        // cannot matter: no operation can run before the last input and each
285        // input adds one live value. Avoid exploring their permutations.
286        let input_block = if state
287            .dependencies
288            .iter()
289            .filter(|deps| !deps.is_empty() && deps.iter().all(|&v| in_degree[v] == 0))
290            .all(|deps| state.ready.iter().all(|v| deps.contains(v)))
291        {
292            state.ready.clone()
293        } else {
294            Vec::new()
295        };
296        if state.backtrack(0, &input_block) {
297            Some(state.config)
298        } else {
299            None
300        }
301    }
302}
303
304struct BnBState {
305    n: usize,
306    bound: usize,
307    config: Vec<usize>,
308    live: Vec<bool>,
309    live_count: usize,
310    computed: Vec<bool>,
311    failed: std::collections::HashSet<Vec<bool>>,
312    remaining_in_degree: Vec<usize>,
313    remaining_deps: Vec<usize>,
314    ready: Vec<usize>,
315    dependents: Vec<Vec<usize>>,
316    dependencies: Vec<Vec<usize>>,
317}
318
319impl BnBState {
320    fn backtrack(&mut self, step: usize, block: &[usize]) -> bool {
321        if step == self.n {
322            return true;
323        }
324        // Liveness and available operations depend only on the computed set,
325        // so a failed continuation never needs searching under another order.
326        if block.is_empty() && self.failed.contains(&self.computed) {
327            return false;
328        }
329
330        // Heuristic: prefer vertices that free the most registers.
331        let mut candidates = if block.is_empty() {
332            self.ready.clone()
333        } else {
334            vec![block[0]]
335        };
336        candidates.sort_by_cached_key(|&v| {
337            let frees = self.dependencies[v]
338                .iter()
339                .filter(|&&dep| self.remaining_deps[dep] == 1 && self.live[dep])
340                .count();
341            let group_size = self
342                .ready
343                .iter()
344                .filter(|&&w| {
345                    self.dependencies[v] == self.dependencies[w]
346                        && self.dependents[v] == self.dependents[w]
347                })
348                .count();
349            let unblocks = self.dependents[v].iter().any(|&consumer| {
350                self.remaining_in_degree[consumer]
351                    == group_size
352                        * self.dependents[v]
353                            .iter()
354                            .filter(|&&w| w == consumer)
355                            .count()
356            });
357            // Prefer work that releases values or makes its consumer ready.
358            (
359                std::cmp::Reverse(frees),
360                std::cmp::Reverse(unblocks),
361                group_size,
362                v,
363            )
364        });
365        if block.is_empty() {
366            candidates.retain(|&v| {
367                !self.ready.iter().any(|&w| {
368                    w < v
369                        && self.dependencies[v] == self.dependencies[w]
370                        && self.dependents[v] == self.dependents[w]
371                })
372            });
373            // Moving a ready operation that frees at least one value earlier
374            // replaces those live values by one result and cannot raise a peak.
375            if candidates.first().is_some_and(|&v| {
376                self.dependencies[v]
377                    .iter()
378                    .any(|&dep| self.remaining_deps[dep] == 1 && self.live[dep])
379            }) {
380                candidates.truncate(1);
381            }
382        }
383
384        for &vertex in &candidates {
385            // No consumer of identical vertices can run until all are done.
386            // Delay partial computation until the last member: a consecutive
387            // block never keeps more values live than the original ordering.
388            let following = if block.is_empty() {
389                self.ready
390                    .iter()
391                    .copied()
392                    .filter(|&v| {
393                        v != vertex
394                            && self.dependencies[v] == self.dependencies[vertex]
395                            && self.dependents[v] == self.dependents[vertex]
396                    })
397                    .collect::<Vec<_>>()
398            } else {
399                block[1..].to_vec()
400            };
401            self.config[vertex] = step;
402            self.computed[vertex] = true;
403
404            let was_live = self.live[vertex];
405            if !was_live {
406                self.live[vertex] = true;
407                self.live_count += 1;
408            }
409
410            let mut freed = Vec::new();
411            for &dep in &self.dependencies[vertex] {
412                self.remaining_deps[dep] -= 1;
413                if self.remaining_deps[dep] == 0 && self.live[dep] {
414                    self.live[dep] = false;
415                    self.live_count -= 1;
416                    freed.push(dep);
417                }
418            }
419
420            if self.live_count <= self.bound {
421                self.ready.retain(|&v| v != vertex);
422                let mut newly_ready = Vec::new();
423                for &dep in &self.dependents[vertex] {
424                    self.remaining_in_degree[dep] -= 1;
425                    if self.remaining_in_degree[dep] == 0 {
426                        self.ready.push(dep);
427                        newly_ready.push(dep);
428                    }
429                }
430
431                if self.backtrack(step + 1, &following) {
432                    return true;
433                }
434
435                for &dep in &newly_ready {
436                    self.ready.retain(|&v| v != dep);
437                }
438                for &dep in &self.dependents[vertex] {
439                    self.remaining_in_degree[dep] += 1;
440                }
441                self.ready.push(vertex);
442                self.ready.sort_unstable();
443            }
444
445            for &dep in &freed {
446                self.live[dep] = true;
447                self.live_count += 1;
448            }
449            for &dep in &self.dependencies[vertex] {
450                self.remaining_deps[dep] += 1;
451            }
452
453            if !was_live {
454                self.live[vertex] = false;
455                self.live_count -= 1;
456            }
457            self.computed[vertex] = false;
458        }
459
460        if block.is_empty() {
461            self.failed.insert(self.computed.clone());
462        }
463        false
464    }
465}
466
467impl Problem for RegisterSufficiency {
468    const NAME: &'static str = "RegisterSufficiency";
469    type Solution = Vec<usize>;
470    type Value = crate::types::Or;
471
472    crate::problem_parameters![
473        ("bound", bound),
474        ("num_arcs", num_arcs),
475        ("num_sinks", num_sinks),
476        ("num_vertices", num_vertices),
477    ];
478
479    fn variant() -> Vec<(&'static str, &'static str)> {
480        crate::variant_params![]
481    }
482
483    fn evaluate(
484        &self,
485        config: &Self::Solution,
486    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
487        if config.len() != self.num_vertices {
488            return Err(crate::traits::EvaluationError::InvalidConfiguration(
489                "evaluation ordering length does not match the graph vertices".into(),
490            ));
491        }
492        if config.iter().any(|&position| position >= self.num_vertices) {
493            return Err(crate::traits::EvaluationError::InvalidConfiguration(
494                "evaluation ordering contains an out-of-range position".into(),
495            ));
496        }
497        let bound = i64::try_from(self.bound).map_err(|_| {
498            crate::traits::EvaluationError::IntegerOverflow(
499                "converting register bound to i64".into(),
500            )
501        })?;
502        Ok(crate::types::Or(
503            self.simulate_registers(config)?
504                .is_some_and(|max_reg| max_reg <= bound),
505        ))
506    }
507}
508
509impl crate::solvers::BruteForceProblem for RegisterSufficiency {
510    fn dimensions(&self) -> Vec<usize> {
511        vec![self.num_vertices; self.num_vertices]
512    }
513}
514
515crate::declare_variants! {
516    default RegisterSufficiency => "num_vertices ^ 2 * 2 ^ num_vertices",
517}
518
519crate::register_brute_force! {
520    RegisterSufficiency,
521}
522
523#[cfg(feature = "example-db")]
524pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
525    vec![crate::example_db::specs::ModelExampleSpec {
526        id: "register_sufficiency",
527        // Issue #515 example: 7 vertices, 8 arcs, K=3
528        // Arcs (0-indexed): (2,0), (2,1), (3,1), (4,2), (4,3), (5,0), (6,4), (6,5)
529        // Order: v0,v1,v2,v3,v5,v4,v6 -> positions [0,1,2,3,5,4,6]
530        instance: Box::new(RegisterSufficiency::new(
531            7,
532            vec![
533                (2, 0),
534                (2, 1),
535                (3, 1),
536                (4, 2),
537                (4, 3),
538                (5, 0),
539                (6, 4),
540                (6, 5),
541            ],
542            3,
543        )),
544        // Order: v1,v2,v3,v4,v6,v5,v7 (1-indexed) = v0,v1,v2,v3,v5,v4,v6 (0-indexed)
545        // Positions: v0->0, v1->1, v2->2, v3->3, v4->5, v5->4, v6->6
546        optimal_config: serde_json::json!(vec![0, 1, 2, 3, 5, 4, 6]),
547        optimal_value: serde_json::json!(true),
548    }]
549}
550
551#[cfg(test)]
552#[path = "../../unit_tests/models/misc/register_sufficiency.rs"]
553mod tests;