Skip to main content

fixture3_ddmin/
types.rs

1use std::num::NonZeroUsize;
2
3/// Input for one `DDMin` reduction run.
4#[derive(Debug, Clone, PartialEq, Eq)]
5pub struct DdminInput<C> {
6    candidates: Vec<C>,
7    options: DdminOptions,
8}
9
10impl<C> DdminInput<C> {
11    /// Create a `DDMin` input from ordered candidates and execution options.
12    #[must_use]
13    pub const fn new(candidates: Vec<C>, options: DdminOptions) -> Self {
14        Self { candidates, options }
15    }
16
17    pub(crate) const fn options(&self) -> DdminOptions {
18        self.options
19    }
20
21    pub(crate) fn into_candidates(self) -> Vec<C> {
22        self.candidates
23    }
24}
25
26/// Execution options for `DDMin`.
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub struct DdminOptions {
29    initial_granularity: NonZeroUsize,
30    max_oracle_calls: Option<usize>,
31}
32
33impl DdminOptions {
34    /// Create execution options.
35    ///
36    /// `initial_granularity` controls the first split count. Values below two are accepted
37    /// and normalized by the algorithm. `max_oracle_calls` stops the run after that many
38    /// oracle evaluations.
39    #[must_use]
40    pub const fn new(initial_granularity: NonZeroUsize, max_oracle_calls: Option<usize>) -> Self {
41        Self { initial_granularity, max_oracle_calls }
42    }
43
44    /// Requested initial split count.
45    #[must_use]
46    pub const fn initial_granularity(self) -> NonZeroUsize {
47        self.initial_granularity
48    }
49
50    /// Optional maximum number of oracle evaluations for one run.
51    #[must_use]
52    pub const fn max_oracle_calls(self) -> Option<usize> {
53        self.max_oracle_calls
54    }
55}
56
57impl Default for DdminOptions {
58    fn default() -> Self {
59        Self { initial_granularity: NonZeroUsize::MIN, max_oracle_calls: None }
60    }
61}
62
63/// Result of evaluating one candidate list.
64#[derive(Debug, Clone, PartialEq, Eq)]
65pub enum OracleOutcome {
66    /// The candidate list still preserves the target property.
67    Interesting,
68    /// The candidate list does not preserve the target property.
69    NotInteresting,
70    /// The oracle could not safely decide for this candidate list.
71    Unresolved(UnresolvedReason),
72}
73
74/// Reason an oracle could not evaluate a candidate list.
75#[derive(Debug, Clone, Copy, PartialEq, Eq)]
76pub enum UnresolvedReason {
77    /// The oracle exceeded its time budget.
78    Timeout,
79    /// The caller could not build the trial state for this candidate list.
80    MaterializationFailed,
81    /// The oracle command, callback, or evaluation code failed.
82    OracleFailed,
83    /// The candidate list is invalid for the caller's domain.
84    InvalidCandidateSet,
85    /// The oracle detected nondeterministic behavior.
86    NonDeterministic,
87}
88
89/// Callback boundary between `DDMin` and the system being reduced.
90pub trait DdminOracle<C> {
91    /// Evaluate a candidate list.
92    ///
93    /// The slice contains the candidates that remain in this trial.
94    fn evaluate(&mut self, remaining: &[C]) -> OracleOutcome;
95}
96
97impl<C, F> DdminOracle<C> for F
98where
99    F: FnMut(&[C]) -> OracleOutcome,
100{
101    fn evaluate(&mut self, remaining: &[C]) -> OracleOutcome {
102        self(remaining)
103    }
104}
105
106/// Output from one `DDMin` reduction run.
107#[derive(Debug, Clone, PartialEq, Eq)]
108pub struct DdminOutput<C> {
109    remaining: Vec<C>,
110    removed: Vec<C>,
111    stats: DdminStats,
112    guarantee: DdminGuarantee,
113}
114
115impl<C> DdminOutput<C> {
116    pub(crate) const fn new(
117        remaining: Vec<C>,
118        removed: Vec<C>,
119        stats: DdminStats,
120        guarantee: DdminGuarantee,
121    ) -> Self {
122        Self { remaining, removed, stats, guarantee }
123    }
124
125    /// Candidates that remain after reduction.
126    #[must_use]
127    pub fn remaining(&self) -> &[C] {
128        &self.remaining
129    }
130
131    /// Candidates removed from the original input.
132    #[must_use]
133    pub fn removed(&self) -> &[C] {
134        &self.removed
135    }
136
137    /// Oracle-call accounting for this run.
138    #[must_use]
139    pub const fn stats(&self) -> DdminStats {
140        self.stats
141    }
142
143    /// Completion guarantee for this run.
144    #[must_use]
145    pub const fn guarantee(&self) -> DdminGuarantee {
146        self.guarantee
147    }
148}
149
150/// Oracle accounting for one `DDMin` run.
151#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
152pub struct DdminStats {
153    oracle_calls: usize,
154    interesting_trials: usize,
155    not_interesting_trials: usize,
156    unresolved_trials: usize,
157}
158
159impl DdminStats {
160    pub(crate) const fn record(&mut self, outcome: &OracleOutcome) {
161        self.oracle_calls = self.oracle_calls.saturating_add(1);
162        match outcome {
163            OracleOutcome::Interesting => {
164                self.interesting_trials = self.interesting_trials.saturating_add(1);
165            }
166            OracleOutcome::NotInteresting => {
167                self.not_interesting_trials = self.not_interesting_trials.saturating_add(1);
168            }
169            OracleOutcome::Unresolved(_) => {
170                self.unresolved_trials = self.unresolved_trials.saturating_add(1);
171            }
172        }
173    }
174
175    /// Total oracle evaluations performed.
176    #[must_use]
177    pub const fn oracle_calls(self) -> usize {
178        self.oracle_calls
179    }
180
181    /// Oracle evaluations that returned `Interesting`.
182    #[must_use]
183    pub const fn interesting_trials(self) -> usize {
184        self.interesting_trials
185    }
186
187    /// Oracle evaluations that returned `NotInteresting`.
188    #[must_use]
189    pub const fn not_interesting_trials(self) -> usize {
190        self.not_interesting_trials
191    }
192
193    /// Oracle evaluations that returned `Unresolved`.
194    #[must_use]
195    pub const fn unresolved_trials(self) -> usize {
196        self.unresolved_trials
197    }
198}
199
200/// Guarantee reached by one `DDMin` run.
201#[derive(Debug, Clone, Copy, PartialEq, Eq)]
202pub enum DdminGuarantee {
203    /// The run completed and the result is one-minimal within the input candidate set.
204    OneMinimalWithinCandidateSet,
205    /// The run stopped before reaching the normal completion guarantee.
206    Incomplete(DdminStopReason),
207}
208
209/// Reason `DDMin` stopped before reaching its normal guarantee.
210#[derive(Debug, Clone, Copy, PartialEq, Eq)]
211pub enum DdminStopReason {
212    /// The configured oracle-call limit was reached.
213    MaxOracleCallsReached,
214    /// The original candidate list was not interesting.
215    BaselineNotInteresting,
216}