use anyhow::anyhow;
use clap::Parser;
use scan_core::{Oracle, Scan};
use super::report::Report;
const NO_PROPS_ERR: &str = "missing properties to verify.
Pass a (space-separated) list of one or more properties specified in the model to be verified.
Alternatively, use the flag --all to verify all properties at once.\n
Examples:
'scan PATH/TO/MODEL verify PROPERTY' verifies the property PROPERTY over the model
'scan PATH/TO/MODEL verify PROPERTY_1 PROPERTY_2' verifies the properties PROPERTY_1 and PROPERTY_2 together over the model
'scan PATH/TO/MODEL verify --all' verifies all specified properties together over the model";
const ALL_PROPS_ERR: &str =
"the --all flag is incompatible with individually-specified properties.\n
Examples:
'scan PATH/TO/MODEL verify PROPERTY' verifies the property PROPERTY over the model
'scan PATH/TO/MODEL verify PROPERTY_1 PROPERTY_2' verifies the properties PROPERTY_1 and PROPERTY_2 together over the model
'scan PATH/TO/MODEL verify --all' verifies all specified properties together over the model";
const BAD_CONFIDENCE: &str =
"confidence has to be a value strictly greater than 0 and strictly less than 1.\n
Example:
'scan PATH/TO/MODEL verify --confidence 0.98 PROPERTY' verifies the property PROPERTY with confidence 0.98 (i.e., 98%)";
const BAD_PRECISION: &str =
"precision has to be a value strictly greater than 0 and strictly less than 1.\n
Example:
'scan PATH/TO/MODEL verify --precision 0.005 PROPERTY' verifies the property PROPERTY with precision 0.005 (i.e., 0.5%)";
#[derive(Debug, Clone, Parser)]
#[deny(missing_docs)]
pub(crate) struct VerifyArgs {
pub(crate) properties: Vec<String>,
#[arg(short, long)]
pub(crate) all: bool,
#[arg(short, long, default_value_t = 0.95)]
pub(crate) confidence: f64,
#[arg(short, long, default_value_t = 0.01)]
pub(crate) precision: f64,
#[arg(long)]
pub(crate) single_thread: bool,
}
impl VerifyArgs {
pub(crate) fn validate(&self) -> anyhow::Result<()> {
if self.properties.is_empty() && !self.all {
Err(anyhow!(NO_PROPS_ERR))
} else if !self.properties.is_empty() && self.all {
Err(anyhow!(ALL_PROPS_ERR))
} else if 0f64 >= self.confidence || self.confidence >= 1f64 {
Err(anyhow!(BAD_CONFIDENCE))
} else if 0f64 >= self.precision || self.precision >= 1f64 {
Err(anyhow!(BAD_PRECISION))
} else {
Ok(())
}
}
pub(crate) fn verify<'a, O>(&self, model: String, scan: &'a Scan<O>) -> Report
where
O: Oracle + Clone + Sync + 'a,
{
let report = if self.single_thread {
scan.adaptive(self.confidence, self.precision)
} else {
scan.par_adaptive(self.confidence, self.precision)
}
.expect("verify");
let successes = report.successes;
let failures = report.failures;
let runs = successes + failures;
let rate = successes as f64 / runs as f64;
let property_failures = self
.properties
.iter()
.cloned()
.zip(report.violations.into_iter().chain([0].into_iter().cycle()))
.collect::<Vec<(String, u32)>>();
Report {
model,
precision: self.precision,
confidence: self.confidence,
rate,
runs,
successes,
failures,
property_failures,
}
}
}