problemreductions/rules/
maximumcommonedgesubgraph_ilp.rs1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
16use crate::models::graph::MaximumCommonEdgeSubgraph;
17use crate::reduction;
18use crate::rules::ilp_helpers::mccormick_product;
19use crate::rules::traits::{ReduceTo, ReductionResult};
20
21#[derive(Debug, Clone)]
29pub struct ReductionMCESToILP {
30 target: ILP<bool>,
31 num_vertices_1: usize,
32 num_vertices_2: usize,
33}
34
35impl ReductionResult for ReductionMCESToILP {
36 type Source = MaximumCommonEdgeSubgraph;
37 type Target = ILP<bool>;
38
39 fn target_problem(&self) -> &ILP<bool> {
40 &self.target
41 }
42
43 fn extract_solution(
47 &self,
48 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
49 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
50 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
51
52 let n2 = self.num_vertices_2;
53 (0..self.num_vertices_1)
54 .map(|vertex| {
55 let mut selected =
56 (0..n2).filter(|&mapped| target_solution[vertex * n2 + mapped] == 1);
57 match (selected.next(), selected.next()) {
58 (Some(mapped), None) => Ok(mapped),
59 (None, _) => Ok(n2),
60 (Some(_), Some(_)) => Err(crate::rules::ExtractionError::invalid(format!(
61 "source vertex {vertex} maps to multiple target vertices"
62 ))),
63 }
64 })
65 .collect()
66 }
67}
68
69#[reduction(transform = upper_bound {
72 max_constraint_magnitude_bits = "2",
73 num_vars = "num_vertices_1 * num_vertices_2 + num_arcs_1 * num_arcs_2",
74 num_constraints = "num_vertices_1 + num_vertices_2 + 3 * num_arcs_1 * num_arcs_2",
75 num_nonzeros = "2 * num_vertices_1 * num_vertices_2 + 7 * num_arcs_1 * num_arcs_2",
76})]
77impl ReduceTo<ILP<bool>> for MaximumCommonEdgeSubgraph {
78 type Result = ReductionMCESToILP;
79
80 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
81 let n1 = self.num_vertices_1();
82 let n2 = self.num_vertices_2();
83 let arcs_1 = self.graph_1().arcs();
84 let arcs_2 = self.graph_2().arcs();
85
86 let num_x = n1 * n2;
87 let x_idx = |u: usize, p: usize| -> usize { u * n2 + p };
88
89 let mut y_pairs: Vec<(usize, usize)> = Vec::new();
92 for (a_idx, a) in arcs_1.iter().enumerate() {
93 for (b_idx, b) in arcs_2.iter().enumerate() {
94 if a.label == b.label {
95 y_pairs.push((a_idx, b_idx));
96 }
97 }
98 }
99
100 let num_y = y_pairs.len();
101 let num_vars = num_x + num_y;
102 let y_idx = |seq: usize| -> usize { num_x + seq };
103
104 let mut constraints: Vec<LinearConstraint> = Vec::new();
105
106 for u in 0..n1 {
108 let terms: Vec<(usize, i64)> = (0..n2).map(|p| (x_idx(u, p), 1)).collect();
109 constraints.push(LinearConstraint::le(terms, 1));
110 }
111
112 for p in 0..n2 {
114 let terms: Vec<(usize, i64)> = (0..n1).map(|u| (x_idx(u, p), 1)).collect();
115 constraints.push(LinearConstraint::le(terms, 1));
116 }
117
118 for (seq, &(a_idx, b_idx)) in y_pairs.iter().enumerate() {
120 let a = arcs_1[a_idx];
121 let b = arcs_2[b_idx];
122 constraints.extend(mccormick_product(
123 y_idx(seq),
124 x_idx(a.src, b.src),
125 x_idx(a.dst, b.dst),
126 ));
127 }
128
129 let objective: Vec<(usize, i64)> = (0..num_y).map(|seq| (y_idx(seq), 1)).collect();
131
132 let target = ILP::new(num_vars, constraints, objective, ObjectiveSense::Maximize)
133 .map_err(Self::target_construction)?;
134
135 Ok(ReductionMCESToILP {
136 target,
137 num_vertices_1: n1,
138 num_vertices_2: n2,
139 })
140 }
141}
142
143#[cfg(feature = "example-db")]
144pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
145 use crate::models::graph::{LabelledArc, LabelledDigraph};
146 vec![crate::example_db::specs::RuleExampleSpec {
147 id: "maximumcommonedgesubgraph_to_ilp",
148 build: || {
149 let source = MaximumCommonEdgeSubgraph::new(
151 LabelledDigraph::new(
152 3,
153 vec![LabelledArc::new(0, 0, 1), LabelledArc::new(1, 1, 2)],
154 ),
155 LabelledDigraph::new(
156 3,
157 vec![LabelledArc::new(0, 0, 1), LabelledArc::new(1, 1, 2)],
158 ),
159 );
160 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
161 },
162 }]
163}
164
165#[cfg(test)]
166#[path = "../unit_tests/rules/maximumcommonedgesubgraph_ilp.rs"]
167mod tests;