Skip to main content

holos_tda/
coverage_frontier.rs

1//! Exact component frontiers for failure-tolerant coverage synthesis.
2//!
3//! [`crate::CoverageSpecification::components`] splits the state-action
4//! incidence graph. Each local search returns nondominated activation-cost
5//! plans. [`compose_coverage_frontiers`] combines them under `max_activations`.
6
7use std::fmt;
8
9use crate::CoverageComponent;
10
11mod compose;
12
13#[cfg(test)]
14mod tests;
15
16pub use compose::compose_coverage_frontiers;
17
18/// Completeness status of a compositional coverage calculation.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum CoverageCompositionStatus {
21    /// Every component frontier is complete. The returned plan is optimal.
22    Optimal,
23    /// Every component frontier is complete. No plan meets the activation limit.
24    Infeasible,
25    /// A producer work limit stopped at least one component search.
26    SearchIncomplete,
27}
28
29impl fmt::Display for CoverageCompositionStatus {
30    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
31        formatter.write_str(match self {
32            Self::Optimal => "optimal",
33            Self::Infeasible => "infeasible",
34            Self::SearchIncomplete => "search incomplete",
35        })
36    }
37}
38
39/// One nondominated local plan on a component frontier.
40#[derive(Debug, Clone, PartialEq, Eq)]
41pub struct CoverageFrontierEntry {
42    activations: usize,
43    cost: u64,
44    selected: Vec<usize>,
45}
46
47impl CoverageFrontierEntry {
48    /// Number of selected actions in this local plan.
49    pub fn activations(&self) -> usize {
50        self.activations
51    }
52
53    /// Total action cost of this local plan.
54    pub fn cost(&self) -> u64 {
55        self.cost
56    }
57
58    /// Selected global action indices in canonical order.
59    pub fn selected(&self) -> &[usize] {
60        &self.selected
61    }
62}
63
64/// Nondominated plan frontier for one incidence component.
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct CoverageComponentFrontier {
67    component: CoverageComponent,
68    entries: Vec<CoverageFrontierEntry>,
69}
70
71impl CoverageComponentFrontier {
72    /// State-action incidence component represented by this frontier.
73    pub fn component(&self) -> &CoverageComponent {
74        &self.component
75    }
76
77    /// Nondominated local plans ordered by activation count.
78    pub fn entries(&self) -> &[CoverageFrontierEntry] {
79        &self.entries
80    }
81}
82
83/// Result of exact frontier construction and dynamic-program composition.
84#[derive(Debug, Clone, PartialEq, Eq)]
85pub struct CoverageComposition {
86    status: CoverageCompositionStatus,
87    frontiers: Vec<CoverageComponentFrontier>,
88    selected: Vec<usize>,
89    cost: Option<u64>,
90    oracle_calls: usize,
91    search_nodes: usize,
92    cache_hits: usize,
93}
94
95impl CoverageComposition {
96    /// Completeness status of the composed calculation.
97    pub fn status(&self) -> CoverageCompositionStatus {
98        self.status
99    }
100
101    /// Component frontiers completed before the reported status.
102    pub fn frontiers(&self) -> &[CoverageComponentFrontier] {
103        &self.frontiers
104    }
105
106    /// Selected global action indices for an optimal plan.
107    pub fn selected(&self) -> &[usize] {
108        &self.selected
109    }
110
111    /// Optimal cost, when a feasible plan was proved.
112    pub fn cost(&self) -> Option<u64> {
113        self.cost
114    }
115
116    /// Coverage predicate calls made by all local searches.
117    pub fn oracle_calls(&self) -> usize {
118        self.oracle_calls
119    }
120
121    /// Branch nodes visited by all local searches.
122    pub fn search_nodes(&self) -> usize {
123        self.search_nodes
124    }
125
126    /// Reused local predicate results across all local searches.
127    pub fn cache_hits(&self) -> usize {
128        self.cache_hits
129    }
130}