1use crate::models::algebraic::{LinearConstraint, ObjectiveSense, ILP};
10use crate::models::misc::ConsistencyOfDatabaseFrequencyTables;
11use crate::reduction;
12use crate::rules::traits::{ReduceTo, ReductionResult};
13
14#[derive(Debug, Clone)]
16pub struct ReductionCDFTToILP {
17 target: ILP<bool>,
18 source: ConsistencyOfDatabaseFrequencyTables,
19}
20
21impl ReductionCDFTToILP {
22 fn assignment_block_size(&self) -> usize {
23 self.source.attribute_domains().iter().sum()
24 }
25
26 fn attribute_offset(&self, attribute: usize) -> usize {
27 self.source.attribute_domains()[..attribute].iter().sum()
28 }
29
30 fn assignment_var_index(&self, object: usize, attribute: usize, value: usize) -> usize {
31 object * self.assignment_block_size() + self.attribute_offset(attribute) + value
32 }
33
34 fn auxiliary_block_start(&self, table_index: usize) -> usize {
35 self.source.num_assignment_indicators()
36 + self.source.frequency_tables()[..table_index]
37 .iter()
38 .map(|table| self.source.num_objects() * table.num_cells())
39 .sum::<usize>()
40 }
41
42 fn auxiliary_var_index(
43 &self,
44 table_index: usize,
45 object: usize,
46 value_a: usize,
47 value_b: usize,
48 ) -> usize {
49 let table = &self.source.frequency_tables()[table_index];
50 let cols = self.source.attribute_domains()[table.attribute_b()];
51 self.auxiliary_block_start(table_index)
52 + object * table.num_cells()
53 + value_a * cols
54 + value_b
55 }
56
57 #[cfg_attr(not(test), allow(dead_code))]
59 pub(crate) fn encode_source_solution(&self, source_solution: &[usize]) -> Vec<usize> {
60 let mut target_solution = vec![0usize; self.target.num_vars];
61 let num_attributes = self.source.num_attributes();
62
63 for object in 0..self.source.num_objects() {
64 for attribute in 0..num_attributes {
65 let source_index = object * num_attributes + attribute;
66 let value = source_solution[source_index];
67 let var = self.assignment_var_index(object, attribute, value);
68 target_solution[var] = 1;
69 }
70 }
71
72 for (table_index, table) in self.source.frequency_tables().iter().enumerate() {
73 for object in 0..self.source.num_objects() {
74 let value_a = source_solution[object * num_attributes + table.attribute_a()];
75 let value_b = source_solution[object * num_attributes + table.attribute_b()];
76 let var = self.auxiliary_var_index(table_index, object, value_a, value_b);
77 target_solution[var] = 1;
78 }
79 }
80
81 target_solution
82 }
83}
84
85impl ReductionResult for ReductionCDFTToILP {
86 type Source = ConsistencyOfDatabaseFrequencyTables;
87 type Target = ILP<bool>;
88
89 fn target_problem(&self) -> &ILP<bool> {
90 &self.target
91 }
92
93 fn extract_solution(&self, target_solution: &[usize]) -> Vec<usize> {
94 let mut source_solution = Vec::with_capacity(self.source.num_assignment_variables());
95 for object in 0..self.source.num_objects() {
96 for (attribute, &domain_size) in self.source.attribute_domains().iter().enumerate() {
97 let value = (0..domain_size)
98 .find(|&candidate| {
99 target_solution
100 .get(self.assignment_var_index(object, attribute, candidate))
101 .copied()
102 .unwrap_or(0)
103 == 1
104 })
105 .unwrap_or(0);
106 source_solution.push(value);
107 }
108 }
109 source_solution
110 }
111}
112
113#[reduction(
114 overhead = {
115 num_vars = "num_assignment_indicators + num_auxiliary_frequency_indicators",
116 num_constraints = "num_assignment_variables + num_known_values + num_frequency_cells + 3 * num_auxiliary_frequency_indicators",
117 }
118)]
119impl ReduceTo<ILP<bool>> for ConsistencyOfDatabaseFrequencyTables {
120 type Result = ReductionCDFTToILP;
121
122 fn reduce_to(&self) -> Self::Result {
123 let source = self.clone();
124 let helper = ReductionCDFTToILP {
125 target: ILP::empty(),
126 source: source.clone(),
127 };
128
129 let mut constraints = Vec::with_capacity(
130 source.num_assignment_variables()
131 + source.num_known_values()
132 + source.num_frequency_cells()
133 + 3 * source.num_auxiliary_frequency_indicators(),
134 );
135
136 for object in 0..source.num_objects() {
137 for (attribute, &domain_size) in source.attribute_domains().iter().enumerate() {
138 let terms = (0..domain_size)
139 .map(|value| (helper.assignment_var_index(object, attribute, value), 1.0))
140 .collect();
141 constraints.push(LinearConstraint::eq(terms, 1.0));
142 }
143 }
144
145 for known_value in source.known_values() {
146 constraints.push(LinearConstraint::eq(
147 vec![(
148 helper.assignment_var_index(
149 known_value.object(),
150 known_value.attribute(),
151 known_value.value(),
152 ),
153 1.0,
154 )],
155 1.0,
156 ));
157 }
158
159 for (table_index, table) in source.frequency_tables().iter().enumerate() {
160 let rows = source.attribute_domains()[table.attribute_a()];
161 let cols = source.attribute_domains()[table.attribute_b()];
162
163 for value_a in 0..rows {
164 for value_b in 0..cols {
165 let count_terms = (0..source.num_objects())
166 .map(|object| {
167 (
168 helper.auxiliary_var_index(table_index, object, value_a, value_b),
169 1.0,
170 )
171 })
172 .collect();
173 constraints.push(LinearConstraint::eq(
174 count_terms,
175 table.counts()[value_a][value_b] as f64,
176 ));
177
178 for object in 0..source.num_objects() {
179 let z = helper.auxiliary_var_index(table_index, object, value_a, value_b);
180 let y_a = helper.assignment_var_index(object, table.attribute_a(), value_a);
181 let y_b = helper.assignment_var_index(object, table.attribute_b(), value_b);
182
183 constraints.push(LinearConstraint::le(vec![(z, 1.0), (y_a, -1.0)], 0.0));
184 constraints.push(LinearConstraint::le(vec![(z, 1.0), (y_b, -1.0)], 0.0));
185 constraints.push(LinearConstraint::ge(
186 vec![(z, 1.0), (y_a, -1.0), (y_b, -1.0)],
187 -1.0,
188 ));
189 }
190 }
191 }
192 }
193
194 let target = ILP::new(
195 source.num_assignment_indicators() + source.num_auxiliary_frequency_indicators(),
196 constraints,
197 vec![],
198 ObjectiveSense::Minimize,
199 );
200
201 ReductionCDFTToILP { target, source }
202 }
203}
204
205#[cfg(feature = "example-db")]
206pub(crate) fn canonical_rule_example_specs() -> Vec<crate::example_db::specs::RuleExampleSpec> {
207 use crate::models::misc::{FrequencyTable, KnownValue};
208
209 vec![crate::example_db::specs::RuleExampleSpec {
210 id: "consistencyofdatabasefrequencytables_to_ilp",
211 build: || {
212 let source = ConsistencyOfDatabaseFrequencyTables::new(
213 6,
214 vec![2, 3, 2],
215 vec![
216 FrequencyTable::new(0, 1, vec![vec![1, 1, 1], vec![1, 1, 1]]),
217 FrequencyTable::new(1, 2, vec![vec![1, 1], vec![0, 2], vec![1, 1]]),
218 ],
219 vec![
220 KnownValue::new(0, 0, 0),
221 KnownValue::new(3, 0, 1),
222 KnownValue::new(1, 2, 1),
223 ],
224 );
225 crate::example_db::specs::rule_example_via_ilp::<_, bool>(source)
226 },
227 }]
228}
229
230#[cfg(test)]
231#[path = "../unit_tests/rules/consistencyofdatabasefrequencytables_ilp.rs"]
232mod tests;