Skip to main content

problemreductions/models/misc/
additional_key.rs

1//! Additional Key problem implementation.
2//!
3//! Given a relational schema (R, F) and a set K of known candidate keys,
4//! determine whether there exists a candidate key of R (under the functional
5//! dependencies F) that is not in K. A candidate key is a minimal set of
6//! attributes whose closure under F covers all of R.
7//!
8//! The problem is NP-complete (Garey & Johnson, SR7).
9
10use crate::registry::{FieldInfo, ProblemSchemaEntry};
11use crate::traits::Problem;
12use serde::{Deserialize, Serialize};
13
14inventory::submit! {
15    ProblemSchemaEntry {
16        name: "AdditionalKey",
17        display_name: "Additional Key",
18        aliases: &[],
19        dimensions: &[],
20        category: crate::registry::ProblemCategory::Misc,
21        module_path: module_path!(),
22        description: "Determine whether a relational schema has a candidate key not in a given set",
23        fields: &[
24            FieldInfo { name: "num_attributes", type_name: "usize", description: "Number of attributes in A" },
25            FieldInfo { name: "dependencies", type_name: "Vec<(Vec<usize>, Vec<usize>)>", description: "Functional dependencies F; each (lhs, rhs)" },
26            FieldInfo { name: "relation_attrs", type_name: "Vec<usize>", description: "Relation scheme attributes R ⊆ A" },
27            FieldInfo { name: "known_keys", type_name: "Vec<Vec<usize>>", description: "Known candidate keys K" },
28        ],
29    }
30}
31
32/// The Additional Key problem.
33///
34/// Given a set `A` of attributes, a set of functional dependencies `F` over `A`,
35/// a relation scheme `R ⊆ A`, and a set `K` of known candidate keys of `R`
36/// under `F`, determine whether `R` has a candidate key not in `K`.
37///
38/// A **candidate key** is a minimal subset `X ⊆ R` such that the closure of `X`
39/// under `F` contains all attributes of `R`.
40///
41/// # Representation
42///
43/// Each attribute in `R` has a binary variable: `x_i = 1` if the attribute is
44/// selected, `0` otherwise. A configuration is satisfying iff the selected
45/// attributes form a candidate key of `R` that is not in `K`.
46///
47/// # Example
48///
49/// ```
50/// use problemreductions::models::misc::AdditionalKey;
51/// use problemreductions::{Problem, BruteForce};
52///
53/// let problem = AdditionalKey::new(
54///     3,
55///     vec![(vec![0], vec![1, 2])],
56///     vec![0, 1, 2],
57///     vec![],
58/// );
59/// let solver = BruteForce::new();
60/// let solution = solver.solve(&problem).unwrap();
61/// assert!(solution.is_some());
62/// ```
63#[derive(Debug, Clone, Serialize, Deserialize)]
64#[serde(try_from = "AdditionalKeyData")]
65pub struct AdditionalKey {
66    num_attributes: usize,
67    dependencies: Vec<(Vec<usize>, Vec<usize>)>,
68    relation_attrs: Vec<usize>,
69    known_keys: Vec<Vec<usize>>,
70}
71
72#[derive(Deserialize)]
73struct AdditionalKeyData {
74    num_attributes: usize,
75    dependencies: Vec<(Vec<usize>, Vec<usize>)>,
76    relation_attrs: Vec<usize>,
77    known_keys: Vec<Vec<usize>>,
78}
79
80impl TryFrom<AdditionalKeyData> for AdditionalKey {
81    type Error = crate::registry::ConstructionError;
82    fn try_from(data: AdditionalKeyData) -> Result<Self, Self::Error> {
83        Self::try_new(
84            data.num_attributes,
85            data.dependencies,
86            data.relation_attrs,
87            data.known_keys,
88        )
89    }
90}
91
92impl AdditionalKey {
93    /// Create a new AdditionalKey instance.
94    ///
95    /// # Panics
96    ///
97    /// Panics if any attribute index is >= `num_attributes`, or if
98    /// `relation_attrs` contains duplicates.
99    pub fn new(
100        num_attributes: usize,
101        dependencies: Vec<(Vec<usize>, Vec<usize>)>,
102        relation_attrs: Vec<usize>,
103        known_keys: Vec<Vec<usize>>,
104    ) -> Self {
105        Self::try_new(num_attributes, dependencies, relation_attrs, known_keys)
106            .unwrap_or_else(|error| panic!("{error}"))
107    }
108
109    fn try_new(
110        num_attributes: usize,
111        dependencies: Vec<(Vec<usize>, Vec<usize>)>,
112        relation_attrs: Vec<usize>,
113        known_keys: Vec<Vec<usize>>,
114    ) -> Result<Self, crate::registry::ConstructionError> {
115        // Validate all attribute indices
116        for &a in &relation_attrs {
117            if a >= num_attributes {
118                return Err(format!(
119                    "relation_attrs element {a} >= num_attributes {num_attributes}"
120                )
121                .into());
122            }
123        }
124        // Validate relation_attrs uniqueness
125        let mut sorted_ra = relation_attrs.clone();
126        sorted_ra.sort_unstable();
127        sorted_ra.dedup();
128        if sorted_ra.len() != relation_attrs.len() {
129            return Err("relation_attrs contains duplicates".into());
130        }
131        for (lhs, rhs) in &dependencies {
132            for &a in lhs {
133                if a >= num_attributes {
134                    return Err(format!(
135                        "dependency lhs attribute {a} >= num_attributes {num_attributes}"
136                    )
137                    .into());
138                }
139            }
140            for &a in rhs {
141                if a >= num_attributes {
142                    return Err(format!(
143                        "dependency rhs attribute {a} >= num_attributes {num_attributes}"
144                    )
145                    .into());
146                }
147            }
148        }
149        for key in &known_keys {
150            for &a in key {
151                if a >= num_attributes {
152                    return Err(format!(
153                        "known_keys attribute {a} >= num_attributes {num_attributes}"
154                    )
155                    .into());
156                }
157            }
158        }
159        // Sort known_keys entries internally for consistent comparison
160        let known_keys: Vec<Vec<usize>> = known_keys
161            .into_iter()
162            .map(|mut k| {
163                k.sort_unstable();
164                k
165            })
166            .collect();
167        Ok(Self {
168            num_attributes,
169            dependencies,
170            relation_attrs,
171            known_keys,
172        })
173    }
174
175    /// Returns the number of attributes in the universal set A.
176    pub fn num_attributes(&self) -> usize {
177        self.num_attributes
178    }
179
180    /// Returns the number of functional dependencies.
181    pub fn num_dependencies(&self) -> usize {
182        self.dependencies.len()
183    }
184
185    /// Returns the number of attributes in the relation scheme R.
186    pub fn num_relation_attrs(&self) -> usize {
187        self.relation_attrs.len()
188    }
189
190    /// Returns the number of known candidate keys.
191    pub fn num_known_keys(&self) -> usize {
192        self.known_keys.len()
193    }
194
195    /// Returns the functional dependencies.
196    pub fn dependencies(&self) -> &[(Vec<usize>, Vec<usize>)] {
197        &self.dependencies
198    }
199
200    /// Returns the relation scheme attributes.
201    pub fn relation_attrs(&self) -> &[usize] {
202        &self.relation_attrs
203    }
204
205    /// Returns the known candidate keys.
206    pub fn known_keys(&self) -> &[Vec<usize>] {
207        &self.known_keys
208    }
209
210    /// Compute the closure of a set of attributes under the functional dependencies.
211    fn compute_closure(&self, attrs: &[bool]) -> Vec<bool> {
212        let mut closure = attrs.to_vec();
213        let mut changed = true;
214        while changed {
215            changed = false;
216            for (lhs, rhs) in &self.dependencies {
217                if lhs.iter().all(|&a| closure[a]) {
218                    for &a in rhs {
219                        if !closure[a] {
220                            closure[a] = true;
221                            changed = true;
222                        }
223                    }
224                }
225            }
226        }
227        closure
228    }
229}
230
231impl Problem for AdditionalKey {
232    const NAME: &'static str = "AdditionalKey";
233    type Solution = Vec<bool>;
234    type Value = crate::types::Or;
235
236    crate::problem_parameters![
237        ("num_attributes", num_attributes),
238        ("num_dependencies", num_dependencies),
239        ("num_relation_attrs", num_relation_attrs),
240        ("num_known_keys", num_known_keys),
241    ];
242
243    fn variant() -> Vec<(&'static str, &'static str)> {
244        crate::variant_params![]
245    }
246
247    fn evaluate(
248        &self,
249        config: &Self::Solution,
250    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
251        Ok({
252            crate::types::Or({
253                // Check config length
254                if config.len() != self.relation_attrs.len() {
255                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
256                        "attribute-selection length does not match the relation".into(),
257                    ));
258                }
259                // Check all values are 0 or 1
260                // Build selected attribute set
261                let selected: Vec<usize> = config
262                    .iter()
263                    .enumerate()
264                    .filter(|(_, &v)| v)
265                    .map(|(i, _)| self.relation_attrs[i])
266                    .collect();
267
268                // Empty selection is not a key
269                if selected.is_empty() {
270                    return Ok(crate::types::Or(false));
271                }
272
273                // Compute closure of selected attributes
274                let mut attr_set = vec![false; self.num_attributes];
275                for &a in &selected {
276                    attr_set[a] = true;
277                }
278                let closure = self.compute_closure(&attr_set);
279
280                // Check closure covers all relation_attrs
281                if !self.relation_attrs.iter().all(|&a| closure[a]) {
282                    return Ok(crate::types::Or(false));
283                }
284
285                // Check minimality: removing any single selected attribute should break coverage
286                for &a in &selected {
287                    let mut reduced = attr_set.clone();
288                    reduced[a] = false;
289                    let reduced_closure = self.compute_closure(&reduced);
290                    if self.relation_attrs.iter().all(|&ra| reduced_closure[ra]) {
291                        return Ok(crate::types::Or(false)); // Not minimal
292                    }
293                }
294
295                // Build sorted selected vec and check it's not in known_keys
296                let mut sorted_selected = selected;
297                sorted_selected.sort_unstable();
298                !self.known_keys.contains(&sorted_selected)
299            })
300        })
301    }
302}
303
304impl crate::solvers::BruteForceProblem for AdditionalKey {
305    fn dimensions(&self) -> Vec<usize> {
306        vec![2; self.relation_attrs.len()]
307    }
308}
309
310crate::declare_variants! {
311    default AdditionalKey => "2^num_relation_attrs * num_dependencies * num_attributes",
312}
313
314crate::register_brute_force! {
315    AdditionalKey decode |_, indices: Vec<usize>| crate::config::config_to_bits(&indices),
316}
317
318#[cfg(feature = "example-db")]
319pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
320    vec![crate::example_db::specs::ModelExampleSpec {
321        id: "additional_key",
322        instance: Box::new(AdditionalKey::new(
323            6,
324            vec![
325                (vec![0, 1], vec![2, 3]),
326                (vec![2, 3], vec![4, 5]),
327                (vec![4, 5], vec![0, 1]),
328                (vec![0, 2], vec![3]),
329                (vec![3, 5], vec![1]),
330            ],
331            vec![0, 1, 2, 3, 4, 5],
332            vec![vec![0, 1], vec![2, 3], vec![4, 5]],
333        )),
334        optimal_config: serde_json::json!(vec![true, false, true, false, false, false]),
335        optimal_value: serde_json::json!(true),
336    }]
337}
338
339#[cfg(test)]
340#[path = "../../unit_tests/models/misc/additional_key.rs"]
341mod tests;