1pub mod andon;
34pub mod cache;
35pub mod code_eda;
36pub mod code_features;
37mod config;
38mod diversity;
39pub mod eda;
40pub mod mixup;
41mod params;
42mod quality;
43pub mod shell;
44mod strategy;
45pub mod template;
46mod validator;
47pub mod weak_supervision;
48
49pub use andon::{AndonConfig, AndonEvent, AndonHandler, AndonSeverity, DefaultAndon, TestAndon};
50pub use config::SyntheticConfig;
51pub use diversity::{DiversityMonitor, DiversityScore};
52pub use params::SyntheticParam;
53pub use quality::QualityDegradationDetector;
54pub use strategy::GenerationStrategy;
55pub use validator::{SyntheticValidator, ValidationResult};
56
57use crate::error::Result;
58
59pub trait SyntheticGenerator {
103 type Input;
105 type Output;
107
108 fn generate(
119 &self,
120 seeds: &[Self::Input],
121 config: &SyntheticConfig,
122 ) -> Result<Vec<Self::Output>>;
123
124 fn quality_score(&self, generated: &Self::Output, seed: &Self::Input) -> f32;
129
130 fn diversity_score(&self, batch: &[Self::Output]) -> f32;
135}
136
137pub trait SyntheticCallback: Send + Sync {
142 fn on_batch_generated(&mut self, count: usize, config: &SyntheticConfig);
144
145 fn on_quality_below_threshold(&mut self, actual: f32, threshold: f32);
147
148 fn on_diversity_collapse(&mut self, score: &DiversityScore);
150}
151
152pub fn check_andon<A: AndonHandler>(
168 accepted: usize,
169 total: usize,
170 diversity: f32,
171 config: &SyntheticConfig,
172 andon: Option<&A>,
173) -> Result<()> {
174 if !config.andon.enabled || total == 0 {
175 return Ok(());
176 }
177
178 let rejection_rate = 1.0 - (accepted as f32 / total as f32);
179
180 if config.andon.exceeds_rejection_threshold(rejection_rate) {
182 let event = AndonEvent::HighRejectionRate {
183 rate: rejection_rate,
184 threshold: config.andon.rejection_threshold,
185 };
186
187 if let Some(handler) = andon {
188 handler.on_event(&event);
189 if handler.should_halt(&event) {
190 return Err(crate::error::AprenderError::Other(format!(
191 "ANDON HALT: Rejection rate {:.1}% exceeds threshold {:.1}%",
192 rejection_rate * 100.0,
193 config.andon.rejection_threshold * 100.0
194 )));
195 }
196 }
197 }
198
199 if config.andon.has_diversity_collapse(diversity) {
201 let event = AndonEvent::DiversityCollapse {
202 score: diversity,
203 minimum: config.andon.diversity_minimum,
204 };
205
206 if let Some(handler) = andon {
207 handler.on_event(&event);
208 }
210 }
211
212 Ok(())
213}
214
215pub fn generate_batched<G>(
251 generator: &G,
252 seeds: &[G::Input],
253 config: &SyntheticConfig,
254 batch_size: usize,
255) -> Result<Vec<G::Output>>
256where
257 G: SyntheticGenerator,
258{
259 let mut all_synthetic = Vec::new();
260
261 for chunk in seeds.chunks(batch_size.max(1)) {
262 let batch = generator.generate(chunk, config)?;
263 all_synthetic.extend(batch);
264 }
265
266 Ok(all_synthetic)
267}
268
269#[derive(Debug)]
274pub struct SyntheticStream<'a, G: SyntheticGenerator + std::fmt::Debug> {
275 generator: &'a G,
276 seeds: &'a [G::Input],
277 config: &'a SyntheticConfig,
278 current_idx: usize,
279 batch_size: usize,
280}
281
282impl<'a, G: SyntheticGenerator + std::fmt::Debug> SyntheticStream<'a, G> {
283 #[must_use]
292 pub fn new(
293 generator: &'a G,
294 seeds: &'a [G::Input],
295 config: &'a SyntheticConfig,
296 batch_size: usize,
297 ) -> Self {
298 Self {
299 generator,
300 seeds,
301 config,
302 current_idx: 0,
303 batch_size: batch_size.max(1),
304 }
305 }
306
307 #[must_use]
309 pub fn has_next(&self) -> bool {
310 self.current_idx < self.seeds.len()
311 }
312
313 #[must_use]
315 pub fn remaining(&self) -> usize {
316 self.seeds.len().saturating_sub(self.current_idx)
317 }
318}
319
320impl<G: SyntheticGenerator + std::fmt::Debug> Iterator for SyntheticStream<'_, G> {
321 type Item = Result<Vec<G::Output>>;
322
323 fn next(&mut self) -> Option<Self::Item> {
324 if self.current_idx >= self.seeds.len() {
325 return None;
326 }
327 let end = (self.current_idx + self.batch_size).min(self.seeds.len());
328 let chunk = &self.seeds[self.current_idx..end];
329 self.current_idx = end;
330 Some(self.generator.generate(chunk, self.config))
331 }
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[derive(Debug)]
340 struct DoubleGenerator;
341
342 impl SyntheticGenerator for DoubleGenerator {
343 type Input = i32;
344 type Output = i32;
345
346 fn generate(&self, seeds: &[i32], _config: &SyntheticConfig) -> Result<Vec<i32>> {
347 Ok(seeds.iter().map(|x| x * 2).collect())
348 }
349
350 fn quality_score(&self, generated: &i32, seed: &i32) -> f32 {
351 if *generated == seed * 2 {
352 1.0
353 } else {
354 0.0
355 }
356 }
357
358 fn diversity_score(&self, batch: &[i32]) -> f32 {
359 use std::collections::HashSet;
360 let unique: HashSet<_> = batch.iter().collect();
361 if batch.is_empty() {
362 0.0
363 } else {
364 unique.len() as f32 / batch.len() as f32
365 }
366 }
367 }
368
369 #[test]
370 fn test_synthetic_generator_trait() {
371 let gen = DoubleGenerator;
372 let seeds = vec![1, 2, 3];
373 let config = SyntheticConfig::default();
374
375 let result = gen.generate(&seeds, &config).expect("generation failed");
376 assert_eq!(result, vec![2, 4, 6]);
377 }
378
379 #[test]
380 fn test_quality_score() {
381 let gen = DoubleGenerator;
382 assert!((gen.quality_score(&4, &2) - 1.0).abs() < f32::EPSILON);
383 assert!((gen.quality_score(&5, &2) - 0.0).abs() < f32::EPSILON);
384 }
385
386 #[test]
387 fn test_diversity_score() {
388 let gen = DoubleGenerator;
389 assert!((gen.diversity_score(&[1, 2, 3]) - 1.0).abs() < f32::EPSILON);
390 assert!((gen.diversity_score(&[1, 1, 1]) - (1.0 / 3.0)).abs() < f32::EPSILON);
391 assert!((gen.diversity_score(&[]) - 0.0).abs() < f32::EPSILON);
392 }
393
394 #[test]
395 fn test_generate_batched() {
396 let gen = DoubleGenerator;
397 let seeds = vec![1, 2, 3, 4, 5];
398 let config = SyntheticConfig::default();
399
400 let result = generate_batched(&gen, &seeds, &config, 2).expect("batched generation failed");
401 assert_eq!(result, vec![2, 4, 6, 8, 10]);
402 }
403
404 #[test]
405 fn test_generate_batched_single_batch() {
406 let gen = DoubleGenerator;
407 let seeds = vec![1, 2, 3];
408 let config = SyntheticConfig::default();
409
410 let result =
411 generate_batched(&gen, &seeds, &config, 100).expect("batched generation failed");
412 assert_eq!(result, vec![2, 4, 6]);
413 }
414
415 #[test]
416 fn test_generate_batched_empty() {
417 let gen = DoubleGenerator;
418 let seeds: Vec<i32> = vec![];
419 let config = SyntheticConfig::default();
420
421 let result = generate_batched(&gen, &seeds, &config, 2).expect("batched generation failed");
422 assert!(result.is_empty());
423 }
424
425 #[test]
426 fn test_synthetic_stream_basic() {
427 let gen = DoubleGenerator;
428 let seeds = vec![1, 2, 3, 4, 5];
429 let config = SyntheticConfig::default();
430
431 let stream = SyntheticStream::new(&gen, &seeds, &config, 2);
432 let results: Vec<_> = stream.map(|r| r.expect("generation failed")).collect();
433
434 assert_eq!(results.len(), 3); assert_eq!(results[0], vec![2, 4]);
436 assert_eq!(results[1], vec![6, 8]);
437 assert_eq!(results[2], vec![10]);
438 }
439
440 #[test]
441 fn test_synthetic_stream_has_next() {
442 let gen = DoubleGenerator;
443 let seeds = vec![1, 2];
444 let config = SyntheticConfig::default();
445
446 let mut stream = SyntheticStream::new(&gen, &seeds, &config, 1);
447 assert!(stream.has_next());
448 assert_eq!(stream.remaining(), 2);
449
450 stream.next();
451 assert!(stream.has_next());
452 assert_eq!(stream.remaining(), 1);
453
454 stream.next();
455 assert!(!stream.has_next());
456 assert_eq!(stream.remaining(), 0);
457 }
458
459 #[test]
460 fn test_synthetic_stream_empty() {
461 let gen = DoubleGenerator;
462 let seeds: Vec<i32> = vec![];
463 let config = SyntheticConfig::default();
464
465 let mut stream = SyntheticStream::new(&gen, &seeds, &config, 2);
466 assert!(!stream.has_next());
467 assert!(stream.next().is_none());
468 }
469
470 #[test]
471 fn test_batch_size_zero_becomes_one() {
472 let gen = DoubleGenerator;
473 let seeds = vec![1, 2, 3];
474 let config = SyntheticConfig::default();
475
476 let result = generate_batched(&gen, &seeds, &config, 0).expect("generation failed");
478 assert_eq!(result, vec![2, 4, 6]);
479 }
480
481 #[test]
486 fn test_check_andon_disabled() {
487 let config = SyntheticConfig::default().with_andon_enabled(false);
488 let andon = TestAndon::new();
489
490 let result = check_andon::<TestAndon>(0, 100, 0.5, &config, Some(&andon));
492 assert!(result.is_ok());
493 assert!(andon.events().is_empty());
494 }
495
496 #[test]
497 fn test_check_andon_empty_total() {
498 let config = SyntheticConfig::default();
499 let andon = TestAndon::new();
500
501 let result = check_andon::<TestAndon>(0, 0, 0.5, &config, Some(&andon));
503 assert!(result.is_ok());
504 }
505
506 #[test]
507 fn test_check_andon_high_rejection_halts() {
508 let config = SyntheticConfig::default().with_andon_rejection_threshold(0.90);
509 let andon = TestAndon::new();
510
511 let result = check_andon::<TestAndon>(5, 100, 0.5, &config, Some(&andon));
513 assert!(result.is_err());
514 assert!(andon.was_halted());
515 assert_eq!(andon.count_high_rejection(), 1);
516 }
517
518 #[test]
519 fn test_check_andon_acceptable_rejection() {
520 let config = SyntheticConfig::default().with_andon_rejection_threshold(0.90);
521 let andon = TestAndon::new();
522
523 let result = check_andon::<TestAndon>(20, 100, 0.5, &config, Some(&andon));
525 assert!(result.is_ok());
526 assert!(!andon.was_halted());
527 }
528
529 #[test]
530 fn test_check_andon_diversity_collapse_warns() {
531 let config =
532 SyntheticConfig::default().with_andon(AndonConfig::new().with_diversity_minimum(0.2));
533 let andon = TestAndon::new();
534
535 let result = check_andon::<TestAndon>(80, 100, 0.1, &config, Some(&andon));
537 assert!(result.is_ok()); assert!(!andon.was_halted());
539 assert_eq!(andon.events().len(), 1);
540 }
541
542 #[test]
543 fn test_check_andon_no_handler() {
544 let config = SyntheticConfig::default().with_andon_rejection_threshold(0.90);
545
546 let result = check_andon::<DefaultAndon>(5, 100, 0.5, &config, None);
548 assert!(result.is_ok());
549 }
550
551 #[test]
552 fn test_check_andon_multiple_conditions() {
553 let config = SyntheticConfig::default()
554 .with_andon_rejection_threshold(0.90)
555 .with_andon(AndonConfig::new().with_diversity_minimum(0.2));
556 let andon = TestAndon::new();
557
558 let result = check_andon::<TestAndon>(3, 100, 0.05, &config, Some(&andon));
560 assert!(result.is_err()); assert!(andon.was_halted());
562 }
563}