1use crate::registry::{CreateSpec, ProblemSchemaEntry};
10use crate::traits::Problem;
11use serde::{Deserialize, Serialize};
12use std::collections::BTreeSet;
13
14#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct FrequencyTable {
16 attribute_a: usize,
17 attribute_b: usize,
18 counts: Vec<Vec<i64>>,
19}
20
21impl FrequencyTable {
22 pub fn new(attribute_a: usize, attribute_b: usize, counts: Vec<Vec<i64>>) -> Self {
24 Self {
25 attribute_a,
26 attribute_b,
27 counts,
28 }
29 }
30
31 pub fn attribute_a(&self) -> usize {
33 self.attribute_a
34 }
35
36 pub fn attribute_b(&self) -> usize {
38 self.attribute_b
39 }
40
41 pub fn counts(&self) -> &[Vec<i64>] {
43 &self.counts
44 }
45
46 pub fn num_cells(&self) -> usize {
48 self.counts.iter().map(Vec::len).sum()
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct KnownValue {
54 object: usize,
55 attribute: usize,
56 value: usize,
57}
58
59impl KnownValue {
60 pub fn new(object: usize, attribute: usize, value: usize) -> Self {
62 Self {
63 object,
64 attribute,
65 value,
66 }
67 }
68
69 pub fn object(&self) -> usize {
71 self.object
72 }
73
74 pub fn attribute(&self) -> usize {
76 self.attribute
77 }
78
79 pub fn value(&self) -> usize {
81 self.value
82 }
83}
84
85inventory::submit! {
86 ProblemSchemaEntry {
87 name: "ConsistencyOfDatabaseFrequencyTables",
88 display_name: "Consistency of Database Frequency Tables",
89 aliases: &[],
90 dimensions: &[],
91 category: crate::registry::ProblemCategory::Misc,
92 module_path: module_path!(),
93 description: "Determine whether pairwise frequency tables and known values admit a consistent complete database assignment",
94 fields: ConsistencyOfDatabaseFrequencyTablesCreateSpec::FIELDS,
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
100#[serde(try_from = "ConsistencyOfDatabaseFrequencyTablesData")]
101pub struct ConsistencyOfDatabaseFrequencyTables {
102 num_objects: usize,
103 attribute_domains: Vec<usize>,
104 frequency_tables: Vec<FrequencyTable>,
105 known_values: Vec<KnownValue>,
106}
107
108#[derive(Deserialize)]
109struct ConsistencyOfDatabaseFrequencyTablesData {
110 num_objects: usize,
111 attribute_domains: Vec<usize>,
112 frequency_tables: Vec<FrequencyTable>,
113 known_values: Vec<KnownValue>,
114}
115
116impl TryFrom<ConsistencyOfDatabaseFrequencyTablesData> for ConsistencyOfDatabaseFrequencyTables {
117 type Error = crate::registry::ConstructionError;
118 fn try_from(data: ConsistencyOfDatabaseFrequencyTablesData) -> Result<Self, Self::Error> {
119 Self::try_new(
120 data.num_objects,
121 data.attribute_domains,
122 data.frequency_tables,
123 data.known_values,
124 )
125 }
126}
127
128#[derive(Debug, Deserialize, crate::CreateSpec)]
129struct ConsistencyOfDatabaseFrequencyTablesCreateSpec {
130 num_objects: usize,
132 #[create(codec = "comma-separated")]
134 attribute_domains: Vec<usize>,
135 #[create(codec = "json")]
137 frequency_tables: Vec<FrequencyTable>,
138 #[create(codec = "json")]
140 known_values: Option<Vec<KnownValue>>,
141}
142
143impl TryFrom<ConsistencyOfDatabaseFrequencyTablesCreateSpec>
144 for ConsistencyOfDatabaseFrequencyTables
145{
146 type Error = crate::registry::ConstructionError;
147 fn try_from(spec: ConsistencyOfDatabaseFrequencyTablesCreateSpec) -> Result<Self, Self::Error> {
148 Self::try_new(
149 spec.num_objects,
150 spec.attribute_domains,
151 spec.frequency_tables,
152 spec.known_values.unwrap_or_default(),
153 )
154 }
155}
156
157fn validate_cdft_create(
158 num_objects: usize,
159 domains: &[usize],
160 tables: &[FrequencyTable],
161 known: &[KnownValue],
162) -> Result<(), crate::registry::ConstructionError> {
163 for (attribute, &size) in domains.iter().enumerate() {
164 if size == 0 {
165 return Err(
166 format!("attribute domain size at index {attribute} must be positive").into(),
167 );
168 }
169 }
170 num_objects.checked_mul(domains.len()).ok_or_else(|| {
171 crate::registry::ConstructionError::IntegerOverflow(
172 "representing the table-assignment witness length".into(),
173 )
174 })?;
175 domains
176 .iter()
177 .try_fold(0usize, |sum, &size| sum.checked_add(size))
178 .ok_or_else(|| {
179 crate::registry::ConstructionError::IntegerOverflow(
180 "representing the total domain size".into(),
181 )
182 })?;
183 tables
184 .iter()
185 .flat_map(|table| table.counts())
186 .try_fold(0usize, |sum, row| sum.checked_add(row.len()))
187 .ok_or_else(|| {
188 crate::registry::ConstructionError::IntegerOverflow(
189 "representing the number of frequency-table cells".into(),
190 )
191 })?;
192 let mut pairs = BTreeSet::new();
193 for table in tables {
194 let a = table.attribute_a();
195 let b = table.attribute_b();
196 if a >= domains.len() || b >= domains.len() {
197 return Err("frequency table attribute is out of range".into());
198 }
199 if a == b {
200 return Err("frequency table attributes must be distinct".into());
201 }
202 let pair = if a < b { (a, b) } else { (b, a) };
203 if !pairs.insert(pair) {
204 return Err(format!("duplicate frequency table pair ({}, {})", pair.0, pair.1).into());
205 }
206 if table.counts().len() != domains[a] {
207 return Err(
208 format!("frequency table rows must equal domain size for attribute {a}").into(),
209 );
210 }
211 if table.counts().iter().any(|row| row.len() != domains[b]) {
212 return Err(format!(
213 "frequency table column count must equal domain size for attribute {b}"
214 )
215 .into());
216 }
217 if table.counts().iter().flatten().any(|&count| count < 0) {
218 return Err("frequency table counts must be nonnegative".into());
219 }
220 let total = table
221 .counts()
222 .iter()
223 .flatten()
224 .try_fold(0_i64, |sum, &value| {
225 sum.checked_add(value)
226 .ok_or("frequency table count total overflows i64")
227 })?;
228 let expected_total =
229 i64::try_from(num_objects).map_err(|_| "num_objects cannot be represented as i64")?;
230 if total != expected_total {
231 return Err(format!(
232 "frequency table total {total} must equal num_objects {num_objects}"
233 )
234 .into());
235 }
236 }
237 for value in known {
238 if value.object() >= num_objects {
239 return Err("known value object is out of range".into());
240 }
241 if value.attribute() >= domains.len() {
242 return Err("known value attribute is out of range".into());
243 }
244 if value.value() >= domains[value.attribute()] {
245 return Err("known value value is outside the attribute domain".into());
246 }
247 }
248 Ok(())
249}
250
251impl ConsistencyOfDatabaseFrequencyTables {
252 pub fn new(
255 num_objects: usize,
256 attribute_domains: Vec<usize>,
257 frequency_tables: Vec<FrequencyTable>,
258 known_values: Vec<KnownValue>,
259 ) -> Self {
260 Self::try_new(
261 num_objects,
262 attribute_domains,
263 frequency_tables,
264 known_values,
265 )
266 .unwrap_or_else(|error| panic!("{error}"))
267 }
268
269 fn try_new(
270 num_objects: usize,
271 attribute_domains: Vec<usize>,
272 frequency_tables: Vec<FrequencyTable>,
273 known_values: Vec<KnownValue>,
274 ) -> Result<Self, crate::registry::ConstructionError> {
275 validate_cdft_create(
276 num_objects,
277 &attribute_domains,
278 &frequency_tables,
279 &known_values,
280 )?;
281
282 Ok(Self {
283 num_objects,
284 attribute_domains,
285 frequency_tables,
286 known_values,
287 })
288 }
289
290 pub fn num_objects(&self) -> usize {
292 self.num_objects
293 }
294
295 pub fn num_attributes(&self) -> usize {
297 self.attribute_domains.len()
298 }
299
300 pub fn attribute_domains(&self) -> &[usize] {
302 &self.attribute_domains
303 }
304
305 pub fn frequency_tables(&self) -> &[FrequencyTable] {
307 &self.frequency_tables
308 }
309
310 pub fn known_values(&self) -> &[KnownValue] {
312 &self.known_values
313 }
314
315 pub fn max_domain_size(&self) -> usize {
317 self.attribute_domains.iter().copied().max().unwrap_or(1)
318 }
319
320 pub fn total_domain_size(&self) -> usize {
322 self.attribute_domains.iter().sum()
323 }
324
325 pub fn num_assignment_variables(&self) -> usize {
327 self.num_objects * self.num_attributes()
328 }
329
330 pub fn num_frequency_tables(&self) -> usize {
332 self.frequency_tables.len()
333 }
334
335 pub fn num_known_values(&self) -> usize {
337 self.known_values.len()
338 }
339
340 pub fn num_frequency_cells(&self) -> usize {
342 self.frequency_tables
343 .iter()
344 .map(FrequencyTable::num_cells)
345 .sum()
346 }
347
348 fn config_index(&self, object: usize, attribute: usize) -> usize {
349 object * self.num_attributes() + attribute
350 }
351}
352
353impl Problem for ConsistencyOfDatabaseFrequencyTables {
354 const NAME: &'static str = "ConsistencyOfDatabaseFrequencyTables";
355 type Solution = Vec<usize>;
356 type Value = crate::types::Or;
357
358 crate::problem_parameters![
359 ("num_objects", num_objects),
360 ("num_attributes", num_attributes),
361 ("total_domain_size", total_domain_size),
362 ("max_domain_size", max_domain_size),
363 ("num_frequency_tables", num_frequency_tables),
364 ("num_frequency_cells", num_frequency_cells),
365 ("num_known_values", num_known_values),
366 ];
367
368 fn variant() -> Vec<(&'static str, &'static str)> {
369 crate::variant_params![]
370 }
371
372 fn evaluate(
373 &self,
374 config: &Self::Solution,
375 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
376 Ok({
377 crate::types::Or({
378 if config.len() != self.num_assignment_variables() {
379 return Err(crate::traits::EvaluationError::InvalidConfiguration(
380 "table-assignment length does not match the instance".into(),
381 ));
382 }
383
384 for object in 0..self.num_objects {
385 for (attribute, &domain_size) in self.attribute_domains.iter().enumerate() {
386 if config[self.config_index(object, attribute)] >= domain_size {
387 return Err(crate::traits::EvaluationError::InvalidConfiguration(
388 "table assignment contains an out-of-range domain value".into(),
389 ));
390 }
391 }
392 }
393
394 for known_value in &self.known_values {
395 if config[self.config_index(known_value.object(), known_value.attribute())]
396 != known_value.value()
397 {
398 return Ok(crate::types::Or(false));
399 }
400 }
401
402 for table in &self.frequency_tables {
403 let rows = self.attribute_domains[table.attribute_a()];
404 let cols = self.attribute_domains[table.attribute_b()];
405 let mut observed = vec![vec![0_i64; cols]; rows];
406
407 for object in 0..self.num_objects {
408 let value_a = config[self.config_index(object, table.attribute_a())];
409 let value_b = config[self.config_index(object, table.attribute_b())];
410 observed[value_a][value_b] =
411 observed[value_a][value_b].checked_add(1).ok_or_else(|| {
412 crate::traits::EvaluationError::IntegerOverflow(
413 "counting observed database frequencies".to_string(),
414 )
415 })?;
416 }
417
418 if observed != table.counts {
419 return Ok(crate::types::Or(false));
420 }
421 }
422
423 true
424 })
425 })
426 }
427}
428
429impl crate::solvers::BruteForceProblem for ConsistencyOfDatabaseFrequencyTables {
430 fn dimensions(&self) -> Vec<usize> {
431 let mut dims = Vec::with_capacity(self.num_assignment_variables());
432 for _ in 0..self.num_objects {
433 dims.extend(self.attribute_domains.iter().copied());
434 }
435 dims
436 }
437}
438
439crate::declare_variants! {
440 default ConsistencyOfDatabaseFrequencyTables => "max_domain_size^(num_objects * num_attributes)" create ConsistencyOfDatabaseFrequencyTablesCreateSpec,
442}
443
444crate::register_brute_force! {
445 ConsistencyOfDatabaseFrequencyTables,
446}
447
448#[cfg(feature = "example-db")]
449pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
450 vec![crate::example_db::specs::ModelExampleSpec {
451 id: "consistency_of_database_frequency_tables",
452 instance: Box::new(ConsistencyOfDatabaseFrequencyTables::new(
453 6,
454 vec![2, 3, 2],
455 vec![
456 FrequencyTable::new(0, 1, vec![vec![1, 1, 1], vec![1, 1, 1]]),
457 FrequencyTable::new(1, 2, vec![vec![1, 1], vec![0, 2], vec![1, 1]]),
458 ],
459 vec![
460 KnownValue::new(0, 0, 0),
461 KnownValue::new(3, 0, 1),
462 KnownValue::new(1, 2, 1),
463 ],
464 )),
465 optimal_config: serde_json::json!(vec![
466 0, 0, 0, 0, 1, 1, 0, 2, 1, 1, 0, 1, 1, 1, 1, 1, 2, 0
467 ]),
468 optimal_value: serde_json::json!(true),
469 }]
470}
471
472#[cfg(test)]
473#[path = "../../unit_tests/models/misc/consistency_of_database_frequency_tables.rs"]
474mod tests;