problemreductions/models/misc/
cyclic_ordering.rs1use 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 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 pub fn num_elements(&self) -> usize {
90 self.num_elements
91 }
92
93 pub fn num_triples(&self) -> usize {
95 self.triples.len()
96 }
97
98 pub fn triples(&self) -> &[(usize, usize, usize)] {
100 &self.triples
101 }
102
103 fn is_valid_solution(&self, config: &[usize]) -> bool {
106 if config.len() != self.num_elements {
107 return false;
108 }
109
110 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 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#[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;