Skip to main content

problemreductions/models/misc/
cyclic_ordering.rs

1//! Cyclic Ordering problem implementation.
2//!
3//! Given a finite set A and a collection C of ordered triples (a, b, c),
4//! determine whether there exists a permutation f: A → {0, ..., |A|-1}
5//! such that for each (a, b, c) ∈ C, the values f(a), f(b), f(c) appear
6//! in cyclic order — i.e., (f(a) < f(b) < f(c)) ∨ (f(b) < f(c) < f(a))
7//! ∨ (f(c) < f(a) < f(b)).
8
9use crate::registry::{FieldInfo, ProblemSchemaEntry};
10use crate::traits::Problem;
11use crate::types::Or;
12use serde::de::Error as _;
13use serde::{Deserialize, Deserializer, Serialize};
14
15inventory::submit! {
16    ProblemSchemaEntry {
17        name: "CyclicOrdering",
18        display_name: "Cyclic Ordering",
19        aliases: &[],
20        dimensions: &[],
21        category: crate::registry::ProblemCategory::Misc,
22        module_path: module_path!(),
23        description: "Find a permutation satisfying cyclic ordering constraints on triples",
24        fields: &[
25            FieldInfo { name: "num_elements", type_name: "usize", description: "Number of elements in the set A" },
26            FieldInfo { name: "triples", type_name: "Vec<(usize, usize, usize)>", description: "Collection of ordered triples (a, b, c) requiring cyclic order" },
27        ],
28    }
29}
30
31#[derive(Debug, Clone, Serialize)]
32pub struct CyclicOrdering {
33    num_elements: usize,
34    triples: Vec<(usize, usize, usize)>,
35}
36
37impl CyclicOrdering {
38    fn validate_inputs(
39        num_elements: usize,
40        triples: &[(usize, usize, usize)],
41    ) -> Result<(), crate::registry::ConstructionError> {
42        if num_elements == 0 {
43            return Err("CyclicOrdering requires at least one element"
44                .to_string()
45                .into());
46        }
47        for (i, &(a, b, c)) in triples.iter().enumerate() {
48            if a >= num_elements || b >= num_elements || c >= num_elements {
49                return Err(format!(
50                    "Triple {} has element(s) out of range 0..{}",
51                    i, num_elements
52                )
53                .into());
54            }
55            if a == b || b == c || a == c {
56                return Err(
57                    format!("Triple {} has duplicate elements ({}, {}, {})", i, a, b, c).into(),
58                );
59            }
60        }
61        Ok(())
62    }
63
64    pub fn try_new(
65        num_elements: usize,
66        triples: Vec<(usize, usize, usize)>,
67    ) -> Result<Self, crate::registry::ConstructionError> {
68        Self::validate_inputs(num_elements, &triples)?;
69        Ok(Self {
70            num_elements,
71            triples,
72        })
73    }
74
75    /// Create a new CyclicOrdering instance.
76    ///
77    /// # Panics
78    ///
79    /// Panics if any triple element is out of range or if any triple has duplicate elements.
80    pub fn new(num_elements: usize, triples: Vec<(usize, usize, usize)>) -> Self {
81        Self::try_new(num_elements, triples).unwrap_or_else(|message| panic!("{message}"))
82    }
83
84    /// Number of elements in the set A.
85    pub fn num_elements(&self) -> usize {
86        self.num_elements
87    }
88
89    /// Number of cyclic ordering triples.
90    pub fn num_triples(&self) -> usize {
91        self.triples.len()
92    }
93
94    /// The collection of ordered triples.
95    pub fn triples(&self) -> &[(usize, usize, usize)] {
96        &self.triples
97    }
98
99    /// Check whether a configuration represents a valid permutation and
100    /// satisfies all cyclic ordering constraints.
101    fn is_valid_solution(&self, config: &[usize]) -> bool {
102        if config.len() != self.num_elements {
103            return false;
104        }
105
106        // Check that config is a valid permutation of 0..n
107        let n = self.num_elements;
108        let mut seen = vec![false; n];
109        for &pos in config {
110            if pos >= n || seen[pos] {
111                return false;
112            }
113            seen[pos] = true;
114        }
115
116        // Check cyclic ordering constraints: for each (a, b, c),
117        // (fa < fb < fc) OR (fb < fc < fa) OR (fc < fa < fb)
118        for &(a, b, c) in &self.triples {
119            if !is_cyclic_order(config[a], config[b], config[c]) {
120                return false;
121            }
122        }
123
124        true
125    }
126}
127
128/// Check whether three distinct values appear in cyclic order:
129/// (a < b < c) OR (b < c < a) OR (c < a < b).
130#[allow(clippy::nonminimal_bool)]
131fn is_cyclic_order(a: usize, b: usize, c: usize) -> bool {
132    (a < b && b < c) || (b < c && c < a) || (c < a && a < b)
133}
134
135#[derive(Deserialize)]
136struct CyclicOrderingData {
137    num_elements: usize,
138    triples: Vec<(usize, usize, usize)>,
139}
140
141impl<'de> Deserialize<'de> for CyclicOrdering {
142    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
143    where
144        D: Deserializer<'de>,
145    {
146        let data = CyclicOrderingData::deserialize(deserializer)?;
147        Self::try_new(data.num_elements, data.triples).map_err(D::Error::custom)
148    }
149}
150
151impl Problem for CyclicOrdering {
152    const NAME: &'static str = "CyclicOrdering";
153    type Solution = Vec<usize>;
154    type Value = Or;
155
156    crate::problem_parameters![("num_elements", num_elements), ("num_triples", num_triples),];
157
158    fn variant() -> Vec<(&'static str, &'static str)> {
159        crate::variant_params![]
160    }
161
162    fn evaluate(&self, config: &Self::Solution) -> Result<Or, crate::traits::EvaluationError> {
163        if config.len() != self.num_elements {
164            return Err(crate::traits::EvaluationError::InvalidConfiguration(
165                "ordering length does not match the elements".into(),
166            ));
167        }
168        if config.iter().any(|&position| position >= self.num_elements) {
169            return Err(crate::traits::EvaluationError::InvalidConfiguration(
170                "ordering contains an out-of-range position".into(),
171            ));
172        }
173        Ok(Or(self.is_valid_solution(config)))
174    }
175}
176
177impl crate::solvers::BruteForceProblem for CyclicOrdering {
178    fn dimensions(&self) -> Vec<usize> {
179        vec![self.num_elements; self.num_elements]
180    }
181}
182
183crate::declare_variants! {
184    default CyclicOrdering => "factorial(num_elements)",
185}
186
187crate::register_brute_force! {
188    CyclicOrdering,
189}
190
191#[cfg(feature = "example-db")]
192pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
193    vec![crate::example_db::specs::ModelExampleSpec {
194        id: "cyclic_ordering",
195        instance: Box::new(CyclicOrdering::new(
196            5,
197            vec![(0, 1, 2), (2, 3, 0), (1, 3, 4)],
198        )),
199        optimal_config: serde_json::json!(vec![1, 3, 4, 0, 2]),
200        optimal_value: serde_json::json!(true),
201    }]
202}
203
204#[cfg(test)]
205#[path = "../../unit_tests/models/misc/cyclic_ordering.rs"]
206mod tests;