flakestorm/rust/src/lib.rs
Entropix 61652be09b Refactor Entropix to FlakeStorm
- Rename all instances of Entropix to FlakeStorm
- Rename package from entropix to flakestorm
- Update all class names (EntropixConfig -> FlakeStormConfig, EntropixRunner -> FlakeStormRunner)
- Update Rust module from entropix_rust to flakestorm_rust
- Update README: remove cloud comparison, update links to flakestorm.com
- Update .gitignore to allow docs files referenced in README
- Add origin remote for VS Code compatibility
- Fix missing imports and type references
- All imports and references updated throughout codebase
2025-12-29 11:15:18 +08:00

185 lines
4.9 KiB
Rust

//! flakestorm Rust Performance Module
//!
//! This module provides high-performance implementations for:
//! - Robustness score calculation
//! - Parallel mutation processing
//! - Fast string similarity scoring
use pyo3::prelude::*;
use rayon::prelude::*;
mod parallel;
mod scoring;
pub use parallel::*;
pub use scoring::*;
/// Calculate the robustness score for a test run.
///
/// The robustness score R is calculated as:
/// R = (W_s * S_passed + W_d * D_passed) / N_total
///
/// Where:
/// - S_passed = Semantic variations passed
/// - D_passed = Deterministic tests passed
/// - W_s, W_d = Weights for semantic and deterministic tests
#[pyfunction]
fn calculate_robustness_score(
semantic_passed: u32,
deterministic_passed: u32,
total: u32,
semantic_weight: f64,
deterministic_weight: f64,
) -> f64 {
if total == 0 {
return 0.0;
}
let weighted_sum = semantic_weight * semantic_passed as f64
+ deterministic_weight * deterministic_passed as f64;
weighted_sum / total as f64
}
/// Calculate weighted robustness score with per-mutation weights.
///
/// Each mutation has its own weight based on difficulty.
/// Passing a prompt injection attack is worth more than passing a typo test.
#[pyfunction]
fn calculate_weighted_score(
results: Vec<(bool, f64)>, // (passed, weight)
) -> f64 {
if results.is_empty() {
return 0.0;
}
let total_weight: f64 = results.iter().map(|(_, w)| w).sum();
let passed_weight: f64 = results
.iter()
.filter(|(passed, _)| *passed)
.map(|(_, w)| w)
.sum();
if total_weight == 0.0 {
return 0.0;
}
passed_weight / total_weight
}
/// Process mutations in parallel and return results.
///
/// Uses Rayon for efficient parallel processing.
#[pyfunction]
fn parallel_process_mutations(
mutations: Vec<String>,
mutation_types: Vec<String>,
weights: Vec<f64>,
) -> Vec<(String, String, f64)> {
mutations
.into_par_iter()
.enumerate()
.map(|(i, mutation)| {
let mutation_type = mutation_types.get(i % mutation_types.len())
.cloned()
.unwrap_or_else(|| "unknown".to_string());
let weight = weights.get(i % weights.len())
.copied()
.unwrap_or(1.0);
(mutation, mutation_type, weight)
})
.collect()
}
/// Fast Levenshtein distance calculation for noise mutation validation.
#[pyfunction]
fn levenshtein_distance(s1: &str, s2: &str) -> usize {
let len1 = s1.chars().count();
let len2 = s2.chars().count();
if len1 == 0 {
return len2;
}
if len2 == 0 {
return len1;
}
let s1_chars: Vec<char> = s1.chars().collect();
let s2_chars: Vec<char> = s2.chars().collect();
let mut prev_row: Vec<usize> = (0..=len2).collect();
let mut curr_row: Vec<usize> = vec![0; len2 + 1];
for i in 1..=len1 {
curr_row[0] = i;
for j in 1..=len2 {
let cost = if s1_chars[i - 1] == s2_chars[j - 1] { 0 } else { 1 };
curr_row[j] = std::cmp::min(
std::cmp::min(prev_row[j] + 1, curr_row[j - 1] + 1),
prev_row[j - 1] + cost,
);
}
std::mem::swap(&mut prev_row, &mut curr_row);
}
prev_row[len2]
}
/// Calculate similarity ratio between two strings (0.0 to 1.0).
#[pyfunction]
fn string_similarity(s1: &str, s2: &str) -> f64 {
let distance = levenshtein_distance(s1, s2);
let max_len = std::cmp::max(s1.chars().count(), s2.chars().count());
if max_len == 0 {
return 1.0;
}
1.0 - (distance as f64 / max_len as f64)
}
/// Python module definition
#[pymodule]
fn flakestorm_rust(_py: Python, m: &PyModule) -> PyResult<()> {
m.add_function(wrap_pyfunction!(calculate_robustness_score, m)?)?;
m.add_function(wrap_pyfunction!(calculate_weighted_score, m)?)?;
m.add_function(wrap_pyfunction!(parallel_process_mutations, m)?)?;
m.add_function(wrap_pyfunction!(levenshtein_distance, m)?)?;
m.add_function(wrap_pyfunction!(string_similarity, m)?)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_robustness_score() {
let score = calculate_robustness_score(8, 10, 20, 1.0, 1.0);
assert!((score - 0.9).abs() < 0.001);
}
#[test]
fn test_weighted_score() {
let results = vec![
(true, 1.0),
(true, 1.5),
(false, 1.0),
];
let score = calculate_weighted_score(results);
assert!((score - 0.714).abs() < 0.01);
}
#[test]
fn test_levenshtein() {
assert_eq!(levenshtein_distance("kitten", "sitting"), 3);
assert_eq!(levenshtein_distance("", "abc"), 3);
assert_eq!(levenshtein_distance("abc", "abc"), 0);
}
#[test]
fn test_string_similarity() {
let sim = string_similarity("hello", "hallo");
assert!(sim > 0.7 && sim < 0.9);
}
}