Skip to main content

problemreductions/models/misc/
minimum_axiom_set.rs

1//! Minimum Axiom Set problem implementation.
2//!
3//! Given a finite set of sentences S, a subset T ⊆ S of true sentences, and a set
4//! of implications (where each implication has a set of antecedent sentences and a
5//! single consequent sentence), find a smallest subset S₀ ⊆ T such that the
6//! deductive closure of S₀ under the implications equals T.
7
8use crate::registry::{FieldInfo, ProblemSchemaEntry};
9use crate::traits::Problem;
10use crate::types::Min;
11use serde::{Deserialize, Serialize};
12
13inventory::submit! {
14    ProblemSchemaEntry {
15        name: "MinimumAxiomSet",
16        display_name: "Minimum Axiom Set",
17        aliases: &[],
18        dimensions: &[],
19        module_path: module_path!(),
20        description: "Find smallest axiom subset whose deductive closure equals the true sentences",
21        fields: &[
22            FieldInfo { name: "num_sentences", type_name: "usize", description: "Total number of sentences |S|" },
23            FieldInfo { name: "true_sentences", type_name: "Vec<usize>", description: "Indices of true sentences T ⊆ S" },
24            FieldInfo { name: "implications", type_name: "Vec<(Vec<usize>, usize)>", description: "Implication rules (antecedents, consequent)" },
25        ],
26    }
27}
28
29/// The Minimum Axiom Set problem.
30///
31/// Given a set of sentences `S = {0, ..., num_sentences - 1}`, a subset
32/// `T ⊆ S` of true sentences, and a list of implications where each
33/// implication `(A, c)` means "if all sentences in A hold, then c holds",
34/// find a smallest subset `S₀ ⊆ T` whose deductive closure under the
35/// implications equals `T`.
36///
37/// # Representation
38///
39/// Each true sentence has a binary variable: `config[i] = 1` if
40/// `true_sentences[i]` is selected as an axiom, `0` otherwise.
41/// The configuration space is `vec![2; |T|]`.
42///
43/// # Example
44///
45/// ```
46/// use problemreductions::models::misc::MinimumAxiomSet;
47/// use problemreductions::{Problem, Solver, BruteForce};
48///
49/// // 8 sentences, all true, with implications forming a cycle
50/// let problem = MinimumAxiomSet::new(
51///     8,
52///     vec![0, 1, 2, 3, 4, 5, 6, 7],
53///     vec![
54///         (vec![0], 2), (vec![0], 3),
55///         (vec![1], 4), (vec![1], 5),
56///         (vec![2, 4], 6), (vec![3, 5], 7),
57///         (vec![6, 7], 0), (vec![6, 7], 1),
58///     ],
59/// );
60/// let solver = BruteForce::new();
61/// let solution = solver.find_witness(&problem);
62/// assert!(solution.is_some());
63/// ```
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct MinimumAxiomSet {
66    /// Total number of sentences |S|.
67    num_sentences: usize,
68    /// Indices of true sentences T ⊆ S.
69    true_sentences: Vec<usize>,
70    /// Implication rules: each (antecedents, consequent).
71    implications: Vec<(Vec<usize>, usize)>,
72}
73
74impl MinimumAxiomSet {
75    /// Create a new Minimum Axiom Set instance.
76    ///
77    /// # Panics
78    ///
79    /// Panics if any true sentence index is out of range,
80    /// if true sentences contain duplicates,
81    /// or if any implication references a sentence outside S.
82    pub fn new(
83        num_sentences: usize,
84        true_sentences: Vec<usize>,
85        implications: Vec<(Vec<usize>, usize)>,
86    ) -> Self {
87        // Validate true sentences
88        for &s in &true_sentences {
89            assert!(
90                s < num_sentences,
91                "True sentence index {s} out of range [0, {num_sentences})"
92            );
93        }
94        // Check no duplicates
95        let mut seen = vec![false; num_sentences];
96        for &s in &true_sentences {
97            assert!(!seen[s], "Duplicate true sentence index {s}");
98            seen[s] = true;
99        }
100        // Validate implications
101        for (antecedents, consequent) in &implications {
102            for &a in antecedents {
103                assert!(
104                    a < num_sentences,
105                    "Implication antecedent {a} out of range [0, {num_sentences})"
106                );
107            }
108            assert!(
109                *consequent < num_sentences,
110                "Implication consequent {consequent} out of range [0, {num_sentences})"
111            );
112        }
113        Self {
114            num_sentences,
115            true_sentences,
116            implications,
117        }
118    }
119
120    /// Returns the total number of sentences |S|.
121    pub fn num_sentences(&self) -> usize {
122        self.num_sentences
123    }
124
125    /// Returns the number of true sentences |T|.
126    pub fn num_true_sentences(&self) -> usize {
127        self.true_sentences.len()
128    }
129
130    /// Returns the number of implications.
131    pub fn num_implications(&self) -> usize {
132        self.implications.len()
133    }
134
135    /// Returns the true sentence indices.
136    pub fn true_sentences(&self) -> &[usize] {
137        &self.true_sentences
138    }
139
140    /// Returns the implications.
141    pub fn implications(&self) -> &[(Vec<usize>, usize)] {
142        &self.implications
143    }
144}
145
146/// Compute the deductive closure of a set of sentences under the given implications.
147///
148/// Starting from `current`, repeatedly applies implications until a fixpoint.
149fn deductive_closure(current: &mut [bool], implications: &[(Vec<usize>, usize)]) {
150    loop {
151        let mut changed = false;
152        for (antecedents, consequent) in implications {
153            if !current[*consequent] && antecedents.iter().all(|&a| current[a]) {
154                current[*consequent] = true;
155                changed = true;
156            }
157        }
158        if !changed {
159            break;
160        }
161    }
162}
163
164impl Problem for MinimumAxiomSet {
165    const NAME: &'static str = "MinimumAxiomSet";
166    type Value = Min<usize>;
167
168    fn variant() -> Vec<(&'static str, &'static str)> {
169        crate::variant_params![]
170    }
171
172    fn dims(&self) -> Vec<usize> {
173        vec![2; self.num_true_sentences()]
174    }
175
176    fn evaluate(&self, config: &[usize]) -> Min<usize> {
177        if config.len() != self.num_true_sentences() {
178            return Min(None);
179        }
180        if config.iter().any(|&v| v >= 2) {
181            return Min(None);
182        }
183
184        // Build the initial set of selected axioms
185        let mut current = vec![false; self.num_sentences];
186        let mut count = 0usize;
187        for (i, &v) in config.iter().enumerate() {
188            if v == 1 {
189                current[self.true_sentences[i]] = true;
190                count += 1;
191            }
192        }
193
194        // Compute deductive closure
195        deductive_closure(&mut current, &self.implications);
196
197        // Check if closure equals T
198        let closure_equals_t = self.true_sentences.iter().all(|&s| current[s]);
199
200        if closure_equals_t {
201            Min(Some(count))
202        } else {
203            Min(None)
204        }
205    }
206}
207
208crate::declare_variants! {
209    default MinimumAxiomSet => "2^num_true_sentences",
210}
211
212#[cfg(feature = "example-db")]
213pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
214    // 8 sentences, all true, with implications forming a cycle
215    // Optimal: select {a, b} (indices 0, 1) → closure = all 8
216    vec![crate::example_db::specs::ModelExampleSpec {
217        id: "minimum_axiom_set",
218        instance: Box::new(MinimumAxiomSet::new(
219            8,
220            vec![0, 1, 2, 3, 4, 5, 6, 7],
221            vec![
222                (vec![0], 2),
223                (vec![0], 3),
224                (vec![1], 4),
225                (vec![1], 5),
226                (vec![2, 4], 6),
227                (vec![3, 5], 7),
228                (vec![6, 7], 0),
229                (vec![6, 7], 1),
230            ],
231        )),
232        optimal_config: vec![1, 1, 0, 0, 0, 0, 0, 0],
233        optimal_value: serde_json::json!(2),
234    }]
235}
236
237#[cfg(test)]
238#[path = "../../unit_tests/models/misc/minimum_axiom_set.rs"]
239mod tests;