problemreductions/models/misc/
cyclic_ordering.rs1use 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 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 pub fn num_elements(&self) -> usize {
86 self.num_elements
87 }
88
89 pub fn num_triples(&self) -> usize {
91 self.triples.len()
92 }
93
94 pub fn triples(&self) -> &[(usize, usize, usize)] {
96 &self.triples
97 }
98
99 fn is_valid_solution(&self, config: &[usize]) -> bool {
102 if config.len() != self.num_elements {
103 return false;
104 }
105
106 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 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#[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;