Skip to main content

problemreductions/models/misc/
minimum_register_sufficiency_for_loops.rs

1//! Minimum Register Sufficiency for Loops problem implementation.
2//!
3//! Given a loop of length N and a set of variables, each active during a
4//! contiguous circular arc of timesteps, assign registers to variables
5//! minimizing the number of distinct registers used, such that no two
6//! conflicting (overlapping) variables share the same register.
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: "MinimumRegisterSufficiencyForLoops",
16        display_name: "Minimum Register Sufficiency for Loops",
17        aliases: &[],
18        dimensions: &[],
19        module_path: module_path!(),
20        description: "Assign registers to loop variables minimizing register count, no two conflicting variables share a register",
21        fields: &[
22            FieldInfo { name: "loop_length", type_name: "usize", description: "Loop length N (number of timesteps)" },
23            FieldInfo { name: "variables", type_name: "Vec<(usize, usize)>", description: "Variables as (start_time, duration) circular arcs" },
24        ],
25    }
26}
27
28/// The Minimum Register Sufficiency for Loops problem.
29///
30/// Given a loop of length N (representing N timesteps arranged in a circle)
31/// and a set of variables, each active during a contiguous circular arc of
32/// timesteps specified by (start_time, duration), assign a register index
33/// to each variable such that:
34/// - No two variables with overlapping circular arcs share the same register
35/// - The number of distinct registers used is minimized
36///
37/// This is equivalent to the circular arc graph coloring problem, where each
38/// variable corresponds to a circular arc and registers correspond to colors.
39///
40/// # Representation
41///
42/// Each variable is assigned a register index from `{0, ..., n-1}` where n is
43/// the number of variables. The configuration `config[i]` gives the register
44/// assigned to variable i.
45///
46/// # Example
47///
48/// ```
49/// use problemreductions::models::misc::MinimumRegisterSufficiencyForLoops;
50/// use problemreductions::{Problem, Solver, BruteForce, Min};
51///
52/// // 3 variables on a loop of length 6, all pairs conflict
53/// let problem = MinimumRegisterSufficiencyForLoops::new(
54///     6,
55///     vec![(0, 3), (2, 3), (4, 3)],
56/// );
57/// let solver = BruteForce::new();
58/// let solution = solver.find_witness(&problem);
59/// assert!(solution.is_some());
60/// let val = problem.evaluate(&solution.unwrap());
61/// assert_eq!(val, Min(Some(3)));
62/// ```
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct MinimumRegisterSufficiencyForLoops {
65    /// Loop length N (number of timesteps in the circular loop).
66    loop_length: usize,
67    /// Variables as (start_time, duration) pairs representing circular arcs.
68    variables: Vec<(usize, usize)>,
69}
70
71impl MinimumRegisterSufficiencyForLoops {
72    /// Create a new Minimum Register Sufficiency for Loops instance.
73    ///
74    /// # Panics
75    ///
76    /// Panics if `loop_length` is zero, if any duration is zero or exceeds
77    /// `loop_length`, or if any `start_time >= loop_length`.
78    pub fn new(loop_length: usize, variables: Vec<(usize, usize)>) -> Self {
79        assert!(loop_length > 0, "loop_length must be positive");
80        for (i, &(start, dur)) in variables.iter().enumerate() {
81            assert!(
82                start < loop_length,
83                "Variable {} start_time {} >= loop_length {}",
84                i,
85                start,
86                loop_length
87            );
88            assert!(
89                dur > 0 && dur <= loop_length,
90                "Variable {} duration {} must be in [1, {}]",
91                i,
92                dur,
93                loop_length
94            );
95        }
96        Self {
97            loop_length,
98            variables,
99        }
100    }
101
102    /// Get the loop length N.
103    pub fn loop_length(&self) -> usize {
104        self.loop_length
105    }
106
107    /// Get the number of variables.
108    pub fn num_variables(&self) -> usize {
109        self.variables.len()
110    }
111
112    /// Get the variables as (start_time, duration) pairs.
113    pub fn variables(&self) -> &[(usize, usize)] {
114        &self.variables
115    }
116
117    /// Check if two circular arcs overlap.
118    ///
119    /// Arc [s, s+l) mod N covers timesteps {s, s+1, ..., s+l-1} mod N.
120    /// Two arcs overlap iff their covered timestep sets intersect.
121    fn arcs_overlap(s1: usize, l1: usize, s2: usize, l2: usize, n: usize) -> bool {
122        // Use the modular distance check:
123        // Timestep t is in arc [s, s+l) mod N iff (t - s) mod N < l
124        // Two arcs are disjoint iff arc2 fits entirely in the gap of arc1
125        // or arc1 fits entirely in the gap of arc2.
126        // Gap of arc [s, s+l) is [(s+l) mod N, s) with length N-l.
127
128        // If either arc covers the entire loop, they always overlap (if both non-empty)
129        if l1 == n || l2 == n {
130            return true;
131        }
132
133        // Check if arc2 is entirely in the gap of arc1.
134        // Gap of arc1 starts at (s1+l1) % n and has length n-l1.
135        // Arc2 fits in this gap if the "gap distance" of s2 from gap_start
136        // plus l2 <= n - l1.
137        let gap1_start = (s1 + l1) % n;
138        let dist_s2_in_gap1 = (s2 + n - gap1_start) % n;
139        if dist_s2_in_gap1 + l2 <= n - l1 {
140            return false;
141        }
142
143        // Check if arc1 is entirely in the gap of arc2.
144        let gap2_start = (s2 + l2) % n;
145        let dist_s1_in_gap2 = (s1 + n - gap2_start) % n;
146        if dist_s1_in_gap2 + l1 <= n - l2 {
147            return false;
148        }
149
150        true
151    }
152}
153
154impl Problem for MinimumRegisterSufficiencyForLoops {
155    const NAME: &'static str = "MinimumRegisterSufficiencyForLoops";
156    type Value = Min<usize>;
157
158    fn variant() -> Vec<(&'static str, &'static str)> {
159        crate::variant_params![]
160    }
161
162    fn dims(&self) -> Vec<usize> {
163        let n = self.variables.len();
164        vec![n; n]
165    }
166
167    fn evaluate(&self, config: &[usize]) -> Min<usize> {
168        let n = self.variables.len();
169        if config.len() != n {
170            return Min(None);
171        }
172        // Check all register indices are in valid range
173        if config.iter().any(|&r| r >= n) {
174            return Min(None);
175        }
176
177        // Check for conflicts: no two overlapping variables share a register
178        for i in 0..n {
179            for j in (i + 1)..n {
180                if config[i] == config[j] {
181                    let (s1, l1) = self.variables[i];
182                    let (s2, l2) = self.variables[j];
183                    if Self::arcs_overlap(s1, l1, s2, l2, self.loop_length) {
184                        return Min(None);
185                    }
186                }
187            }
188        }
189
190        // Count distinct registers used
191        let mut used = vec![false; n];
192        for &r in config {
193            used[r] = true;
194        }
195        let count = used.iter().filter(|&&u| u).count();
196        Min(Some(count))
197    }
198}
199
200crate::declare_variants! {
201    default MinimumRegisterSufficiencyForLoops => "num_variables ^ num_variables",
202}
203
204#[cfg(feature = "example-db")]
205pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
206    vec![crate::example_db::specs::ModelExampleSpec {
207        id: "minimum_register_sufficiency_for_loops",
208        // 3 variables on a loop of length 6, all pairs conflict (K3)
209        // Optimal: 3 registers (chromatic number of K3)
210        instance: Box::new(MinimumRegisterSufficiencyForLoops::new(
211            6,
212            vec![(0, 3), (2, 3), (4, 3)],
213        )),
214        optimal_config: vec![0, 1, 2],
215        optimal_value: serde_json::json!(3),
216    }]
217}
218
219#[cfg(test)]
220#[path = "../../unit_tests/models/misc/minimum_register_sufficiency_for_loops.rs"]
221mod tests;