Skip to main content

problemreductions/solvers/customized/
solver.rs

1//! CustomizedSolver: structure-exploiting exact witness solver.
2//!
3//! Uses direct downcast dispatch to call dedicated backends for
4//! supported problem types, returning `None` for unsupported problems.
5
6use super::fd_subset_search::{
7    self, compute_closure, find_essential_attributes, find_essential_attributes_restricted,
8    is_minimal_key, is_superkey, BranchDecision,
9};
10use crate::models::graph::{PartialFeedbackEdgeSet, RootedTreeArrangement};
11use crate::models::misc::{AdditionalKey, BoyceCoddNormalFormViolation};
12use crate::models::set::{MinimumCardinalityKey, PrimeAttributeName};
13use crate::topology::SimpleGraph;
14use std::collections::HashSet;
15
16/// A solver that uses problem-specific backends for exact witness recovery.
17///
18/// Unlike `BruteForce`, which enumerates all configurations, `CustomizedSolver`
19/// exploits problem structure (functional-dependency closure, cycle hitting,
20/// tree arrangement) to prune search and find witnesses more efficiently.
21///
22/// Returns `None` for unsupported problem types.
23#[derive(Default)]
24pub struct CustomizedSolver;
25
26impl CustomizedSolver {
27    /// Create a new `CustomizedSolver`.
28    pub fn new() -> Self {
29        Self
30    }
31
32    /// Check whether a type-erased problem is supported by the customized solver.
33    pub fn supports_problem(any: &dyn std::any::Any) -> bool {
34        any.is::<MinimumCardinalityKey>()
35            || any.is::<AdditionalKey>()
36            || any.is::<PrimeAttributeName>()
37            || any.is::<BoyceCoddNormalFormViolation>()
38            || any.is::<PartialFeedbackEdgeSet<SimpleGraph>>()
39            || any.is::<RootedTreeArrangement<SimpleGraph>>()
40    }
41
42    /// Attempt to solve a type-erased problem using a dedicated backend.
43    ///
44    /// Returns `Some(config)` if a satisfying witness is found, `None` if
45    /// the problem type is unsupported or no witness exists.
46    pub fn solve_dyn(&self, any: &dyn std::any::Any) -> Option<Vec<usize>> {
47        if let Some(p) = any.downcast_ref::<MinimumCardinalityKey>() {
48            return solve_minimum_cardinality_key(p);
49        }
50        if let Some(p) = any.downcast_ref::<AdditionalKey>() {
51            return solve_additional_key(p);
52        }
53        if let Some(p) = any.downcast_ref::<PrimeAttributeName>() {
54            return solve_prime_attribute_name(p);
55        }
56        if let Some(p) = any.downcast_ref::<BoyceCoddNormalFormViolation>() {
57            return solve_bcnf_violation(p);
58        }
59        if let Some(p) = any.downcast_ref::<PartialFeedbackEdgeSet<SimpleGraph>>() {
60            return super::partial_feedback_edge_set::find_witness(p);
61        }
62        if let Some(p) = any.downcast_ref::<RootedTreeArrangement<SimpleGraph>>() {
63            return super::rooted_tree_arrangement::find_witness(p);
64        }
65        None
66    }
67}
68
69/// Solve MinimumCardinalityKey: find a minimal key with smallest cardinality.
70///
71/// Uses iterative deepening by cardinality to guarantee the first solution
72/// found has the minimum number of attributes.
73fn solve_minimum_cardinality_key(problem: &MinimumCardinalityKey) -> Option<Vec<usize>> {
74    let n = problem.num_attributes();
75    let deps = problem.dependencies().to_vec();
76
77    let essential = find_essential_attributes(n, &deps);
78    let essential_count = essential.len();
79
80    // Build branch order: non-essential attributes
81    let essential_set: HashSet<usize> = essential.iter().copied().collect();
82    let branch_order: Vec<usize> = (0..n).filter(|i| !essential_set.contains(i)).collect();
83
84    // Iterative deepening: try smallest cardinality first
85    for max_total in essential_count..=n {
86        let result = fd_subset_search::search_fd_subset(
87            n,
88            &essential,
89            &branch_order,
90            |selected, _depth| {
91                let count = selected.iter().filter(|&&v| v).count();
92                if count > max_total {
93                    BranchDecision::Prune
94                } else {
95                    BranchDecision::Continue
96                }
97            },
98            |selected| {
99                selected.iter().filter(|&&v| v).count() == max_total
100                    && is_minimal_key(selected, &deps)
101            },
102        );
103
104        if let Some(indices) = result {
105            let mut config = vec![0; n];
106            for i in indices {
107                config[i] = 1;
108            }
109            return Some(config);
110        }
111    }
112    None
113}
114
115/// Solve AdditionalKey: find a candidate key not in the known set.
116fn solve_additional_key(problem: &AdditionalKey) -> Option<Vec<usize>> {
117    let n_attrs = problem.num_attributes();
118    let deps = problem.dependencies().to_vec();
119    let relation_attrs = problem.relation_attrs();
120    let known_keys = problem.known_keys();
121
122    let essential = find_essential_attributes_restricted(n_attrs, &deps, relation_attrs);
123
124    // Build branch order over relation_attrs indices (excluding essential)
125    let essential_set: HashSet<usize> = essential.iter().copied().collect();
126    let branch_indices: Vec<usize> = relation_attrs
127        .iter()
128        .copied()
129        .filter(|a| !essential_set.contains(a))
130        .collect();
131
132    // We search over a boolean vector of size n_attrs
133    let result = fd_subset_search::search_fd_subset(
134        n_attrs,
135        &essential,
136        &branch_indices,
137        |_selected, _depth| BranchDecision::Continue,
138        |selected| {
139            // Check that selected forms a superkey over relation_attrs
140            let closure = compute_closure(selected, &deps);
141            if !relation_attrs.iter().all(|&a| closure[a]) {
142                return false;
143            }
144            // Check minimality: removing any selected relation_attr breaks coverage
145            let selected_ra: Vec<usize> = relation_attrs
146                .iter()
147                .copied()
148                .filter(|&a| selected[a])
149                .collect();
150            if selected_ra.is_empty() {
151                return false;
152            }
153            for &a in &selected_ra {
154                let mut reduced = selected.to_vec();
155                reduced[a] = false;
156                let reduced_closure = compute_closure(&reduced, &deps);
157                if relation_attrs.iter().all(|&ra| reduced_closure[ra]) {
158                    return false; // Not minimal
159                }
160            }
161            // Check it's not in known_keys
162            let mut sorted_selected: Vec<usize> = selected_ra;
163            sorted_selected.sort_unstable();
164            !known_keys.contains(&sorted_selected)
165        },
166    );
167
168    // Convert to config format (binary vector over relation_attrs positions)
169    result.map(|indices| {
170        let index_set: HashSet<usize> = indices.into_iter().collect();
171        relation_attrs
172            .iter()
173            .map(|&attr| if index_set.contains(&attr) { 1 } else { 0 })
174            .collect()
175    })
176}
177
178/// Solve PrimeAttributeName: find a candidate key containing the query attribute.
179fn solve_prime_attribute_name(problem: &PrimeAttributeName) -> Option<Vec<usize>> {
180    let n = problem.num_attributes();
181    let deps = problem.dependencies().to_vec();
182    let query = problem.query_attribute();
183
184    let essential = find_essential_attributes(n, &deps);
185
186    // Query attribute must be forcibly included
187    let mut forced: Vec<usize> = essential.clone();
188    if !forced.contains(&query) {
189        forced.push(query);
190    }
191    forced.sort_unstable();
192    forced.dedup();
193
194    let forced_set: HashSet<usize> = forced.iter().copied().collect();
195    let branch_order: Vec<usize> = (0..n).filter(|i| !forced_set.contains(i)).collect();
196
197    let result = fd_subset_search::search_fd_subset(
198        n,
199        &forced,
200        &branch_order,
201        |selected, _depth| {
202            // Early superkey check: if already a superkey, try to check completeness
203            if is_superkey(selected, &deps) {
204                // Even if already superkey, we want to continue to minimality check
205                return BranchDecision::Continue;
206            }
207            BranchDecision::Continue
208        },
209        |selected| selected[query] && is_minimal_key(selected, &deps),
210    );
211
212    result.map(|indices| {
213        let mut config = vec![0; n];
214        for i in indices {
215            config[i] = 1;
216        }
217        config
218    })
219}
220
221/// Solve BoyceCoddNormalFormViolation: find a subset X of target_subset such that
222/// the closure of X contains some but not all of target_subset \ X.
223fn solve_bcnf_violation(problem: &BoyceCoddNormalFormViolation) -> Option<Vec<usize>> {
224    let n_attrs = problem.num_attributes();
225    let deps = problem.functional_deps().to_vec();
226    let target = problem.target_subset();
227
228    // Branch over target_subset attributes
229    let branch_order: Vec<usize> = target.to_vec();
230
231    let result = fd_subset_search::search_fd_subset(
232        n_attrs,
233        &[],
234        &branch_order,
235        |_selected, _depth| BranchDecision::Continue,
236        |selected| {
237            let x: HashSet<usize> = target.iter().copied().filter(|&a| selected[a]).collect();
238            let closure = compute_closure(selected, &deps);
239            // Check: ∃ y, z ∈ target \ X s.t. y ∈ closure ∧ z ∉ closure
240            let mut has_in_closure = false;
241            let mut has_not_in_closure = false;
242            for &a in target {
243                if !x.contains(&a) {
244                    if closure[a] {
245                        has_in_closure = true;
246                    } else {
247                        has_not_in_closure = true;
248                    }
249                }
250            }
251            has_in_closure && has_not_in_closure
252        },
253    );
254
255    // Convert: binary vector over target_subset positions
256    result.map(|indices| {
257        let index_set: HashSet<usize> = indices.into_iter().collect();
258        target
259            .iter()
260            .map(|&attr| if index_set.contains(&attr) { 1 } else { 0 })
261            .collect()
262    })
263}
264
265#[cfg(test)]
266#[path = "../../unit_tests/solvers/customized/solver.rs"]
267mod tests;