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    /// When enumerating: how many densities the failed weight includes, as
24    /// the main program's worlds count them, and whether that's as many in
25    /// each.
26    pub densities: Option<u32>,
27    pub mixed_densities: bool,
28    /// Whether the failed weight includes another number of densities than
29    /// the finished worlds', which puts it in other units: it can't be added
30    /// to theirs either.
31    pub other_units: bool,
32}
33
34/// The worlds that failed at one place, with one kind of fault.
35#[derive(Clone, Debug)]
36pub struct Failure {
37    /// The first of them.
38    pub error: RuntimeError,
39    pub weight: Weight,
40    /// When sampling: Σ w² over the runs that failed here.
41    pub squares: Weight,
42    /// When sampling: how many runs failed here, and the first, in batch
43    /// order.
44    pub runs: u64,
45    pub first_run: Option<u32>,
46    /// Whether evidence could still have been applied to them.
47    pub before_evidence: bool,
48}
49
50impl Failures {
51    pub fn is_empty(&self) -> bool {
52        self.groups.is_empty()
53    }
54
55    /// Whether the failed weight can be added to the finished worlds'.
56    pub fn comparable(&self) -> bool {
57        !self.before_evidence && !self.other_units
58    }
59
60    fn note_densities(&mut self, n: u32) {
61        match self.densities {
62            Some(m) if m != n => self.mixed_densities = true,
63            _ => self.densities = Some(n),
64        }
65    }
66
67    /// A world of this weight, with `densities` in it, failed with `error`.
68    /// `run` is its run, when sampling.
69    pub fn record(
70        &mut self,
71        error: RuntimeError,
72        weight: Weight,
73        run: Option<u32>,
74        before_evidence: bool,
75        densities: u32,
76    ) {
77        self.note_densities(densities);
78        let runs = run.is_some() as u64;
79        let squares = if run.is_some() { weight * weight } else { Weight::ZERO };
80        self.weight += weight;
81        self.squares += squares;
82        self.runs += runs;
83        self.before_evidence |= before_evidence;
84        let (span, fault) = (error.span, error.fault);
85        match self.group(span, fault) {
86            Some(g) => {
87                g.weight += weight;
88                g.squares += squares;
89                g.runs += runs;
90                g.before_evidence |= before_evidence;
91            }
92            None => self.groups.push(Failure {
93                error,
94                weight,
95                squares,
96                runs,
97                first_run: run,
98                before_evidence,
99            }),
100        }
101    }
102
103    /// Add what failed inside a call, or in a loop's state, `times` as
104    /// often. When sampling, a call follows one path: whatever failed in it
105    /// failed in the caller's run `run`. `before_evidence`: whether evidence
106    /// can follow where it's added.
107    pub fn absorb(&mut self, other: &Failures, times: Weight, run: Option<u32>, before_evidence: bool) {
108        for theirs in &other.groups {
109            self.absorb_group(theirs, times, run, before_evidence, other.densities.unwrap_or(0));
110        }
111        self.mixed_densities |= other.mixed_densities;
112    }
113
114    /// Add one group of what failed inside a call, as [`absorb`](Self::absorb)
115    /// does, from a world with `densities` in it.
116    pub fn absorb_group(
117        &mut self,
118        theirs: &Failure,
119        times: Weight,
120        run: Option<u32>,
121        before_evidence: bool,
122        densities: u32,
123    ) {
124        self.note_densities(densities);
125        let weight = theirs.weight * times;
126        let squares = theirs.squares * times * times;
127        let before_evidence = theirs.before_evidence || before_evidence;
128        self.weight += weight;
129        self.squares += squares;
130        self.runs += theirs.runs;
131        self.before_evidence |= before_evidence;
132        match self.group(theirs.error.span, theirs.error.fault) {
133            Some(g) => {
134                g.weight += weight;
135                g.squares += squares;
136                g.runs += theirs.runs;
137                g.before_evidence |= before_evidence;
138            }
139            None => self.groups.push(Failure {
140                error: theirs.error.clone(),
141                weight,
142                squares,
143                runs: theirs.runs,
144                first_run: run.or(theirs.first_run),
145                before_evidence,
146            }),
147        }
148    }
149
150    /// Add a later batch's failures.
151    pub fn append(&mut self, later: Failures) {
152        self.weight += later.weight;
153        self.squares += later.squares;
154        self.runs += later.runs;
155        self.before_evidence |= later.before_evidence;
156        if let Some(n) = later.densities {
157            self.note_densities(n);
158        }
159        self.mixed_densities |= later.mixed_densities;
160        self.other_units |= later.other_units;
161        for theirs in later.groups {
162            match self.group(theirs.error.span, theirs.error.fault) {
163                Some(g) => {
164                    g.weight += theirs.weight;
165                    g.squares += theirs.squares;
166                    g.runs += theirs.runs;
167                    g.before_evidence |= theirs.before_evidence;
168                }
169                None => self.groups.push(theirs),
170            }
171        }
172    }
173
174    fn group(&mut self, span: Span, fault: Option<Fault>) -> Option<&mut Failure> {
175        self.groups
176            .iter_mut()
177            .find(|g| g.error.span == span && g.error.fault == fault)
178    }
179}