use std::collections::{HashMap, HashSet};
pub struct IntervalWidening;
impl IntervalWidening {
pub fn apply(a: &IntervalDomain, b: &IntervalDomain) -> IntervalDomain {
a.widen(b)
}
}
#[derive(Debug, Clone)]
pub struct AbstractState {
pub vars: HashMap<String, IntervalDomain>,
}
impl AbstractState {
pub fn bottom() -> Self {
Self {
vars: HashMap::new(),
}
}
pub fn get(&self, var: &str) -> IntervalDomain {
self.vars
.get(var)
.cloned()
.unwrap_or_else(IntervalDomain::top)
}
pub fn set(&mut self, var: impl Into<String>, val: IntervalDomain) {
self.vars.insert(var.into(), val);
}
pub fn join(&self, other: &Self) -> Self {
let mut result = self.clone();
for (k, v) in &other.vars {
let joined = result.get(k).join(v);
result.vars.insert(k.clone(), joined);
}
result
}
pub fn widen(&self, other: &Self) -> Self {
let mut result = self.clone();
for (k, v) in &other.vars {
let widened = result.get(k).widen(v);
result.vars.insert(k.clone(), widened);
}
result
}
}
pub struct PolyhedralDomain {
pub n: usize,
pub constraints: Vec<(Vec<f64>, f64)>,
}
impl PolyhedralDomain {
pub fn top(n: usize) -> Self {
Self {
n,
constraints: vec![],
}
}
pub fn add_constraint(&mut self, coeffs: Vec<f64>, rhs: f64) {
if coeffs.len() == self.n {
self.constraints.push((coeffs, rhs));
}
}
pub fn contains(&self, point: &[f64]) -> bool {
if point.len() != self.n {
return false;
}
self.constraints.iter().all(|(coeffs, rhs)| {
let sum: f64 = coeffs.iter().zip(point.iter()).map(|(a, x)| a * x).sum();
sum <= *rhs
})
}
pub fn num_constraints(&self) -> usize {
self.constraints.len()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParityDomain {
Bottom,
Even,
Odd,
Top,
}
impl ParityDomain {
pub fn join(&self, other: &Self) -> Self {
use ParityDomain::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => x.clone(),
(Top, _) | (_, Top) => Top,
(a, b) if a == b => a.clone(),
_ => Top,
}
}
pub fn add(&self, other: &Self) -> Self {
use ParityDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Top, _) | (_, Top) => Top,
(Even, Even) => Even,
(Odd, Odd) => Even,
(Even, Odd) | (Odd, Even) => Odd,
}
}
pub fn mul(&self, other: &Self) -> Self {
use ParityDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Even, _) | (_, Even) => Even,
(Odd, Odd) => Odd,
_ => Top,
}
}
}
#[allow(dead_code)]
pub struct ProbabilisticAbstractDomain {
pub k: usize,
pub prob_lower: Vec<f64>,
pub prob_upper: Vec<f64>,
}
#[allow(dead_code)]
impl ProbabilisticAbstractDomain {
pub fn uniform(k: usize) -> Self {
let p = 1.0 / k as f64;
Self {
k,
prob_lower: vec![p; k],
prob_upper: vec![p; k],
}
}
pub fn top(k: usize) -> Self {
Self {
k,
prob_lower: vec![0.0; k],
prob_upper: vec![1.0; k],
}
}
pub fn dirac(k: usize, i: usize) -> Self {
let mut lower = vec![0.0; k];
let mut upper = vec![0.0; k];
lower[i] = 1.0;
upper[i] = 1.0;
Self {
k,
prob_lower: lower,
prob_upper: upper,
}
}
pub fn join(&self, other: &Self) -> Self {
assert_eq!(self.k, other.k);
Self {
k: self.k,
prob_lower: self
.prob_lower
.iter()
.zip(&other.prob_lower)
.map(|(a, b)| a.min(*b))
.collect(),
prob_upper: self
.prob_upper
.iter()
.zip(&other.prob_upper)
.map(|(a, b)| a.max(*b))
.collect(),
}
}
pub fn abstract_expectation(&self, f: &[f64]) -> (f64, f64) {
assert_eq!(f.len(), self.k);
let lower_e: f64 = self
.prob_lower
.iter()
.zip(f.iter())
.map(|(p, fi)| p * fi)
.sum();
let upper_e: f64 = self
.prob_upper
.iter()
.zip(f.iter())
.map(|(p, fi)| p * fi)
.sum();
(lower_e, upper_e)
}
pub fn is_sound(&self) -> bool {
let lo_ok = self
.prob_lower
.iter()
.zip(&self.prob_upper)
.all(|(l, u)| l <= u && *l >= 0.0 && *u <= 1.0);
let sum_lo: f64 = self.prob_lower.iter().sum();
let sum_hi: f64 = self.prob_upper.iter().sum();
lo_ok && sum_lo <= 1.0 + 1e-9 && sum_hi >= 1.0 - 1e-9
}
}
#[derive(Debug, Clone)]
#[allow(dead_code)]
pub struct DeepPolyNeuron {
pub lb_coeffs: Vec<f64>,
pub lb_bias: f64,
pub ub_coeffs: Vec<f64>,
pub ub_bias: f64,
pub concrete_lb: f64,
pub concrete_ub: f64,
}
#[allow(dead_code)]
impl DeepPolyNeuron {
pub fn constant(val: f64, input_dim: usize) -> Self {
Self {
lb_coeffs: vec![0.0; input_dim],
lb_bias: val,
ub_coeffs: vec![0.0; input_dim],
ub_bias: val,
concrete_lb: val,
concrete_ub: val,
}
}
pub fn abstract_relu(&self) -> Self {
let input_dim = self.lb_coeffs.len();
if self.concrete_ub <= 0.0 {
DeepPolyNeuron::constant(0.0, input_dim)
} else if self.concrete_lb >= 0.0 {
self.clone()
} else {
let slope = self.concrete_ub / (self.concrete_ub - self.concrete_lb);
let ub_coeffs: Vec<f64> = self.ub_coeffs.iter().map(|c| slope * c).collect();
let ub_bias = slope * (self.ub_bias - self.concrete_lb);
DeepPolyNeuron {
lb_coeffs: vec![0.0; input_dim],
lb_bias: 0.0,
ub_coeffs,
ub_bias,
concrete_lb: 0.0,
concrete_ub: slope * (self.concrete_ub - self.concrete_lb),
}
}
}
pub fn contains_concrete(&self, val: f64) -> bool {
val >= self.concrete_lb && val <= self.concrete_ub
}
}
pub struct LoopBound {
pub bound: Option<u64>,
}
impl LoopBound {
pub fn unknown() -> Self {
Self { bound: None }
}
pub fn finite(n: u64) -> Self {
Self { bound: Some(n) }
}
pub fn from_interval(interval: &IntervalDomain) -> Self {
match &interval.upper {
Bound::Finite(n) if *n >= 0 => Self::finite(*n as u64 + 1),
_ => Self::unknown(),
}
}
pub fn terminates(&self) -> bool {
self.bound.is_some()
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Bound {
NegInf,
Finite(i64),
PosInf,
}
impl Bound {
pub fn add(&self, other: &Self) -> Self {
match (self, other) {
(Bound::NegInf, Bound::PosInf) | (Bound::PosInf, Bound::NegInf) => Bound::NegInf,
(Bound::NegInf, _) | (_, Bound::NegInf) => Bound::NegInf,
(Bound::PosInf, _) | (_, Bound::PosInf) => Bound::PosInf,
(Bound::Finite(a), Bound::Finite(b)) => Bound::Finite(a.saturating_add(*b)),
}
}
pub fn neg(&self) -> Self {
match self {
Bound::NegInf => Bound::PosInf,
Bound::PosInf => Bound::NegInf,
Bound::Finite(n) => Bound::Finite(-n),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TaintValue {
Clean,
Tainted,
Bottom,
}
pub struct AbstractionFunction {
gc: GaloisConnection,
}
impl AbstractionFunction {
pub fn new() -> Self {
Self {
gc: GaloisConnection::interval_galois(),
}
}
pub fn apply(&self, vals: &[i64]) -> IntervalDomain {
self.gc.abstract_of(vals)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SignDomain {
Bottom,
Neg,
Zero,
Pos,
NonNeg,
NonPos,
Top,
}
impl SignDomain {
pub fn join(&self, other: &Self) -> Self {
use SignDomain::*;
match (self, other) {
(Bottom, x) | (x, Bottom) => x.clone(),
(Top, _) | (_, Top) => Top,
(a, b) if a == b => a.clone(),
(Zero, Pos) | (Pos, Zero) => NonNeg,
(Zero, Neg) | (Neg, Zero) => NonPos,
(NonNeg, Neg) | (Neg, NonNeg) => Top,
(NonPos, Pos) | (Pos, NonPos) => Top,
(Neg, Pos) | (Pos, Neg) => Top,
(NonNeg, NonPos) | (NonPos, NonNeg) => Top,
_ => Top,
}
}
pub fn widen(&self, other: &Self) -> Self {
self.join(other)
}
pub fn add(&self, other: &Self) -> Self {
use SignDomain::*;
match (self, other) {
(Bottom, _) | (_, Bottom) => Bottom,
(Top, _) | (_, Top) => Top,
(Zero, x) | (x, Zero) => x.clone(),
(Pos, Pos) => Pos,
(Neg, Neg) => Neg,
(NonNeg, NonNeg) => NonNeg,
(NonPos, NonPos) => NonPos,
(Pos, NonNeg) | (NonNeg, Pos) => Pos,
(Neg, NonPos) | (NonPos, Neg) => Neg,
_ => Top,
}
}
pub fn neg(&self) -> Self {
use SignDomain::*;
match self {
Bottom => Bottom,
Neg => Pos,
Zero => Zero,
Pos => Neg,
NonNeg => NonPos,
NonPos => NonNeg,
Top => Top,
}
}
}
pub struct OctagonDomain {
pub n: usize,
pub matrix: Vec<Vec<Option<i64>>>,
}
impl OctagonDomain {
pub fn top(n: usize) -> Self {
let size = 2 * n;
Self {
n,
matrix: vec![vec![None; size]; size],
}
}
pub fn bottom(n: usize) -> Self {
let size = 2 * n;
let mut matrix = vec![vec![None; size]; size];
for i in 0..size {
matrix[i][i] = Some(-1);
}
Self { n, matrix }
}
pub fn add_upper_bound(&mut self, i: usize, c: i64) {
if i < self.n {
let pos = 2 * i;
let neg = 2 * i + 1;
let cur = self.matrix[pos][neg];
let val = 2 * c;
self.matrix[pos][neg] = Some(match cur {
None => val,
Some(v) => v.min(val),
});
}
}
pub fn is_satisfiable(&self) -> bool {
for i in 0..2 * self.n {
if let Some(v) = self.matrix[i][i] {
if v < 0 {
return false;
}
}
}
true
}
}
pub struct FixpointComputation {
pub max_iter: usize,
pub widen_delay: usize,
}
impl FixpointComputation {
pub fn new() -> Self {
Self {
max_iter: 1000,
widen_delay: 3,
}
}
pub fn compute<F>(&self, f: F, init: AbstractState) -> AbstractState
where
F: Fn(&AbstractState) -> AbstractState,
{
let mut current = init;
let mut delay = DelayedWidening::new(self.widen_delay);
for _ in 0..self.max_iter {
let next = f(¤t);
let widened = AbstractState {
vars: {
let mut vars = current.vars.clone();
for (k, v) in &next.vars {
let cur_v = current.get(k);
let w = delay.apply(&cur_v, v);
vars.insert(k.clone(), w);
}
vars
},
};
if widened
.vars
.iter()
.all(|(k, v)| current.vars.get(k) == Some(v))
&& current.vars.len() == widened.vars.len()
{
return widened;
}
current = widened;
}
current
}
}
pub struct ConcretizationFunction {
gc: GaloisConnection,
}
impl ConcretizationFunction {
pub fn new() -> Self {
Self {
gc: GaloisConnection::interval_galois(),
}
}
pub fn apply(&self, a: &IntervalDomain) -> Option<Vec<i64>> {
self.gc.concretize(a)
}
}
pub struct TaintAnalysis {
pub taint: HashMap<String, TaintValue>,
}
impl TaintAnalysis {
pub fn new() -> Self {
Self {
taint: HashMap::new(),
}
}
pub fn add_source(&mut self, var: impl Into<String>) {
self.taint.insert(var.into(), TaintValue::Tainted);
}
pub fn is_tainted(&self, var: &str) -> bool {
matches!(self.taint.get(var), Some(TaintValue::Tainted))
}
pub fn propagate(&mut self, result: impl Into<String>, inputs: &[&str]) {
let tainted = inputs.iter().any(|v| self.is_tainted(v));
let val = if tainted {
TaintValue::Tainted
} else {
TaintValue::Clean
};
self.taint.insert(result.into(), val);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntervalDomain {
pub lower: Bound,
pub upper: Bound,
pub is_bottom: bool,
}
impl IntervalDomain {
pub fn bottom() -> Self {
Self {
lower: Bound::PosInf,
upper: Bound::NegInf,
is_bottom: true,
}
}
pub fn top() -> Self {
Self {
lower: Bound::NegInf,
upper: Bound::PosInf,
is_bottom: false,
}
}
pub fn constant(n: i64) -> Self {
Self {
lower: Bound::Finite(n),
upper: Bound::Finite(n),
is_bottom: false,
}
}
pub fn new(lower: Bound, upper: Bound) -> Self {
if lower > upper {
Self::bottom()
} else {
Self {
lower,
upper,
is_bottom: false,
}
}
}
pub fn join(&self, other: &Self) -> Self {
if self.is_bottom {
return other.clone();
}
if other.is_bottom {
return self.clone();
}
Self::new(
self.lower.clone().min(other.lower.clone()),
self.upper.clone().max(other.upper.clone()),
)
}
pub fn meet(&self, other: &Self) -> Self {
if self.is_bottom || other.is_bottom {
return Self::bottom();
}
Self::new(
self.lower.clone().max(other.lower.clone()),
self.upper.clone().min(other.upper.clone()),
)
}
pub fn widen(&self, other: &Self) -> Self {
if self.is_bottom {
return other.clone();
}
if other.is_bottom {
return self.clone();
}
let new_lower = if other.lower < self.lower {
Bound::NegInf
} else {
self.lower.clone()
};
let new_upper = if other.upper > self.upper {
Bound::PosInf
} else {
self.upper.clone()
};
Self::new(new_lower, new_upper)
}
pub fn narrow(&self, other: &Self) -> Self {
if self.is_bottom || other.is_bottom {
return Self::bottom();
}
let new_lower = if self.lower == Bound::NegInf {
other.lower.clone()
} else {
self.lower.clone()
};
let new_upper = if self.upper == Bound::PosInf {
other.upper.clone()
} else {
self.upper.clone()
};
Self::new(new_lower, new_upper)
}
pub fn add(&self, other: &Self) -> Self {
if self.is_bottom || other.is_bottom {
return Self::bottom();
}
Self::new(self.lower.add(&other.lower), self.upper.add(&other.upper))
}
pub fn contains(&self, n: i64) -> bool {
if self.is_bottom {
return false;
}
let lo_ok = match &self.lower {
Bound::NegInf => true,
Bound::Finite(l) => *l <= n,
Bound::PosInf => false,
};
let hi_ok = match &self.upper {
Bound::NegInf => false,
Bound::Finite(u) => n <= *u,
Bound::PosInf => true,
};
lo_ok && hi_ok
}
}
pub struct AbstractTransformer {
pub transform: Box<dyn Fn(&AbstractState) -> AbstractState>,
}
impl AbstractTransformer {
pub fn new<F: Fn(&AbstractState) -> AbstractState + 'static>(f: F) -> Self {
Self {
transform: Box::new(f),
}
}
pub fn apply(&self, state: &AbstractState) -> AbstractState {
(self.transform)(state)
}
pub fn compose(self, other: Self) -> Self {
Self::new(move |s| other.apply(&self.apply(s)))
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AnalysisDirection {
Forward,
Backward,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum NullValue {
MustNull,
NonNull,
MayNull,
Bottom,
}
#[allow(dead_code)]
pub struct ZonotopeDomain {
pub dim: usize,
pub center: Vec<f64>,
pub generators: Vec<Vec<f64>>,
}
#[allow(dead_code)]
impl ZonotopeDomain {
pub fn point(center: Vec<f64>) -> Self {
let dim = center.len();
Self {
dim,
center,
generators: vec![],
}
}
pub fn from_intervals(lower: &[f64], upper: &[f64]) -> Self {
assert_eq!(lower.len(), upper.len());
let dim = lower.len();
let center: Vec<f64> = lower
.iter()
.zip(upper.iter())
.map(|(l, h)| (l + h) / 2.0)
.collect();
let generators: Vec<Vec<f64>> = (0..dim)
.map(|i| {
let mut g = vec![0.0; dim];
g[i] = (upper[i] - lower[i]) / 2.0;
g
})
.collect();
Self {
dim,
center,
generators,
}
}
pub fn to_intervals(&self) -> (Vec<f64>, Vec<f64>) {
let mut lower = self.center.clone();
let mut upper = self.center.clone();
for g in &self.generators {
for (i, gi) in g.iter().enumerate() {
let abs_g = gi.abs();
lower[i] -= abs_g;
upper[i] += abs_g;
}
}
(lower, upper)
}
pub fn affine_map(&self, matrix: &[Vec<f64>], bias: &[f64]) -> Self {
let out_dim = matrix.len();
let new_center: Vec<f64> = (0..out_dim)
.map(|i| {
let dot: f64 = matrix[i]
.iter()
.zip(self.center.iter())
.map(|(a, c)| a * c)
.sum();
dot + bias[i]
})
.collect();
let new_generators: Vec<Vec<f64>> = self
.generators
.iter()
.map(|g| {
(0..out_dim)
.map(|i| matrix[i].iter().zip(g.iter()).map(|(a, gi)| a * gi).sum())
.collect()
})
.collect();
Self {
dim: out_dim,
center: new_center,
generators: new_generators,
}
}
pub fn join(&self, other: &Self) -> Self {
assert_eq!(self.dim, other.dim);
let (l1, u1) = self.to_intervals();
let (l2, u2) = other.to_intervals();
let lower: Vec<f64> = l1.iter().zip(l2.iter()).map(|(a, b)| a.min(*b)).collect();
let upper: Vec<f64> = u1.iter().zip(u2.iter()).map(|(a, b)| a.max(*b)).collect();
Self::from_intervals(&lower, &upper)
}
pub fn may_contain(&self, point: &[f64]) -> bool {
if point.len() != self.dim {
return false;
}
let (lower, upper) = self.to_intervals();
lower
.iter()
.zip(upper.iter())
.zip(point.iter())
.all(|((l, u), p)| *l <= *p && *p <= *u)
}
}
pub struct ArrayBoundsAnalysis {
pub index_bounds: HashMap<String, IntervalDomain>,
pub array_sizes: HashMap<String, IntervalDomain>,
}
impl ArrayBoundsAnalysis {
pub fn new() -> Self {
Self {
index_bounds: HashMap::new(),
array_sizes: HashMap::new(),
}
}
pub fn is_safe_access(&self, array: &str, idx_var: &str) -> bool {
let idx = self
.index_bounds
.get(idx_var)
.cloned()
.unwrap_or_else(IntervalDomain::top);
let sz = match self.array_sizes.get(array) {
Some(s) => s.clone(),
None => return false,
};
if idx.is_bottom {
return true;
}
let lo_ok = match &idx.lower {
Bound::Finite(l) => *l >= 0,
_ => false,
};
let hi_ok = match (&idx.upper, &sz.lower) {
(Bound::Finite(hi), Bound::Finite(sz_lo)) => *hi < *sz_lo,
_ => false,
};
lo_ok && hi_ok
}
}
pub struct DelayedWidening {
pub delay: usize,
pub step: usize,
}
impl DelayedWidening {
pub fn new(delay: usize) -> Self {
Self { delay, step: 0 }
}
pub fn apply(&mut self, a: &IntervalDomain, b: &IntervalDomain) -> IntervalDomain {
self.step += 1;
if self.step <= self.delay {
a.join(b)
} else {
a.widen(b)
}
}
pub fn reset(&mut self) {
self.step = 0;
}
}
pub struct GaloisConnection {
pub alpha: Box<dyn Fn(&[i64]) -> IntervalDomain>,
pub gamma: Box<dyn Fn(&IntervalDomain) -> Option<Vec<i64>>>,
}
impl GaloisConnection {
pub fn interval_galois() -> Self {
Self {
alpha: Box::new(|vals: &[i64]| {
if vals.is_empty() {
return IntervalDomain::bottom();
}
let lo = vals
.iter()
.copied()
.min()
.expect("vals is non-empty: checked by early return");
let hi = vals
.iter()
.copied()
.max()
.expect("vals is non-empty: checked by early return");
IntervalDomain::new(Bound::Finite(lo), Bound::Finite(hi))
}),
gamma: Box::new(|interval: &IntervalDomain| {
if interval.is_bottom {
return Some(vec![]);
}
match (&interval.lower, &interval.upper) {
(Bound::Finite(l), Bound::Finite(u)) if u - l <= 1000 => {
Some((*l..=*u).collect())
}
_ => None,
}
}),
}
}
pub fn abstract_of(&self, vals: &[i64]) -> IntervalDomain {
(self.alpha)(vals)
}
pub fn concretize(&self, a: &IntervalDomain) -> Option<Vec<i64>> {
(self.gamma)(a)
}
}
pub struct ReachabilityAnalysis {
pub reachable: std::collections::HashSet<usize>,
}
impl ReachabilityAnalysis {
pub fn new() -> Self {
Self {
reachable: std::collections::HashSet::new(),
}
}
pub fn mark_reachable(&mut self, label: usize) {
self.reachable.insert(label);
}
pub fn is_reachable(&self, label: usize) -> bool {
self.reachable.contains(&label)
}
}
#[allow(dead_code)]
pub struct AssumeGuaranteeContract {
pub assumption: AbstractState,
pub guarantee: AbstractState,
pub component_name: String,
}
#[allow(dead_code)]
impl AssumeGuaranteeContract {
pub fn new(
component_name: impl Into<String>,
assumption: AbstractState,
guarantee: AbstractState,
) -> Self {
Self {
assumption,
guarantee,
component_name: component_name.into(),
}
}
pub fn compose(&self, next: &Self) -> Option<Self> {
let compatible = next.assumption.vars.iter().all(|(var, needed)| {
if let Some(provided) = self.guarantee.vars.get(var) {
match (
&provided.lower,
&needed.lower,
&provided.upper,
&needed.upper,
) {
(
Bound::Finite(pl),
Bound::Finite(nl),
Bound::Finite(pu),
Bound::Finite(nu),
) => pl >= nl && pu <= nu,
_ => true,
}
} else {
true
}
});
if compatible {
Some(Self::new(
format!("{};{}", self.component_name, next.component_name),
self.assumption.clone(),
next.guarantee.clone(),
))
} else {
None
}
}
pub fn check(&self, pre: &AbstractState, post: &AbstractState) -> bool {
let pre_ok = self.assumption.vars.iter().all(|(var, assumed)| {
let actual = pre.get(var);
match (&actual.lower, &assumed.lower, &actual.upper, &assumed.upper) {
(Bound::Finite(al), Bound::Finite(asl), Bound::Finite(au), Bound::Finite(asu)) => {
al >= asl && au <= asu
}
_ => !actual.is_bottom,
}
});
let post_ok = self.guarantee.vars.iter().all(|(var, guaranteed)| {
let actual = post.get(var);
match (
&actual.lower,
&guaranteed.lower,
&actual.upper,
&guaranteed.upper,
) {
(Bound::Finite(al), Bound::Finite(gl), Bound::Finite(au), Bound::Finite(gu)) => {
al >= gl && au <= gu
}
_ => !actual.is_bottom,
}
});
pre_ok && post_ok
}
}
#[allow(dead_code)]
pub struct DeepPolyLayer {
pub neurons: Vec<DeepPolyNeuron>,
}
#[allow(dead_code)]
impl DeepPolyLayer {
pub fn input_layer(lower: &[f64], upper: &[f64]) -> Self {
let dim = lower.len();
let neurons = (0..dim)
.map(|i| {
let mut lb_coeffs = vec![0.0; dim];
let mut ub_coeffs = vec![0.0; dim];
lb_coeffs[i] = 1.0;
ub_coeffs[i] = 1.0;
DeepPolyNeuron {
lb_coeffs,
lb_bias: 0.0,
ub_coeffs,
ub_bias: 0.0,
concrete_lb: lower[i],
concrete_ub: upper[i],
}
})
.collect();
Self { neurons }
}
pub fn affine(&self, weights: &[Vec<f64>], bias: &[f64]) -> Self {
let in_dim = self.neurons.len();
let out_dim = weights.len();
let neurons = (0..out_dim)
.map(|j| {
let mut concrete_lb = bias[j];
let mut concrete_ub = bias[j];
for (i, neuron) in self.neurons.iter().enumerate() {
let w = weights[j][i];
if w >= 0.0 {
concrete_lb += w * neuron.concrete_lb;
concrete_ub += w * neuron.concrete_ub;
} else {
concrete_lb += w * neuron.concrete_ub;
concrete_ub += w * neuron.concrete_lb;
}
}
let lb_coeffs = vec![0.0; in_dim];
let ub_coeffs = vec![0.0; in_dim];
DeepPolyNeuron {
lb_coeffs,
lb_bias: concrete_lb,
ub_coeffs,
ub_bias: concrete_ub,
concrete_lb,
concrete_ub,
}
})
.collect();
Self { neurons }
}
pub fn relu(&self) -> Self {
Self {
neurons: self.neurons.iter().map(|n| n.abstract_relu()).collect(),
}
}
pub fn concrete_lower(&self) -> Vec<f64> {
self.neurons.iter().map(|n| n.concrete_lb).collect()
}
pub fn concrete_upper(&self) -> Vec<f64> {
self.neurons.iter().map(|n| n.concrete_ub).collect()
}
}
#[allow(dead_code)]
pub struct TemplatePolyhedronDomain {
pub dim: usize,
pub template: Vec<Vec<f64>>,
pub bounds: Vec<f64>,
pub is_bottom: bool,
}
#[allow(dead_code)]
impl TemplatePolyhedronDomain {
pub fn top(dim: usize, template: Vec<Vec<f64>>) -> Self {
let k = template.len();
Self {
dim,
template,
bounds: vec![f64::INFINITY; k],
is_bottom: false,
}
}
pub fn bottom(dim: usize, template: Vec<Vec<f64>>) -> Self {
let k = template.len();
Self {
dim,
template,
bounds: vec![f64::NEG_INFINITY; k],
is_bottom: true,
}
}
pub fn from_point(dim: usize, template: Vec<Vec<f64>>, point: &[f64]) -> Self {
assert_eq!(point.len(), dim);
let bounds: Vec<f64> = template
.iter()
.map(|row| row.iter().zip(point.iter()).map(|(ci, xi)| ci * xi).sum())
.collect();
Self {
dim,
template,
bounds,
is_bottom: false,
}
}
pub fn join(&self, other: &Self) -> Self {
assert_eq!(self.template.len(), other.template.len());
if self.is_bottom {
return other.clone();
}
if other.is_bottom {
return self.clone();
}
let bounds: Vec<f64> = self
.bounds
.iter()
.zip(other.bounds.iter())
.map(|(a, b)| a.max(*b))
.collect();
Self {
dim: self.dim,
template: self.template.clone(),
bounds,
is_bottom: false,
}
}
pub fn meet(&self, other: &Self) -> Self {
assert_eq!(self.template.len(), other.template.len());
if self.is_bottom || other.is_bottom {
return Self::bottom(self.dim, self.template.clone());
}
let bounds: Vec<f64> = self
.bounds
.iter()
.zip(other.bounds.iter())
.map(|(a, b)| a.min(*b))
.collect();
Self {
dim: self.dim,
template: self.template.clone(),
bounds,
is_bottom: false,
}
}
pub fn widen(&self, next: &Self) -> Self {
assert_eq!(self.bounds.len(), next.bounds.len());
if self.is_bottom {
return next.clone();
}
let bounds: Vec<f64> = self
.bounds
.iter()
.zip(next.bounds.iter())
.map(|(a, b)| if *b <= *a { *a } else { f64::INFINITY })
.collect();
Self {
dim: self.dim,
template: self.template.clone(),
bounds,
is_bottom: false,
}
}
pub fn contains(&self, point: &[f64]) -> bool {
if self.is_bottom || point.len() != self.dim {
return false;
}
self.template
.iter()
.zip(self.bounds.iter())
.all(|(row, &d)| {
let lhs: f64 = row.iter().zip(point.iter()).map(|(c, x)| c * x).sum();
lhs <= d
})
}
}
pub struct DataflowAnalysis {
pub direction: AnalysisDirection,
pub values: HashMap<usize, AbstractState>,
}
impl DataflowAnalysis {
pub fn new_forward() -> Self {
Self {
direction: AnalysisDirection::Forward,
values: HashMap::new(),
}
}
pub fn new_backward() -> Self {
Self {
direction: AnalysisDirection::Backward,
values: HashMap::new(),
}
}
pub fn at(&self, label: usize) -> AbstractState {
self.values
.get(&label)
.cloned()
.unwrap_or_else(AbstractState::bottom)
}
pub fn set_at(&mut self, label: usize, state: AbstractState) {
self.values.insert(label, state);
}
}
pub struct NullPointerAnalysis {
pub nullness: HashMap<String, NullValue>,
}
impl NullPointerAnalysis {
pub fn new() -> Self {
Self {
nullness: HashMap::new(),
}
}
pub fn may_be_null(&self, var: &str) -> bool {
matches!(
self.nullness.get(var),
Some(NullValue::MayNull) | Some(NullValue::MustNull) | None
)
}
pub fn must_be_null(&self, var: &str) -> bool {
matches!(self.nullness.get(var), Some(NullValue::MustNull))
}
}