Skip to main content

holos_tda/intervention/
model.rs

1use std::fmt;
2
3use crate::{
4    EdgeKey, Error, ExplainedDiagram, IntervalGroupId, ProgramTraceArtifact,
5    ProgramTraceDecodeLimits,
6};
7
8/// Deterministic search budget for a restricted intervention.
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub struct InterventionBudget {
11    /// Largest number of candidate edits to check.
12    pub max_candidates: usize,
13}
14
15impl InterventionBudget {
16    /// Create a candidate-count budget.
17    pub fn new(max_candidates: usize) -> Self {
18        Self { max_candidates }
19    }
20}
21
22impl Default for InterventionBudget {
23    fn default() -> Self {
24        Self { max_candidates: 1 }
25    }
26}
27
28/// Strength of the returned intervention claim.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30#[non_exhaustive]
31pub enum InterventionStatus {
32    /// The feasible edit meets its lower bound inside the checked region.
33    Optimal,
34    /// The feasible edit has distinct checked lower and upper bounds.
35    BoundedGap,
36    /// The declared candidate budget ended without a continued feasible edit.
37    BudgetLimited,
38}
39
40/// One independent edge-weight change.
41#[derive(Debug, Clone, Copy, PartialEq)]
42pub struct EdgeWeightEdit {
43    /// Edited edge.
44    pub edge: EdgeKey,
45    /// Weight before the edit.
46    pub before: f64,
47    /// Weight after the edit.
48    pub after: f64,
49}
50
51/// Result of one restricted H1 intervention search.
52#[derive(Debug, Clone)]
53pub struct H1Intervention {
54    /// Target class space at the initial graph.
55    pub target: IntervalGroupId,
56    /// Requested latest death scale.
57    pub target_scale: f64,
58    /// Strength of the returned claim.
59    pub status: InterventionStatus,
60    /// Lower bound on the maximum absolute edge change.
61    pub lower_bound: f64,
62    /// Feasible maximum absolute edge change, when present.
63    pub upper_bound: Option<f64>,
64    /// Feasible edge edits, empty when no candidate was certified.
65    pub edits: Vec<EdgeWeightEdit>,
66    /// Exact result after the edit, when present.
67    pub result: Option<ExplainedDiagram>,
68    /// Intervention artifact, when present.
69    pub artifact: Option<InterventionArtifact>,
70}
71/// Failure while decoding or checking an intervention artifact.
72#[derive(Debug, Clone, PartialEq, Eq)]
73pub struct InterventionError {
74    message: String,
75}
76
77impl InterventionError {
78    pub(super) fn new(message: impl Into<String>) -> Self {
79        Self {
80            message: message.into(),
81        }
82    }
83
84    /// Description of the violated intervention rule.
85    pub fn message(&self) -> &str {
86        &self.message
87    }
88}
89
90impl fmt::Display for InterventionError {
91    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92        write!(f, "intervention artifact: {}", self.message)
93    }
94}
95
96impl std::error::Error for InterventionError {}
97
98impl From<InterventionError> for Error {
99    fn from(error: InterventionError) -> Self {
100        Self::InvalidInput(error.to_string())
101    }
102}
103
104/// Decoder limits applied before intervention records are allocated.
105#[derive(Debug, Clone, Copy, PartialEq, Eq)]
106#[non_exhaustive]
107pub struct InterventionDecodeLimits {
108    /// Largest accepted envelope in bytes.
109    pub max_bytes: usize,
110    /// Largest accepted edge-edit count.
111    pub max_edits: usize,
112    /// Largest accepted nested trace in bytes.
113    pub max_trace_bytes: usize,
114    /// Limits for the nested program trace.
115    pub trace: ProgramTraceDecodeLimits,
116}
117
118impl Default for InterventionDecodeLimits {
119    fn default() -> Self {
120        Self {
121            max_bytes: 1 << 30,
122            max_edits: 100_000_000,
123            max_trace_bytes: 1 << 30,
124            trace: ProgramTraceDecodeLimits::default(),
125        }
126    }
127}
128
129/// Feasible H1 intervention with a nested program trace.
130#[derive(Debug, Clone)]
131pub struct InterventionArtifact {
132    pub(super) target: IntervalGroupId,
133    pub(super) target_scale: f64,
134    pub(super) status: InterventionStatus,
135    pub(super) lower_bound: f64,
136    pub(super) upper_bound: f64,
137    pub(super) edits: Vec<EdgeWeightEdit>,
138    pub(super) trace: ProgramTraceArtifact,
139}
140
141impl InterventionArtifact {
142    /// Target class-space identifier.
143    pub fn target(&self) -> IntervalGroupId {
144        self.target
145    }
146
147    /// Requested latest death scale.
148    pub fn target_scale(&self) -> f64 {
149        self.target_scale
150    }
151
152    /// Strength of the claim.
153    pub fn status(&self) -> InterventionStatus {
154        self.status
155    }
156
157    /// Lower bound on the maximum edge change.
158    pub fn lower_bound(&self) -> f64 {
159        self.lower_bound
160    }
161
162    /// Feasible maximum edge change.
163    pub fn upper_bound(&self) -> f64 {
164        self.upper_bound
165    }
166
167    /// Applied independent edge edits.
168    pub fn edits(&self) -> &[EdgeWeightEdit] {
169        &self.edits
170    }
171
172    /// Nested program trace.
173    pub fn trace(&self) -> &ProgramTraceArtifact {
174        &self.trace
175    }
176}
177
178/// Result of verifying a feasible intervention.
179#[derive(Debug, Clone)]
180pub struct VerifiedIntervention {
181    /// Strength of the claim.
182    pub status: InterventionStatus,
183    /// Initial target class space.
184    pub target: IntervalGroupId,
185    /// Requested latest death scale.
186    pub target_scale: f64,
187    /// Lower bound on the maximum edge change.
188    pub lower_bound: f64,
189    /// Feasible upper bound.
190    pub upper_bound: f64,
191    /// Edge edits.
192    pub edits: Vec<EdgeWeightEdit>,
193    /// Exact final diagram and class spaces.
194    pub result: ExplainedDiagram,
195}