1use crate::registry::{CreateSpec, ProblemSchemaEntry, VariantDimension};
7use crate::topology::{Graph, SimpleGraph};
8use crate::traits::Problem;
9use crate::types::{Max, WeightElement};
10use num_traits::Zero;
11use serde::{Deserialize, Serialize};
12use std::collections::VecDeque;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "LongestCircuit",
17 display_name: "Longest Circuit",
18 aliases: &[],
19 dimensions: &[
20 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21 VariantDimension::new("weight", "i64", &["i64"]),
22 ],
23 category: crate::registry::ProblemCategory::Graph,
24 module_path: module_path!(),
25 description: "Find a simple circuit in a graph that maximizes total edge length",
26 fields: LongestCircuitCreateSpec::FIELDS,
27 }
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
44#[serde(try_from = "LongestCircuitData<G, W>")]
45#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
46pub struct LongestCircuit<G, W: WeightElement> {
47 graph: G,
48 edge_lengths: Vec<W>,
49}
50
51#[derive(Deserialize)]
52#[serde(bound(deserialize = "G: Graph + Deserialize<'de>, W: WeightElement + Deserialize<'de>"))]
53struct LongestCircuitData<G, W: WeightElement> {
54 graph: G,
55 edge_lengths: Vec<W>,
56}
57
58impl<G, W> TryFrom<LongestCircuitData<G, W>> for LongestCircuit<G, W>
59where
60 G: Graph,
61 W: WeightElement,
62{
63 type Error = crate::registry::ConstructionError;
64
65 fn try_from(data: LongestCircuitData<G, W>) -> Result<Self, Self::Error> {
66 Self::try_new(data.graph, data.edge_lengths)
67 }
68}
69
70#[derive(Debug, Deserialize, crate::CreateSpec)]
71struct LongestCircuitCreateSpec {
72 #[create(codec = "edge-list")]
73 graph: Vec<(usize, usize)>,
74 num_vertices: Option<usize>,
75 #[create(codec = "comma-separated")]
76 edge_weights: Option<Vec<i64>>,
77}
78
79impl TryFrom<LongestCircuitCreateSpec> for LongestCircuit<SimpleGraph, i64> {
80 type Error = crate::registry::ConstructionError;
81
82 fn try_from(spec: LongestCircuitCreateSpec) -> Result<Self, Self::Error> {
83 let graph = simple_graph_from_create(spec.graph, spec.num_vertices)?;
84 let edge_lengths = spec
85 .edge_weights
86 .unwrap_or_else(|| vec![1; graph.num_edges()]);
87 Self::try_new(graph, edge_lengths)
88 }
89}
90
91fn simple_graph_from_create(
92 edges: Vec<(usize, usize)>,
93 num_vertices: Option<usize>,
94) -> Result<SimpleGraph, crate::registry::ConstructionError> {
95 if edges.is_empty() && num_vertices.is_none() {
96 return Err("num_vertices is required for an empty graph"
97 .to_string()
98 .into());
99 }
100 for (index, &(u, v)) in edges.iter().enumerate() {
101 if u == v {
102 return Err(format!("graph edge {index} is a self-loop at vertex {u}").into());
103 }
104 }
105 let inferred = edges
106 .iter()
107 .flat_map(|&(u, v)| [u, v])
108 .max()
109 .map(|vertex| vertex.checked_add(1).ok_or("vertex count overflows usize"))
110 .transpose()?
111 .unwrap_or(0);
112 let num_vertices = num_vertices.unwrap_or(inferred);
113 if num_vertices < inferred {
114 return Err(format!(
115 "num_vertices {num_vertices} is too small for graph endpoints; need at least {inferred}"
116 )
117 .into());
118 }
119 Ok(SimpleGraph::new(num_vertices, edges))
120}
121
122impl<G: Graph, W: WeightElement> LongestCircuit<G, W> {
123 pub fn new(graph: G, edge_lengths: Vec<W>) -> Self {
130 Self::try_new(graph, edge_lengths).unwrap_or_else(|error| panic!("{error}"))
131 }
132
133 fn try_new(graph: G, edge_lengths: Vec<W>) -> Result<Self, crate::registry::ConstructionError> {
134 Self::check_weights(&graph, &edge_lengths)?;
135 Ok(Self {
136 graph,
137 edge_lengths,
138 })
139 }
140
141 pub fn graph(&self) -> &G {
143 &self.graph
144 }
145
146 pub fn edge_lengths(&self) -> &[W] {
148 &self.edge_lengths
149 }
150
151 pub fn set_lengths(&mut self, edge_lengths: Vec<W>) {
153 Self::check_weights(&self.graph, &edge_lengths).unwrap_or_else(|error| panic!("{error}"));
154 self.edge_lengths = edge_lengths;
155 }
156
157 fn check_weights(graph: &G, weights: &[W]) -> Result<(), crate::registry::ConstructionError> {
158 if weights.len() != graph.num_edges() {
159 return Err(crate::registry::ConstructionError::length_mismatch(
160 "weights",
161 weights.len(),
162 graph.num_edges(),
163 ));
164 }
165 if !weights
166 .iter()
167 .all(|weight| weight.to_sum() > W::Sum::zero())
168 {
169 return Err("All edge lengths must be positive (> 0)".into());
170 }
171 Ok(())
172 }
173
174 pub fn set_weights(&mut self, weights: Vec<W>) {
176 self.set_lengths(weights);
177 }
178
179 pub fn weights(&self) -> Vec<W> {
181 self.edge_lengths.clone()
182 }
183
184 pub fn max_length_bits(&self) -> u64 {
186 crate::types::max_numeric_magnitude_bits(
187 self.edge_lengths.iter().map(|length| length.to_sum()),
188 )
189 }
190
191 pub fn num_vertices(&self) -> usize {
193 self.graph.num_vertices()
194 }
195
196 pub fn num_edges(&self) -> usize {
198 self.graph.num_edges()
199 }
200
201 pub fn is_weighted(&self) -> bool {
203 !W::IS_UNIT
204 }
205
206 pub fn is_valid_solution(&self, config: &[bool]) -> bool {
208 is_simple_circuit(&self.graph, config)
209 }
210}
211
212impl<G, W> Problem for LongestCircuit<G, W>
213where
214 G: Graph + crate::variant::VariantParam,
215 W: WeightElement + crate::variant::VariantParam,
216{
217 const NAME: &'static str = "LongestCircuit";
218 type Solution = Vec<bool>;
219 type Value = Max<W::Sum>;
220
221 crate::problem_parameters![
222 ("max_length_bits", max_length_bits),
223 ("num_edges", num_edges),
224 ("num_vertices", num_vertices),
225 ];
226
227 fn variant() -> Vec<(&'static str, &'static str)> {
228 crate::variant_params![G, W]
229 }
230
231 fn evaluate(
232 &self,
233 config: &Self::Solution,
234 ) -> Result<Max<W::Sum>, crate::traits::EvaluationError> {
235 if config.len() != self.graph.num_edges() {
236 return Err(crate::traits::EvaluationError::InvalidConfiguration(
237 "edge-selection length does not match the graph".into(),
238 ));
239 }
240 Ok({
241 if !is_simple_circuit(&self.graph, config) {
242 return Ok(Max(None));
243 }
244 let mut total = W::Sum::zero();
245 for (idx, &selected) in config.iter().enumerate() {
246 if selected {
247 total = W::checked_add_to_sum(
248 total,
249 self.edge_lengths[idx].to_sum(),
250 "summing circuit edge lengths",
251 )?;
252 }
253 }
254 Max(Some(total))
255 })
256 }
257}
258
259impl<G, W> crate::solvers::BruteForceProblem for LongestCircuit<G, W>
260where
261 G: Graph + crate::variant::VariantParam,
262 W: WeightElement + crate::variant::VariantParam,
263{
264 fn dimensions(&self) -> Vec<usize> {
265 vec![2; self.graph.num_edges()]
266 }
267}
268
269pub(crate) fn is_simple_circuit<G: Graph>(graph: &G, config: &[bool]) -> bool {
271 if config.len() != graph.num_edges() {
272 return false;
273 }
274
275 let edges = graph.edges();
276 let n = graph.num_vertices();
277 let mut degree = vec![0usize; n];
278 let mut adjacency = vec![Vec::new(); n];
279 let mut selected_count = 0usize;
280 let mut start = None;
281
282 for (idx, &selected) in config.iter().enumerate() {
283 if !selected {
284 continue;
285 }
286 let (u, v) = edges[idx];
287 degree[u] += 1;
288 degree[v] += 1;
289 adjacency[u].push(v);
290 adjacency[v].push(u);
291 selected_count += 1;
292 if start.is_none() {
293 start = Some(u);
294 }
295 }
296
297 if selected_count < 3 {
298 return false;
299 }
300
301 let selected_vertices: Vec<usize> = degree
302 .iter()
303 .enumerate()
304 .filter_map(|(vertex, °)| (deg > 0).then_some(vertex))
305 .collect();
306
307 if selected_vertices.is_empty() || selected_vertices.iter().any(|&vertex| degree[vertex] != 2) {
308 return false;
309 }
310
311 let start = match start {
312 Some(vertex) => vertex,
313 None => return false,
314 };
315
316 let mut visited = vec![false; n];
317 let mut queue = VecDeque::new();
318 visited[start] = true;
319 queue.push_back(start);
320 let mut visited_selected_vertices = 0usize;
321
322 while let Some(vertex) = queue.pop_front() {
323 visited_selected_vertices += 1;
324 for &neighbor in &adjacency[vertex] {
325 if !visited[neighbor] {
326 visited[neighbor] = true;
327 queue.push_back(neighbor);
328 }
329 }
330 }
331
332 visited_selected_vertices == selected_vertices.len()
333}
334
335#[cfg(feature = "example-db")]
336pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
337 vec![crate::example_db::specs::ModelExampleSpec {
338 id: "longest_circuit_simplegraph",
339 instance: Box::new(LongestCircuit::new(
340 SimpleGraph::new(
341 6,
342 vec![
343 (0, 1),
344 (1, 2),
345 (2, 3),
346 (3, 4),
347 (4, 5),
348 (5, 0),
349 (0, 3),
350 (1, 4),
351 (2, 5),
352 (3, 5),
353 ],
354 ),
355 vec![3, 2, 4, 1, 5, 2, 3, 2, 1, 2],
356 )),
357 optimal_config: serde_json::json!(vec![
358 true, false, true, false, true, false, true, true, true, false
359 ]),
360 optimal_value: serde_json::json!(18),
361 }]
362}
363
364crate::impl_random_generate!(LongestCircuit<SimpleGraph, i64>, crate::random::SimpleGraphRandomSpec, |spec| {
365 let graph = spec.graph()?;
366 let lengths = vec![1; graph.num_edges()];
367 Ok(LongestCircuit::new(graph, lengths))
368});
369
370crate::declare_variants! {
371 default LongestCircuit<SimpleGraph, i64> => "2^num_vertices * num_vertices^2" create LongestCircuitCreateSpec random,
372}
373
374crate::register_brute_force! {
375 LongestCircuit<SimpleGraph, i64> decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
376}
377
378#[cfg(test)]
379#[path = "../../unit_tests/models/graph/longest_circuit.rs"]
380mod tests;
381
382crate::decision_problem_meta!(LongestCircuit<SimpleGraph, i64>, "DecisionLongestCircuit");
383crate::register_decision_variant!(
384 LongestCircuit<SimpleGraph, i64>, "DecisionLongestCircuit", "2^num_vertices * num_vertices^2", &[],
385 "Does a feasible solution have objective value >= the bound?",
386 category: crate::registry::ProblemCategory::Graph,
387 dims: [
388 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
389 VariantDimension::new("weight", "i64", &["i64"]),
390 ],
391 fields: [
392 crate::registry::FieldInfo { name: "graph", type_name: "Vec<(usize,usize)>", description: "Graph edges as comma-separated vertex pairs." },
393 crate::registry::FieldInfo { name: "num_vertices", type_name: "usize", description: "Number of vertices, including isolated vertices." },
394 crate::registry::FieldInfo { name: "edge_weights", type_name: "Vec<i64>", description: "Weights for each edge in graph order." },
395 crate::registry::FieldInfo { name: "bound", type_name: "i64", description: "Accept objective values >= this bound" },
396 ],
397 decode: |_, indices: Vec<usize>| crate::config::config_to_bits(&indices)
398);
399
400#[cfg(feature = "example-db")]
401pub(crate) fn decision_canonical_rule_example_specs(
402) -> Vec<crate::example_db::specs::RuleExampleSpec> {
403 vec![crate::example_db::specs::RuleExampleSpec {
404 id: "decision_longest_circuit_to_longest_circuit",
405 build: || {
406 let source = crate::models::decision::Decision::new(
407 LongestCircuit::new(
408 SimpleGraph::new(
409 6,
410 vec![
411 (0, 1),
412 (1, 2),
413 (2, 3),
414 (3, 4),
415 (4, 5),
416 (5, 0),
417 (0, 3),
418 (1, 4),
419 (2, 5),
420 (3, 5),
421 ],
422 ),
423 vec![3, 2, 4, 1, 5, 2, 3, 2, 1, 2],
424 ),
425 18,
426 );
427 let witness = serde_json::json!(vec![
428 true, false, true, false, true, false, true, true, true, false
429 ]);
430 crate::example_db::specs::rule_example_with_witness::<_, LongestCircuit<SimpleGraph, i64>>(
431 source,
432 crate::export::SolutionPair {
433 source_config: witness.clone(),
434 target_config: witness,
435 },
436 )
437 },
438 }]
439}