#[derive(Debug)]
pub struct EquivalentMutantDetector {
equivalence_patterns: HashMap<String, f64>,
pattern_confidence: HashMap<String, f64>,
trained: bool,
training_samples: usize,
}
impl EquivalentMutantDetector {
pub fn new() -> Self {
Self {
equivalence_patterns: HashMap::new(),
pattern_confidence: HashMap::new(),
trained: false,
training_samples: 0,
}
}
pub fn train(&mut self, training_data: &[EquivalenceTrainingData]) -> Result<()> {
if training_data.is_empty() {
anyhow::bail!("Training data cannot be empty");
}
for sample in training_data {
let features =
EquivalenceFeatures::from_mutant_pair(&sample.mutant, &sample.original_source);
for pattern in &features.operator_patterns {
let entry = self
.equivalence_patterns
.entry(pattern.clone())
.or_insert(0.0);
if sample.is_equivalent {
*entry += 1.0;
}
let confidence = if sample.verified_manually { 1.0 } else { 0.7 };
self.pattern_confidence.insert(pattern.clone(), confidence);
}
}
let total = training_data.len() as f64;
for score in self.equivalence_patterns.values_mut() {
*score /= total;
}
self.trained = true;
self.training_samples = training_data.len();
Ok(())
}
pub fn update(&mut self, new_data: &[EquivalenceTrainingData]) -> Result<()> {
if !self.trained {
return self.train(new_data);
}
self.training_samples += new_data.len();
for sample in new_data {
let features =
EquivalenceFeatures::from_mutant_pair(&sample.mutant, &sample.original_source);
for pattern in &features.operator_patterns {
let current = self
.equivalence_patterns
.get(pattern)
.copied()
.unwrap_or(0.5);
let alpha = 0.3; let new_score = if sample.is_equivalent {
current * (1.0 - alpha) + alpha
} else {
current * (1.0 - alpha)
};
self.equivalence_patterns.insert(pattern.clone(), new_score);
}
}
Ok(())
}
pub fn detect_equivalent(&self, mutant: &Mutant, original: &str) -> Result<EquivalenceResult> {
if !self.trained {
anyhow::bail!("Detector not trained");
}
let features = EquivalenceFeatures::from_mutant_pair(mutant, original);
let patterns = features.operator_patterns.clone();
let (is_equivalent, confidence, reason) = if features.has_identity_ops {
(
true,
0.9,
"Contains identity operation (e.g., +0, *1, -0, /1)".to_string(),
)
} else if features.has_tautology {
(
true,
0.85,
"Contains boolean tautology (e.g., x || true → true)".to_string(),
)
} else if features.has_commutative {
(
true,
0.8,
"commutative operation swap (e.g., a + b → b + a)".to_string(),
)
} else {
let mut total_score = 0.0;
let mut pattern_count = 0;
for pattern in &features.operator_patterns {
if let Some(&score) = self.equivalence_patterns.get(pattern) {
total_score += score;
pattern_count += 1;
}
}
if pattern_count > 0 {
let avg_score = total_score / pattern_count as f64;
(
avg_score > 0.6,
avg_score,
format!("Pattern-based detection (score: {:.2})", avg_score),
)
} else {
(
false,
0.5,
"No known equivalence patterns detected".to_string(),
)
}
};
Ok(EquivalenceResult {
is_equivalent,
confidence,
reason,
patterns,
})
}
pub fn detect_with_explanation(
&self,
mutant: &Mutant,
original: &str,
) -> Result<(EquivalenceResult, String)> {
let result = self.detect_equivalent(mutant, original)?;
let explanation = if result.is_equivalent {
format!(
"Mutant is EQUIVALENT to original (confidence: {:.1}%). Reason: {}",
result.confidence * 100.0,
result.reason
)
} else {
format!(
"Mutant is NOT EQUIVALENT to original (confidence: {:.1}%). Reason: {}",
result.confidence * 100.0,
result.reason
)
};
Ok((result, explanation))
}
pub fn filter_equivalents(
&self,
mutants: &[Mutant],
original_sources: &[(&str, &str)],
) -> Result<Vec<(Mutant, EquivalenceResult)>> {
let mut non_equivalents = Vec::new();
for (i, mutant) in mutants.iter().enumerate() {
let original = if i < original_sources.len() {
original_sources[i].1
} else {
""
};
let result = self.detect_equivalent(mutant, original)?;
if !result.is_equivalent {
non_equivalents.push((mutant.clone(), result));
}
}
Ok(non_equivalents)
}
pub fn get_accuracy_estimate(&self) -> f64 {
if !self.trained {
return 0.0;
}
let pattern_count = self.equivalence_patterns.len();
(pattern_count as f64 / (pattern_count as f64 + 10.0)).min(0.95)
}
pub fn is_trained(&self) -> bool {
self.trained
}
pub fn save(&self, path: &Path) -> Result<()> {
let serialized = bincode::serialize(self)?;
std::fs::write(path, serialized)?;
Ok(())
}
pub fn load(path: &Path) -> Result<Self> {
let data = std::fs::read(path)?;
let detector = bincode::deserialize(&data)?;
Ok(detector)
}
}
impl Default for EquivalentMutantDetector {
fn default() -> Self {
Self::new()
}
}
impl Serialize for EquivalentMutantDetector {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut state = serializer.serialize_struct("EquivalentMutantDetector", 4)?;
state.serialize_field("equivalence_patterns", &self.equivalence_patterns)?;
state.serialize_field("pattern_confidence", &self.pattern_confidence)?;
state.serialize_field("trained", &self.trained)?;
state.serialize_field("training_samples", &self.training_samples)?;
state.end()
}
}
impl<'de> Deserialize<'de> for EquivalentMutantDetector {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
#[derive(Deserialize)]
struct DetectorData {
equivalence_patterns: HashMap<String, f64>,
pattern_confidence: HashMap<String, f64>,
trained: bool,
training_samples: usize,
}
let data = DetectorData::deserialize(deserializer)?;
Ok(Self {
equivalence_patterns: data.equivalence_patterns,
pattern_confidence: data.pattern_confidence,
trained: data.trained,
training_samples: data.training_samples,
})
}
}