Skip to main content

problemreductions/models/formula/
qbf.rs

1//! Quantified Boolean Formulas (QBF) problem implementation.
2//!
3//! QBF is the problem of determining whether a fully quantified Boolean formula
4//! with alternating universal and existential quantifiers is true. It is the
5//! canonical PSPACE-complete problem (Stockmeyer & Meyer, 1973).
6//!
7//! Given F = (Q_1 u_1)(Q_2 u_2)...(Q_n u_n) E, where each Q_i is either
8//! ∀ (ForAll) or ∃ (Exists) and E is a Boolean expression in CNF,
9//! determine whether F is true.
10
11use crate::models::formula::CNFClause;
12use crate::registry::{FieldInfo, ProblemSchemaEntry};
13use crate::traits::Problem;
14use serde::{Deserialize, Serialize};
15
16inventory::submit! {
17    ProblemSchemaEntry {
18        name: "QuantifiedBooleanFormulas",
19        display_name: "Quantified Boolean Formulas",
20        aliases: &["QBF"],
21        dimensions: &[],
22        module_path: module_path!(),
23        description: "Determine if a quantified Boolean formula is true",
24        fields: &[
25            FieldInfo { name: "num_vars", type_name: "usize", description: "Number of Boolean variables" },
26            FieldInfo { name: "quantifiers", type_name: "Vec<Quantifier>", description: "Quantifier for each variable (Exists or ForAll)" },
27            FieldInfo { name: "clauses", type_name: "Vec<CNFClause>", description: "CNF clauses of the Boolean expression E" },
28        ],
29    }
30}
31
32/// Quantifier type for QBF variables.
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
34pub enum Quantifier {
35    /// Existential quantifier (∃)
36    Exists,
37    /// Universal quantifier (∀)
38    ForAll,
39}
40
41/// Quantified Boolean Formulas (QBF) problem.
42///
43/// Given a fully quantified Boolean formula F = (Q_1 u_1)...(Q_n u_n) E,
44/// where each Q_i is ∀ or ∃ and E is in CNF, determine whether F is true.
45///
46/// # Example
47///
48/// ```
49/// use problemreductions::models::formula::{QuantifiedBooleanFormulas, Quantifier, CNFClause};
50/// use problemreductions::Problem;
51///
52/// // F = ∃u_1 ∀u_2 (u_1 ∨ u_2) ∧ (u_1 ∨ ¬u_2)
53/// let problem = QuantifiedBooleanFormulas::new(
54///     2,
55///     vec![Quantifier::Exists, Quantifier::ForAll],
56///     vec![
57///         CNFClause::new(vec![1, 2]),   // u_1 OR u_2
58///         CNFClause::new(vec![1, -2]),  // u_1 OR NOT u_2
59///     ],
60/// );
61///
62/// // With u_1=true, both clauses are satisfied regardless of u_2
63/// assert!(problem.is_true());
64/// ```
65#[derive(Debug, Clone, Serialize, Deserialize)]
66pub struct QuantifiedBooleanFormulas {
67    /// Number of variables.
68    num_vars: usize,
69    /// Quantifier for each variable (indexed 0..num_vars).
70    quantifiers: Vec<Quantifier>,
71    /// Clauses in CNF representing the Boolean expression E.
72    clauses: Vec<CNFClause>,
73}
74
75impl QuantifiedBooleanFormulas {
76    /// Create a new QBF problem.
77    ///
78    /// # Panics
79    ///
80    /// Panics if `quantifiers.len() != num_vars`.
81    pub fn new(num_vars: usize, quantifiers: Vec<Quantifier>, clauses: Vec<CNFClause>) -> Self {
82        assert_eq!(
83            quantifiers.len(),
84            num_vars,
85            "quantifiers length ({}) must equal num_vars ({})",
86            quantifiers.len(),
87            num_vars
88        );
89        Self {
90            num_vars,
91            quantifiers,
92            clauses,
93        }
94    }
95
96    /// Get the number of variables.
97    pub fn num_vars(&self) -> usize {
98        self.num_vars
99    }
100
101    /// Get the number of clauses.
102    pub fn num_clauses(&self) -> usize {
103        self.clauses.len()
104    }
105
106    /// Get the quantifiers.
107    pub fn quantifiers(&self) -> &[Quantifier] {
108        &self.quantifiers
109    }
110
111    /// Get the clauses.
112    pub fn clauses(&self) -> &[CNFClause] {
113        &self.clauses
114    }
115
116    /// Evaluate whether the QBF formula is true using game-tree search.
117    ///
118    /// This implements a recursive minimax-style evaluation:
119    /// - For ∃ quantifiers: true if ANY assignment to the variable leads to true
120    /// - For ∀ quantifiers: true if ALL assignments to the variable lead to true
121    ///
122    /// Runtime is O(2^n) in the worst case.
123    pub fn is_true(&self) -> bool {
124        let mut assignment = vec![false; self.num_vars];
125        self.evaluate_recursive(&mut assignment, 0)
126    }
127
128    /// Recursive QBF evaluation.
129    fn evaluate_recursive(&self, assignment: &mut Vec<bool>, var_idx: usize) -> bool {
130        if var_idx == self.num_vars {
131            // All variables assigned — evaluate the CNF matrix
132            return self.clauses.iter().all(|c| c.is_satisfied(assignment));
133        }
134
135        match self.quantifiers[var_idx] {
136            Quantifier::Exists => {
137                // Try both values; true if either works
138                assignment[var_idx] = false;
139                if self.evaluate_recursive(assignment, var_idx + 1) {
140                    return true;
141                }
142                assignment[var_idx] = true;
143                self.evaluate_recursive(assignment, var_idx + 1)
144            }
145            Quantifier::ForAll => {
146                // Try both values; true only if both work
147                assignment[var_idx] = false;
148                if !self.evaluate_recursive(assignment, var_idx + 1) {
149                    return false;
150                }
151                assignment[var_idx] = true;
152                self.evaluate_recursive(assignment, var_idx + 1)
153            }
154        }
155    }
156}
157
158impl Problem for QuantifiedBooleanFormulas {
159    const NAME: &'static str = "QuantifiedBooleanFormulas";
160    type Value = crate::types::Or;
161
162    fn dims(&self) -> Vec<usize> {
163        vec![]
164    }
165
166    fn evaluate(&self, config: &[usize]) -> crate::types::Or {
167        crate::types::Or({
168            if !config.is_empty() {
169                return crate::types::Or(false);
170            }
171            self.is_true()
172        })
173    }
174
175    fn variant() -> Vec<(&'static str, &'static str)> {
176        crate::variant_params![]
177    }
178}
179
180crate::declare_variants! {
181    default QuantifiedBooleanFormulas => "2^num_vars",
182}
183
184#[cfg(feature = "example-db")]
185pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
186    vec![crate::example_db::specs::ModelExampleSpec {
187        id: "quantified_boolean_formulas",
188        instance: Box::new(QuantifiedBooleanFormulas::new(
189            2,
190            vec![Quantifier::Exists, Quantifier::ForAll],
191            vec![
192                CNFClause::new(vec![1, 2]),  // u_1 OR u_2
193                CNFClause::new(vec![1, -2]), // u_1 OR NOT u_2
194            ],
195        )),
196        optimal_config: vec![],
197        optimal_value: serde_json::json!(true),
198    }]
199}
200
201#[cfg(test)]
202#[path = "../../unit_tests/models/formula/qbf.rs"]
203mod tests;