pub mod andon;
pub mod cache;
pub mod code_eda;
pub mod code_features;
mod config;
mod diversity;
pub mod eda;
pub mod mixup;
mod params;
mod quality;
pub mod shell;
mod strategy;
pub mod template;
mod validator;
pub mod weak_supervision;
pub use andon::{AndonConfig, AndonEvent, AndonHandler, AndonSeverity, DefaultAndon, TestAndon};
pub use config::SyntheticConfig;
pub use diversity::{DiversityMonitor, DiversityScore};
pub use params::SyntheticParam;
pub use quality::QualityDegradationDetector;
pub use strategy::GenerationStrategy;
pub use validator::{SyntheticValidator, ValidationResult};
use crate::error::Result;
pub trait SyntheticGenerator {
type Input;
type Output;
fn generate(
&self,
seeds: &[Self::Input],
config: &SyntheticConfig,
) -> Result<Vec<Self::Output>>;
fn quality_score(&self, generated: &Self::Output, seed: &Self::Input) -> f32;
fn diversity_score(&self, batch: &[Self::Output]) -> f32;
}
pub trait SyntheticCallback: Send + Sync {
fn on_batch_generated(&mut self, count: usize, config: &SyntheticConfig);
fn on_quality_below_threshold(&mut self, actual: f32, threshold: f32);
fn on_diversity_collapse(&mut self, score: &DiversityScore);
}
pub fn check_andon<A: AndonHandler>(
accepted: usize,
total: usize,
diversity: f32,
config: &SyntheticConfig,
andon: Option<&A>,
) -> Result<()> {
if !config.andon.enabled || total == 0 {
return Ok(());
}
let rejection_rate = 1.0 - (accepted as f32 / total as f32);
check_rejection_rate(rejection_rate, config, andon)?;
check_diversity(diversity, config, andon);
Ok(())
}
fn check_rejection_rate<A: AndonHandler>(
rejection_rate: f32,
config: &SyntheticConfig,
andon: Option<&A>,
) -> Result<()> {
if !config.andon.exceeds_rejection_threshold(rejection_rate) {
return Ok(());
}
let event = AndonEvent::HighRejectionRate {
rate: rejection_rate,
threshold: config.andon.rejection_threshold,
};
if let Some(handler) = andon {
handler.on_event(&event);
if handler.should_halt(&event) {
return Err(crate::error::AprenderError::Other(format!(
"ANDON HALT: Rejection rate {:.1}% exceeds threshold {:.1}%",
rejection_rate * 100.0,
config.andon.rejection_threshold * 100.0
)));
}
}
Ok(())
}
fn check_diversity<A: AndonHandler>(diversity: f32, config: &SyntheticConfig, andon: Option<&A>) {
if !config.andon.has_diversity_collapse(diversity) {
return;
}
let event = AndonEvent::DiversityCollapse {
score: diversity,
minimum: config.andon.diversity_minimum,
};
if let Some(handler) = andon {
handler.on_event(&event);
}
}
pub fn generate_batched<G>(
generator: &G,
seeds: &[G::Input],
config: &SyntheticConfig,
batch_size: usize,
) -> Result<Vec<G::Output>>
where
G: SyntheticGenerator,
{
let mut all_synthetic = Vec::new();
for chunk in seeds.chunks(batch_size.max(1)) {
let batch = generator.generate(chunk, config)?;
all_synthetic.extend(batch);
}
Ok(all_synthetic)
}
#[derive(Debug)]
pub struct SyntheticStream<'a, G: SyntheticGenerator + std::fmt::Debug> {
generator: &'a G,
seeds: &'a [G::Input],
config: &'a SyntheticConfig,
current_idx: usize,
batch_size: usize,
}
impl<'a, G: SyntheticGenerator + std::fmt::Debug> SyntheticStream<'a, G> {
#[must_use]
pub fn new(
generator: &'a G,
seeds: &'a [G::Input],
config: &'a SyntheticConfig,
batch_size: usize,
) -> Self {
Self {
generator,
seeds,
config,
current_idx: 0,
batch_size: batch_size.max(1),
}
}
#[must_use]
pub fn has_next(&self) -> bool {
self.current_idx < self.seeds.len()
}
#[must_use]
pub fn remaining(&self) -> usize {
self.seeds.len().saturating_sub(self.current_idx)
}
}
impl<G: SyntheticGenerator + std::fmt::Debug> Iterator for SyntheticStream<'_, G> {
type Item = Result<Vec<G::Output>>;
fn next(&mut self) -> Option<Self::Item> {
if self.current_idx >= self.seeds.len() {
return None;
}
let end = (self.current_idx + self.batch_size).min(self.seeds.len());
let chunk = &self.seeds[self.current_idx..end];
self.current_idx = end;
Some(self.generator.generate(chunk, self.config))
}
}
#[cfg(test)]
#[path = "synthetic_tests.rs"]
mod tests;