Skip to main content

probl_engine/
failure.rs

1//! Worlds that ended at a fault, when the failure mode is partial
2//! (docs/semantics.md, section 11).
3
4use crate::error::{Fault, RuntimeError};
5use crate::weight::Weight;
6use probl_syntax::Span;
7
8/// The worlds that failed, grouped by where and how.
9#[derive(Clone, Debug, Default)]
10pub struct Failures {
11    /// In the order they first failed (in batch order, when sampling).
12    pub groups: Vec<Failure>,
13    /// Their total weight: when sampling, of the runs that failed.
14    pub weight: Weight,
15    /// When sampling: Σ w² over the runs that failed.
16    pub squares: Weight,
17    /// When sampling: how many runs failed.
18    pub runs: u64,
19    /// Whether evidence could still have been applied to a world that
20    /// failed, had it gone on. Its weight then stops short of what the
21    /// finished worlds' includes, and the two can't be added up.
22    pub before_evidence: bool,
23}
24
25/// The worlds that failed at one place, with one kind of fault.
26#[derive(Clone, Debug)]
27pub struct Failure {
28    /// The first of them.
29    pub error: RuntimeError,
30    pub weight: Weight,
31    /// When sampling: Σ w² over the runs that failed here.
32    pub squares: Weight,
33    /// When sampling: how many runs failed here, and the first, in batch
34    /// order.
35    pub runs: u64,
36    pub first_run: Option<u32>,
37    /// Whether evidence could still have been applied to them.
38    pub before_evidence: bool,
39}
40
41impl Failures {
42    pub fn is_empty(&self) -> bool {
43        self.groups.is_empty()
44    }
45
46    /// A world of this weight failed with `error`. `run` is its run, when
47    /// sampling.
48    pub fn record(&mut self, error: RuntimeError, weight: Weight, run: Option<u32>, before_evidence: bool) {
49        let runs = run.is_some() as u64;
50        let squares = if run.is_some() { weight * weight } else { Weight::ZERO };
51        self.weight += weight;
52        self.squares += squares;
53        self.runs += runs;
54        self.before_evidence |= before_evidence;
55        let (span, fault) = (error.span, error.fault);
56        match self.group(span, fault) {
57            Some(g) => {
58                g.weight += weight;
59                g.squares += squares;
60                g.runs += runs;
61                g.before_evidence |= before_evidence;
62            }
63            None => self.groups.push(Failure {
64                error,
65                weight,
66                squares,
67                runs,
68                first_run: run,
69                before_evidence,
70            }),
71        }
72    }
73
74    /// Add what failed inside a call, or in a loop's state, `times` as
75    /// often. When sampling, a call follows one path: whatever failed in it
76    /// failed in the caller's run `run`. `before_evidence`: whether evidence
77    /// can follow where it's added.
78    pub fn absorb(&mut self, other: &Failures, times: Weight, run: Option<u32>, before_evidence: bool) {
79        for theirs in &other.groups {
80            self.absorb_group(theirs, times, run, before_evidence);
81        }
82    }
83
84    /// Add one group of what failed inside a call, as [`absorb`](Self::absorb) does.
85    pub fn absorb_group(&mut self, theirs: &Failure, times: Weight, run: Option<u32>, before_evidence: bool) {
86        let weight = theirs.weight * times;
87        let squares = theirs.squares * times * times;
88        let before_evidence = theirs.before_evidence || before_evidence;
89        self.weight += weight;
90        self.squares += squares;
91        self.runs += theirs.runs;
92        self.before_evidence |= before_evidence;
93        match self.group(theirs.error.span, theirs.error.fault) {
94            Some(g) => {
95                g.weight += weight;
96                g.squares += squares;
97                g.runs += theirs.runs;
98                g.before_evidence |= before_evidence;
99            }
100            None => self.groups.push(Failure {
101                error: theirs.error.clone(),
102                weight,
103                squares,
104                runs: theirs.runs,
105                first_run: run.or(theirs.first_run),
106                before_evidence,
107            }),
108        }
109    }
110
111    /// Add a later batch's failures.
112    pub fn append(&mut self, later: Failures) {
113        self.weight += later.weight;
114        self.squares += later.squares;
115        self.runs += later.runs;
116        self.before_evidence |= later.before_evidence;
117        for theirs in later.groups {
118            match self.group(theirs.error.span, theirs.error.fault) {
119                Some(g) => {
120                    g.weight += theirs.weight;
121                    g.squares += theirs.squares;
122                    g.runs += theirs.runs;
123                    g.before_evidence |= theirs.before_evidence;
124                }
125                None => self.groups.push(theirs),
126            }
127        }
128    }
129
130    fn group(&mut self, span: Span, fault: Option<Fault>) -> Option<&mut Failure> {
131        self.groups
132            .iter_mut()
133            .find(|g| g.error.span == span && g.error.fault == fault)
134    }
135}