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}