smt_scope/analysis/
dependencies.rs

1use std::{cmp::Ordering, ops::Deref};
2
3use fxhash::FxHashSet;
4
5use crate::{
6    items::{QuantIdx, QuantPatVec},
7    BoxSlice, FxHashMap, TiVec, Z3Parser,
8};
9
10use super::{
11    raw::{IndexesInstGraph, NodeKind},
12    InstGraph, RawNodeIndex,
13};
14
15#[derive(Clone)]
16pub struct QuantifierAnalysis(QuantPatVec<QuantPatInfo>);
17
18#[derive(Debug, Default, Clone)]
19pub struct QuantPatInfo {
20    /// How much total cost did this quantifier + pattern accrue from individual
21    /// instantiations.
22    pub costs: f64,
23    // Issue 4: storing inst children in all nodes huge memory overhead
24    #[cfg(any())]
25    /// How many other instantiations of other quantifiers is this quantifier
26    /// _directly_ responsible for.
27    pub children: f64,
28    /// How many times does an instantiation of this quantifier depend on an
29    /// instantiation of the other quantifier.
30    pub direct_deps: Vec<DirectDep>,
31}
32
33#[derive(Debug, Clone, Default)]
34pub struct DirectDep {
35    pub enode: FxHashMap<Option<QuantIdx>, u32>,
36    pub eqs: FxHashMap<BoxSlice<QuantIdx>, u32>,
37}
38
39type TransQuantAnalaysis = TiVec<QuantIdx, FxHashSet<QuantIdx>>;
40
41impl Deref for QuantifierAnalysis {
42    type Target = QuantPatVec<QuantPatInfo>;
43    fn deref(&self) -> &Self::Target {
44        &self.0
45    }
46}
47
48impl QuantifierAnalysis {
49    pub fn new(parser: &Z3Parser, inst_graph: &InstGraph) -> Self {
50        let mut quant_deps = FxHashMap::<RawNodeIndex, FxHashSet<QuantIdx>>::default();
51        for node in inst_graph.subgraphs.topo_node_indices() {
52            let qdeps = quant_deps.entry(node).or_default();
53            let ig = &inst_graph.raw[node];
54            if let NodeKind::Instantiation(i) = *ig.kind() {
55                if let Some(q) = parser.get_inst(i).match_.kind.quant_idx() {
56                    qdeps.insert(q);
57                    continue;
58                }
59            }
60            let graph = &inst_graph.raw.graph;
61            for parent in graph.neighbors_directed(node.0, petgraph::Direction::Incoming) {
62                let parents = RawNodeIndex(parent);
63                let parent = quant_deps[&parents].clone();
64                quant_deps.get_mut(&node).unwrap().extend(parent);
65            }
66        }
67
68        let mut self_ = QuantifierAnalysis(parser.new_quant_pat_vec(|_| QuantPatInfo::default()));
69        for data in parser.instantiations_data() {
70            let Some(qpat) = data.match_.kind.quant_pat() else {
71                continue;
72            };
73            let qinfo = &mut self_.0[qpat];
74
75            let ginst = &inst_graph.raw[data.iidx];
76            qinfo.costs += ginst.cost;
77
78            // Issue 4: storing inst children in all nodes huge memory overhead
79            #[cfg(any())]
80            for &child in ginst.children.insts.iter() {
81                let cq = parser.get_inst(child).match_.kind.quant_idx();
82                if cq.is_some_and(|q| q == qpat.quant) {
83                    // Skip self references.
84                    continue;
85                }
86                let parents = inst_graph.raw[child].parents.insts.len() as f64;
87                qinfo.children += 1.0 / parents;
88            }
89
90            let pat = parser.get_pattern_term(qpat);
91            let subpats = pat.map(|p| p.child_ids.len()).unwrap_or_default();
92            for (i, blame) in data.match_.pattern_matches().enumerate() {
93                // Increment the count for each expression in the pattern.
94                if i == qinfo.direct_deps.len() {
95                    if i >= subpats {
96                        // TODO: there is a bug in z3 whereby more matched
97                        // expressions are printed than subpatterns available.
98                        // This happens rarely so don't bother trying to
99                        // backtrack here (the results will be slightly wrong).
100                        break;
101                    }
102                    qinfo.direct_deps.push(DirectDep::default());
103                }
104                let direct_dep = &mut qinfo.direct_deps[i];
105
106                let created_by = parser[blame.enode].blame.inst();
107                let created_by =
108                    created_by.and_then(|iidx| parser.get_inst(iidx).match_.kind.quant_idx());
109                *direct_dep.enode.entry(created_by).or_default() += 1;
110
111                for &eq in blame.equalities.iter() {
112                    let nidx = eq.index(&inst_graph.raw);
113                    let quants = &quant_deps[&nidx];
114                    let mut quants: BoxSlice<_> = quants.iter().copied().collect();
115                    quants.sort();
116                    *direct_dep.eqs.entry(quants).or_default() += 1;
117                }
118            }
119        }
120        self_
121    }
122
123    pub fn total_costs(&self) -> f64 {
124        self.iter_enumerated().map(|(_, info)| info.costs).sum()
125    }
126
127    pub fn quant_sum_cost(&self, quant: QuantIdx) -> f64 {
128        let data = &self.0 .0[quant];
129        data.mbqi.costs + data.pats.iter().map(|d| d.costs).sum::<f64>()
130    }
131
132    pub fn quants_costs(&self) -> impl Iterator<Item = (QuantIdx, f64)> + '_ {
133        self.0
134             .0
135            .iter_enumerated()
136            .map(|(quant, data)| (quant, data.iter_enumerated().map(|(_, d)| d.costs).sum()))
137    }
138
139    // Issue 4: storing inst children in all nodes huge memory overhead
140    #[cfg(any())]
141    pub fn quants_children(&self) -> impl Iterator<Item = (QuantIdx, f64)> + '_ {
142        self.0
143             .0
144            .iter_enumerated()
145            .map(|(quant, data)| (quant, data.iter_enumerated().map(|(_, d)| d.children).sum()))
146    }
147
148    pub fn calculate_transitive(&self, mut steps: Option<u32>) -> TransQuantAnalaysis {
149        let mut initial: TiVec<QuantIdx, _> =
150            (0..self.0 .0.len()).map(|_| FxHashSet::default()).collect();
151        for (qpat, data) in self.iter_enumerated() {
152            initial[qpat.quant].extend(data.keys());
153        }
154        while steps.is_none_or(|steps| steps != 0) {
155            if !self.calculate_transitive_one(&mut initial) {
156                break;
157            }
158            if let Some(steps) = &mut steps {
159                *steps -= 1;
160            }
161        }
162        initial
163    }
164    fn calculate_transitive_one(&self, analysis: &mut TransQuantAnalaysis) -> bool {
165        let mut changed = false;
166        for (idx, info) in self.iter_enumerated() {
167            for ddep in info.keys() {
168                let (curr, ddep) = match idx.quant.cmp(&ddep) {
169                    Ordering::Less => {
170                        let (left, right) = analysis.split_at_mut(ddep);
171                        (&mut left[idx.quant], right.first().unwrap())
172                    }
173                    Ordering::Greater => {
174                        let (left, right) = analysis.split_at_mut(idx.quant);
175                        (right.first_mut().unwrap(), &left[ddep])
176                    }
177                    Ordering::Equal => continue,
178                };
179                let old_len = curr.len();
180                curr.extend(ddep);
181                changed |= old_len != curr.len();
182            }
183        }
184        changed
185    }
186}
187
188impl QuantPatInfo {
189    /// This function will not provide an accurate view of dependencies, use
190    /// only for debugging.
191    pub fn keys(&self) -> impl Iterator<Item = QuantIdx> + '_ {
192        self.iter().map(|(q, _)| q)
193    }
194
195    /// This function will not provide an accurate view of dependencies, use
196    /// only for debugging.
197    pub fn values(&self) -> impl Iterator<Item = u32> + '_ {
198        self.iter().map(|(_, c)| c)
199    }
200
201    /// This function will not provide an accurate view of dependencies, use
202    /// only for debugging.
203    pub fn iter(&self) -> impl Iterator<Item = (QuantIdx, u32)> + '_ {
204        self.direct_deps.iter().flat_map(|ddep| {
205            let enode = ddep.enode.iter().filter_map(|(q, c)| q.zip(Some(*c)));
206            let eqs = ddep
207                .eqs
208                .iter()
209                .flat_map(|(q, c)| q.iter().map(move |q| (*q, *c)));
210            enode.chain(eqs)
211        })
212    }
213}