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)]
64pub struct AdditionalKey {
65    num_attributes: usize,
66    dependencies: Vec<(Vec<usize>, Vec<usize>)>,
67    relation_attrs: Vec<usize>,
68    known_keys: Vec<Vec<usize>>,
69}
70
71impl AdditionalKey {
72    /// Create a new AdditionalKey instance.
73    ///
74    /// # Panics
75    ///
76    /// Panics if any attribute index is >= `num_attributes`, or if
77    /// `relation_attrs` contains duplicates.
78    pub fn new(
79        num_attributes: usize,
80        dependencies: Vec<(Vec<usize>, Vec<usize>)>,
81        relation_attrs: Vec<usize>,
82        known_keys: Vec<Vec<usize>>,
83    ) -> Self {
84        // Validate all attribute indices
85        for &a in &relation_attrs {
86            assert!(
87                a < num_attributes,
88                "relation_attrs element {a} >= num_attributes {num_attributes}"
89            );
90        }
91        // Validate relation_attrs uniqueness
92        let mut sorted_ra = relation_attrs.clone();
93        sorted_ra.sort_unstable();
94        sorted_ra.dedup();
95        assert_eq!(
96            sorted_ra.len(),
97            relation_attrs.len(),
98            "relation_attrs contains duplicates"
99        );
100        for (lhs, rhs) in &dependencies {
101            for &a in lhs {
102                assert!(
103                    a < num_attributes,
104                    "dependency lhs attribute {a} >= num_attributes {num_attributes}"
105                );
106            }
107            for &a in rhs {
108                assert!(
109                    a < num_attributes,
110                    "dependency rhs attribute {a} >= num_attributes {num_attributes}"
111                );
112            }
113        }
114        for key in &known_keys {
115            for &a in key {
116                assert!(
117                    a < num_attributes,
118                    "known_keys attribute {a} >= num_attributes {num_attributes}"
119                );
120            }
121        }
122        // Sort known_keys entries internally for consistent comparison
123        let known_keys: Vec<Vec<usize>> = known_keys
124            .into_iter()
125            .map(|mut k| {
126                k.sort_unstable();
127                k
128            })
129            .collect();
130        Self {
131            num_attributes,
132            dependencies,
133            relation_attrs,
134            known_keys,
135        }
136    }
137
138    /// Returns the number of attributes in the universal set A.
139    pub fn num_attributes(&self) -> usize {
140        self.num_attributes
141    }
142
143    /// Returns the number of functional dependencies.
144    pub fn num_dependencies(&self) -> usize {
145        self.dependencies.len()
146    }
147
148    /// Returns the number of attributes in the relation scheme R.
149    pub fn num_relation_attrs(&self) -> usize {
150        self.relation_attrs.len()
151    }
152
153    /// Returns the number of known candidate keys.
154    pub fn num_known_keys(&self) -> usize {
155        self.known_keys.len()
156    }
157
158    /// Returns the functional dependencies.
159    pub fn dependencies(&self) -> &[(Vec<usize>, Vec<usize>)] {
160        &self.dependencies
161    }
162
163    /// Returns the relation scheme attributes.
164    pub fn relation_attrs(&self) -> &[usize] {
165        &self.relation_attrs
166    }
167
168    /// Returns the known candidate keys.
169    pub fn known_keys(&self) -> &[Vec<usize>] {
170        &self.known_keys
171    }
172
173    /// Compute the closure of a set of attributes under the functional dependencies.
174    fn compute_closure(&self, attrs: &[bool]) -> Vec<bool> {
175        let mut closure = attrs.to_vec();
176        let mut changed = true;
177        while changed {
178            changed = false;
179            for (lhs, rhs) in &self.dependencies {
180                if lhs.iter().all(|&a| closure[a]) {
181                    for &a in rhs {
182                        if !closure[a] {
183                            closure[a] = true;
184                            changed = true;
185                        }
186                    }
187                }
188            }
189        }
190        closure
191    }
192}
193
194impl Problem for AdditionalKey {
195    const NAME: &'static str = "AdditionalKey";
196    type Solution = Vec<bool>;
197    type Value = crate::types::Or;
198
199    crate::problem_parameters![
200        ("num_attributes", num_attributes),
201        ("num_dependencies", num_dependencies),
202        ("num_relation_attrs", num_relation_attrs),
203        ("num_known_keys", num_known_keys),
204    ];
205
206    fn variant() -> Vec<(&'static str, &'static str)> {
207        crate::variant_params![]
208    }
209
210    fn evaluate(
211        &self,
212        config: &Self::Solution,
213    ) -> Result<crate::types::Or, crate::traits::EvaluationError> {
214        Ok({
215            crate::types::Or({
216                // Check config length
217                if config.len() != self.relation_attrs.len() {
218                    return Err(crate::traits::EvaluationError::InvalidConfiguration(
219                        "attribute-selection length does not match the relation".into(),
220                    ));
221                }
222                // Check all values are 0 or 1
223                // Build selected attribute set
224                let selected: Vec<usize> = config
225                    .iter()
226                    .enumerate()
227                    .filter(|(_, &v)| v)
228                    .map(|(i, _)| self.relation_attrs[i])
229                    .collect();
230
231                // Empty selection is not a key
232                if selected.is_empty() {
233                    return Ok(crate::types::Or(false));
234                }
235
236                // Compute closure of selected attributes
237                let mut attr_set = vec![false; self.num_attributes];
238                for &a in &selected {
239                    attr_set[a] = true;
240                }
241                let closure = self.compute_closure(&attr_set);
242
243                // Check closure covers all relation_attrs
244                if !self.relation_attrs.iter().all(|&a| closure[a]) {
245                    return Ok(crate::types::Or(false));
246                }
247
248                // Check minimality: removing any single selected attribute should break coverage
249                for &a in &selected {
250                    let mut reduced = attr_set.clone();
251                    reduced[a] = false;
252                    let reduced_closure = self.compute_closure(&reduced);
253                    if self.relation_attrs.iter().all(|&ra| reduced_closure[ra]) {
254                        return Ok(crate::types::Or(false)); // Not minimal
255                    }
256                }
257
258                // Build sorted selected vec and check it's not in known_keys
259                let mut sorted_selected = selected;
260                sorted_selected.sort_unstable();
261                !self.known_keys.contains(&sorted_selected)
262            })
263        })
264    }
265}
266
267impl crate::solvers::BruteForceProblem for AdditionalKey {
268    fn dimensions(&self) -> Vec<usize> {
269        vec![2; self.relation_attrs.len()]
270    }
271}
272
273crate::declare_variants! {
274    default AdditionalKey => "2^num_relation_attrs * num_dependencies * num_attributes",
275}
276
277crate::register_brute_force! {
278    AdditionalKey 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: "additional_key",
285        instance: Box::new(AdditionalKey::new(
286            6,
287            vec![
288                (vec![0, 1], vec![2, 3]),
289                (vec![2, 3], vec![4, 5]),
290                (vec![4, 5], vec![0, 1]),
291                (vec![0, 2], vec![3]),
292                (vec![3, 5], vec![1]),
293            ],
294            vec![0, 1, 2, 3, 4, 5],
295            vec![vec![0, 1], vec![2, 3], vec![4, 5]],
296        )),
297        optimal_config: serde_json::json!(vec![true, false, true, false, false, false]),
298        optimal_value: serde_json::json!(true),
299    }]
300}
301
302#[cfg(test)]
303#[path = "../../unit_tests/models/misc/additional_key.rs"]
304mod tests;