1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
9use crate::models::graph::MaximumCoKPlex;
10use crate::reduction;
11use crate::rules::traits::{ReduceTo, ReductionResult};
12use crate::topology::{Graph, SimpleGraph};
13use crate::types::{One, WeightElement};
14use crate::variant::{VariantParam, KN};
15use std::marker::PhantomData;
16
17#[derive(Debug, Clone)]
18pub struct ReductionCoKPlexToILP<W> {
19 target: ILP<bool>,
20 _marker: PhantomData<W>,
21}
22
23impl<W> ReductionResult for ReductionCoKPlexToILP<W>
24where
25 W: WeightElement + VariantParam,
26{
27 type Source = MaximumCoKPlex<SimpleGraph, W, KN>;
28 type Target = ILP<bool>;
29
30 fn target_problem(&self) -> &ILP<bool> {
31 &self.target
32 }
33
34 fn extract_solution(
35 &self,
36 target_solution: &<Self::Target as crate::traits::Problem>::Solution,
37 ) -> crate::rules::ExtractionResult<<Self::Source as crate::traits::Problem>::Solution> {
38 crate::rules::traits::validate_target_solution(self.target_problem(), target_solution)?;
39
40 Ok(target_solution.iter().map(|&value| value == 1).collect())
41 }
42}
43
44fn build_constraints(graph: &SimpleGraph, bound_k: usize) -> Result<Vec<LinearConstraint>, ()> {
45 (0..graph.num_vertices())
46 .map(|v| {
47 let degree = graph.degree(v);
48 let allowed_neighbors =
49 i64::try_from(bound_k.checked_sub(1).ok_or(())?.min(degree)).map_err(|_| ())?;
50 let degree = i64::try_from(degree).map_err(|_| ())?;
51 let mut terms: Vec<(usize, i64)> =
52 graph.neighbors(v).into_iter().map(|u| (u, 1)).collect();
53 if degree > 0 {
54 terms.push((v, degree));
55 }
56 let rhs = degree.checked_add(allowed_neighbors).ok_or(())?;
57 Ok(LinearConstraint::le(terms, rhs))
58 })
59 .collect()
60}
61
62fn reduce_cokplex_to_ilp<W>(
63 src: &MaximumCoKPlex<SimpleGraph, W, KN>,
64 constraints: Vec<LinearConstraint>,
65 objective: Vec<(usize, i64)>,
66) -> Result<ReductionCoKPlexToILP<W>, crate::registry::ConstructionError>
67where
68 W: WeightElement + VariantParam,
69{
70 let target = ILP::new(
71 src.num_vertices(),
72 constraints,
73 objective,
74 ObjectiveSense::Maximize,
75 )?;
76 Ok(ReductionCoKPlexToILP {
77 target,
78 _marker: PhantomData,
79 })
80}
81
82#[reduction(transform = {
83 exact {
84 num_vars = "num_vertices",
85 num_constraints = "num_vertices",
86 },
87 upper_bound {
88 max_constraint_magnitude_bits = "4 * num_edges + 2",
89 num_nonzeros = "num_vertices * num_vertices",
90 },
91})]
92impl ReduceTo<ILP<bool>> for MaximumCoKPlex<SimpleGraph, i64, KN> {
93 type Result = ReductionCoKPlexToILP<i64>;
94
95 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
96 let objective: Vec<(usize, i64)> = self
97 .weights()
98 .iter()
99 .enumerate()
100 .map(|(vertex, &weight)| (vertex, weight))
101 .collect();
102 let constraints = build_constraints(self.graph(), self.bound_k()).map_err(|_| {
103 crate::rules::ReductionError::integer_overflow::<
104 MaximumCoKPlex<SimpleGraph, i64, KN>,
105 ILP<bool>,
106 >("encoding a degree or co-k-plex bound")
107 })?;
108 reduce_cokplex_to_ilp(self, constraints, objective).map_err(Self::target_construction)
109 }
110}
111
112#[reduction(transform = {
113 exact {
114 num_vars = "num_vertices",
115 num_constraints = "num_vertices",
116 },
117 upper_bound {
118 max_constraint_magnitude_bits = "4 * num_edges + 2",
119 num_nonzeros = "num_vertices * num_vertices",
120 },
121})]
122impl ReduceTo<ILP<bool>> for MaximumCoKPlex<SimpleGraph, One, KN> {
123 type Result = ReductionCoKPlexToILP<One>;
124
125 fn reduce_to(&self) -> Result<Self::Result, crate::rules::ReductionError> {
126 let objective: Vec<(usize, i64)> = self
127 .weights()
128 .iter()
129 .enumerate()
130 .map(|(v, _)| (v, 1))
131 .collect();
132 let constraints = build_constraints(self.graph(), self.bound_k()).map_err(|_| {
133 crate::rules::ReductionError::integer_overflow::<
134 MaximumCoKPlex<SimpleGraph, One, KN>,
135 ILP<bool>,
136 >("encoding a degree or co-k-plex bound")
137 })?;
138 reduce_cokplex_to_ilp(self, constraints, objective).map_err(Self::target_construction)
139 }
140}
141
142#[cfg(feature = "example-db")]
143pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
144 vec![
145 crate::example_db::specs::RuleExampleSpec {
146 id: "weighted_maximumcokplex_to_ilp",
147 build: || {
148 let source = MaximumCoKPlex::<_, i64, KN>::with_k(
149 SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]),
150 vec![5, 1, 4, 1, 3],
151 2,
152 );
153 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
154 },
155 },
156 crate::example_db::specs::RuleExampleSpec {
157 id: "cardinality_maximumcokplex_to_ilp",
158 build: || {
159 let source = MaximumCoKPlex::<_, One, KN>::with_k(
160 SimpleGraph::new(5, vec![(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]),
161 vec![One; 5],
162 2,
163 );
164 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
165 },
166 },
167 ]
168}
169
170#[cfg(test)]
171#[path = "../unit_tests/rules/maximumcokplex_ilp.rs"]
172mod tests;