problemreductions/registry/info.rs
1//! Problem metadata and information types.
2//!
3//! This module provides types for describing problem characteristics:
4//!
5//! - [`ComplexityClass`] - Computational complexity (P, NP-complete, etc.)
6//! - [`ProblemInfo`] - Rich metadata about a problem type
7//! - [`ProblemMetadata`] - Trait for problems to provide their metadata
8//!
9//! # Example
10//!
11//! ```rust
12//! use problemreductions::registry::{ProblemInfo, ComplexityClass};
13//!
14//! let info = ProblemInfo::new("3-SAT", "Satisfiability with 3-literal clauses")
15//! .with_aliases(&["3-CNF-SAT", "3SAT"])
16//! .with_complexity(ComplexityClass::NpComplete)
17//! .with_reduction_from("SAT");
18//!
19//! assert!(info.is_np_complete());
20//! assert_eq!(info.all_names().len(), 3);
21//! ```
22
23use super::ProblemCategory;
24use std::fmt;
25
26/// Computational complexity class of a problem.
27///
28/// Used to classify problems by their computational difficulty.
29/// Most problems in this library are [`NpComplete`](ComplexityClass::NpComplete).
30///
31/// # Example
32///
33/// ```rust
34/// use problemreductions::registry::ComplexityClass;
35///
36/// let class = ComplexityClass::NpComplete;
37/// assert!(class.is_hard());
38/// assert_eq!(class.name(), "NP-complete");
39/// ```
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
41pub enum ComplexityClass {
42 /// In P (polynomial time)
43 P,
44 /// NP-complete
45 NpComplete,
46 /// NP-hard (at least as hard as NP-complete)
47 NpHard,
48 /// PSPACE-complete
49 PspaceComplete,
50 /// Unknown or unclassified
51 Unknown,
52}
53
54impl ComplexityClass {
55 /// Get the complexity class name.
56 pub fn name(&self) -> &'static str {
57 match self {
58 ComplexityClass::P => "P",
59 ComplexityClass::NpComplete => "NP-complete",
60 ComplexityClass::NpHard => "NP-hard",
61 ComplexityClass::PspaceComplete => "PSPACE-complete",
62 ComplexityClass::Unknown => "Unknown",
63 }
64 }
65
66 /// Check if this problem is at least NP-hard.
67 pub fn is_hard(&self) -> bool {
68 matches!(
69 self,
70 ComplexityClass::NpComplete
71 | ComplexityClass::NpHard
72 | ComplexityClass::PspaceComplete
73 )
74 }
75}
76
77impl fmt::Display for ComplexityClass {
78 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79 write!(f, "{}", self.name())
80 }
81}
82
83/// Metadata about a problem type.
84///
85/// Contains static information about a problem definition, including its name,
86/// description, complexity class, and relationships to other problems.
87/// Use the builder methods to construct instances.
88///
89/// # Example
90///
91/// ```rust
92/// use problemreductions::registry::{ProblemInfo, ComplexityClass};
93///
94/// let info = ProblemInfo::new("Vertex Cover", "Find minimum vertices covering all edges")
95/// .with_aliases(&["VC", "Minimum Vertex Cover"])
96/// .with_complexity(ComplexityClass::NpComplete)
97/// .with_reduction_from("Independent Set")
98/// .with_reference("https://en.wikipedia.org/wiki/Vertex_cover");
99///
100/// println!("{}", info); // "Vertex Cover (NP-complete)"
101/// ```
102///
103/// # Builder Pattern
104///
105/// All builder methods are `const fn` and can be used in const contexts:
106///
107/// ```rust
108/// use problemreductions::registry::{ProblemInfo, ComplexityClass};
109///
110/// const MY_PROBLEM_INFO: ProblemInfo = ProblemInfo::new("My Problem", "Description")
111/// .with_complexity(ComplexityClass::NpComplete);
112/// ```
113#[derive(Debug, Clone, PartialEq, Eq)]
114pub struct ProblemInfo {
115 /// The canonical name of the problem.
116 pub name: &'static str,
117 /// Alternative names for the problem.
118 pub aliases: &'static [&'static str],
119 /// A brief description of the problem.
120 pub description: &'static str,
121 /// The computational complexity class.
122 pub complexity_class: ComplexityClass,
123 /// Whether this has a decision version (yes/no answer).
124 pub decision_version: bool,
125 /// Whether this has an optimization version.
126 pub optimization_version: bool,
127 /// The canonical problem this reduces from (for NP-completeness proof).
128 pub canonical_reduction_from: Option<&'static str>,
129 /// Wikipedia or reference URL.
130 pub reference_url: Option<&'static str>,
131}
132
133impl ProblemInfo {
134 /// Create a new ProblemInfo with minimal required fields.
135 pub const fn new(name: &'static str, description: &'static str) -> Self {
136 Self {
137 name,
138 aliases: &[],
139 description,
140 complexity_class: ComplexityClass::NpComplete,
141 decision_version: true,
142 optimization_version: true,
143 canonical_reduction_from: None,
144 reference_url: None,
145 }
146 }
147
148 /// Builder method to add aliases.
149 pub const fn with_aliases(mut self, aliases: &'static [&'static str]) -> Self {
150 self.aliases = aliases;
151 self
152 }
153
154 /// Builder method to set complexity class.
155 pub const fn with_complexity(mut self, class: ComplexityClass) -> Self {
156 self.complexity_class = class;
157 self
158 }
159
160 /// Builder method to set decision version availability.
161 pub const fn with_decision(mut self, has_decision: bool) -> Self {
162 self.decision_version = has_decision;
163 self
164 }
165
166 /// Builder method to set optimization version availability.
167 pub const fn with_optimization(mut self, has_optimization: bool) -> Self {
168 self.optimization_version = has_optimization;
169 self
170 }
171
172 /// Builder method to set the canonical reduction source.
173 pub const fn with_reduction_from(mut self, source: &'static str) -> Self {
174 self.canonical_reduction_from = Some(source);
175 self
176 }
177
178 /// Builder method to set reference URL.
179 pub const fn with_reference(mut self, url: &'static str) -> Self {
180 self.reference_url = Some(url);
181 self
182 }
183
184 /// Check if this problem is NP-complete.
185 pub fn is_np_complete(&self) -> bool {
186 self.complexity_class == ComplexityClass::NpComplete
187 }
188
189 /// Get all names (canonical + aliases).
190 pub fn all_names(&self) -> Vec<&'static str> {
191 let mut names = vec![self.name];
192 names.extend_from_slice(self.aliases);
193 names
194 }
195}
196
197impl fmt::Display for ProblemInfo {
198 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
199 write!(f, "{} ({})", self.name, self.complexity_class)
200 }
201}
202
203/// Trait for problems that provide static metadata.
204///
205/// Implement this trait to enable introspection and discovery for problem types.
206/// The [`GraphProblem`](crate::models::graph::GraphProblem) template automatically
207/// implements this trait based on the constraint type.
208///
209/// # Example
210///
211/// ```rust
212/// use problemreductions::registry::{ProblemMetadata, ProblemInfo, ProblemCategory};
213/// use problemreductions::models::graph::IndependentSetT;
214/// use problemreductions::topology::SimpleGraph;
215///
216/// // Get problem metadata
217/// let info = IndependentSetT::<SimpleGraph, i32>::problem_info();
218/// assert_eq!(info.name, "Independent Set");
219///
220/// let category = IndependentSetT::<SimpleGraph, i32>::category();
221/// assert_eq!(category.path(), "graph/independent");
222/// ```
223///
224/// # Implementing for Custom Problems
225///
226/// ```rust
227/// use problemreductions::registry::{
228/// ProblemMetadata, ProblemInfo, ProblemCategory,
229/// GraphSubcategory, ComplexityClass
230/// };
231///
232/// struct MyProblem;
233///
234/// impl ProblemMetadata for MyProblem {
235/// fn problem_info() -> ProblemInfo {
236/// ProblemInfo::new("My Problem", "Description of my problem")
237/// .with_complexity(ComplexityClass::NpComplete)
238/// }
239///
240/// fn category() -> ProblemCategory {
241/// ProblemCategory::Graph(GraphSubcategory::Independent)
242/// }
243/// }
244/// ```
245pub trait ProblemMetadata {
246 /// Returns the problem info for this problem type.
247 ///
248 /// This includes the problem name, description, aliases, complexity class,
249 /// and known reductions.
250 fn problem_info() -> ProblemInfo;
251
252 /// Returns the problem category.
253 ///
254 /// This is a hierarchical classification like "graph/independent" or
255 /// "satisfiability/sat".
256 fn category() -> ProblemCategory;
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
262
263 #[test]
264 fn test_complexity_class() {
265 assert_eq!(ComplexityClass::NpComplete.name(), "NP-complete");
266 assert!(ComplexityClass::NpComplete.is_hard());
267 assert!(ComplexityClass::NpHard.is_hard());
268 assert!(!ComplexityClass::P.is_hard());
269 }
270
271 #[test]
272 fn test_problem_info_builder() {
273 let info = ProblemInfo::new("Independent Set", "Find a maximum weight independent set")
274 .with_aliases(&["MIS", "MWIS"])
275 .with_complexity(ComplexityClass::NpComplete)
276 .with_reduction_from("3-SAT")
277 .with_reference("https://en.wikipedia.org/wiki/Independent_set_(graph_theory)");
278
279 assert_eq!(info.name, "Independent Set");
280 assert_eq!(info.aliases, &["MIS", "MWIS"]);
281 assert!(info.is_np_complete());
282 assert_eq!(info.canonical_reduction_from, Some("3-SAT"));
283 assert_eq!(info.all_names(), vec!["Independent Set", "MIS", "MWIS"]);
284 }
285
286 #[test]
287 fn test_problem_info_display() {
288 let info = ProblemInfo::new("Vertex Cover", "Find a minimum vertex cover");
289 assert_eq!(format!("{}", info), "Vertex Cover (NP-complete)");
290 }
291
292 #[test]
293 fn test_problem_info_versions() {
294 let decision_only = ProblemInfo::new("Decision Problem", "A yes/no problem")
295 .with_optimization(false);
296 assert!(decision_only.decision_version);
297 assert!(!decision_only.optimization_version);
298
299 let opt_only = ProblemInfo::new("Optimization Problem", "An optimization problem")
300 .with_decision(false);
301 assert!(!opt_only.decision_version);
302 assert!(opt_only.optimization_version);
303 }
304}