problemreductions/models/misc/
boyce_codd_normal_form_violation.rs1use crate::registry::{CreateSpec, ProblemSchemaEntry};
9use crate::traits::Problem;
10use serde::{Deserialize, Serialize};
11use std::collections::HashSet;
12
13inventory::submit! {
14 ProblemSchemaEntry {
15 name: "BoyceCoddNormalFormViolation",
16 display_name: "Boyce-Codd Normal Form Violation",
17 aliases: &["BCNFViolation", "BCNF"],
18 dimensions: &[],
19 category: crate::registry::ProblemCategory::Misc,
20 module_path: module_path!(),
21 description: "Test whether a subset of attributes violates Boyce-Codd normal form",
22 fields: BoyceCoddNormalFormViolationCreateSpec::FIELDS,
23 }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize)]
61#[serde(try_from = "BoyceCoddNormalFormViolationData")]
62pub struct BoyceCoddNormalFormViolation {
63 num_attributes: usize,
65 functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
67 target_subset: Vec<usize>,
69}
70
71#[derive(Deserialize)]
72struct BoyceCoddNormalFormViolationData {
73 num_attributes: usize,
74 functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
75 target_subset: Vec<usize>,
76}
77
78impl TryFrom<BoyceCoddNormalFormViolationData> for BoyceCoddNormalFormViolation {
79 type Error = crate::registry::ConstructionError;
80 fn try_from(data: BoyceCoddNormalFormViolationData) -> Result<Self, Self::Error> {
81 Self::try_new(
82 data.num_attributes,
83 data.functional_deps,
84 data.target_subset,
85 )
86 }
87}
88
89#[derive(Debug, Deserialize, crate::CreateSpec)]
90struct BoyceCoddNormalFormViolationCreateSpec {
91 n: usize,
93 #[create(codec = "functional-dependency-list")]
95 subsets: Vec<(Vec<usize>, Vec<usize>)>,
96 target: Vec<usize>,
98}
99
100impl TryFrom<BoyceCoddNormalFormViolationCreateSpec> for BoyceCoddNormalFormViolation {
101 type Error = crate::registry::ConstructionError;
102
103 fn try_from(spec: BoyceCoddNormalFormViolationCreateSpec) -> Result<Self, Self::Error> {
104 Self::try_new(spec.n, spec.subsets, spec.target)
105 }
106}
107
108impl BoyceCoddNormalFormViolation {
109 pub fn new(
122 num_attributes: usize,
123 functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
124 target_subset: Vec<usize>,
125 ) -> Self {
126 Self::try_new(num_attributes, functional_deps, target_subset)
127 .unwrap_or_else(|error| panic!("{error}"))
128 }
129
130 fn try_new(
131 num_attributes: usize,
132 functional_deps: Vec<(Vec<usize>, Vec<usize>)>,
133 target_subset: Vec<usize>,
134 ) -> Result<Self, crate::registry::ConstructionError> {
135 if target_subset.is_empty() {
136 return Err("target_subset must be non-empty".into());
137 }
138
139 let mut functional_deps = functional_deps;
140 for (fd_index, (lhs, rhs)) in functional_deps.iter_mut().enumerate() {
141 if lhs.is_empty() {
142 return Err(format!("Functional dependency {} has an empty LHS", fd_index).into());
143 }
144 lhs.sort_unstable();
145 lhs.dedup();
146 rhs.sort_unstable();
147 rhs.dedup();
148 for &attr in lhs.iter().chain(rhs.iter()) {
149 if attr >= num_attributes {
150 return Err(format!("Functional dependency {} contains attribute {} which is out of range (num_attributes = {})", fd_index, attr, num_attributes).into());
151 }
152 }
153 }
154
155 let mut target_subset = target_subset;
156 target_subset.sort_unstable();
157 target_subset.dedup();
158 for &attr in &target_subset {
159 if attr >= num_attributes {
160 return Err(format!("target_subset contains attribute {} which is out of range (num_attributes = {})", attr, num_attributes).into());
161 }
162 }
163
164 Ok(Self {
165 num_attributes,
166 functional_deps,
167 target_subset,
168 })
169 }
170
171 pub fn num_attributes(&self) -> usize {
173 self.num_attributes
174 }
175
176 pub fn num_functional_deps(&self) -> usize {
178 self.functional_deps.len()
179 }
180
181 pub fn num_target_attributes(&self) -> usize {
183 self.target_subset.len()
184 }
185
186 pub fn functional_deps(&self) -> &[(Vec<usize>, Vec<usize>)] {
188 &self.functional_deps
189 }
190
191 pub fn target_subset(&self) -> &[usize] {
193 &self.target_subset
194 }
195
196 fn compute_closure(x: &HashSet<usize>, fds: &[(Vec<usize>, Vec<usize>)]) -> HashSet<usize> {
198 let mut closure = x.clone();
199 let mut changed = true;
200 while changed {
201 changed = false;
202 for (lhs, rhs) in fds {
203 if lhs.iter().all(|a| closure.contains(a)) {
204 for &a in rhs {
205 if closure.insert(a) {
206 changed = true;
207 }
208 }
209 }
210 }
211 }
212 closure
213 }
214}
215
216impl Problem for BoyceCoddNormalFormViolation {
217 const NAME: &'static str = "BoyceCoddNormalFormViolation";
218 type Solution = Vec<bool>;
219 type Value = crate::types::Or;
220
221 crate::problem_parameters![
222 ("num_attributes", num_attributes),
223 ("num_functional_deps", num_functional_deps),
224 ("num_target_attributes", num_target_attributes),
225 ];
226
227 fn evaluate(
228 &self,
229 config: &Self::Solution,
230 ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
231 Ok({
232 crate::types::Or({
233 if config.len() != self.target_subset.len() {
234 return Err(crate::traits::EvaluationError::InvalidConfiguration(
235 "attribute-selection length does not match the target subset".into(),
236 ));
237 }
238 let x: HashSet<usize> = config
239 .iter()
240 .enumerate()
241 .filter(|(_, &v)| v)
242 .map(|(i, _)| self.target_subset[i])
243 .collect();
244 let closure = Self::compute_closure(&x, &self.functional_deps);
245 let mut has_in_closure = false;
247 let mut has_not_in_closure = false;
248 for &a in &self.target_subset {
249 if !x.contains(&a) {
250 if closure.contains(&a) {
251 has_in_closure = true;
252 } else {
253 has_not_in_closure = true;
254 }
255 }
256 }
257 has_in_closure && has_not_in_closure
258 })
259 })
260 }
261
262 fn variant() -> Vec<(&'static str, &'static str)> {
263 crate::variant_params![]
264 }
265}
266
267impl crate::solvers::BruteForceProblem for BoyceCoddNormalFormViolation {
268 fn dimensions(&self) -> Vec<usize> {
269 vec![2; self.target_subset.len()]
270 }
271}
272
273crate::declare_variants! {
274 default BoyceCoddNormalFormViolation => "2^num_target_attributes * num_target_attributes^2 * num_functional_deps" create BoyceCoddNormalFormViolationCreateSpec,
275}
276
277crate::register_brute_force! {
278 BoyceCoddNormalFormViolation decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
279}
280
281#[cfg(feature = "example-db")]
282pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
283 vec![crate::example_db::specs::ModelExampleSpec {
284 id: "boyce_codd_normal_form_violation",
285 instance: Box::new(BoyceCoddNormalFormViolation::new(
286 6,
287 vec![
288 (vec![0, 1], vec![2]),
289 (vec![2], vec![3]),
290 (vec![3, 4], vec![5]),
291 ],
292 vec![0, 1, 2, 3, 4, 5],
293 )),
294 optimal_config: serde_json::json!(vec![false, false, true, false, false, false]),
296 optimal_value: serde_json::json!(true),
297 }]
298}
299
300#[cfg(test)]
301#[path = "../../unit_tests/models/misc/boyce_codd_normal_form_violation.rs"]
302mod tests;