Skip to main content

problemreductions/models/misc/
minimum_code_generation_one_register.rs

1//! Minimum Code Generation on a One-Register Machine.
2//!
3//! Given a directed acyclic graph G = (V, A) with maximum out-degree 2
4//! (an expression DAG), find a program of minimum number of instructions
5//! for a one-register machine (LOAD, STORE, OP) that computes all root
6//! vertices. NP-complete [Bruno and Sethi, 1976].
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "MinimumCodeGenerationOneRegister",
16        display_name: "Minimum Code Generation (One Register)",
17        aliases: &[],
18        dimensions: &[],
19        category: crate::registry::ProblemCategory::Misc,
20        module_path: module_path!(),
21        description: "Find minimum-length instruction sequence for a one-register machine to evaluate an expression DAG",
22        fields: &[
23            FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices n = |V|" },
24            FieldInfo { name: "edges", type_name: "Vec<(usize, usize)>", description: "Directed arcs (parent, child) in the expression DAG" },
25            FieldInfo { name: "num_leaves", type_name: "usize", description: "Number of leaf vertices (out-degree 0)" },
26        ],
27    }
28}
29
30/// Minimum Code Generation on a One-Register Machine.
31///
32/// Given a directed acyclic graph G = (V, A) with maximum out-degree 2,
33/// where leaves (out-degree 0) are input values in memory, internal vertices
34/// are operations, and roots (in-degree 0) are values to compute, find a
35/// program of minimum instructions using LOAD, STORE, and OP.
36///
37/// # Representation
38///
39/// The configuration is a permutation of internal (non-leaf) vertices
40/// giving their evaluation order. `config[i]` is the evaluation position
41/// for internal vertex `i` (0-indexed among internal vertices).
42///
43/// # Example
44///
45/// ```
46/// use problemreductions::models::misc::MinimumCodeGenerationOneRegister;
47/// use problemreductions::{Problem, BruteForce, Min};
48///
49/// // 7 vertices: leaves {4,5,6}, internal {0,1,2,3}
50/// // v3 = op(v5, v6), v1 = op(v3, v4), v2 = op(v3, v5), v0 = op(v1, v2)
51/// let problem = MinimumCodeGenerationOneRegister::new(
52///     7,
53///     vec![(0,1),(0,2),(1,3),(1,4),(2,3),(2,5),(3,5),(3,6)],
54///     3,
55/// );
56/// let solution = BruteForce::new().solve(&problem).unwrap().unwrap();
57/// assert_eq!(problem.evaluate(&solution).unwrap(), Min(Some(8)));
58/// ```
59#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct MinimumCodeGenerationOneRegister {
61    /// Number of vertices |V|.
62    num_vertices: usize,
63    /// Directed arcs (parent, child) in the expression DAG.
64    edges: Vec<(usize, usize)>,
65    /// Number of leaf vertices (out-degree 0).
66    num_leaves: usize,
67}
68
69impl MinimumCodeGenerationOneRegister {
70    /// Create a new instance.
71    ///
72    /// # Arguments
73    ///
74    /// * `num_vertices` - Total number of vertices
75    /// * `edges` - Directed arcs (parent, child); parent depends on child
76    /// * `num_leaves` - Number of leaf vertices (out-degree 0)
77    ///
78    /// # Panics
79    ///
80    /// Panics if any edge index is out of bounds, if any vertex has
81    /// out-degree > 2, or if `num_leaves > num_vertices`.
82    pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>, num_leaves: usize) -> Self {
83        assert!(
84            num_leaves <= num_vertices,
85            "num_leaves ({num_leaves}) exceeds num_vertices ({num_vertices})"
86        );
87        let mut out_degree = vec![0usize; num_vertices];
88        for &(parent, child) in &edges {
89            assert!(
90                parent < num_vertices && child < num_vertices,
91                "Edge ({parent}, {child}) out of bounds for {num_vertices} vertices"
92            );
93            assert!(
94                parent != child,
95                "Self-loop ({parent}, {parent}) not allowed"
96            );
97            out_degree[parent] += 1;
98        }
99        for (v, &deg) in out_degree.iter().enumerate() {
100            assert!(deg <= 2, "Vertex {v} has out-degree {deg} > 2");
101        }
102        // Verify leaf count: leaves are vertices with out-degree 0
103        let actual_leaves = out_degree.iter().filter(|&&d| d == 0).count();
104        assert_eq!(
105            actual_leaves, num_leaves,
106            "Declared num_leaves ({num_leaves}) != actual leaf count ({actual_leaves})"
107        );
108        Self {
109            num_vertices,
110            edges,
111            num_leaves,
112        }
113    }
114
115    /// Get the number of vertices.
116    pub fn num_vertices(&self) -> usize {
117        self.num_vertices
118    }
119
120    /// Get the number of edges.
121    pub fn num_edges(&self) -> usize {
122        self.edges.len()
123    }
124
125    /// Get the number of leaf vertices.
126    pub fn num_leaves(&self) -> usize {
127        self.num_leaves
128    }
129
130    /// Get the number of internal (non-leaf) vertices.
131    pub fn num_internal(&self) -> usize {
132        self.num_vertices - self.num_leaves
133    }
134
135    /// Get the edges.
136    pub fn edges(&self) -> &[(usize, usize)] {
137        &self.edges
138    }
139
140    /// Compute the children (operands) of each vertex from the edge list.
141    fn children(&self) -> Vec<Vec<usize>> {
142        let mut ch = vec![vec![]; self.num_vertices];
143        for &(parent, child) in &self.edges {
144            ch[parent].push(child);
145        }
146        ch
147    }
148
149    /// Determine which vertices are internal (non-leaf, i.e. out-degree > 0).
150    fn internal_vertices(&self) -> Vec<usize> {
151        let children = self.children();
152        (0..self.num_vertices)
153            .filter(|&v| !children[v].is_empty())
154            .collect()
155    }
156
157    /// Determine which vertices are leaves (out-degree 0).
158    fn leaf_set(&self) -> Vec<bool> {
159        let children = self.children();
160        (0..self.num_vertices)
161            .map(|v| children[v].is_empty())
162            .collect()
163    }
164
165    /// Simulate the one-register machine for a given evaluation order of
166    /// internal vertices and return the instruction count, or `None` if the
167    /// ordering is invalid (not a permutation or violates dependencies).
168    pub fn simulate(
169        &self,
170        config: &[usize],
171    ) -> Result<Option<i64>, crate::traits::EvaluationError> {
172        let internal = self.internal_vertices();
173        let n_internal = internal.len();
174        if config.len() != n_internal {
175            return Ok(None);
176        }
177
178        // config[i] = evaluation position for internal vertex index i
179        // (i indexes into the `internal` array)
180        // Build order: order[pos] = index into `internal`
181        let mut order = vec![0usize; n_internal];
182        let mut used = vec![false; n_internal];
183        for (i, &pos) in config.iter().enumerate() {
184            if pos >= n_internal {
185                return Ok(None);
186            }
187            if used[pos] {
188                return Ok(None);
189            }
190            used[pos] = true;
191            order[pos] = i;
192        }
193
194        let children = self.children();
195        let is_leaf = self.leaf_set();
196
197        // Track which internal vertices have been computed
198        let mut computed = vec![false; self.num_vertices];
199        // All leaves are "computed" (available in memory)
200        for v in 0..self.num_vertices {
201            if is_leaf[v] {
202                computed[v] = true;
203            }
204        }
205
206        // Build: for each vertex, which future internal vertices need it?
207        // We'll track this dynamically.
208        let mut future_uses = vec![0usize; self.num_vertices];
209        for &idx in &order {
210            let v = internal[idx];
211            for &c in &children[v] {
212                future_uses[c] += 1;
213            }
214        }
215
216        let mut register: Option<usize> = None; // which vertex value is in register
217        let mut in_memory = vec![false; self.num_vertices];
218        // Leaves start in memory
219        for v in 0..self.num_vertices {
220            if is_leaf[v] {
221                in_memory[v] = true;
222            }
223        }
224
225        let mut instructions = 0_i64;
226
227        for step in 0..n_internal {
228            let v = internal[order[step]];
229
230            // Check dependencies: all children must be available
231            for &c in &children[v] {
232                let available = in_memory[c] || register == Some(c);
233                if !available {
234                    return Ok(None); // child was computed but lost (not stored, overwritten)
235                }
236            }
237
238            // Decrement future uses for children of v
239            for &c in &children[v] {
240                future_uses[c] -= 1;
241            }
242
243            let operands = &children[v];
244
245            // Before computing v, check if we need to STORE the current register value
246            // We need to store if:
247            // 1. Register holds a value
248            // 2. That value is still needed in the future
249            // 3. That value is not already in memory
250            if let Some(r) = register {
251                if !in_memory[r] && future_uses[r] > 0 {
252                    instructions = instructions.checked_add(1).ok_or_else(|| {
253                        crate::traits::EvaluationError::IntegerOverflow(
254                            "counting one-register instructions".to_string(),
255                        )
256                    })?; // STORE
257                    in_memory[r] = true;
258                }
259            }
260
261            // Now compute v
262            if operands.len() == 2 {
263                let c0 = operands[0];
264                let c1 = operands[1];
265                let one_in_register = (register == Some(c0) && in_memory[c1])
266                    || (register == Some(c1) && in_memory[c0]);
267                if one_in_register {
268                    instructions = instructions.checked_add(1).ok_or_else(|| {
269                        crate::traits::EvaluationError::IntegerOverflow(
270                            "counting one-register instructions".to_string(),
271                        )
272                    })?; // OP v
273                } else {
274                    // Need to LOAD one operand, OP with the other from memory
275                    instructions = instructions.checked_add(2).ok_or_else(|| {
276                        crate::traits::EvaluationError::IntegerOverflow(
277                            "counting one-register instructions".to_string(),
278                        )
279                    })?; // LOAD + OP
280                }
281            } else if operands.len() == 1 {
282                let c0 = operands[0];
283                if register == Some(c0) {
284                    instructions = instructions.checked_add(1).ok_or_else(|| {
285                        crate::traits::EvaluationError::IntegerOverflow(
286                            "counting one-register instructions".to_string(),
287                        )
288                    })?; // OP v
289                } else {
290                    instructions = instructions.checked_add(2).ok_or_else(|| {
291                        crate::traits::EvaluationError::IntegerOverflow(
292                            "counting one-register instructions".to_string(),
293                        )
294                    })?; // LOAD + OP
295                }
296            }
297
298            register = Some(v);
299        }
300
301        Ok(Some(instructions))
302    }
303}
304
305impl Problem for MinimumCodeGenerationOneRegister {
306    const NAME: &'static str = "MinimumCodeGenerationOneRegister";
307    type Solution = Vec<usize>;
308    type Value = Min<i64>;
309
310    crate::problem_parameters![
311        ("num_vertices", num_vertices),
312        ("num_edges", num_edges),
313        ("num_leaves", num_leaves),
314        ("num_internal", num_internal),
315    ];
316
317    fn variant() -> Vec<(&'static str, &'static str)> {
318        crate::variant_params![]
319    }
320
321    fn evaluate(
322        &self,
323        config: &Self::Solution,
324    ) -> Result<Min<i64>, crate::traits::EvaluationError> {
325        let n = self.internal_vertices().len();
326        if config.len() != n {
327            return Err(crate::traits::EvaluationError::InvalidConfiguration(
328                "evaluation ordering length does not match the internal vertices".into(),
329            ));
330        }
331        if config.iter().any(|&position| position >= n) {
332            return Err(crate::traits::EvaluationError::InvalidConfiguration(
333                "evaluation ordering contains an out-of-range position".into(),
334            ));
335        }
336        Ok(Min(self.simulate(config)?))
337    }
338}
339
340impl crate::solvers::BruteForceProblem for MinimumCodeGenerationOneRegister {
341    fn dimensions(&self) -> Vec<usize> {
342        let n_internal = self.num_internal();
343        vec![n_internal; n_internal]
344    }
345}
346
347crate::declare_variants! {
348    default MinimumCodeGenerationOneRegister => "2 ^ num_vertices",
349}
350
351crate::register_brute_force! {
352    MinimumCodeGenerationOneRegister,
353}
354
355#[cfg(feature = "example-db")]
356pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
357    vec![crate::example_db::specs::ModelExampleSpec {
358        id: "minimum_code_generation_one_register",
359        // Issue #900 example: 7 vertices, leaves {4,5,6}, internal {0,1,2,3}
360        // Edges: (0,1),(0,2),(1,3),(1,4),(2,3),(2,5),(3,5),(3,6)
361        // Optimal order: v3, v2, v1, v0 with positions [3, 2, 1, 0]
362        // Wait — config[i] = position for internal vertex i.
363        // Internal vertices sorted: [0, 1, 2, 3]
364        // Optimal evaluation order: v3, v2, v1, v0
365        // v3 at position 0, v2 at position 1, v1 at position 2, v0 at position 3
366        // So config = [3, 2, 1, 0] (internal idx 0=v0 -> pos 3, idx 1=v1 -> pos 2, ...)
367        instance: Box::new(MinimumCodeGenerationOneRegister::new(
368            7,
369            vec![
370                (0, 1),
371                (0, 2),
372                (1, 3),
373                (1, 4),
374                (2, 3),
375                (2, 5),
376                (3, 5),
377                (3, 6),
378            ],
379            3,
380        )),
381        optimal_config: serde_json::json!(vec![3, 2, 1, 0]),
382        optimal_value: serde_json::json!(8),
383    }]
384}
385
386#[cfg(test)]
387#[path = "../../unit_tests/models/misc/minimum_code_generation_one_register.rs"]
388mod tests;