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