Skip to main content

problemreductions/models/misc/
feasible_register_assignment.rs

1//! Feasible Register Assignment problem implementation.
2//!
3//! Given a directed acyclic graph G = (V, A), K registers, and a fixed
4//! register assignment f: V → {0, ..., K-1}, determine whether there
5//! exists a topological ordering of the vertices such that no register
6//! conflict arises during execution. NP-complete [Bouchez et al., 2006].
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Deserializer, Serialize};
11
12inventory::submit! {
13    ProblemSchemaEntry {
14        name: "FeasibleRegisterAssignment",
15        display_name: "Feasible Register Assignment",
16        aliases: &[],
17        dimensions: &[],
18        category: crate::registry::ProblemCategory::Misc,
19        module_path: module_path!(),
20        description: "Determine whether a DAG computation can be scheduled without register conflicts under a fixed assignment",
21        fields: &[
22            FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices n = |V|" },
23            FieldInfo { name: "arcs", type_name: "Vec<(usize, usize)>", description: "Directed arcs (v, u) meaning v depends on u" },
24            FieldInfo { name: "num_registers", type_name: "usize", description: "Number of registers K" },
25            FieldInfo { name: "assignment", type_name: "Vec<usize>", description: "Register assignment f(v) for each vertex" },
26        ],
27    }
28}
29
30/// The Feasible Register Assignment problem.
31///
32/// Given a directed acyclic graph G = (V, A) where arcs represent data
33/// dependencies, K registers, and an assignment f: V → {0, ..., K-1},
34/// determine whether there exists a topological evaluation ordering such
35/// that no two simultaneously live values share the same register.
36///
37/// # Representation
38///
39/// An arc `(v, u)` means vertex `v` depends on vertex `u` (i.e., `u` must
40/// be computed before `v`). Each variable represents a vertex, with domain
41/// `{0, ..., n-1}` giving its evaluation position (the config must be a
42/// valid permutation).
43///
44/// # Example
45///
46/// ```
47/// use problemreductions::models::misc::FeasibleRegisterAssignment;
48/// use problemreductions::{Problem, BruteForce};
49///
50/// // 4 vertices: v0 depends on v1 and v2, v1 depends on v3
51/// let problem = FeasibleRegisterAssignment::new(
52///     4,
53///     vec![(0, 1), (0, 2), (1, 3)],
54///     2,
55///     vec![0, 1, 0, 0],
56/// );
57/// let solver = BruteForce::new();
58/// let solution = solver.solve(&problem).unwrap();
59/// assert!(solution.is_some());
60/// ```
61#[derive(Debug, Clone, Serialize)]
62pub struct FeasibleRegisterAssignment {
63    /// Number of vertices.
64    num_vertices: usize,
65    /// Directed arcs (v, u) meaning v depends on u.
66    arcs: Vec<(usize, usize)>,
67    /// Number of registers K.
68    num_registers: usize,
69    /// Register assignment f(v) for each vertex.
70    assignment: Vec<usize>,
71    /// Precomputed: dependencies[v] = vertices that v depends on.
72    #[serde(skip)]
73    dependencies: Vec<Vec<usize>>,
74    /// Precomputed: dependents[u] = vertices that depend on u.
75    #[serde(skip)]
76    dependents: Vec<Vec<usize>>,
77}
78
79#[derive(Deserialize)]
80struct FeasibleRegisterAssignmentData {
81    num_vertices: usize,
82    arcs: Vec<(usize, usize)>,
83    num_registers: usize,
84    assignment: Vec<usize>,
85}
86
87impl<'de> Deserialize<'de> for FeasibleRegisterAssignment {
88    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
89    where
90        D: Deserializer<'de>,
91    {
92        let data = FeasibleRegisterAssignmentData::deserialize(deserializer)?;
93        Self::try_new(
94            data.num_vertices,
95            data.arcs,
96            data.num_registers,
97            data.assignment,
98        )
99        .map_err(serde::de::Error::custom)
100    }
101}
102
103impl FeasibleRegisterAssignment {
104    /// Create a new Feasible Register Assignment instance.
105    ///
106    /// # Panics
107    ///
108    /// Panics if any arc index is out of bounds (>= num_vertices),
109    /// if any arc is a self-loop, if the assignment length does not
110    /// match num_vertices, or if any assignment value >= num_registers.
111    pub fn new(
112        num_vertices: usize,
113        arcs: Vec<(usize, usize)>,
114        num_registers: usize,
115        assignment: Vec<usize>,
116    ) -> Self {
117        Self::try_new(num_vertices, arcs, num_registers, assignment)
118            .unwrap_or_else(|error| panic!("{error}"))
119    }
120
121    fn try_new(
122        num_vertices: usize,
123        arcs: Vec<(usize, usize)>,
124        num_registers: usize,
125        assignment: Vec<usize>,
126    ) -> Result<Self, crate::registry::ConstructionError> {
127        for &(v, u) in &arcs {
128            if v >= num_vertices || u >= num_vertices {
129                return Err(format!(
130                    "Arc ({}, {}) out of bounds for {} vertices",
131                    v, u, num_vertices
132                )
133                .into());
134            }
135            if v == u {
136                return Err(format!("Self-loop ({}, {}) not allowed in a DAG", v, u).into());
137            }
138        }
139        if assignment.len() != num_vertices {
140            return Err(format!(
141                "Assignment length {} does not match num_vertices {}",
142                assignment.len(),
143                num_vertices
144            )
145            .into());
146        }
147        if num_vertices > 0 && num_registers == 0 {
148            return Err("num_registers must be positive when there are vertices".into());
149        }
150        for (v, &r) in assignment.iter().enumerate() {
151            if r >= num_registers {
152                return Err(format!(
153                    "Assignment[{}] = {} is out of bounds for {} registers",
154                    v, r, num_registers
155                )
156                .into());
157            }
158        }
159        let (dependencies, dependents) = Self::build_adjacency(num_vertices, &arcs);
160        Ok(Self {
161            num_vertices,
162            arcs,
163            num_registers,
164            assignment,
165            dependencies,
166            dependents,
167        })
168    }
169
170    /// Build dependency and dependent adjacency lists from arcs.
171    fn build_adjacency(
172        num_vertices: usize,
173        arcs: &[(usize, usize)],
174    ) -> (Vec<Vec<usize>>, Vec<Vec<usize>>) {
175        let mut dependencies = vec![vec![]; num_vertices];
176        let mut dependents = vec![vec![]; num_vertices];
177        for &(v, u) in arcs {
178            dependencies[v].push(u);
179            dependents[u].push(v);
180        }
181        (dependencies, dependents)
182    }
183
184    /// Get the number of vertices.
185    pub fn num_vertices(&self) -> usize {
186        self.num_vertices
187    }
188
189    /// Get the number of arcs.
190    pub fn num_arcs(&self) -> usize {
191        self.arcs.len()
192    }
193
194    /// Get the number of registers.
195    pub fn num_registers(&self) -> usize {
196        self.num_registers
197    }
198
199    /// Count unordered vertex pairs that share a register.
200    pub fn num_same_register_pairs(&self) -> usize {
201        let mut counts = vec![0usize; self.num_registers];
202        for &register in &self.assignment {
203            counts[register] += 1;
204        }
205        counts
206            .into_iter()
207            .map(|count| count.saturating_sub(1) * count / 2)
208            .sum()
209    }
210
211    /// Get the arcs.
212    pub fn arcs(&self) -> &[(usize, usize)] {
213        &self.arcs
214    }
215
216    /// Get the register assignment.
217    pub fn assignment(&self) -> &[usize] {
218        &self.assignment
219    }
220
221    /// Check whether the given config (position assignment) is feasible.
222    ///
223    /// Returns `true` if the config is a valid permutation, respects
224    /// topological ordering, and has no register conflicts.
225    pub fn is_feasible(&self, config: &[usize]) -> bool {
226        let n = self.num_vertices;
227        if config.len() != n {
228            return false;
229        }
230
231        // Check valid permutation: each position 0..n-1 used exactly once
232        let mut order = vec![0usize; n]; // order[position] = vertex
233        let mut used = vec![false; n];
234        for (vertex, &position) in config.iter().enumerate() {
235            if position >= n {
236                return false;
237            }
238            if used[position] {
239                return false;
240            }
241            used[position] = true;
242            order[position] = vertex;
243        }
244
245        // Check topological ordering and register conflicts
246        let mut computed = vec![false; n];
247
248        for step in 0..n {
249            let vertex = order[step];
250
251            // Check dependencies: all dependencies must have been computed
252            for &dep in &self.dependencies[vertex] {
253                if !computed[dep] {
254                    return false;
255                }
256            }
257
258            // Check register conflict: the register assigned to this vertex
259            // must not be currently occupied by a live value.
260            let reg = self.assignment[vertex];
261            for &w in &order[..step] {
262                if self.assignment[w] == reg {
263                    let still_live = self.dependents[w]
264                        .iter()
265                        .any(|&d| d != vertex && !computed[d]);
266                    if still_live {
267                        return false;
268                    }
269                }
270            }
271
272            computed[vertex] = true;
273        }
274
275        true
276    }
277}
278
279impl Problem for FeasibleRegisterAssignment {
280    const NAME: &'static str = "FeasibleRegisterAssignment";
281    type Solution = Vec<usize>;
282    type Value = crate::types::Or;
283
284    crate::problem_parameters![
285        ("num_arcs", num_arcs),
286        ("num_registers", num_registers),
287        ("num_same_register_pairs", num_same_register_pairs),
288        ("num_vertices", num_vertices),
289    ];
290
291    fn variant() -> Vec<(&'static str, &'static str)> {
292        crate::variant_params![]
293    }
294
295    fn evaluate(
296        &self,
297        config: &Self::Solution,
298    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
299        if config.len() != self.num_vertices {
300            return Err(crate::traits::EvaluationError::InvalidConfiguration(
301                "ordering length does not match the graph vertices".into(),
302            ));
303        }
304        if config.iter().any(|&position| position >= self.num_vertices) {
305            return Err(crate::traits::EvaluationError::InvalidConfiguration(
306                "ordering contains an out-of-range position".into(),
307            ));
308        }
309        Ok(crate::types::Or(self.is_feasible(config)))
310    }
311}
312
313impl crate::solvers::BruteForceProblem for FeasibleRegisterAssignment {
314    fn dimensions(&self) -> Vec<usize> {
315        vec![self.num_vertices; self.num_vertices]
316    }
317}
318
319crate::declare_variants! {
320    default FeasibleRegisterAssignment => "factorial(num_vertices)",
321}
322
323crate::register_brute_force! {
324    FeasibleRegisterAssignment,
325}
326
327#[cfg(feature = "example-db")]
328pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
329    vec![crate::example_db::specs::ModelExampleSpec {
330        id: "feasible_register_assignment",
331        // 4 vertices, arcs: (0,1),(0,2),(1,3), K=2, assignment [0,1,0,0]
332        // Valid order: v3, v1, v2, v0 -> config [3, 1, 2, 0]
333        instance: Box::new(FeasibleRegisterAssignment::new(
334            4,
335            vec![(0, 1), (0, 2), (1, 3)],
336            2,
337            vec![0, 1, 0, 0],
338        )),
339        // config[v] = position: v0 at pos 3, v1 at pos 1, v2 at pos 2, v3 at pos 0
340        // Order: v3(pos0), v1(pos1), v2(pos2), v0(pos3)
341        optimal_config: serde_json::json!(vec![3, 1, 2, 0]),
342        optimal_value: serde_json::json!(true),
343    }]
344}
345
346#[cfg(test)]
347#[path = "../../unit_tests/models/misc/feasible_register_assignment.rs"]
348mod tests;