use crate::types::{ConditionPredicate, MetricFn};
pub struct Hypothesis<R> {
pub name: String,
pub condition_fn: Box<ConditionPredicate<R>>,
pub property_name: String,
pub condition_desc: String,
pub complexity: f64,
pub accuracy: f64,
pub score: f64,
}
impl<R> Hypothesis<R> {
pub fn new(
name: impl Into<String>,
condition_fn: impl Fn(&[R]) -> bool + Send + Sync + 'static,
property_name: impl Into<String>,
condition_desc: impl Into<String>,
complexity: f64,
) -> Self {
let property_name = property_name.into();
assert!(
property_name == "persistence"
|| property_name == "storage"
|| property_name == "memory",
"property_name must be 'persistence', 'storage', or 'memory'"
);
Self {
name: name.into(),
condition_fn: Box::new(condition_fn),
property_name,
condition_desc: condition_desc.into(),
complexity,
accuracy: 0.0,
score: 0.0,
}
}
pub fn test<T>(
&mut self,
test_data: &[(&[R], &[Vec<T>])],
metric_fn: &MetricFn<T>,
threshold: f64,
) -> f64 {
let mut positive_condition = 0usize;
let mut correct_predictions = 0usize;
for (rules, trajectories) in test_data {
if (self.condition_fn)(rules) {
positive_condition += 1;
let metric_value = metric_fn(trajectories);
if metric_value > threshold {
correct_predictions += 1;
}
}
}
self.accuracy = if positive_condition > 0 {
correct_predictions as f64 / positive_condition as f64
} else {
0.0
};
let lambda = 0.1;
self.score = self.accuracy - lambda * self.complexity;
self.accuracy
}
pub fn survives(&self) -> bool {
self.score > 0.0 && self.accuracy >= 0.5
}
}
impl<R> std::fmt::Display for Hypothesis<R> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let status = if self.survives() { "SURVIVES" } else { "FAILS" };
write!(
f,
"{}: {} (acc={:.3}, score={:.3}, {})",
self.name, self.condition_desc, self.accuracy, self.score, status
)
}
}
pub fn surviving_hypotheses<R>(hypotheses: &[Hypothesis<R>]) -> Vec<&Hypothesis<R>> {
hypotheses.iter().filter(|h| h.survives()).collect()
}