Skip to main content

problemreductions/models/misc/
minimum_code_generation_parallel_assignments.rs

1//! Minimum Code Generation for Parallel Assignments problem implementation.
2//!
3//! Given a set of simultaneous variable assignments, find an execution ordering
4//! (permutation) that minimizes the number of backward dependencies -- cases where
5//! a variable is overwritten before a later assignment reads its old value.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry};
8use crate::traits::Problem;
9use crate::types::Min;
10use serde::{Deserialize, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "MinimumCodeGenerationParallelAssignments",
15        display_name: "Minimum Code Generation (Parallel Assignments)",
16        aliases: &[],
17        dimensions: &[],
18        category: crate::registry::ProblemCategory::Misc,
19        module_path: module_path!(),
20        description: "Find an ordering of parallel assignments minimizing backward dependencies",
21        fields: &[
22            FieldInfo { name: "num_variables", type_name: "usize", description: "Number of variables" },
23            FieldInfo { name: "assignments", type_name: "Vec<(usize, Vec<usize>)>", description: "Each assignment (target_var, read_vars)" },
24        ],
25    }
26}
27
28/// The Minimum Code Generation for Parallel Assignments problem.
29///
30/// Given a set V of variables and a collection of assignments A_i: "v_i <- op(B_i)"
31/// where v_i is the target variable and B_i is the set of variables read,
32/// find a permutation of the assignments that minimizes the number of backward
33/// dependencies. A backward dependency occurs when assignment pi(i) writes
34/// variable v and assignment pi(j) (j > i) reads v.
35///
36/// # Example
37///
38/// ```
39/// use problemreductions::models::misc::MinimumCodeGenerationParallelAssignments;
40/// use problemreductions::{Problem, BruteForce};
41///
42/// // 4 variables, 4 assignments:
43/// // A_0: a <- op(b, c)   -> (0, [1, 2])
44/// // A_1: b <- op(a)      -> (1, [0])
45/// // A_2: c <- op(d)      -> (2, [3])
46/// // A_3: d <- op(b, c)   -> (3, [1, 2])
47/// let assignments = vec![
48///     (0, vec![1, 2]),
49///     (1, vec![0]),
50///     (2, vec![3]),
51///     (3, vec![1, 2]),
52/// ];
53/// let problem = MinimumCodeGenerationParallelAssignments::new(4, assignments);
54/// let solver = BruteForce::new();
55/// let solution = solver.solve(&problem).unwrap();
56/// assert!(solution.is_some());
57/// ```
58#[derive(Debug, Clone, Serialize, Deserialize)]
59pub struct MinimumCodeGenerationParallelAssignments {
60    num_variables: usize,
61    assignments: Vec<(usize, Vec<usize>)>,
62}
63
64impl MinimumCodeGenerationParallelAssignments {
65    /// Create a new MinimumCodeGenerationParallelAssignments instance.
66    ///
67    /// # Panics
68    /// Panics if any target variable or read variable index is >= num_variables.
69    pub fn new(num_variables: usize, assignments: Vec<(usize, Vec<usize>)>) -> Self {
70        for (i, (target, reads)) in assignments.iter().enumerate() {
71            assert!(
72                *target < num_variables,
73                "assignment {i}: target variable {target} >= num_variables {num_variables}"
74            );
75            for &r in reads {
76                assert!(
77                    r < num_variables,
78                    "assignment {i}: read variable {r} >= num_variables {num_variables}"
79                );
80            }
81        }
82        Self {
83            num_variables,
84            assignments,
85        }
86    }
87
88    /// Returns the number of variables.
89    pub fn num_variables(&self) -> usize {
90        self.num_variables
91    }
92
93    /// Returns the number of assignments.
94    pub fn num_assignments(&self) -> usize {
95        self.assignments.len()
96    }
97
98    /// Returns the assignments.
99    pub fn assignments(&self) -> &[(usize, Vec<usize>)] {
100        &self.assignments
101    }
102}
103
104impl Problem for MinimumCodeGenerationParallelAssignments {
105    const NAME: &'static str = "MinimumCodeGenerationParallelAssignments";
106    type Solution = Vec<usize>;
107    type Value = Min<i64>;
108
109    crate::problem_parameters![
110        ("num_variables", num_variables),
111        ("num_assignments", num_assignments),
112    ];
113
114    fn variant() -> Vec<(&'static str, &'static str)> {
115        crate::variant_params![]
116    }
117
118    fn evaluate(
119        &self,
120        config: &Self::Solution,
121    ) -> Result<Min<i64>, crate::traits::EvaluationError> {
122        Ok({
123            let m = self.num_assignments();
124
125            // Validate config length
126            if config.len() != m {
127                return Err(crate::traits::EvaluationError::InvalidConfiguration(
128                    "assignment length does not match the internal nodes".into(),
129                ));
130            }
131
132            if config.iter().any(|&position| position >= m) {
133                return Err(crate::traits::EvaluationError::InvalidConfiguration(
134                    "assignment contains an out-of-range execution position".into(),
135                ));
136            }
137
138            // Validate permutation: all values must be distinct and in 0..m
139            let mut seen = vec![false; m];
140            for &pos in config {
141                if seen[pos] {
142                    return Ok(Min(None));
143                }
144                seen[pos] = true;
145            }
146
147            // config[i] = position of assignment i in execution order
148            // Build execution order: order[pos] = assignment index
149            let mut order = vec![0usize; m];
150            for (assignment_idx, &pos) in config.iter().enumerate() {
151                order[pos] = assignment_idx;
152            }
153
154            // Count backward dependencies: for each pair (i, j) where i < j
155            // (i executes before j), check if the target variable of order[i]
156            // is in the read set of order[j]
157            let mut count = 0usize;
158            for (i, &earlier) in order.iter().enumerate() {
159                let (target_var, _) = &self.assignments[earlier];
160                for &later in &order[(i + 1)..] {
161                    let (_, read_vars) = &self.assignments[later];
162                    if read_vars.contains(target_var) {
163                        count += 1;
164                    }
165                }
166            }
167
168            Min(Some(i64::try_from(count).map_err(|_| {
169                crate::traits::EvaluationError::IntegerOverflow(
170                    "converting parallel instruction count to i64".into(),
171                )
172            })?))
173        })
174    }
175}
176
177impl crate::solvers::BruteForceProblem for MinimumCodeGenerationParallelAssignments {
178    fn dimensions(&self) -> Vec<usize> {
179        let m = self.num_assignments();
180        vec![m; m]
181    }
182}
183
184crate::declare_variants! {
185    default MinimumCodeGenerationParallelAssignments => "2^num_assignments",
186}
187
188crate::register_brute_force! {
189    MinimumCodeGenerationParallelAssignments,
190}
191
192#[cfg(feature = "example-db")]
193pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
194    // 4 variables, 4 assignments:
195    // A_0: a <- op(b, c) -> (0, [1, 2])
196    // A_1: b <- op(a)    -> (1, [0])
197    // A_2: c <- op(d)    -> (2, [3])
198    // A_3: d <- op(b, c) -> (3, [1, 2])
199    //
200    // Optimal ordering: config [0, 3, 1, 2] means
201    // A_0 at position 0, A_1 at position 3, A_2 at position 1, A_3 at position 2
202    // Order: (A_0, A_2, A_3, A_1)
203    // Backward deps: A_0 writes a, A_1 reads a (later) -> 1
204    //                A_2 writes c, A_3 reads c (later) -> 1
205    //                Total: 2
206    let assignments = vec![(0, vec![1, 2]), (1, vec![0]), (2, vec![3]), (3, vec![1, 2])];
207    vec![crate::example_db::specs::ModelExampleSpec {
208        id: "minimum_code_generation_parallel_assignments",
209        instance: Box::new(MinimumCodeGenerationParallelAssignments::new(
210            4,
211            assignments,
212        )),
213        optimal_config: serde_json::json!(vec![0, 3, 1, 2]),
214        optimal_value: serde_json::json!(2),
215    }]
216}
217
218#[cfg(test)]
219#[path = "../../unit_tests/models/misc/minimum_code_generation_parallel_assignments.rs"]
220mod tests;