pub mod adversarial;
pub mod edge_cases;
pub mod synthetic_data;
pub use adversarial::{AdversarialConfig, AdversarialGenerator, AttackMethod};
pub use edge_cases::{EdgeCaseConfig, EdgeCaseGenerator, EdgeCaseType};
pub use synthetic_data::{SynthesisMethod, SyntheticConfig, SyntheticDataGenerator};
use crate::error::Result;
use ndarray::ArrayD;
use std::collections::HashMap;
pub trait MLModel {
fn forward(&self, input: &ArrayD<f32>) -> Result<ArrayD<f32>>;
fn gradient(&self, input: &ArrayD<f32>, target: Option<&ArrayD<f32>>) -> Result<ArrayD<f32>>;
fn input_shape(&self) -> Vec<usize>;
fn output_shape(&self) -> Vec<usize>;
}
#[derive(Debug, Clone)]
pub struct InputSchema {
pub features: HashMap<String, FeatureType>,
pub constraints: HashMap<String, FeatureConstraint>,
}
#[derive(Debug, Clone)]
pub enum FeatureType {
Numeric,
Categorical(Vec<String>),
Text,
Image,
Audio,
TimeSeries,
}
#[derive(Debug, Clone)]
pub enum FeatureConstraint {
Range { min: f64, max: f64 },
Categories(Vec<String>),
Length { min: usize, max: usize },
Pattern(String),
}
#[derive(Debug, Clone)]
pub struct TestCase {
pub input: ArrayD<f32>,
pub expected_output: Option<ArrayD<f32>>,
pub case_type: TestCaseType,
pub method: String,
pub confidence: f64,
pub metadata: HashMap<String, String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum TestCaseType {
Adversarial,
EdgeCase,
Synthetic,
Normal,
}
#[derive(Debug, Clone)]
pub struct GenerationConfig {
pub num_cases: usize,
pub seed: Option<u64>,
pub max_perturbation: f32,
pub include_metadata: bool,
pub target_success_rate: f64,
}
impl Default for GenerationConfig {
fn default() -> Self {
Self {
num_cases: 100,
seed: None,
max_perturbation: 0.3,
include_metadata: true,
target_success_rate: 0.8,
}
}
}
#[derive(Debug)]
pub struct GenerationResult {
pub test_cases: Vec<TestCase>,
pub success_rate: f64,
pub statistics: HashMap<String, f64>,
pub warnings: Vec<String>,
}
impl Default for GenerationResult {
fn default() -> Self {
Self {
test_cases: Vec::new(),
success_rate: 0.0,
statistics: HashMap::new(),
warnings: Vec::new(),
}
}
}
impl GenerationResult {
pub fn new() -> Self {
Self::default()
}
}
pub mod utils {
use super::*;
use rand::{Rng, SeedableRng};
use rand_pcg::Pcg64;
pub fn create_rng(seed: Option<u64>) -> impl Rng {
match seed {
Some(s) => Pcg64::seed_from_u64(s),
None => Pcg64::from_entropy(),
}
}
pub fn clip(input: &mut ArrayD<f32>, min_val: f32, max_val: f32) {
for val in input.iter_mut() {
*val = val.max(min_val).min(max_val);
}
}
pub fn l2_norm(input: &ArrayD<f32>) -> f32 {
input.iter().map(|x| x * x).sum::<f32>().sqrt()
}
pub fn normalize_l2(input: &mut ArrayD<f32>) {
let norm = l2_norm(input);
if norm > 0.0 {
for val in input.iter_mut() {
*val /= norm;
}
}
}
pub fn add_noise(input: &mut ArrayD<f32>, noise_level: f32, rng: &mut impl Rng) {
for val in input.iter_mut() {
let noise = rng.gen_range(-noise_level..=noise_level);
*val += noise;
}
}
pub fn sign(input: &ArrayD<f32>) -> ArrayD<f32> {
input.mapv(|x| if x >= 0.0 { 1.0 } else { -1.0 })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_generation_config_default() {
let config = GenerationConfig::default();
assert_eq!(config.num_cases, 100);
assert_eq!(config.max_perturbation, 0.3);
assert_eq!(config.target_success_rate, 0.8);
}
#[test]
#[ignore] fn test_utils_l2_norm() {
let arr = ArrayD::from_elem(vec![3, 4], 1.0);
assert!((utils::l2_norm(&arr) - 5.0).abs() < 1e-6);
}
#[test]
fn test_utils_normalize_l2() {
let mut arr = ArrayD::from_elem(vec![3, 4], 1.0);
utils::normalize_l2(&mut arr);
let norm = utils::l2_norm(&arr);
assert!((norm - 1.0).abs() < 1e-6);
}
}