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 check_rejection_rate(rejection_rate, config, andon)?;
180 check_diversity(diversity, config, andon);
181
182 Ok(())
183}
184
185fn check_rejection_rate<A: AndonHandler>(
187 rejection_rate: f32,
188 config: &SyntheticConfig,
189 andon: Option<&A>,
190) -> Result<()> {
191 if !config.andon.exceeds_rejection_threshold(rejection_rate) {
192 return Ok(());
193 }
194
195 let event = AndonEvent::HighRejectionRate {
196 rate: rejection_rate,
197 threshold: config.andon.rejection_threshold,
198 };
199
200 if let Some(handler) = andon {
201 handler.on_event(&event);
202 if handler.should_halt(&event) {
203 return Err(crate::error::AprenderError::Other(format!(
204 "ANDON HALT: Rejection rate {:.1}% exceeds threshold {:.1}%",
205 rejection_rate * 100.0,
206 config.andon.rejection_threshold * 100.0
207 )));
208 }
209 }
210 Ok(())
211}
212
213fn check_diversity<A: AndonHandler>(diversity: f32, config: &SyntheticConfig, andon: Option<&A>) {
215 if !config.andon.has_diversity_collapse(diversity) {
216 return;
217 }
218
219 let event = AndonEvent::DiversityCollapse {
220 score: diversity,
221 minimum: config.andon.diversity_minimum,
222 };
223
224 if let Some(handler) = andon {
225 handler.on_event(&event);
226 }
228}
229
230pub fn generate_batched<G>(
266 generator: &G,
267 seeds: &[G::Input],
268 config: &SyntheticConfig,
269 batch_size: usize,
270) -> Result<Vec<G::Output>>
271where
272 G: SyntheticGenerator,
273{
274 let mut all_synthetic = Vec::new();
275
276 for chunk in seeds.chunks(batch_size.max(1)) {
277 let batch = generator.generate(chunk, config)?;
278 all_synthetic.extend(batch);
279 }
280
281 Ok(all_synthetic)
282}
283
284#[derive(Debug)]
289pub struct SyntheticStream<'a, G: SyntheticGenerator + std::fmt::Debug> {
290 generator: &'a G,
291 seeds: &'a [G::Input],
292 config: &'a SyntheticConfig,
293 current_idx: usize,
294 batch_size: usize,
295}
296
297impl<'a, G: SyntheticGenerator + std::fmt::Debug> SyntheticStream<'a, G> {
298 #[must_use]
307 pub fn new(
308 generator: &'a G,
309 seeds: &'a [G::Input],
310 config: &'a SyntheticConfig,
311 batch_size: usize,
312 ) -> Self {
313 Self {
314 generator,
315 seeds,
316 config,
317 current_idx: 0,
318 batch_size: batch_size.max(1),
319 }
320 }
321
322 #[must_use]
324 pub fn has_next(&self) -> bool {
325 self.current_idx < self.seeds.len()
326 }
327
328 #[must_use]
330 pub fn remaining(&self) -> usize {
331 self.seeds.len().saturating_sub(self.current_idx)
332 }
333}
334
335impl<G: SyntheticGenerator + std::fmt::Debug> Iterator for SyntheticStream<'_, G> {
336 type Item = Result<Vec<G::Output>>;
337
338 fn next(&mut self) -> Option<Self::Item> {
339 if self.current_idx >= self.seeds.len() {
340 return None;
341 }
342 let end = (self.current_idx + self.batch_size).min(self.seeds.len());
343 let chunk = &self.seeds[self.current_idx..end];
344 self.current_idx = end;
345 Some(self.generator.generate(chunk, self.config))
346 }
347}
348
349#[cfg(test)]
350mod tests {
351 use super::*;
352
353 #[derive(Debug)]
355 struct DoubleGenerator;
356
357 impl SyntheticGenerator for DoubleGenerator {
358 type Input = i32;
359 type Output = i32;
360
361 fn generate(&self, seeds: &[i32], _config: &SyntheticConfig) -> Result<Vec<i32>> {
362 Ok(seeds.iter().map(|x| x * 2).collect())
363 }
364
365 fn quality_score(&self, generated: &i32, seed: &i32) -> f32 {
366 if *generated == seed * 2 {
367 1.0
368 } else {
369 0.0
370 }
371 }
372
373 fn diversity_score(&self, batch: &[i32]) -> f32 {
374 use std::collections::HashSet;
375 let unique: HashSet<_> = batch.iter().collect();
376 if batch.is_empty() {
377 0.0
378 } else {
379 unique.len() as f32 / batch.len() as f32
380 }
381 }
382 }
383
384 #[test]
385 fn test_synthetic_generator_trait() {
386 let gen = DoubleGenerator;
387 let seeds = vec![1, 2, 3];
388 let config = SyntheticConfig::default();
389
390 let result = gen.generate(&seeds, &config).expect("generation failed");
391 assert_eq!(result, vec![2, 4, 6]);
392 }
393
394 #[test]
395 fn test_quality_score() {
396 let gen = DoubleGenerator;
397 assert!((gen.quality_score(&4, &2) - 1.0).abs() < f32::EPSILON);
398 assert!((gen.quality_score(&5, &2) - 0.0).abs() < f32::EPSILON);
399 }
400
401 #[test]
402 fn test_diversity_score() {
403 let gen = DoubleGenerator;
404 assert!((gen.diversity_score(&[1, 2, 3]) - 1.0).abs() < f32::EPSILON);
405 assert!((gen.diversity_score(&[1, 1, 1]) - (1.0 / 3.0)).abs() < f32::EPSILON);
406 assert!((gen.diversity_score(&[]) - 0.0).abs() < f32::EPSILON);
407 }
408
409 #[test]
410 fn test_generate_batched() {
411 let gen = DoubleGenerator;
412 let seeds = vec![1, 2, 3, 4, 5];
413 let config = SyntheticConfig::default();
414
415 let result = generate_batched(&gen, &seeds, &config, 2).expect("batched generation failed");
416 assert_eq!(result, vec![2, 4, 6, 8, 10]);
417 }
418
419 #[test]
420 fn test_generate_batched_single_batch() {
421 let gen = DoubleGenerator;
422 let seeds = vec![1, 2, 3];
423 let config = SyntheticConfig::default();
424
425 let result =
426 generate_batched(&gen, &seeds, &config, 100).expect("batched generation failed");
427 assert_eq!(result, vec![2, 4, 6]);
428 }
429
430 #[test]
431 fn test_generate_batched_empty() {
432 let gen = DoubleGenerator;
433 let seeds: Vec<i32> = vec![];
434 let config = SyntheticConfig::default();
435
436 let result = generate_batched(&gen, &seeds, &config, 2).expect("batched generation failed");
437 assert!(result.is_empty());
438 }
439
440 #[test]
441 fn test_synthetic_stream_basic() {
442 let gen = DoubleGenerator;
443 let seeds = vec![1, 2, 3, 4, 5];
444 let config = SyntheticConfig::default();
445
446 let stream = SyntheticStream::new(&gen, &seeds, &config, 2);
447 let results: Vec<_> = stream.map(|r| r.expect("generation failed")).collect();
448
449 assert_eq!(results.len(), 3); assert_eq!(results[0], vec![2, 4]);
451 assert_eq!(results[1], vec![6, 8]);
452 assert_eq!(results[2], vec![10]);
453 }
454
455 #[test]
456 fn test_synthetic_stream_has_next() {
457 let gen = DoubleGenerator;
458 let seeds = vec![1, 2];
459 let config = SyntheticConfig::default();
460
461 let mut stream = SyntheticStream::new(&gen, &seeds, &config, 1);
462 assert!(stream.has_next());
463 assert_eq!(stream.remaining(), 2);
464
465 stream.next();
466 assert!(stream.has_next());
467 assert_eq!(stream.remaining(), 1);
468
469 stream.next();
470 assert!(!stream.has_next());
471 assert_eq!(stream.remaining(), 0);
472 }
473
474 #[test]
475 fn test_synthetic_stream_empty() {
476 let gen = DoubleGenerator;
477 let seeds: Vec<i32> = vec![];
478 let config = SyntheticConfig::default();
479
480 let mut stream = SyntheticStream::new(&gen, &seeds, &config, 2);
481 assert!(!stream.has_next());
482 assert!(stream.next().is_none());
483 }
484
485 #[test]
486 fn test_batch_size_zero_becomes_one() {
487 let gen = DoubleGenerator;
488 let seeds = vec![1, 2, 3];
489 let config = SyntheticConfig::default();
490
491 let result = generate_batched(&gen, &seeds, &config, 0).expect("generation failed");
493 assert_eq!(result, vec![2, 4, 6]);
494 }
495
496 #[test]
501 fn test_check_andon_disabled() {
502 let config = SyntheticConfig::default().with_andon_enabled(false);
503 let andon = TestAndon::new();
504
505 let result = check_andon::<TestAndon>(0, 100, 0.5, &config, Some(&andon));
507 assert!(result.is_ok());
508 assert!(andon.events().is_empty());
509 }
510
511 #[test]
512 fn test_check_andon_empty_total() {
513 let config = SyntheticConfig::default();
514 let andon = TestAndon::new();
515
516 let result = check_andon::<TestAndon>(0, 0, 0.5, &config, Some(&andon));
518 assert!(result.is_ok());
519 }
520
521 #[test]
522 fn test_check_andon_high_rejection_halts() {
523 let config = SyntheticConfig::default().with_andon_rejection_threshold(0.90);
524 let andon = TestAndon::new();
525
526 let result = check_andon::<TestAndon>(5, 100, 0.5, &config, Some(&andon));
528 assert!(result.is_err());
529 assert!(andon.was_halted());
530 assert_eq!(andon.count_high_rejection(), 1);
531 }
532
533 #[test]
534 fn test_check_andon_acceptable_rejection() {
535 let config = SyntheticConfig::default().with_andon_rejection_threshold(0.90);
536 let andon = TestAndon::new();
537
538 let result = check_andon::<TestAndon>(20, 100, 0.5, &config, Some(&andon));
540 assert!(result.is_ok());
541 assert!(!andon.was_halted());
542 }
543
544 #[test]
545 fn test_check_andon_diversity_collapse_warns() {
546 let config =
547 SyntheticConfig::default().with_andon(AndonConfig::new().with_diversity_minimum(0.2));
548 let andon = TestAndon::new();
549
550 let result = check_andon::<TestAndon>(80, 100, 0.1, &config, Some(&andon));
552 assert!(result.is_ok()); assert!(!andon.was_halted());
554 assert_eq!(andon.events().len(), 1);
555 }
556
557 #[test]
558 fn test_check_andon_no_handler() {
559 let config = SyntheticConfig::default().with_andon_rejection_threshold(0.90);
560
561 let result = check_andon::<DefaultAndon>(5, 100, 0.5, &config, None);
563 assert!(result.is_ok());
564 }
565
566 #[test]
567 fn test_check_andon_multiple_conditions() {
568 let config = SyntheticConfig::default()
569 .with_andon_rejection_threshold(0.90)
570 .with_andon(AndonConfig::new().with_diversity_minimum(0.2));
571 let andon = TestAndon::new();
572
573 let result = check_andon::<TestAndon>(3, 100, 0.05, &config, Some(&andon));
575 assert!(result.is_err()); assert!(andon.was_halted());
577 }
578}