problemreductions/models/graph/minimum_intersection_graph_basis.rs
1//! Minimum Intersection Graph Basis problem implementation.
2//!
3//! Given a graph G = (V, E), find a universe U of minimum cardinality such that
4//! each vertex v can be assigned a subset S[v] ⊆ U with the intersection graph
5//! of {S[v]} equal to G.
6
7use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
8use crate::topology::{Graph, SimpleGraph};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12use std::collections::HashSet;
13
14inventory::submit! {
15 ProblemSchemaEntry {
16 name: "MinimumIntersectionGraphBasis",
17 display_name: "Minimum Intersection Graph Basis",
18 aliases: &[],
19 dimensions: &[
20 VariantDimension::new("graph", "SimpleGraph", &["SimpleGraph"]),
21 ],
22 module_path: module_path!(),
23 description: "Find minimum universe size for intersection graph representation",
24 fields: &[
25 FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
26 ],
27 }
28}
29
30/// The Minimum Intersection Graph Basis problem.
31///
32/// Given a graph G = (V, E), find a universe U of minimum cardinality and
33/// an assignment of subsets S[v] ⊆ U for each vertex v ∈ V such that:
34/// - For every edge (u, v) ∈ E: S[u] ∩ S[v] ≠ ∅
35/// - For every non-edge pair (u, v) ∉ E: S[u] ∩ S[v] = ∅
36/// - |U| is minimized
37///
38/// The minimum |U| is the *intersection number* of G.
39///
40/// Variables: n × |E| binary variables where n = |V| and |E| is the upper bound
41/// on universe size. config[v * |E| + s] = 1 means element s ∈ S[v].
42///
43/// # Type Parameters
44///
45/// * `G` - The graph type (e.g., `SimpleGraph`)
46///
47/// # Example
48///
49/// ```
50/// use problemreductions::models::graph::MinimumIntersectionGraphBasis;
51/// use problemreductions::topology::SimpleGraph;
52/// use problemreductions::{Problem, Solver, BruteForce};
53///
54/// // Path P3: 3 vertices, edges (0,1), (1,2)
55/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]);
56/// let problem = MinimumIntersectionGraphBasis::new(graph);
57///
58/// let solver = BruteForce::new();
59/// let solution = solver.find_witness(&problem).unwrap();
60/// let value = problem.evaluate(&solution);
61/// assert_eq!(value, problemreductions::types::Min(Some(2)));
62/// ```
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct MinimumIntersectionGraphBasis<G> {
65 /// The underlying graph.
66 graph: G,
67}
68
69impl<G: Graph> MinimumIntersectionGraphBasis<G> {
70 /// Create a MinimumIntersectionGraphBasis problem from a graph.
71 pub fn new(graph: G) -> Self {
72 Self { graph }
73 }
74
75 /// Get a reference to the underlying graph.
76 pub fn graph(&self) -> &G {
77 &self.graph
78 }
79
80 /// Get the number of vertices in the underlying graph.
81 pub fn num_vertices(&self) -> usize {
82 self.graph.num_vertices()
83 }
84
85 /// Get the number of edges in the underlying graph.
86 pub fn num_edges(&self) -> usize {
87 self.graph.num_edges()
88 }
89}
90
91impl<G> Problem for MinimumIntersectionGraphBasis<G>
92where
93 G: Graph + crate::variant::VariantParam,
94{
95 const NAME: &'static str = "MinimumIntersectionGraphBasis";
96 type Value = Min<usize>;
97
98 fn variant() -> Vec<(&'static str, &'static str)> {
99 crate::variant_params![G]
100 }
101
102 fn dims(&self) -> Vec<usize> {
103 let n = self.graph.num_vertices();
104 let m = self.graph.num_edges();
105 if m == 0 {
106 // No edges: no variables needed; empty assignment is trivially valid.
107 return vec![];
108 }
109 vec![2; n * m]
110 }
111
112 fn evaluate(&self, config: &[usize]) -> Min<usize> {
113 let n = self.graph.num_vertices();
114 let m = self.graph.num_edges();
115
116 if m == 0 {
117 // No edges: universe size 0 suffices (all subsets empty, no
118 // adjacency constraints). But we must also check that no two
119 // vertices are adjacent — which is guaranteed when m == 0.
120 if config.is_empty() {
121 return Min(Some(0));
122 } else {
123 return Min(None);
124 }
125 }
126
127 if config.len() != n * m {
128 return Min(None);
129 }
130
131 // Parse subsets: S[v] = set of elements s where config[v * m + s] == 1
132 let subsets: Vec<HashSet<usize>> = (0..n)
133 .map(|v| (0..m).filter(|&s| config[v * m + s] == 1).collect())
134 .collect();
135
136 // Check edge constraints: for every edge (u, v), S[u] ∩ S[v] ≠ ∅
137 let edges = self.graph.edges();
138 for &(u, v) in &edges {
139 if subsets[u].is_disjoint(&subsets[v]) {
140 return Min(None);
141 }
142 }
143
144 // Check non-edge constraints: for every non-edge pair (u, v), S[u] ∩ S[v] = ∅
145 for u in 0..n {
146 for v in (u + 1)..n {
147 if !self.graph.has_edge(u, v) && !subsets[u].is_disjoint(&subsets[v]) {
148 return Min(None);
149 }
150 }
151 }
152
153 // Count elements used (union of all subsets)
154 let used: HashSet<usize> = subsets.iter().flat_map(|s| s.iter().copied()).collect();
155 Min(Some(used.len()))
156 }
157}
158
159crate::declare_variants! {
160 default MinimumIntersectionGraphBasis<SimpleGraph> => "num_edges^num_edges",
161}
162
163#[cfg(feature = "example-db")]
164pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
165 // P3: 3 vertices, edges (0,1), (1,2), num_edges=2
166 // Intersection number = 2: S[0]={0}, S[1]={0,1}, S[2]={1}
167 // Config: vertex 0: [1,0], vertex 1: [1,1], vertex 2: [0,1]
168 // Full config: [1,0, 1,1, 0,1]
169 vec![crate::example_db::specs::ModelExampleSpec {
170 id: "minimum_intersection_graph_basis_simplegraph",
171 instance: Box::new(MinimumIntersectionGraphBasis::new(SimpleGraph::new(
172 3,
173 vec![(0, 1), (1, 2)],
174 ))),
175 optimal_config: vec![1, 0, 1, 1, 0, 1],
176 optimal_value: serde_json::json!(2),
177 }]
178}
179
180#[cfg(test)]
181#[path = "../../unit_tests/models/graph/minimum_intersection_graph_basis.rs"]
182mod tests;