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)]
60#[serde(try_from = "MinimumCodeGenerationOneRegisterData")]
61pub struct MinimumCodeGenerationOneRegister {
62    /// Number of vertices |V|.
63    num_vertices: usize,
64    /// Directed arcs (parent, child) in the expression DAG.
65    edges: Vec<(usize, usize)>,
66    /// Number of leaf vertices (out-degree 0).
67    num_leaves: usize,
68}
69
70#[derive(Deserialize)]
71struct MinimumCodeGenerationOneRegisterData {
72    num_vertices: usize,
73    edges: Vec<(usize, usize)>,
74    num_leaves: usize,
75}
76
77impl TryFrom<MinimumCodeGenerationOneRegisterData> for MinimumCodeGenerationOneRegister {
78    type Error = crate::registry::ConstructionError;
79    fn try_from(data: MinimumCodeGenerationOneRegisterData) -> Result<Self, Self::Error> {
80        Self::try_new(data.num_vertices, data.edges, data.num_leaves)
81    }
82}
83
84impl MinimumCodeGenerationOneRegister {
85    /// Create a new instance.
86    ///
87    /// # Arguments
88    ///
89    /// * `num_vertices` - Total number of vertices
90    /// * `edges` - Directed arcs (parent, child); parent depends on child
91    /// * `num_leaves` - Number of leaf vertices (out-degree 0)
92    ///
93    /// # Panics
94    ///
95    /// Panics if any edge index is out of bounds, if any vertex has
96    /// out-degree > 2, or if `num_leaves > num_vertices`.
97    pub fn new(num_vertices: usize, edges: Vec<(usize, usize)>, num_leaves: usize) -> Self {
98        Self::try_new(num_vertices, edges, num_leaves).unwrap_or_else(|error| panic!("{error}"))
99    }
100
101    fn try_new(
102        num_vertices: usize,
103        edges: Vec<(usize, usize)>,
104        num_leaves: usize,
105    ) -> Result<Self, crate::registry::ConstructionError> {
106        if num_leaves > num_vertices {
107            return Err(
108                format!("num_leaves ({num_leaves}) exceeds num_vertices ({num_vertices})").into(),
109            );
110        }
111        let mut out_degree = vec![0usize; num_vertices];
112        for &(parent, child) in &edges {
113            if parent >= num_vertices || child >= num_vertices {
114                return Err(format!(
115                    "Edge ({parent}, {child}) out of bounds for {num_vertices} vertices"
116                )
117                .into());
118            }
119            if parent == child {
120                return Err(format!("Self-loop ({parent}, {parent}) not allowed").into());
121            }
122            out_degree[parent] += 1;
123        }
124        for (v, &deg) in out_degree.iter().enumerate() {
125            if deg > 2 {
126                return Err(format!("Vertex {v} has out-degree {deg} > 2").into());
127            }
128        }
129        // Verify leaf count: leaves are vertices with out-degree 0
130        let actual_leaves = out_degree.iter().filter(|&&d| d == 0).count();
131        if actual_leaves != num_leaves {
132            return Err(format!(
133                "Declared num_leaves ({num_leaves}) != actual leaf count ({actual_leaves})"
134            )
135            .into());
136        }
137        Ok(Self {
138            num_vertices,
139            edges,
140            num_leaves,
141        })
142    }
143
144    /// Get the number of vertices.
145    pub fn num_vertices(&self) -> usize {
146        self.num_vertices
147    }
148
149    /// Get the number of edges.
150    pub fn num_edges(&self) -> usize {
151        self.edges.len()
152    }
153
154    /// Get the number of leaf vertices.
155    pub fn num_leaves(&self) -> usize {
156        self.num_leaves
157    }
158
159    /// Get the number of internal (non-leaf) vertices.
160    pub fn num_internal(&self) -> usize {
161        self.num_vertices - self.num_leaves
162    }
163
164    /// Get the edges.
165    pub fn edges(&self) -> &[(usize, usize)] {
166        &self.edges
167    }
168
169    /// Compute the children (operands) of each vertex from the edge list.
170    fn children(&self) -> Vec<Vec<usize>> {
171        let mut ch = vec![vec![]; self.num_vertices];
172        for &(parent, child) in &self.edges {
173            ch[parent].push(child);
174        }
175        ch
176    }
177
178    /// Determine which vertices are internal (non-leaf, i.e. out-degree > 0).
179    fn internal_vertices(&self) -> Vec<usize> {
180        let children = self.children();
181        (0..self.num_vertices)
182            .filter(|&v| !children[v].is_empty())
183            .collect()
184    }
185
186    /// Determine which vertices are leaves (out-degree 0).
187    fn leaf_set(&self) -> Vec<bool> {
188        let children = self.children();
189        (0..self.num_vertices)
190            .map(|v| children[v].is_empty())
191            .collect()
192    }
193
194    /// Simulate the one-register machine for a given evaluation order of
195    /// internal vertices and return the instruction count, or `None` if the
196    /// ordering is invalid (not a permutation or violates dependencies).
197    pub fn simulate(
198        &self,
199        config: &[usize],
200    ) -> Result<Option<i64>, crate::traits::EvaluationError> {
201        let internal = self.internal_vertices();
202        let n_internal = internal.len();
203        if config.len() != n_internal {
204            return Ok(None);
205        }
206
207        // config[i] = evaluation position for internal vertex index i
208        // (i indexes into the `internal` array)
209        // Build order: order[pos] = index into `internal`
210        let mut order = vec![0usize; n_internal];
211        let mut used = vec![false; n_internal];
212        for (i, &pos) in config.iter().enumerate() {
213            if pos >= n_internal {
214                return Ok(None);
215            }
216            if used[pos] {
217                return Ok(None);
218            }
219            used[pos] = true;
220            order[pos] = i;
221        }
222
223        let children = self.children();
224        let is_leaf = self.leaf_set();
225
226        // Track which internal vertices have been computed
227        let mut computed = vec![false; self.num_vertices];
228        // All leaves are "computed" (available in memory)
229        for v in 0..self.num_vertices {
230            if is_leaf[v] {
231                computed[v] = true;
232            }
233        }
234
235        // Build: for each vertex, which future internal vertices need it?
236        // We'll track this dynamically.
237        let mut future_uses = vec![0usize; self.num_vertices];
238        for &idx in &order {
239            let v = internal[idx];
240            for &c in &children[v] {
241                future_uses[c] += 1;
242            }
243        }
244
245        let mut register: Option<usize> = None; // which vertex value is in register
246        let mut in_memory = vec![false; self.num_vertices];
247        // Leaves start in memory
248        for v in 0..self.num_vertices {
249            if is_leaf[v] {
250                in_memory[v] = true;
251            }
252        }
253
254        let mut instructions = 0_i64;
255
256        for step in 0..n_internal {
257            let v = internal[order[step]];
258
259            // Check dependencies: all children must be available
260            for &c in &children[v] {
261                let available = in_memory[c] || register == Some(c);
262                if !available {
263                    return Ok(None); // child was computed but lost (not stored, overwritten)
264                }
265            }
266
267            // Decrement future uses for children of v
268            for &c in &children[v] {
269                future_uses[c] -= 1;
270            }
271
272            let operands = &children[v];
273
274            // Before computing v, check if we need to STORE the current register value
275            // We need to store if:
276            // 1. Register holds a value
277            // 2. That value is still needed in the future
278            // 3. That value is not already in memory
279            if let Some(r) = register {
280                if !in_memory[r] && future_uses[r] > 0 {
281                    instructions = instructions.checked_add(1).ok_or_else(|| {
282                        crate::traits::EvaluationError::IntegerOverflow(
283                            "counting one-register instructions".to_string(),
284                        )
285                    })?; // STORE
286                    in_memory[r] = true;
287                }
288            }
289
290            // Now compute v
291            if operands.len() == 2 {
292                let c0 = operands[0];
293                let c1 = operands[1];
294                let one_in_register = (register == Some(c0) && in_memory[c1])
295                    || (register == Some(c1) && in_memory[c0]);
296                if one_in_register {
297                    instructions = instructions.checked_add(1).ok_or_else(|| {
298                        crate::traits::EvaluationError::IntegerOverflow(
299                            "counting one-register instructions".to_string(),
300                        )
301                    })?; // OP v
302                } else {
303                    // Need to LOAD one operand, OP with the other from memory
304                    instructions = instructions.checked_add(2).ok_or_else(|| {
305                        crate::traits::EvaluationError::IntegerOverflow(
306                            "counting one-register instructions".to_string(),
307                        )
308                    })?; // LOAD + OP
309                }
310            } else if operands.len() == 1 {
311                let c0 = operands[0];
312                if register == Some(c0) {
313                    instructions = instructions.checked_add(1).ok_or_else(|| {
314                        crate::traits::EvaluationError::IntegerOverflow(
315                            "counting one-register instructions".to_string(),
316                        )
317                    })?; // OP v
318                } else {
319                    instructions = instructions.checked_add(2).ok_or_else(|| {
320                        crate::traits::EvaluationError::IntegerOverflow(
321                            "counting one-register instructions".to_string(),
322                        )
323                    })?; // LOAD + OP
324                }
325            }
326
327            register = Some(v);
328        }
329
330        Ok(Some(instructions))
331    }
332}
333
334impl Problem for MinimumCodeGenerationOneRegister {
335    const NAME: &'static str = "MinimumCodeGenerationOneRegister";
336    type Solution = Vec<usize>;
337    type Value = Min<i64>;
338
339    crate::problem_parameters![
340        ("num_vertices", num_vertices),
341        ("num_edges", num_edges),
342        ("num_leaves", num_leaves),
343        ("num_internal", num_internal),
344    ];
345
346    fn variant() -> Vec<(&'static str, &'static str)> {
347        crate::variant_params![]
348    }
349
350    fn evaluate(
351        &self,
352        config: &Self::Solution,
353    ) -> Result<Min<i64>, crate::traits::EvaluationError> {
354        let n = self.internal_vertices().len();
355        if config.len() != n {
356            return Err(crate::traits::EvaluationError::InvalidConfiguration(
357                "evaluation ordering length does not match the internal vertices".into(),
358            ));
359        }
360        if config.iter().any(|&position| position >= n) {
361            return Err(crate::traits::EvaluationError::InvalidConfiguration(
362                "evaluation ordering contains an out-of-range position".into(),
363            ));
364        }
365        Ok(Min(self.simulate(config)?))
366    }
367}
368
369impl crate::solvers::BruteForceProblem for MinimumCodeGenerationOneRegister {
370    fn dimensions(&self) -> Vec<usize> {
371        let n_internal = self.num_internal();
372        vec![n_internal; n_internal]
373    }
374}
375
376crate::declare_variants! {
377    default MinimumCodeGenerationOneRegister => "2 ^ num_vertices",
378}
379
380crate::register_brute_force! {
381    MinimumCodeGenerationOneRegister,
382}
383
384#[cfg(feature = "example-db")]
385pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
386    vec![crate::example_db::specs::ModelExampleSpec {
387        id: "minimum_code_generation_one_register",
388        // Issue #900 example: 7 vertices, leaves {4,5,6}, internal {0,1,2,3}
389        // Edges: (0,1),(0,2),(1,3),(1,4),(2,3),(2,5),(3,5),(3,6)
390        // Optimal order: v3, v2, v1, v0 with positions [3, 2, 1, 0]
391        // Wait — config[i] = position for internal vertex i.
392        // Internal vertices sorted: [0, 1, 2, 3]
393        // Optimal evaluation order: v3, v2, v1, v0
394        // v3 at position 0, v2 at position 1, v1 at position 2, v0 at position 3
395        // So config = [3, 2, 1, 0] (internal idx 0=v0 -> pos 3, idx 1=v1 -> pos 2, ...)
396        instance: Box::new(MinimumCodeGenerationOneRegister::new(
397            7,
398            vec![
399                (0, 1),
400                (0, 2),
401                (1, 3),
402                (1, 4),
403                (2, 3),
404                (2, 5),
405                (3, 5),
406                (3, 6),
407            ],
408            3,
409        )),
410        optimal_config: serde_json::json!(vec![3, 2, 1, 0]),
411        optimal_value: serde_json::json!(8),
412    }]
413}
414
415#[cfg(test)]
416#[path = "../../unit_tests/models/misc/minimum_code_generation_one_register.rs"]
417mod tests;