Skip to main content

ternary_muse/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Creative generation and artistic exploration with ternary systems.
4//!
5//! Provides a `Muse` struct (creativity engine), `PatternGenerator` for novel
6//! ternary patterns, `StyleTransfer` for cross-domain pattern application,
7//! `AestheticScorer` for evaluating ternary pattern beauty, `MutationEngine`
8//! for controlled creative randomness, and `CrossDomainMapper` for translating
9//! patterns between domains (e.g., music → visual).
10
11use std::collections::HashMap;
12
13// ── Ternary Value ──────────────────────────────────────────────────────────
14
15/// A balanced ternary digit: Negative (-1), Zero (0), or Positive (+1).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub enum Ternary {
18    Negative,
19    Zero,
20    Positive,
21}
22
23impl Ternary {
24    /// Convert to integer value.
25    pub fn to_i8(self) -> i8 {
26        match self {
27            Ternary::Negative => -1,
28            Ternary::Zero => 0,
29            Ternary::Positive => 1,
30        }
31    }
32
33    /// Convert from integer, clamping to {-1, 0, 1}.
34    pub fn from_i8(v: i8) -> Self {
35        match v {
36            ..=-1 => Ternary::Negative,
37            0 => Ternary::Zero,
38            1.. => Ternary::Positive,
39        }
40    }
41
42    /// Negate the value.
43    pub fn negate(self) -> Self {
44        match self {
45            Ternary::Negative => Ternary::Positive,
46            Ternary::Zero => Ternary::Zero,
47            Ternary::Positive => Ternary::Negative,
48        }
49    }
50}
51
52// ── Pattern ────────────────────────────────────────────────────────────────
53
54/// A ternary pattern: a sequence of ternary values.
55#[derive(Debug, Clone, PartialEq, Eq)]
56pub struct Pattern {
57    pub values: Vec<Ternary>,
58}
59
60impl Pattern {
61    pub fn new(values: Vec<Ternary>) -> Self {
62        Self { values }
63    }
64
65    /// Length of the pattern.
66    pub fn len(&self) -> usize {
67        self.values.len()
68    }
69
70    /// Is the pattern empty?
71    pub fn is_empty(&self) -> bool {
72        self.values.is_empty()
73    }
74
75    /// Convert to integer vector.
76    pub fn to_i8_vec(&self) -> Vec<i8> {
77        self.values.iter().map(|t| t.to_i8()).collect()
78    }
79
80    /// Compute symmetry score: how well the pattern mirrors around its center.
81    /// Returns 0.0 (no symmetry) to 1.0 (perfect symmetry).
82    pub fn symmetry(&self) -> f64 {
83        if self.values.len() < 2 {
84            return 1.0;
85        }
86        let n = self.values.len();
87        let matches = (0..n / 2)
88            .filter(|&i| self.values[i] == self.values[n - 1 - i])
89            .count();
90        matches as f64 / (n / 2) as f64
91    }
92
93    /// Compute balance: ratio of zero values. 1.0 = all zeros.
94    pub fn balance(&self) -> f64 {
95        if self.values.is_empty() {
96            return 1.0;
97        }
98        let zeros = self.values.iter().filter(|&&t| t == Ternary::Zero).count();
99        zeros as f64 / self.values.len() as f64
100    }
101
102    /// Compute complexity: ratio of transitions between different values.
103    pub fn complexity(&self) -> f64 {
104        if self.values.len() < 2 {
105            return 0.0;
106        }
107        let transitions = self
108            .values
109            .windows(2)
110            .filter(|w| w[0] != w[1])
111            .count();
112        transitions as f64 / (self.values.len() - 1) as f64
113    }
114}
115
116// ── Aesthetic Scorer ───────────────────────────────────────────────────────
117
118/// Evaluates the aesthetic quality of a ternary pattern.
119///
120/// Combines symmetry, complexity, and balance into a single score.
121/// Weights are configurable.
122#[derive(Debug, Clone)]
123pub struct AestheticScorer {
124    pub symmetry_weight: f64,
125    pub complexity_weight: f64,
126    pub balance_weight: f64,
127}
128
129impl Default for AestheticScorer {
130    fn default() -> Self {
131        Self {
132            symmetry_weight: 0.4,
133            complexity_weight: 0.35,
134            balance_weight: 0.25,
135        }
136    }
137}
138
139impl AestheticScorer {
140    pub fn new(symmetry_weight: f64, complexity_weight: f64, balance_weight: f64) -> Self {
141        Self {
142            symmetry_weight,
143            complexity_weight,
144            balance_weight,
145        }
146    }
147
148    /// Score a pattern from 0.0 to 1.0.
149    pub fn score(&self, pattern: &Pattern) -> f64 {
150        let total_weight = self.symmetry_weight + self.complexity_weight + self.balance_weight;
151        if total_weight == 0.0 {
152            return 0.0;
153        }
154        let raw = self.symmetry_weight * pattern.symmetry()
155            + self.complexity_weight * pattern.complexity()
156            + self.balance_weight * pattern.balance();
157        raw / total_weight
158    }
159}
160
161// ── Mutation Engine ────────────────────────────────────────────────────────
162
163/// Controlled randomness for creative exploration of ternary patterns.
164///
165/// Mutates patterns by flipping, rotating, inserting, or deleting values
166/// based on a mutation rate.
167#[derive(Debug, Clone)]
168pub struct MutationEngine {
169    /// Probability of each element being mutated (0.0 to 1.0).
170    pub mutation_rate: f64,
171}
172
173impl MutationEngine {
174    pub fn new(mutation_rate: f64) -> Self {
175        Self {
176            mutation_rate: mutation_rate.clamp(0.0, 1.0),
177        }
178    }
179
180    /// Deterministic flip: negate values at indices determined by seed.
181    pub fn flip_mutate(&self, pattern: &Pattern, seed: usize) -> Pattern {
182        let values: Vec<Ternary> = pattern
183            .values
184            .iter()
185            .enumerate()
186            .map(|(i, &v)| {
187                if self.should_mutate(i, seed) {
188                    v.negate()
189                } else {
190                    v
191                }
192            })
193            .collect();
194        Pattern::new(values)
195    }
196
197    /// Rotate pattern by `shift` positions.
198    pub fn rotate(&self, pattern: &Pattern, shift: usize) -> Pattern {
199        if pattern.values.is_empty() {
200            return pattern.clone();
201        }
202        let n = pattern.values.len();
203        let s = shift % n;
204        let mut values = pattern.values.clone();
205        values.rotate_right(s);
206        Pattern::new(values)
207    }
208
209    /// Reverse the pattern.
210    pub fn reverse(&self, pattern: &Pattern) -> Pattern {
211        let mut values = pattern.values.clone();
212        values.reverse();
213        Pattern::new(values)
214    }
215
216    /// Insert a value at a position determined by seed.
217    pub fn insert_mutate(&self, pattern: &Pattern, value: Ternary, seed: usize) -> Pattern {
218        let mut values = pattern.values.clone();
219        if !values.is_empty() {
220            let pos = seed % values.len();
221            values.insert(pos, value);
222        } else {
223            values.push(value);
224        }
225        Pattern::new(values)
226    }
227
228    /// Remove a value at a position determined by seed.
229    pub fn delete_mutate(&self, pattern: &Pattern, seed: usize) -> Pattern {
230        let mut values = pattern.values.clone();
231        if !values.is_empty() {
232            let pos = seed % values.len();
233            values.remove(pos);
234        }
235        Pattern::new(values)
236    }
237
238    /// Deterministic mutation decision based on index and seed.
239    fn should_mutate(&self, index: usize, seed: usize) -> bool {
240        // Simple hash: use multiplication and xor to spread bits
241        let hash = (index.wrapping_mul(31)).wrapping_add(seed).wrapping_mul(2654435761);
242        // Map to 0..1000 and compare against rate
243        let normalized = (hash % 1000) as f64 / 1000.0;
244        normalized < self.mutation_rate
245    }
246}
247
248// ── Pattern Generator ──────────────────────────────────────────────────────
249
250/// Generates novel ternary patterns using deterministic seeds.
251#[derive(Debug, Clone)]
252pub struct PatternGenerator {
253    /// Base seed for generation.
254    pub seed: usize,
255}
256
257impl PatternGenerator {
258    pub fn new(seed: usize) -> Self {
259        Self { seed }
260    }
261
262    /// Generate a pattern of given length using the seed.
263    pub fn generate(&self, length: usize) -> Pattern {
264        let values: Vec<Ternary> = (0..length)
265            .map(|i| {
266                let hash = (i.wrapping_mul(7919)).wrapping_add(self.seed).wrapping_mul(1103515245);
267                match hash % 3 {
268                    0 => Ternary::Negative,
269                    1 => Ternary::Zero,
270                    _ => Ternary::Positive,
271                }
272            })
273            .collect();
274        Pattern::new(values)
275    }
276
277    /// Generate a symmetric pattern.
278    pub fn generate_symmetric(&self, half_length: usize) -> Pattern {
279        let half: Vec<Ternary> = (0..half_length)
280            .map(|i| {
281                let hash = (i.wrapping_mul(6271)).wrapping_add(self.seed).wrapping_mul(2147483647);
282                match hash % 3 {
283                    0 => Ternary::Negative,
284                    1 => Ternary::Zero,
285                    _ => Ternary::Positive,
286                }
287            })
288            .collect();
289        let mut values = half.clone();
290        values.extend(half.iter().rev());
291        Pattern::new(values)
292    }
293
294    /// Generate a pattern with a specific ratio of each value type.
295    pub fn generate_weighted(&self, length: usize, neg_weight: f64, zero_weight: f64, pos_weight: f64) -> Pattern {
296        let total = neg_weight + zero_weight + pos_weight;
297        if total == 0.0 {
298            return Pattern::new(vec![Ternary::Zero; length]);
299        }
300        let neg_thresh = neg_weight / total;
301        let zero_thresh = neg_thresh + zero_weight / total;
302        let values: Vec<Ternary> = (0..length)
303            .map(|i| {
304                let hash = (i.wrapping_mul(3571)).wrapping_add(self.seed).wrapping_mul(48271);
305                let normalized = (hash % 1000) as f64 / 1000.0;
306                if normalized < neg_thresh {
307                    Ternary::Negative
308                } else if normalized < zero_thresh {
309                    Ternary::Zero
310                } else {
311                    Ternary::Positive
312                }
313            })
314            .collect();
315        Pattern::new(values)
316    }
317}
318
319// ── Style Transfer ─────────────────────────────────────────────────────────
320
321/// Applies patterns from one domain onto another by mapping ternary values.
322#[derive(Debug, Clone)]
323pub struct StyleTransfer {
324    /// Maps source domain patterns to target domain transformations.
325    rules: HashMap<String, Ternary>,
326}
327
328impl StyleTransfer {
329    pub fn new() -> Self {
330        Self {
331            rules: HashMap::new(),
332        }
333    }
334
335    /// Add a style rule: source value → target value.
336    pub fn add_rule(&mut self, source: &str, target: Ternary) {
337        self.rules.insert(source.to_string(), target);
338    }
339
340    /// Apply style rules to a pattern, replacing values that match rules.
341    pub fn apply(&self, pattern: &Pattern) -> Pattern {
342        let values: Vec<Ternary> = pattern
343            .values
344            .iter()
345            .map(|&v| {
346                let key = format!("{:?}", v);
347                match self.rules.get(&key) {
348                    Some(&replacement) => replacement,
349                    None => v,
350                }
351            })
352            .collect();
353        Pattern::new(values)
354    }
355
356    /// Blend two patterns by taking the element-wise average (rounded).
357    pub fn blend(a: &Pattern, b: &Pattern) -> Pattern {
358        let len = a.len().min(b.len());
359        let values: Vec<Ternary> = (0..len)
360            .map(|i| {
361                let avg = (a.values[i].to_i8() as i16 + b.values[i].to_i8() as i16) / 2;
362                Ternary::from_i8(avg as i8)
363            })
364            .collect();
365        Pattern::new(values)
366    }
367
368    /// Overlay pattern b onto a at the given offset.
369    pub fn overlay(a: &Pattern, b: &Pattern, offset: usize) -> Pattern {
370        let mut values = a.values.clone();
371        for (i, &v) in b.values.iter().enumerate() {
372            let pos = offset + i;
373            if pos < values.len() {
374                values[pos] = v;
375            }
376        }
377        Pattern::new(values)
378    }
379}
380
381// ── Cross-Domain Mapper ────────────────────────────────────────────────────
382
383/// Translates ternary patterns between different domains.
384///
385/// Domains are identified by string names. Each domain has a mapping that
386/// interprets ternary values in its own context.
387#[derive(Debug, Clone)]
388pub struct CrossDomainMapper {
389    /// Domain definitions: name → interpretation of {-1, 0, +1}.
390    domains: HashMap<String, DomainMapping>,
391}
392
393/// How a domain interprets the three ternary values.
394#[derive(Debug, Clone)]
395pub struct DomainMapping {
396    pub negative_label: String,
397    pub zero_label: String,
398    pub positive_label: String,
399}
400
401impl CrossDomainMapper {
402    pub fn new() -> Self {
403        Self {
404            domains: HashMap::new(),
405        }
406    }
407
408    /// Register a domain with its ternary value interpretations.
409    pub fn register_domain(
410        &mut self,
411        name: &str,
412        negative_label: &str,
413        zero_label: &str,
414        positive_label: &str,
415    ) {
416        self.domains.insert(
417            name.to_string(),
418            DomainMapping {
419                negative_label: negative_label.to_string(),
420                zero_label: zero_label.to_string(),
421                positive_label: positive_label.to_string(),
422            },
423        );
424    }
425
426    /// Translate a pattern from one domain's interpretation to another's.
427    /// The ternary values stay the same; only the labels change.
428    pub fn translate(&self, pattern: &Pattern, from_domain: &str, to_domain: &str) -> Option<Vec<String>> {
429        let _from = self.domains.get(from_domain)?;
430        let to = self.domains.get(to_domain)?;
431        Some(
432            pattern
433                .values
434                .iter()
435                .map(|&v| match v {
436                    Ternary::Negative => to.negative_label.clone(),
437                    Ternary::Zero => to.zero_label.clone(),
438                    Ternary::Positive => to.positive_label.clone(),
439                })
440                .collect(),
441        )
442    }
443
444    /// Get the label for a value in a specific domain.
445    pub fn label_for(&self, domain: &str, value: Ternary) -> Option<&str> {
446        let mapping = self.domains.get(domain)?;
447        Some(match value {
448            Ternary::Negative => &mapping.negative_label,
449            Ternary::Zero => &mapping.zero_label,
450            Ternary::Positive => &mapping.positive_label,
451        })
452    }
453
454    /// List registered domain names.
455    pub fn domain_names(&self) -> Vec<&str> {
456        self.domains.keys().map(|s| s.as_str()).collect()
457    }
458}
459
460// ── Muse ───────────────────────────────────────────────────────────────────
461
462/// The central creativity engine.
463///
464/// Combines pattern generation, mutation, style transfer, and aesthetic
465/// scoring into a single creative workflow.
466#[derive(Debug, Clone)]
467pub struct Muse {
468    pub generator: PatternGenerator,
469    pub mutator: MutationEngine,
470    pub scorer: AestheticScorer,
471    pub style: StyleTransfer,
472}
473
474impl Muse {
475    pub fn new(seed: usize, mutation_rate: f64) -> Self {
476        Self {
477            generator: PatternGenerator::new(seed),
478            mutator: MutationEngine::new(mutation_rate),
479            scorer: AestheticScorer::default(),
480            style: StyleTransfer::new(),
481        }
482    }
483
484    /// Create a muse with custom aesthetic weights.
485    pub fn with_scoring(seed: usize, mutation_rate: f64, sym_w: f64, comp_w: f64, bal_w: f64) -> Self {
486        Self {
487            generator: PatternGenerator::new(seed),
488            mutator: MutationEngine::new(mutation_rate),
489            scorer: AestheticScorer::new(sym_w, comp_w, bal_w),
490            style: StyleTransfer::new(),
491        }
492    }
493
494    /// Generate a base pattern and evolve it through mutation cycles.
495    /// Returns the best-scoring variant found.
496    pub fn create_and_evolve(&self, length: usize, generations: usize) -> Pattern {
497        let base = self.generator.generate(length);
498        let mut best = base.clone();
499        let mut best_score = self.scorer.score(&best);
500
501        for gen in 0..generations {
502            let candidate = self.mutator.flip_mutate(&best, gen);
503            let candidate_score = self.scorer.score(&candidate);
504            if candidate_score > best_score {
505                best = candidate;
506                best_score = candidate_score;
507            }
508        }
509        best
510    }
511
512    /// Generate multiple creative variants from a seed pattern.
513    pub fn variants(&self, base: &Pattern, count: usize) -> Vec<Pattern> {
514        (0..count)
515            .map(|i| {
516                let rotated = self.mutator.rotate(base, i);
517                self.mutator.flip_mutate(&rotated, i)
518            })
519            .collect()
520    }
521
522    /// Score a pattern using the internal scorer.
523    pub fn score(&self, pattern: &Pattern) -> f64 {
524        self.scorer.score(pattern)
525    }
526}
527
528// ── Tests ──────────────────────────────────────────────────────────────────
529
530#[cfg(test)]
531mod tests {
532    use super::*;
533
534    #[test]
535    fn test_ternary_to_i8() {
536        assert_eq!(Ternary::Negative.to_i8(), -1);
537        assert_eq!(Ternary::Zero.to_i8(), 0);
538        assert_eq!(Ternary::Positive.to_i8(), 1);
539    }
540
541    #[test]
542    fn test_ternary_from_i8() {
543        assert_eq!(Ternary::from_i8(-5), Ternary::Negative);
544        assert_eq!(Ternary::from_i8(0), Ternary::Zero);
545        assert_eq!(Ternary::from_i8(3), Ternary::Positive);
546    }
547
548    #[test]
549    fn test_ternary_negate() {
550        assert_eq!(Ternary::Negative.negate(), Ternary::Positive);
551        assert_eq!(Ternary::Zero.negate(), Ternary::Zero);
552        assert_eq!(Ternary::Positive.negate(), Ternary::Negative);
553    }
554
555    #[test]
556    fn test_pattern_symmetry_perfect() {
557        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Positive]);
558        assert!((p.symmetry() - 1.0).abs() < 1e-9);
559    }
560
561    #[test]
562    fn test_pattern_symmetry_none() {
563        let p = Pattern::new(vec![Ternary::Positive, Ternary::Negative]);
564        assert!((p.symmetry() - 0.0).abs() < 1e-9);
565    }
566
567    #[test]
568    fn test_pattern_balance() {
569        let p = Pattern::new(vec![Ternary::Zero, Ternary::Positive, Ternary::Zero, Ternary::Negative]);
570        assert!((p.balance() - 0.5).abs() < 1e-9);
571    }
572
573    #[test]
574    fn test_pattern_complexity_max() {
575        let p = Pattern::new(vec![Ternary::Positive, Ternary::Negative, Ternary::Positive, Ternary::Negative]);
576        assert!((p.complexity() - 1.0).abs() < 1e-9);
577    }
578
579    #[test]
580    fn test_aesthetic_scorer() {
581        let scorer = AestheticScorer::default();
582        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Positive]);
583        let score = scorer.score(&p);
584        assert!(score > 0.0 && score <= 1.0);
585    }
586
587    #[test]
588    fn test_aesthetic_scorer_custom_weights() {
589        let scorer = AestheticScorer::new(1.0, 0.0, 0.0);
590        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Positive]);
591        assert!((scorer.score(&p) - 1.0).abs() < 1e-9);
592    }
593
594    #[test]
595    fn test_mutation_flip() {
596        let engine = MutationEngine::new(1.0); // 100% mutation
597        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero]);
598        let mutated = engine.flip_mutate(&p, 42);
599        assert_eq!(mutated.values[0], Ternary::Negative); // always flipped at 100%
600    }
601
602    #[test]
603    fn test_mutation_rotate() {
604        let engine = MutationEngine::new(0.5);
605        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Negative]);
606        let rotated = engine.rotate(&p, 1);
607        assert_eq!(rotated.values[0], Ternary::Negative);
608        assert_eq!(rotated.values[1], Ternary::Positive);
609    }
610
611    #[test]
612    fn test_mutation_reverse() {
613        let engine = MutationEngine::new(0.5);
614        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Negative]);
615        let reversed = engine.reverse(&p);
616        assert_eq!(reversed.values[0], Ternary::Negative);
617        assert_eq!(reversed.values[2], Ternary::Positive);
618    }
619
620    #[test]
621    fn test_mutation_insert() {
622        let engine = MutationEngine::new(0.5);
623        let p = Pattern::new(vec![Ternary::Positive, Ternary::Negative]);
624        let inserted = engine.insert_mutate(&p, Ternary::Zero, 0);
625        assert_eq!(inserted.len(), 3);
626    }
627
628    #[test]
629    fn test_mutation_delete() {
630        let engine = MutationEngine::new(0.5);
631        let p = Pattern::new(vec![Ternary::Positive, Ternary::Negative]);
632        let deleted = engine.delete_mutate(&p, 0);
633        assert_eq!(deleted.len(), 1);
634    }
635
636    #[test]
637    fn test_pattern_generator_deterministic() {
638        let gen = PatternGenerator::new(42);
639        let a = gen.generate(10);
640        let b = gen.generate(10);
641        assert_eq!(a, b);
642    }
643
644    #[test]
645    fn test_pattern_generator_symmetric() {
646        let gen = PatternGenerator::new(7);
647        let p = gen.generate_symmetric(3);
648        assert_eq!(p.len(), 6);
649        assert!((p.symmetry() - 1.0).abs() < 1e-9);
650    }
651
652    #[test]
653    fn test_pattern_generator_weighted() {
654        let gen = PatternGenerator::new(99);
655        let p = gen.generate_weighted(20, 1.0, 0.0, 0.0);
656        assert!(p.values.iter().all(|&v| v == Ternary::Negative));
657    }
658
659    #[test]
660    fn test_style_transfer_blend() {
661        let a = Pattern::new(vec![Ternary::Positive, Ternary::Negative]);
662        let b = Pattern::new(vec![Ternary::Negative, Ternary::Positive]);
663        let blended = StyleTransfer::blend(&a, &b);
664        assert_eq!(blended.values[0], Ternary::Zero);
665        assert_eq!(blended.values[1], Ternary::Zero);
666    }
667
668    #[test]
669    fn test_style_transfer_overlay() {
670        let a = Pattern::new(vec![Ternary::Zero, Ternary::Zero, Ternary::Zero, Ternary::Zero]);
671        let b = Pattern::new(vec![Ternary::Positive, Ternary::Negative]);
672        let overlaid = StyleTransfer::overlay(&a, &b, 1);
673        assert_eq!(overlaid.values[0], Ternary::Zero);
674        assert_eq!(overlaid.values[1], Ternary::Positive);
675        assert_eq!(overlaid.values[2], Ternary::Negative);
676    }
677
678    #[test]
679    fn test_cross_domain_mapper() {
680        let mut mapper = CrossDomainMapper::new();
681        mapper.register_domain("music", "flat", "natural", "sharp");
682        mapper.register_domain("visual", "dark", "neutral", "bright");
683        let p = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Negative]);
684        let translated = mapper.translate(&p, "music", "visual").unwrap();
685        assert_eq!(translated, vec!["bright", "neutral", "dark"]);
686    }
687
688    #[test]
689    fn test_cross_domain_mapper_label_for() {
690        let mut mapper = CrossDomainMapper::new();
691        mapper.register_domain("spatial", "left", "center", "right");
692        assert_eq!(mapper.label_for("spatial", Ternary::Positive), Some("right"));
693        assert_eq!(mapper.label_for("nonexistent", Ternary::Positive), None);
694    }
695
696    #[test]
697    fn test_muse_create_and_evolve() {
698        let muse = Muse::new(42, 0.3);
699        let result = muse.create_and_evolve(8, 10);
700        assert_eq!(result.len(), 8);
701    }
702
703    #[test]
704    fn test_muse_variants() {
705        let muse = Muse::new(42, 0.5);
706        let base = Pattern::new(vec![Ternary::Positive, Ternary::Zero, Ternary::Negative]);
707        let variants = muse.variants(&base, 5);
708        assert_eq!(variants.len(), 5);
709        for v in &variants {
710            assert_eq!(v.len(), 3);
711        }
712    }
713
714    #[test]
715    fn test_muse_scoring_integration() {
716        let muse = Muse::new(1, 0.2);
717        let p = muse.generator.generate(6);
718        let score = muse.score(&p);
719        assert!(score >= 0.0 && score <= 1.0);
720    }
721
722    #[test]
723    fn test_empty_pattern_metrics() {
724        let p = Pattern::new(vec![]);
725        assert!(p.is_empty());
726        assert!((p.symmetry() - 1.0).abs() < 1e-9);
727        assert!((p.balance() - 1.0).abs() < 1e-9);
728        assert!((p.complexity() - 0.0).abs() < 1e-9);
729    }
730}