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