Skip to main content

oxilean_std/abstract_interpretation/
types.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use std::collections::{HashMap, HashSet};
6
7/// Standard interval widening operator.
8pub struct IntervalWidening;
9impl IntervalWidening {
10    /// Apply interval widening: a â–½ b.
11    pub fn apply(a: &IntervalDomain, b: &IntervalDomain) -> IntervalDomain {
12        a.widen(b)
13    }
14}
15/// An abstract state: a map from variable name to interval abstract value.
16#[derive(Debug, Clone)]
17pub struct AbstractState {
18    /// Variable name → interval abstract value
19    pub vars: HashMap<String, IntervalDomain>,
20}
21impl AbstractState {
22    /// The bottom state (unreachable program point).
23    pub fn bottom() -> Self {
24        Self {
25            vars: HashMap::new(),
26        }
27    }
28    /// Look up the abstract value of a variable (top if unknown).
29    pub fn get(&self, var: &str) -> IntervalDomain {
30        self.vars
31            .get(var)
32            .cloned()
33            .unwrap_or_else(IntervalDomain::top)
34    }
35    /// Set the abstract value of a variable.
36    pub fn set(&mut self, var: impl Into<String>, val: IntervalDomain) {
37        self.vars.insert(var.into(), val);
38    }
39    /// Join two abstract states pointwise.
40    pub fn join(&self, other: &Self) -> Self {
41        let mut result = self.clone();
42        for (k, v) in &other.vars {
43            let joined = result.get(k).join(v);
44            result.vars.insert(k.clone(), joined);
45        }
46        result
47    }
48    /// Widening of two abstract states pointwise.
49    pub fn widen(&self, other: &Self) -> Self {
50        let mut result = self.clone();
51        for (k, v) in &other.vars {
52            let widened = result.get(k).widen(v);
53            result.vars.insert(k.clone(), widened);
54        }
55        result
56    }
57}
58/// A lightweight polyhedral domain element: a set of linear inequalities.
59pub struct PolyhedralDomain {
60    /// Number of variables
61    pub n: usize,
62    /// Constraints: each is (coefficients, rhs) meaning sum(a_i * x_i) ≤ b
63    pub constraints: Vec<(Vec<f64>, f64)>,
64}
65impl PolyhedralDomain {
66    /// Create the top polyhedron (no constraints).
67    pub fn top(n: usize) -> Self {
68        Self {
69            n,
70            constraints: vec![],
71        }
72    }
73    /// Add a constraint: coeffs · x ≤ rhs.
74    pub fn add_constraint(&mut self, coeffs: Vec<f64>, rhs: f64) {
75        if coeffs.len() == self.n {
76            self.constraints.push((coeffs, rhs));
77        }
78    }
79    /// Check if a point satisfies all constraints.
80    pub fn contains(&self, point: &[f64]) -> bool {
81        if point.len() != self.n {
82            return false;
83        }
84        self.constraints.iter().all(|(coeffs, rhs)| {
85            let sum: f64 = coeffs.iter().zip(point.iter()).map(|(a, x)| a * x).sum();
86            sum <= *rhs
87        })
88    }
89    /// Number of constraints.
90    pub fn num_constraints(&self) -> usize {
91        self.constraints.len()
92    }
93}
94/// The abstract value for the parity domain.
95#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum ParityDomain {
97    /// No concrete values (unreachable)
98    Bottom,
99    /// Even integers only
100    Even,
101    /// Odd integers only
102    Odd,
103    /// All integers (no parity information)
104    Top,
105}
106impl ParityDomain {
107    /// Join of two parity abstract values.
108    pub fn join(&self, other: &Self) -> Self {
109        use ParityDomain::*;
110        match (self, other) {
111            (Bottom, x) | (x, Bottom) => x.clone(),
112            (Top, _) | (_, Top) => Top,
113            (a, b) if a == b => a.clone(),
114            _ => Top,
115        }
116    }
117    /// Abstract semantics of addition for parity.
118    pub fn add(&self, other: &Self) -> Self {
119        use ParityDomain::*;
120        match (self, other) {
121            (Bottom, _) | (_, Bottom) => Bottom,
122            (Top, _) | (_, Top) => Top,
123            (Even, Even) => Even,
124            (Odd, Odd) => Even,
125            (Even, Odd) | (Odd, Even) => Odd,
126        }
127    }
128    /// Abstract semantics of multiplication for parity.
129    pub fn mul(&self, other: &Self) -> Self {
130        use ParityDomain::*;
131        match (self, other) {
132            (Bottom, _) | (_, Bottom) => Bottom,
133            (Even, _) | (_, Even) => Even,
134            (Odd, Odd) => Odd,
135            _ => Top,
136        }
137    }
138}
139/// Abstract element for a probabilistic program: an interval over the
140/// probability of being in each of k abstract states (partition of the state space).
141///
142/// Represents distributions where each partition cell has a probability
143/// in [lo_i, hi_i] with Σ lo_i ≤ 1 ≤ Σ hi_i.
144#[allow(dead_code)]
145pub struct ProbabilisticAbstractDomain {
146    /// Number of abstract partition cells
147    pub k: usize,
148    /// Lower bound on probability of each cell
149    pub prob_lower: Vec<f64>,
150    /// Upper bound on probability of each cell
151    pub prob_upper: Vec<f64>,
152}
153#[allow(dead_code)]
154impl ProbabilisticAbstractDomain {
155    /// Create a uniform distribution abstraction over k cells.
156    pub fn uniform(k: usize) -> Self {
157        let p = 1.0 / k as f64;
158        Self {
159            k,
160            prob_lower: vec![p; k],
161            prob_upper: vec![p; k],
162        }
163    }
164    /// Create the top element (no probability information: [0, 1] per cell).
165    pub fn top(k: usize) -> Self {
166        Self {
167            k,
168            prob_lower: vec![0.0; k],
169            prob_upper: vec![1.0; k],
170        }
171    }
172    /// Create a Dirac distribution concentrated on cell i.
173    pub fn dirac(k: usize, i: usize) -> Self {
174        let mut lower = vec![0.0; k];
175        let mut upper = vec![0.0; k];
176        lower[i] = 1.0;
177        upper[i] = 1.0;
178        Self {
179            k,
180            prob_lower: lower,
181            prob_upper: upper,
182        }
183    }
184    /// Join: componentwise interval join (least upper bound).
185    pub fn join(&self, other: &Self) -> Self {
186        assert_eq!(self.k, other.k);
187        Self {
188            k: self.k,
189            prob_lower: self
190                .prob_lower
191                .iter()
192                .zip(&other.prob_lower)
193                .map(|(a, b)| a.min(*b))
194                .collect(),
195            prob_upper: self
196                .prob_upper
197                .iter()
198                .zip(&other.prob_upper)
199                .map(|(a, b)| a.max(*b))
200                .collect(),
201        }
202    }
203    /// Compute abstract expected value of a bounded function f: f[i] = value on cell i.
204    /// Returns [lower, upper] interval on E[f].
205    pub fn abstract_expectation(&self, f: &[f64]) -> (f64, f64) {
206        assert_eq!(f.len(), self.k);
207        let lower_e: f64 = self
208            .prob_lower
209            .iter()
210            .zip(f.iter())
211            .map(|(p, fi)| p * fi)
212            .sum();
213        let upper_e: f64 = self
214            .prob_upper
215            .iter()
216            .zip(f.iter())
217            .map(|(p, fi)| p * fi)
218            .sum();
219        (lower_e, upper_e)
220    }
221    /// Check if the abstract element is sound (lower ≤ upper per cell,
222    /// and lower bounds sum ≤ 1, upper bounds sum ≥ 1 where meaningful).
223    pub fn is_sound(&self) -> bool {
224        let lo_ok = self
225            .prob_lower
226            .iter()
227            .zip(&self.prob_upper)
228            .all(|(l, u)| l <= u && *l >= 0.0 && *u <= 1.0);
229        let sum_lo: f64 = self.prob_lower.iter().sum();
230        let sum_hi: f64 = self.prob_upper.iter().sum();
231        lo_ok && sum_lo <= 1.0 + 1e-9 && sum_hi >= 1.0 - 1e-9
232    }
233}
234/// A single-neuron abstract element in the DeepPoly domain.
235///
236/// Each neuron y_i has a concrete value bounded by a linear expression:
237///   lb_coeffs · x + lb_bias ≤ y_i ≤ ub_coeffs · x + ub_bias
238/// where x are the (input) neurons from a preceding layer.
239#[derive(Debug, Clone)]
240#[allow(dead_code)]
241pub struct DeepPolyNeuron {
242    /// Coefficients of the lower linear bound (over preceding layer neurons)
243    pub lb_coeffs: Vec<f64>,
244    /// Constant of the lower linear bound
245    pub lb_bias: f64,
246    /// Coefficients of the upper linear bound
247    pub ub_coeffs: Vec<f64>,
248    /// Constant of the upper linear bound
249    pub ub_bias: f64,
250    /// Concrete lower bound (after back-substitution)
251    pub concrete_lb: f64,
252    /// Concrete upper bound (after back-substitution)
253    pub concrete_ub: f64,
254}
255#[allow(dead_code)]
256impl DeepPolyNeuron {
257    /// Create a constant neuron (no uncertainty).
258    pub fn constant(val: f64, input_dim: usize) -> Self {
259        Self {
260            lb_coeffs: vec![0.0; input_dim],
261            lb_bias: val,
262            ub_coeffs: vec![0.0; input_dim],
263            ub_bias: val,
264            concrete_lb: val,
265            concrete_ub: val,
266        }
267    }
268    /// Apply abstract ReLU: case analysis on the concrete bounds.
269    pub fn abstract_relu(&self) -> Self {
270        let input_dim = self.lb_coeffs.len();
271        if self.concrete_ub <= 0.0 {
272            DeepPolyNeuron::constant(0.0, input_dim)
273        } else if self.concrete_lb >= 0.0 {
274            self.clone()
275        } else {
276            let slope = self.concrete_ub / (self.concrete_ub - self.concrete_lb);
277            let ub_coeffs: Vec<f64> = self.ub_coeffs.iter().map(|c| slope * c).collect();
278            let ub_bias = slope * (self.ub_bias - self.concrete_lb);
279            DeepPolyNeuron {
280                lb_coeffs: vec![0.0; input_dim],
281                lb_bias: 0.0,
282                ub_coeffs,
283                ub_bias,
284                concrete_lb: 0.0,
285                concrete_ub: slope * (self.concrete_ub - self.concrete_lb),
286            }
287        }
288    }
289    /// Check if a concrete value is within the abstract bounds.
290    pub fn contains_concrete(&self, val: f64) -> bool {
291        val >= self.concrete_lb && val <= self.concrete_ub
292    }
293}
294/// Estimated loop bound: an upper bound on iteration count.
295pub struct LoopBound {
296    /// Estimated maximum iterations
297    pub bound: Option<u64>,
298}
299impl LoopBound {
300    /// Unknown bound.
301    pub fn unknown() -> Self {
302        Self { bound: None }
303    }
304    /// Known finite bound.
305    pub fn finite(n: u64) -> Self {
306        Self { bound: Some(n) }
307    }
308    /// Estimate loop bound from interval [0, n]: bound is n+1 iterations.
309    pub fn from_interval(interval: &IntervalDomain) -> Self {
310        match &interval.upper {
311            Bound::Finite(n) if *n >= 0 => Self::finite(*n as u64 + 1),
312            _ => Self::unknown(),
313        }
314    }
315    /// Check if the loop is known to terminate.
316    pub fn terminates(&self) -> bool {
317        self.bound.is_some()
318    }
319}
320/// A bound value: either a finite integer or ±∞.
321#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
322pub enum Bound {
323    /// Negative infinity
324    NegInf,
325    /// A finite integer value
326    Finite(i64),
327    /// Positive infinity
328    PosInf,
329}
330impl Bound {
331    /// Add two bounds (with infinity arithmetic).
332    pub fn add(&self, other: &Self) -> Self {
333        match (self, other) {
334            (Bound::NegInf, Bound::PosInf) | (Bound::PosInf, Bound::NegInf) => Bound::NegInf,
335            (Bound::NegInf, _) | (_, Bound::NegInf) => Bound::NegInf,
336            (Bound::PosInf, _) | (_, Bound::PosInf) => Bound::PosInf,
337            (Bound::Finite(a), Bound::Finite(b)) => Bound::Finite(a.saturating_add(*b)),
338        }
339    }
340    /// Negate a bound.
341    pub fn neg(&self) -> Self {
342        match self {
343            Bound::NegInf => Bound::PosInf,
344            Bound::PosInf => Bound::NegInf,
345            Bound::Finite(n) => Bound::Finite(-n),
346        }
347    }
348}
349/// Taint analysis: tracks tainted (user-controlled) values.
350#[derive(Debug, Clone, PartialEq, Eq)]
351pub enum TaintValue {
352    /// Value is clean (no user input)
353    Clean,
354    /// Value may be tainted
355    Tainted,
356    /// Unreachable (bottom)
357    Bottom,
358}
359/// The abstraction function: smallest interval containing a concrete set.
360pub struct AbstractionFunction {
361    gc: GaloisConnection,
362}
363impl AbstractionFunction {
364    /// Create from the standard interval Galois connection.
365    pub fn new() -> Self {
366        Self {
367            gc: GaloisConnection::interval_galois(),
368        }
369    }
370    /// Compute α(S): best abstract value for a set of integers.
371    pub fn apply(&self, vals: &[i64]) -> IntervalDomain {
372        self.gc.abstract_of(vals)
373    }
374}
375/// The abstract value for the sign domain.
376#[derive(Debug, Clone, PartialEq, Eq)]
377pub enum SignDomain {
378    /// No concrete values (unreachable)
379    Bottom,
380    /// Strictly negative integers (< 0)
381    Neg,
382    /// Zero only
383    Zero,
384    /// Strictly positive integers (> 0)
385    Pos,
386    /// Non-negative integers (≥ 0)
387    NonNeg,
388    /// Non-positive integers (≤ 0)
389    NonPos,
390    /// All integers (no information)
391    Top,
392}
393impl SignDomain {
394    /// Join (least upper bound ⊔) of two sign abstract values.
395    pub fn join(&self, other: &Self) -> Self {
396        use SignDomain::*;
397        match (self, other) {
398            (Bottom, x) | (x, Bottom) => x.clone(),
399            (Top, _) | (_, Top) => Top,
400            (a, b) if a == b => a.clone(),
401            (Zero, Pos) | (Pos, Zero) => NonNeg,
402            (Zero, Neg) | (Neg, Zero) => NonPos,
403            (NonNeg, Neg) | (Neg, NonNeg) => Top,
404            (NonPos, Pos) | (Pos, NonPos) => Top,
405            (Neg, Pos) | (Pos, Neg) => Top,
406            (NonNeg, NonPos) | (NonPos, NonNeg) => Top,
407            _ => Top,
408        }
409    }
410    /// Widening for sign domain (same as join since it's a finite lattice).
411    pub fn widen(&self, other: &Self) -> Self {
412        self.join(other)
413    }
414    /// Abstract semantics of addition.
415    pub fn add(&self, other: &Self) -> Self {
416        use SignDomain::*;
417        match (self, other) {
418            (Bottom, _) | (_, Bottom) => Bottom,
419            (Top, _) | (_, Top) => Top,
420            (Zero, x) | (x, Zero) => x.clone(),
421            (Pos, Pos) => Pos,
422            (Neg, Neg) => Neg,
423            (NonNeg, NonNeg) => NonNeg,
424            (NonPos, NonPos) => NonPos,
425            (Pos, NonNeg) | (NonNeg, Pos) => Pos,
426            (Neg, NonPos) | (NonPos, Neg) => Neg,
427            _ => Top,
428        }
429    }
430    /// Abstract semantics of negation.
431    pub fn neg(&self) -> Self {
432        use SignDomain::*;
433        match self {
434            Bottom => Bottom,
435            Neg => Pos,
436            Zero => Zero,
437            Pos => Neg,
438            NonNeg => NonPos,
439            NonPos => NonNeg,
440            Top => Top,
441        }
442    }
443}
444/// The octagon domain for n variables: stores bounds on ±xi ± xj.
445/// Encoded as a difference-bound matrix (DBM) for simplicity.
446pub struct OctagonDomain {
447    /// Number of variables
448    pub n: usize,
449    /// DBM entries: entry [2i][2j] = bound on xi - xj, etc.
450    pub matrix: Vec<Vec<Option<i64>>>,
451}
452impl OctagonDomain {
453    /// Create a top octagon (no constraints) for n variables.
454    pub fn top(n: usize) -> Self {
455        let size = 2 * n;
456        Self {
457            n,
458            matrix: vec![vec![None; size]; size],
459        }
460    }
461    /// Create a bottom octagon (all bounds = -∞, contradictory).
462    pub fn bottom(n: usize) -> Self {
463        let size = 2 * n;
464        let mut matrix = vec![vec![None; size]; size];
465        for i in 0..size {
466            matrix[i][i] = Some(-1);
467        }
468        Self { n, matrix }
469    }
470    /// Add a unary constraint xi ≤ c.
471    pub fn add_upper_bound(&mut self, i: usize, c: i64) {
472        if i < self.n {
473            let pos = 2 * i;
474            let neg = 2 * i + 1;
475            let cur = self.matrix[pos][neg];
476            let val = 2 * c;
477            self.matrix[pos][neg] = Some(match cur {
478                None => val,
479                Some(v) => v.min(val),
480            });
481        }
482    }
483    /// Check if the octagon is satisfiable (no negative-weight cycles).
484    pub fn is_satisfiable(&self) -> bool {
485        for i in 0..2 * self.n {
486            if let Some(v) = self.matrix[i][i] {
487                if v < 0 {
488                    return false;
489                }
490            }
491        }
492        true
493    }
494}
495/// Fixpoint computation via Kleene iteration with widening.
496pub struct FixpointComputation {
497    /// Maximum number of iterations before forcing termination
498    pub max_iter: usize,
499    /// Widening delay
500    pub widen_delay: usize,
501}
502impl FixpointComputation {
503    /// Create a fixpoint computation with standard settings.
504    pub fn new() -> Self {
505        Self {
506            max_iter: 1000,
507            widen_delay: 3,
508        }
509    }
510    /// Compute the least fixpoint of f starting from init, using widening.
511    pub fn compute<F>(&self, f: F, init: AbstractState) -> AbstractState
512    where
513        F: Fn(&AbstractState) -> AbstractState,
514    {
515        let mut current = init;
516        let mut delay = DelayedWidening::new(self.widen_delay);
517        for _ in 0..self.max_iter {
518            let next = f(&current);
519            let widened = AbstractState {
520                vars: {
521                    let mut vars = current.vars.clone();
522                    for (k, v) in &next.vars {
523                        let cur_v = current.get(k);
524                        let w = delay.apply(&cur_v, v);
525                        vars.insert(k.clone(), w);
526                    }
527                    vars
528                },
529            };
530            if widened
531                .vars
532                .iter()
533                .all(|(k, v)| current.vars.get(k) == Some(v))
534                && current.vars.len() == widened.vars.len()
535            {
536                return widened;
537            }
538            current = widened;
539        }
540        current
541    }
542}
543/// The concretization function: set of integers represented by an abstract value.
544pub struct ConcretizationFunction {
545    gc: GaloisConnection,
546}
547impl ConcretizationFunction {
548    /// Create from the standard interval Galois connection.
549    pub fn new() -> Self {
550        Self {
551            gc: GaloisConnection::interval_galois(),
552        }
553    }
554    /// Compute γ(a): all concrete integers represented (None if infinite).
555    pub fn apply(&self, a: &IntervalDomain) -> Option<Vec<i64>> {
556        self.gc.concretize(a)
557    }
558}
559/// Taint analysis state: map from variable to taint status.
560pub struct TaintAnalysis {
561    /// Variable → taint status
562    pub taint: HashMap<String, TaintValue>,
563}
564impl TaintAnalysis {
565    /// Create empty taint analysis state.
566    pub fn new() -> Self {
567        Self {
568            taint: HashMap::new(),
569        }
570    }
571    /// Mark a variable as a taint source.
572    pub fn add_source(&mut self, var: impl Into<String>) {
573        self.taint.insert(var.into(), TaintValue::Tainted);
574    }
575    /// Check if a variable is tainted.
576    pub fn is_tainted(&self, var: &str) -> bool {
577        matches!(self.taint.get(var), Some(TaintValue::Tainted))
578    }
579    /// Propagate taint: result of operation is tainted if any input is.
580    pub fn propagate(&mut self, result: impl Into<String>, inputs: &[&str]) {
581        let tainted = inputs.iter().any(|v| self.is_tainted(v));
582        let val = if tainted {
583            TaintValue::Tainted
584        } else {
585            TaintValue::Clean
586        };
587        self.taint.insert(result.into(), val);
588    }
589}
590/// The interval domain: an abstract value [lower, upper].
591/// Represents the set of integers n with lower ≤ n ≤ upper.
592#[derive(Debug, Clone, PartialEq, Eq)]
593pub struct IntervalDomain {
594    /// Lower bound (inclusive), or NegInf
595    pub lower: Bound,
596    /// Upper bound (inclusive), or PosInf
597    pub upper: Bound,
598    /// Whether this is the bottom element (empty set)
599    pub is_bottom: bool,
600}
601impl IntervalDomain {
602    /// Bottom element (empty set, unreachable).
603    pub fn bottom() -> Self {
604        Self {
605            lower: Bound::PosInf,
606            upper: Bound::NegInf,
607            is_bottom: true,
608        }
609    }
610    /// Top element (all integers).
611    pub fn top() -> Self {
612        Self {
613            lower: Bound::NegInf,
614            upper: Bound::PosInf,
615            is_bottom: false,
616        }
617    }
618    /// A constant interval [n, n].
619    pub fn constant(n: i64) -> Self {
620        Self {
621            lower: Bound::Finite(n),
622            upper: Bound::Finite(n),
623            is_bottom: false,
624        }
625    }
626    /// An interval [l, u].
627    pub fn new(lower: Bound, upper: Bound) -> Self {
628        if lower > upper {
629            Self::bottom()
630        } else {
631            Self {
632                lower,
633                upper,
634                is_bottom: false,
635            }
636        }
637    }
638    /// Join (least upper bound): smallest interval containing both.
639    pub fn join(&self, other: &Self) -> Self {
640        if self.is_bottom {
641            return other.clone();
642        }
643        if other.is_bottom {
644            return self.clone();
645        }
646        Self::new(
647            self.lower.clone().min(other.lower.clone()),
648            self.upper.clone().max(other.upper.clone()),
649        )
650    }
651    /// Meet (greatest lower bound): intersection of two intervals.
652    pub fn meet(&self, other: &Self) -> Self {
653        if self.is_bottom || other.is_bottom {
654            return Self::bottom();
655        }
656        Self::new(
657            self.lower.clone().max(other.lower.clone()),
658            self.upper.clone().min(other.upper.clone()),
659        )
660    }
661    /// Widening: accelerate convergence by jumping to ±∞.
662    pub fn widen(&self, other: &Self) -> Self {
663        if self.is_bottom {
664            return other.clone();
665        }
666        if other.is_bottom {
667            return self.clone();
668        }
669        let new_lower = if other.lower < self.lower {
670            Bound::NegInf
671        } else {
672            self.lower.clone()
673        };
674        let new_upper = if other.upper > self.upper {
675            Bound::PosInf
676        } else {
677            self.upper.clone()
678        };
679        Self::new(new_lower, new_upper)
680    }
681    /// Narrowing: refine using constraints from other.
682    pub fn narrow(&self, other: &Self) -> Self {
683        if self.is_bottom || other.is_bottom {
684            return Self::bottom();
685        }
686        let new_lower = if self.lower == Bound::NegInf {
687            other.lower.clone()
688        } else {
689            self.lower.clone()
690        };
691        let new_upper = if self.upper == Bound::PosInf {
692            other.upper.clone()
693        } else {
694            self.upper.clone()
695        };
696        Self::new(new_lower, new_upper)
697    }
698    /// Abstract addition: [a,b] + [c,d] = [a+c, b+d].
699    pub fn add(&self, other: &Self) -> Self {
700        if self.is_bottom || other.is_bottom {
701            return Self::bottom();
702        }
703        Self::new(self.lower.add(&other.lower), self.upper.add(&other.upper))
704    }
705    /// Check if a concrete integer is in the interval.
706    pub fn contains(&self, n: i64) -> bool {
707        if self.is_bottom {
708            return false;
709        }
710        let lo_ok = match &self.lower {
711            Bound::NegInf => true,
712            Bound::Finite(l) => *l <= n,
713            Bound::PosInf => false,
714        };
715        let hi_ok = match &self.upper {
716            Bound::NegInf => false,
717            Bound::Finite(u) => n <= *u,
718            Bound::PosInf => true,
719        };
720        lo_ok && hi_ok
721    }
722}
723/// An abstract transformer: a function from abstract states to abstract states.
724pub struct AbstractTransformer {
725    /// The transformation function
726    pub transform: Box<dyn Fn(&AbstractState) -> AbstractState>,
727}
728impl AbstractTransformer {
729    /// Create an abstract transformer from a closure.
730    pub fn new<F: Fn(&AbstractState) -> AbstractState + 'static>(f: F) -> Self {
731        Self {
732            transform: Box::new(f),
733        }
734    }
735    /// Apply the transformer to an abstract state.
736    pub fn apply(&self, state: &AbstractState) -> AbstractState {
737        (self.transform)(state)
738    }
739    /// Compose two transformers: first apply self, then other.
740    pub fn compose(self, other: Self) -> Self {
741        Self::new(move |s| other.apply(&self.apply(s)))
742    }
743}
744/// Direction of a dataflow analysis.
745#[derive(Debug, Clone, PartialEq, Eq)]
746pub enum AnalysisDirection {
747    /// Forward: propagate from entry to exit
748    Forward,
749    /// Backward: propagate from exit to entry
750    Backward,
751}
752/// Null pointer analysis: may/must nullness at each program point.
753#[derive(Debug, Clone, PartialEq, Eq)]
754pub enum NullValue {
755    /// Definitely null
756    MustNull,
757    /// Definitely non-null
758    NonNull,
759    /// May or may not be null
760    MayNull,
761    /// Unreachable (bottom)
762    Bottom,
763}
764/// A zonotope: x = center + Σ_i ε_i * generators[i], |ε_i| ≤ 1.
765///
766/// Zonotopes are closed under affine maps and provide compact relational
767/// over-approximations. Each generator g_i encodes a direction of uncertainty.
768#[allow(dead_code)]
769pub struct ZonotopeDomain {
770    /// Number of dimensions
771    pub dim: usize,
772    /// Center point
773    pub center: Vec<f64>,
774    /// Generators (each has `dim` components)
775    pub generators: Vec<Vec<f64>>,
776}
777#[allow(dead_code)]
778impl ZonotopeDomain {
779    /// Create a point zonotope (no uncertainty).
780    pub fn point(center: Vec<f64>) -> Self {
781        let dim = center.len();
782        Self {
783            dim,
784            center,
785            generators: vec![],
786        }
787    }
788    /// Create an interval zonotope from lower and upper bounds.
789    /// center = (lo + hi) / 2, one generator per dimension = (hi - lo) / 2.
790    pub fn from_intervals(lower: &[f64], upper: &[f64]) -> Self {
791        assert_eq!(lower.len(), upper.len());
792        let dim = lower.len();
793        let center: Vec<f64> = lower
794            .iter()
795            .zip(upper.iter())
796            .map(|(l, h)| (l + h) / 2.0)
797            .collect();
798        let generators: Vec<Vec<f64>> = (0..dim)
799            .map(|i| {
800                let mut g = vec![0.0; dim];
801                g[i] = (upper[i] - lower[i]) / 2.0;
802                g
803            })
804            .collect();
805        Self {
806            dim,
807            center,
808            generators,
809        }
810    }
811    /// Compute the interval over-approximation of the zonotope.
812    /// Returns (lower, upper) bounds per dimension.
813    pub fn to_intervals(&self) -> (Vec<f64>, Vec<f64>) {
814        let mut lower = self.center.clone();
815        let mut upper = self.center.clone();
816        for g in &self.generators {
817            for (i, gi) in g.iter().enumerate() {
818                let abs_g = gi.abs();
819                lower[i] -= abs_g;
820                upper[i] += abs_g;
821            }
822        }
823        (lower, upper)
824    }
825    /// Apply an affine map: y = A * x + b (exact in the zonotope domain).
826    /// `matrix` is (out_dim x self.dim), `bias` has length out_dim.
827    pub fn affine_map(&self, matrix: &[Vec<f64>], bias: &[f64]) -> Self {
828        let out_dim = matrix.len();
829        let new_center: Vec<f64> = (0..out_dim)
830            .map(|i| {
831                let dot: f64 = matrix[i]
832                    .iter()
833                    .zip(self.center.iter())
834                    .map(|(a, c)| a * c)
835                    .sum();
836                dot + bias[i]
837            })
838            .collect();
839        let new_generators: Vec<Vec<f64>> = self
840            .generators
841            .iter()
842            .map(|g| {
843                (0..out_dim)
844                    .map(|i| matrix[i].iter().zip(g.iter()).map(|(a, gi)| a * gi).sum())
845                    .collect()
846            })
847            .collect();
848        Self {
849            dim: out_dim,
850            center: new_center,
851            generators: new_generators,
852        }
853    }
854    /// Join two zonotopes: take their interval hull as a new zonotope.
855    pub fn join(&self, other: &Self) -> Self {
856        assert_eq!(self.dim, other.dim);
857        let (l1, u1) = self.to_intervals();
858        let (l2, u2) = other.to_intervals();
859        let lower: Vec<f64> = l1.iter().zip(l2.iter()).map(|(a, b)| a.min(*b)).collect();
860        let upper: Vec<f64> = u1.iter().zip(u2.iter()).map(|(a, b)| a.max(*b)).collect();
861        Self::from_intervals(&lower, &upper)
862    }
863    /// Check if a point is (conservatively) in the zonotope
864    /// by checking against the interval over-approximation.
865    pub fn may_contain(&self, point: &[f64]) -> bool {
866        if point.len() != self.dim {
867            return false;
868        }
869        let (lower, upper) = self.to_intervals();
870        lower
871            .iter()
872            .zip(upper.iter())
873            .zip(point.iter())
874            .all(|((l, u), p)| *l <= *p && *p <= *u)
875    }
876}
877/// Array bounds analysis: interval analysis for array indices.
878pub struct ArrayBoundsAnalysis {
879    /// Variable → index interval
880    pub index_bounds: HashMap<String, IntervalDomain>,
881    /// Array name → size (as interval)
882    pub array_sizes: HashMap<String, IntervalDomain>,
883}
884impl ArrayBoundsAnalysis {
885    /// Create a new empty array bounds analysis.
886    pub fn new() -> Self {
887        Self {
888            index_bounds: HashMap::new(),
889            array_sizes: HashMap::new(),
890        }
891    }
892    /// Check if access array[idx_var] is provably safe.
893    pub fn is_safe_access(&self, array: &str, idx_var: &str) -> bool {
894        let idx = self
895            .index_bounds
896            .get(idx_var)
897            .cloned()
898            .unwrap_or_else(IntervalDomain::top);
899        let sz = match self.array_sizes.get(array) {
900            Some(s) => s.clone(),
901            None => return false,
902        };
903        if idx.is_bottom {
904            return true;
905        }
906        let lo_ok = match &idx.lower {
907            Bound::Finite(l) => *l >= 0,
908            _ => false,
909        };
910        let hi_ok = match (&idx.upper, &sz.lower) {
911            (Bound::Finite(hi), Bound::Finite(sz_lo)) => *hi < *sz_lo,
912            _ => false,
913        };
914        lo_ok && hi_ok
915    }
916}
917/// Delayed widening: apply regular join for first k steps, then widen.
918pub struct DelayedWidening {
919    /// Number of steps before switching to widening
920    pub delay: usize,
921    /// Current step counter
922    pub step: usize,
923}
924impl DelayedWidening {
925    /// Create a delayed widening operator with given delay.
926    pub fn new(delay: usize) -> Self {
927        Self { delay, step: 0 }
928    }
929    /// Apply: join for step < delay, widening otherwise.
930    pub fn apply(&mut self, a: &IntervalDomain, b: &IntervalDomain) -> IntervalDomain {
931        self.step += 1;
932        if self.step <= self.delay {
933            a.join(b)
934        } else {
935            a.widen(b)
936        }
937    }
938    /// Reset the step counter.
939    pub fn reset(&mut self) {
940        self.step = 0;
941    }
942}
943/// A Galois connection (α, γ) between concrete sets (Vec<i64>) and intervals.
944pub struct GaloisConnection {
945    /// α: ℘(ℤ) → IntervalDomain (abstraction)
946    pub alpha: Box<dyn Fn(&[i64]) -> IntervalDomain>,
947    /// γ: IntervalDomain → Vec<i64> (concretization, approximate for ∞)
948    pub gamma: Box<dyn Fn(&IntervalDomain) -> Option<Vec<i64>>>,
949}
950impl GaloisConnection {
951    /// Build the standard Galois connection for the interval domain.
952    pub fn interval_galois() -> Self {
953        Self {
954            alpha: Box::new(|vals: &[i64]| {
955                if vals.is_empty() {
956                    return IntervalDomain::bottom();
957                }
958                let lo = vals
959                    .iter()
960                    .copied()
961                    .min()
962                    .expect("vals is non-empty: checked by early return");
963                let hi = vals
964                    .iter()
965                    .copied()
966                    .max()
967                    .expect("vals is non-empty: checked by early return");
968                IntervalDomain::new(Bound::Finite(lo), Bound::Finite(hi))
969            }),
970            gamma: Box::new(|interval: &IntervalDomain| {
971                if interval.is_bottom {
972                    return Some(vec![]);
973                }
974                match (&interval.lower, &interval.upper) {
975                    (Bound::Finite(l), Bound::Finite(u)) if u - l <= 1000 => {
976                        Some((*l..=*u).collect())
977                    }
978                    _ => None,
979                }
980            }),
981        }
982    }
983    /// Compute the abstraction of a concrete set.
984    pub fn abstract_of(&self, vals: &[i64]) -> IntervalDomain {
985        (self.alpha)(vals)
986    }
987    /// Compute (a finite approximation of) the concretization.
988    pub fn concretize(&self, a: &IntervalDomain) -> Option<Vec<i64>> {
989        (self.gamma)(a)
990    }
991}
992/// Reachability analysis: computes which program points are reachable.
993pub struct ReachabilityAnalysis {
994    /// Set of reachable labels
995    pub reachable: std::collections::HashSet<usize>,
996}
997impl ReachabilityAnalysis {
998    /// Create a new empty reachability analysis.
999    pub fn new() -> Self {
1000        Self {
1001            reachable: std::collections::HashSet::new(),
1002        }
1003    }
1004    /// Mark a program point as reachable.
1005    pub fn mark_reachable(&mut self, label: usize) {
1006        self.reachable.insert(label);
1007    }
1008    /// Check if a program point is reachable.
1009    pub fn is_reachable(&self, label: usize) -> bool {
1010        self.reachable.contains(&label)
1011    }
1012}
1013/// An assume-guarantee contract for a program component.
1014///
1015/// Represents (A, G): if pre-state satisfies A, then post-state satisfies G.
1016/// Enables compositional verification without analyzing full systems.
1017#[allow(dead_code)]
1018pub struct AssumeGuaranteeContract {
1019    /// The assumption: abstract pre-condition (lower bound on what env provides)
1020    pub assumption: AbstractState,
1021    /// The guarantee: abstract post-condition (upper bound on what component delivers)
1022    pub guarantee: AbstractState,
1023    /// Human-readable name of the component
1024    pub component_name: String,
1025}
1026#[allow(dead_code)]
1027impl AssumeGuaranteeContract {
1028    /// Create a new contract with given assumption and guarantee.
1029    pub fn new(
1030        component_name: impl Into<String>,
1031        assumption: AbstractState,
1032        guarantee: AbstractState,
1033    ) -> Self {
1034        Self {
1035            assumption,
1036            guarantee,
1037            component_name: component_name.into(),
1038        }
1039    }
1040    /// Compose two contracts: component B's assumption must be implied by A's guarantee.
1041    /// Returns the composed contract if compatible, None if incompatible.
1042    pub fn compose(&self, next: &Self) -> Option<Self> {
1043        let compatible = next.assumption.vars.iter().all(|(var, needed)| {
1044            if let Some(provided) = self.guarantee.vars.get(var) {
1045                match (
1046                    &provided.lower,
1047                    &needed.lower,
1048                    &provided.upper,
1049                    &needed.upper,
1050                ) {
1051                    (
1052                        Bound::Finite(pl),
1053                        Bound::Finite(nl),
1054                        Bound::Finite(pu),
1055                        Bound::Finite(nu),
1056                    ) => pl >= nl && pu <= nu,
1057                    _ => true,
1058                }
1059            } else {
1060                true
1061            }
1062        });
1063        if compatible {
1064            Some(Self::new(
1065                format!("{};{}", self.component_name, next.component_name),
1066                self.assumption.clone(),
1067                next.guarantee.clone(),
1068            ))
1069        } else {
1070            None
1071        }
1072    }
1073    /// Check if a given (pre, post) pair satisfies this contract.
1074    pub fn check(&self, pre: &AbstractState, post: &AbstractState) -> bool {
1075        let pre_ok = self.assumption.vars.iter().all(|(var, assumed)| {
1076            let actual = pre.get(var);
1077            match (&actual.lower, &assumed.lower, &actual.upper, &assumed.upper) {
1078                (Bound::Finite(al), Bound::Finite(asl), Bound::Finite(au), Bound::Finite(asu)) => {
1079                    al >= asl && au <= asu
1080                }
1081                _ => !actual.is_bottom,
1082            }
1083        });
1084        let post_ok = self.guarantee.vars.iter().all(|(var, guaranteed)| {
1085            let actual = post.get(var);
1086            match (
1087                &actual.lower,
1088                &guaranteed.lower,
1089                &actual.upper,
1090                &guaranteed.upper,
1091            ) {
1092                (Bound::Finite(al), Bound::Finite(gl), Bound::Finite(au), Bound::Finite(gu)) => {
1093                    al >= gl && au <= gu
1094                }
1095                _ => !actual.is_bottom,
1096            }
1097        });
1098        pre_ok && post_ok
1099    }
1100}
1101/// A full DeepPoly abstract element for one layer.
1102#[allow(dead_code)]
1103pub struct DeepPolyLayer {
1104    /// Neurons in this layer
1105    pub neurons: Vec<DeepPolyNeuron>,
1106}
1107#[allow(dead_code)]
1108impl DeepPolyLayer {
1109    /// Create from interval bounds on neurons (input layer).
1110    pub fn input_layer(lower: &[f64], upper: &[f64]) -> Self {
1111        let dim = lower.len();
1112        let neurons = (0..dim)
1113            .map(|i| {
1114                let mut lb_coeffs = vec![0.0; dim];
1115                let mut ub_coeffs = vec![0.0; dim];
1116                lb_coeffs[i] = 1.0;
1117                ub_coeffs[i] = 1.0;
1118                DeepPolyNeuron {
1119                    lb_coeffs,
1120                    lb_bias: 0.0,
1121                    ub_coeffs,
1122                    ub_bias: 0.0,
1123                    concrete_lb: lower[i],
1124                    concrete_ub: upper[i],
1125                }
1126            })
1127            .collect();
1128        Self { neurons }
1129    }
1130    /// Apply an affine layer W * x + b to produce a new abstract layer.
1131    pub fn affine(&self, weights: &[Vec<f64>], bias: &[f64]) -> Self {
1132        let in_dim = self.neurons.len();
1133        let out_dim = weights.len();
1134        let neurons = (0..out_dim)
1135            .map(|j| {
1136                let mut concrete_lb = bias[j];
1137                let mut concrete_ub = bias[j];
1138                for (i, neuron) in self.neurons.iter().enumerate() {
1139                    let w = weights[j][i];
1140                    if w >= 0.0 {
1141                        concrete_lb += w * neuron.concrete_lb;
1142                        concrete_ub += w * neuron.concrete_ub;
1143                    } else {
1144                        concrete_lb += w * neuron.concrete_ub;
1145                        concrete_ub += w * neuron.concrete_lb;
1146                    }
1147                }
1148                let lb_coeffs = vec![0.0; in_dim];
1149                let ub_coeffs = vec![0.0; in_dim];
1150                DeepPolyNeuron {
1151                    lb_coeffs,
1152                    lb_bias: concrete_lb,
1153                    ub_coeffs,
1154                    ub_bias: concrete_ub,
1155                    concrete_lb,
1156                    concrete_ub,
1157                }
1158            })
1159            .collect();
1160        Self { neurons }
1161    }
1162    /// Apply abstract ReLU to all neurons.
1163    pub fn relu(&self) -> Self {
1164        Self {
1165            neurons: self.neurons.iter().map(|n| n.abstract_relu()).collect(),
1166        }
1167    }
1168    /// Get the concrete lower bound on all neurons.
1169    pub fn concrete_lower(&self) -> Vec<f64> {
1170        self.neurons.iter().map(|n| n.concrete_lb).collect()
1171    }
1172    /// Get the concrete upper bound on all neurons.
1173    pub fn concrete_upper(&self) -> Vec<f64> {
1174        self.neurons.iter().map(|n| n.concrete_ub).collect()
1175    }
1176}
1177/// A template polyhedron: { x | C * x ≤ d } where C (the template) is fixed.
1178///
1179/// Fixing C allows efficient join, meet, and widening without full
1180/// Fourier–Motzkin elimination. Precision is controlled by the choice of C.
1181#[allow(dead_code)]
1182pub struct TemplatePolyhedronDomain {
1183    /// Number of variables
1184    pub dim: usize,
1185    /// Template matrix C: each row is a constraint direction (len = dim)
1186    pub template: Vec<Vec<f64>>,
1187    /// RHS bounds d: C * x ≤ d
1188    pub bounds: Vec<f64>,
1189    /// Whether this is the bottom (infeasible) element
1190    pub is_bottom: bool,
1191}
1192#[allow(dead_code)]
1193impl TemplatePolyhedronDomain {
1194    /// Create the top element (no constraints: all bounds = +∞).
1195    pub fn top(dim: usize, template: Vec<Vec<f64>>) -> Self {
1196        let k = template.len();
1197        Self {
1198            dim,
1199            template,
1200            bounds: vec![f64::INFINITY; k],
1201            is_bottom: false,
1202        }
1203    }
1204    /// Create the bottom element (infeasible: one bound = -∞).
1205    pub fn bottom(dim: usize, template: Vec<Vec<f64>>) -> Self {
1206        let k = template.len();
1207        Self {
1208            dim,
1209            template,
1210            bounds: vec![f64::NEG_INFINITY; k],
1211            is_bottom: true,
1212        }
1213    }
1214    /// Create from a point: d_i = c_i · point (tight bounds).
1215    pub fn from_point(dim: usize, template: Vec<Vec<f64>>, point: &[f64]) -> Self {
1216        assert_eq!(point.len(), dim);
1217        let bounds: Vec<f64> = template
1218            .iter()
1219            .map(|row| row.iter().zip(point.iter()).map(|(ci, xi)| ci * xi).sum())
1220            .collect();
1221        Self {
1222            dim,
1223            template,
1224            bounds,
1225            is_bottom: false,
1226        }
1227    }
1228    /// Join: take componentwise max of bounds (least upper bound).
1229    pub fn join(&self, other: &Self) -> Self {
1230        assert_eq!(self.template.len(), other.template.len());
1231        if self.is_bottom {
1232            return other.clone();
1233        }
1234        if other.is_bottom {
1235            return self.clone();
1236        }
1237        let bounds: Vec<f64> = self
1238            .bounds
1239            .iter()
1240            .zip(other.bounds.iter())
1241            .map(|(a, b)| a.max(*b))
1242            .collect();
1243        Self {
1244            dim: self.dim,
1245            template: self.template.clone(),
1246            bounds,
1247            is_bottom: false,
1248        }
1249    }
1250    /// Meet: take componentwise min of bounds (greatest lower bound).
1251    pub fn meet(&self, other: &Self) -> Self {
1252        assert_eq!(self.template.len(), other.template.len());
1253        if self.is_bottom || other.is_bottom {
1254            return Self::bottom(self.dim, self.template.clone());
1255        }
1256        let bounds: Vec<f64> = self
1257            .bounds
1258            .iter()
1259            .zip(other.bounds.iter())
1260            .map(|(a, b)| a.min(*b))
1261            .collect();
1262        Self {
1263            dim: self.dim,
1264            template: self.template.clone(),
1265            bounds,
1266            is_bottom: false,
1267        }
1268    }
1269    /// Widening: keep bound if stable, otherwise set to +∞.
1270    pub fn widen(&self, next: &Self) -> Self {
1271        assert_eq!(self.bounds.len(), next.bounds.len());
1272        if self.is_bottom {
1273            return next.clone();
1274        }
1275        let bounds: Vec<f64> = self
1276            .bounds
1277            .iter()
1278            .zip(next.bounds.iter())
1279            .map(|(a, b)| if *b <= *a { *a } else { f64::INFINITY })
1280            .collect();
1281        Self {
1282            dim: self.dim,
1283            template: self.template.clone(),
1284            bounds,
1285            is_bottom: false,
1286        }
1287    }
1288    /// Check if a point satisfies all template constraints.
1289    pub fn contains(&self, point: &[f64]) -> bool {
1290        if self.is_bottom || point.len() != self.dim {
1291            return false;
1292        }
1293        self.template
1294            .iter()
1295            .zip(self.bounds.iter())
1296            .all(|(row, &d)| {
1297                let lhs: f64 = row.iter().zip(point.iter()).map(|(c, x)| c * x).sum();
1298                lhs <= d
1299            })
1300    }
1301}
1302/// A dataflow analysis: associates abstract values to program points.
1303pub struct DataflowAnalysis {
1304    /// Whether this is a forward or backward analysis
1305    pub direction: AnalysisDirection,
1306    /// Abstract values at each program point (indexed by label)
1307    pub values: HashMap<usize, AbstractState>,
1308}
1309impl DataflowAnalysis {
1310    /// Create a new (initially bottom) forward dataflow analysis.
1311    pub fn new_forward() -> Self {
1312        Self {
1313            direction: AnalysisDirection::Forward,
1314            values: HashMap::new(),
1315        }
1316    }
1317    /// Create a new (initially bottom) backward dataflow analysis.
1318    pub fn new_backward() -> Self {
1319        Self {
1320            direction: AnalysisDirection::Backward,
1321            values: HashMap::new(),
1322        }
1323    }
1324    /// Get the abstract value at a program point.
1325    pub fn at(&self, label: usize) -> AbstractState {
1326        self.values
1327            .get(&label)
1328            .cloned()
1329            .unwrap_or_else(AbstractState::bottom)
1330    }
1331    /// Set the abstract value at a program point.
1332    pub fn set_at(&mut self, label: usize, state: AbstractState) {
1333        self.values.insert(label, state);
1334    }
1335}
1336/// Null pointer analysis state: map from variable to null status.
1337pub struct NullPointerAnalysis {
1338    /// Variable → nullness
1339    pub nullness: HashMap<String, NullValue>,
1340}
1341impl NullPointerAnalysis {
1342    /// Create empty null pointer analysis state.
1343    pub fn new() -> Self {
1344        Self {
1345            nullness: HashMap::new(),
1346        }
1347    }
1348    /// Check if a variable may be null.
1349    pub fn may_be_null(&self, var: &str) -> bool {
1350        matches!(
1351            self.nullness.get(var),
1352            Some(NullValue::MayNull) | Some(NullValue::MustNull) | None
1353        )
1354    }
1355    /// Check if a variable must be null.
1356    pub fn must_be_null(&self, var: &str) -> bool {
1357        matches!(self.nullness.get(var), Some(NullValue::MustNull))
1358    }
1359}