Skip to main content

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        category: crate::registry::ProblemCategory::Graph,
23        module_path: module_path!(),
24        description: "Find minimum universe size for intersection graph representation",
25        fields: &[
26            FieldInfo { name: "graph", type_name: "G", description: "The underlying graph G=(V,E)" },
27        ],
28    }
29}
30
31/// The Minimum Intersection Graph Basis problem.
32///
33/// Given a graph G = (V, E), find a universe U of minimum cardinality and
34/// an assignment of subsets `S[v]` ⊆ U for each vertex v ∈ V such that:
35/// - For every edge (u, v) ∈ E: `S[u] ∩ S[v] ≠ ∅`
36/// - For every non-edge pair (u, v) ∉ E: `S[u] ∩ S[v] = ∅`
37/// - |U| is minimized
38///
39/// The minimum |U| is the *intersection number* of G.
40///
41/// Variables: n × |E| binary variables where n = |V| and |E| is the upper bound
42/// on universe size. `config[v * |E| + s] = 1` means element s ∈ `S[v]`.
43///
44/// # Type Parameters
45///
46/// * `G` - The graph type (e.g., `SimpleGraph`)
47///
48/// # Example
49///
50/// ```
51/// use problemreductions::models::graph::MinimumIntersectionGraphBasis;
52/// use problemreductions::topology::SimpleGraph;
53/// use problemreductions::{Problem, BruteForce};
54///
55/// // Path P3: 3 vertices, edges (0,1), (1,2)
56/// let graph = SimpleGraph::new(3, vec![(0, 1), (1, 2)]);
57/// let problem = MinimumIntersectionGraphBasis::new(graph);
58///
59/// let solver = BruteForce::new();
60/// let solution = solver.solve(&problem).unwrap().unwrap();
61/// let value = problem.evaluate(&solution).unwrap();
62/// assert_eq!(value, problemreductions::types::Min(Some(2)));
63/// ```
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct MinimumIntersectionGraphBasis<G> {
66    /// The underlying graph.
67    graph: G,
68}
69
70impl<G: Graph> MinimumIntersectionGraphBasis<G> {
71    /// Create a MinimumIntersectionGraphBasis problem from a graph.
72    pub fn new(graph: G) -> Self {
73        Self { graph }
74    }
75
76    /// Get a reference to the underlying graph.
77    pub fn graph(&self) -> &G {
78        &self.graph
79    }
80
81    /// Get the number of vertices in the underlying graph.
82    pub fn num_vertices(&self) -> usize {
83        self.graph.num_vertices()
84    }
85
86    /// Get the number of edges in the underlying graph.
87    pub fn num_edges(&self) -> usize {
88        self.graph.num_edges()
89    }
90}
91
92impl<G> Problem for MinimumIntersectionGraphBasis<G>
93where
94    G: Graph + crate::variant::VariantParam,
95{
96    const NAME: &'static str = "MinimumIntersectionGraphBasis";
97    type Solution = Vec<Vec<bool>>;
98    type Value = Min<i64>;
99
100    crate::problem_parameters![("num_edges", num_edges), ("num_vertices", num_vertices),];
101
102    fn variant() -> Vec<(&'static str, &'static str)> {
103        crate::variant_params![G]
104    }
105
106    fn evaluate(
107        &self,
108        solution: &Self::Solution,
109    ) -> Result<Min<i64>, crate::traits::EvaluationError> {
110        let n = self.graph.num_vertices();
111        let m = self.graph.num_edges();
112        if solution.len() != n || solution.iter().any(|subset| subset.len() != m) {
113            return Err(crate::traits::EvaluationError::InvalidConfiguration(
114                "intersection-basis dimensions do not match the graph".into(),
115            ));
116        }
117        Ok({
118            if m == 0 {
119                return Ok(Min(Some(0)));
120            }
121
122            // Parse subsets: S[v] contains element s when solution[v][s] is true.
123            let subsets: Vec<HashSet<usize>> = solution
124                .iter()
125                .map(|row| {
126                    row.iter()
127                        .enumerate()
128                        .filter_map(|(element, &selected)| selected.then_some(element))
129                        .collect()
130                })
131                .collect();
132
133            // Check edge constraints: for every edge (u, v), S[u] ∩ S[v] ≠ ∅
134            let edges = self.graph.edges();
135            for &(u, v) in &edges {
136                if subsets[u].is_disjoint(&subsets[v]) {
137                    return Ok(Min(None));
138                }
139            }
140
141            // Check non-edge constraints: for every non-edge pair (u, v), S[u] ∩ S[v] = ∅
142            for u in 0..n {
143                for v in (u + 1)..n {
144                    if !self.graph.has_edge(u, v) && !subsets[u].is_disjoint(&subsets[v]) {
145                        return Ok(Min(None));
146                    }
147                }
148            }
149
150            // Count elements used (union of all subsets)
151            let used: HashSet<usize> = subsets.iter().flat_map(|s| s.iter().copied()).collect();
152            Min(Some(i64::try_from(used.len()).map_err(|_| {
153                crate::traits::EvaluationError::IntegerOverflow(
154                    "converting intersection-basis size to i64".into(),
155                )
156            })?))
157        })
158    }
159}
160
161impl<G> crate::solvers::BruteForceProblem for MinimumIntersectionGraphBasis<G>
162where
163    G: Graph + crate::variant::VariantParam,
164{
165    fn dimensions(&self) -> Vec<usize> {
166        let n = self.graph.num_vertices();
167        let m = self.graph.num_edges();
168        if m == 0 {
169            // No edges: no variables needed; empty assignment is trivially valid.
170            return vec![];
171        }
172        vec![2; n * m]
173    }
174}
175
176crate::impl_random_generate!(
177    MinimumIntersectionGraphBasis<SimpleGraph>,
178    crate::random::SimpleGraphRandomSpec,
179    |spec| { Ok(MinimumIntersectionGraphBasis::new(spec.graph()?)) }
180);
181
182crate::declare_variants! {
183    default MinimumIntersectionGraphBasis<SimpleGraph> => "num_edges^num_edges" random,
184}
185
186crate::register_brute_force! {
187    MinimumIntersectionGraphBasis<SimpleGraph> decode |problem: &MinimumIntersectionGraphBasis<SimpleGraph>, indices: Vec<usize>| if problem.num_edges() == 0 { vec![Vec::new(); problem.num_vertices()] } else { indices.chunks(problem.num_edges()).map(crate::config::config_to_bits).collect() },
188}
189
190#[cfg(feature = "example-db")]
191pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
192    // P3: 3 vertices, edges (0,1), (1,2), num_edges=2
193    // Intersection number = 2: S[0]={0}, S[1]={0,1}, S[2]={1}
194    // Incidence rows: vertex 0: [true,false], vertex 1: [true,true],
195    // vertex 2: [false,true].
196    vec![crate::example_db::specs::ModelExampleSpec {
197        id: "minimum_intersection_graph_basis_simplegraph",
198        instance: Box::new(MinimumIntersectionGraphBasis::new(SimpleGraph::new(
199            3,
200            vec![(0, 1), (1, 2)],
201        ))),
202        optimal_config: serde_json::json!(vec![
203            vec![true, false],
204            vec![true, true],
205            vec![false, true]
206        ]),
207        optimal_value: serde_json::json!(2),
208    }]
209}
210
211#[cfg(test)]
212#[path = "../../unit_tests/models/graph/minimum_intersection_graph_basis.rs"]
213mod tests;