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