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