1use std::collections::HashMap;
33
34use rand::Rng;
35use serde::{Deserialize, Serialize};
36use symbios_genetics::Genotype;
37
38use crate::expr::Expr;
39use crate::interpreter::{Interpreter, RuleDef};
40use crate::ops::{RuleVariant, ShapeOp, SplitEntry, SplitSize, VariantSelector};
41
42#[derive(Clone, Debug, Serialize, Deserialize)]
52pub struct ShapeGenotype {
53 pub rules: HashMap<String, RuleDef>,
57}
58
59impl ShapeGenotype {
60 pub fn from_interpreter(interp: &Interpreter) -> Self {
62 Self {
63 rules: interp.rules().clone(),
64 }
65 }
66
67 pub fn to_interpreter(&self) -> Interpreter {
73 let mut interp = Interpreter::new();
74 for (name, def) in &self.rules {
75 let _ =
78 interp.add_rule_variants(name.clone(), def.params.clone(), def.variants.clone());
79 }
80 interp
81 }
82}
83
84impl Genotype for ShapeGenotype {
87 fn mutate<R: Rng>(&mut self, rng: &mut R, rate: f32) {
109 let mut keys: Vec<&String> = self.rules.keys().collect();
112 keys.sort();
113 let keys: Vec<String> = keys.into_iter().cloned().collect();
114 for key in keys {
115 if let Some(def) = self.rules.get_mut(&key) {
116 for variant in def.variants.iter_mut() {
117 for op in variant.ops.iter_mut() {
118 mutate_op(op, rng, rate);
119 }
120 }
121 }
122 }
123 }
124
125 fn crossover<R: Rng>(&self, other: &Self, rng: &mut R) -> Self {
139 let mut child_rules = self.rules.clone();
140
141 for (name, self_def) in &self.rules {
142 if let Some(other_def) = other.rules.get(name) {
143 if self_def.variants.len() == other_def.variants.len() {
144 let blended: Vec<RuleVariant> = self_def
145 .variants
146 .iter()
147 .zip(other_def.variants.iter())
148 .map(|(sv, ov)| crossover_variant(sv, ov, rng))
149 .collect();
150 child_rules.insert(
151 name.clone(),
152 RuleDef {
153 params: self_def.params.clone(),
154 variants: blended,
155 },
156 );
157 } else if rng.random::<f32>() < 0.5 {
158 child_rules.insert(name.clone(), other_def.clone());
159 }
160 }
162 }
163
164 for (name, def) in &other.rules {
166 if !child_rules.contains_key(name) {
167 child_rules.insert(name.clone(), def.clone());
168 }
169 }
170
171 ShapeGenotype { rules: child_rules }
172 }
173}
174
175fn jitter<R: Rng>(rng: &mut R, rate: f32, value: f64, sigma: f64) -> f64 {
181 if rng.random::<f32>() < rate {
182 let u1: f64 = rng.random::<f64>().max(1e-15); let u2: f64 = rng.random::<f64>();
184 let gauss = (-2.0 * u1.ln()).sqrt() * (std::f64::consts::TAU * u2).cos();
185 value + sigma * gauss
186 } else {
187 value
188 }
189}
190
191fn jitter_expr<R: Rng>(rng: &mut R, rate: f32, e: &mut Expr, sigma: f64, post: fn(f64) -> f64) {
201 if let Expr::Lit(v) = e {
202 *v = post(jitter(rng, rate, *v, sigma));
203 } else {
204 e.visit_literals_mut(&mut |v| *v = jitter(rng, rate, *v, sigma));
205 }
206}
207
208fn mutate_op<R: Rng>(op: &mut ShapeOp, rng: &mut R, rate: f32) {
209 match op {
210 ShapeOp::Extrude(h) => {
211 jitter_expr(rng, rate, h, 0.5, |v| v.max(0.1));
212 }
213 ShapeOp::Taper(t) => {
214 jitter_expr(rng, rate, t, 0.1, |v| v.clamp(0.0, 1.0));
215 }
216 ShapeOp::Scale(v) => {
217 for c in v.iter_mut() {
218 jitter_expr(rng, rate, c, 0.2, |x| x.max(0.1));
219 }
220 }
221 ShapeOp::Translate(v) => {
222 for c in v.iter_mut() {
223 jitter_expr(rng, rate, c, 0.5, |x| x);
224 }
225 }
226 ShapeOp::Split { entries, .. } => {
227 let original_abs_sum =
234 sum_split_entries(entries, |s| matches!(s, SplitSize::Absolute(_)));
235 let original_rel_sum =
236 sum_split_entries(entries, |s| matches!(s, SplitSize::Relative(_)));
237 for entry in entries.iter_mut() {
238 match entry {
239 SplitEntry::Slot(slot) => mutate_split_size(&mut slot.size, rng, rate),
240 SplitEntry::Group(slots) => {
241 for slot in slots.iter_mut() {
242 mutate_split_size(&mut slot.size, rng, rate);
243 }
244 }
245 }
246 }
247 repair_split_entry_sums(entries, original_abs_sum, original_rel_sum);
248 }
249 ShapeOp::SplitArea { slots, .. } => {
250 for slot in slots.iter_mut() {
251 mutate_split_size(&mut slot.size, rng, rate);
252 }
253 }
254 ShapeOp::Fit { candidates, .. } => {
255 for c in candidates.iter_mut() {
256 jitter_expr(rng, rate, &mut c.min_size, 0.2, |v| v.max(0.0));
257 }
258 }
259 ShapeOp::Size(v) => {
260 for c in v.iter_mut() {
261 jitter_expr(rng, rate, c, 0.2, |x| x.max(0.0));
262 }
263 }
264 ShapeOp::ShapeL { front, side, .. } => {
265 jitter_expr(rng, rate, front, 0.3, |v| v.max(0.1));
266 jitter_expr(rng, rate, side, 0.3, |v| v.max(0.1));
267 }
268 ShapeOp::ShapeU {
269 front, left, right, ..
270 } => {
271 jitter_expr(rng, rate, front, 0.3, |v| v.max(0.1));
272 jitter_expr(rng, rate, left, 0.3, |v| v.max(0.1));
273 jitter_expr(rng, rate, right, 0.3, |v| v.max(0.1));
274 }
275 ShapeOp::Scatter { count, .. } => {
276 jitter_expr(rng, rate, count, 1.0, |v| v.max(0.0));
277 }
278 ShapeOp::Repeat { tile_sizes, .. } => {
279 for ts in tile_sizes.iter_mut() {
280 jitter_expr(rng, rate, ts, 0.3, |v| v.max(0.1));
281 }
282 }
283 ShapeOp::Roof { spec, .. } => {
284 jitter_expr(rng, rate, &mut spec.pitch, 5.0, |v| v.clamp(1.0, 89.0));
285 jitter_expr(rng, rate, &mut spec.overhang, 0.2, |v| v.clamp(0.0, 2.0));
286 if let Some(h) = &mut spec.height {
287 jitter_expr(rng, rate, h, 0.5, |v| v.max(0.1));
288 }
289 }
290 ShapeOp::Polygon(verts) => {
291 for v in verts.iter_mut() {
292 v.x = jitter(rng, rate, v.x, 0.2);
293 v.y = jitter(rng, rate, v.y, 0.2);
294 }
295 }
296 ShapeOp::Rotate(_)
300 | ShapeOp::Comp(_)
301 | ShapeOp::Offset { .. }
302 | ShapeOp::I(_)
303 | ShapeOp::Mat(_)
304 | ShapeOp::Rule(_)
305 | ShapeOp::Align { .. }
306 | ShapeOp::Attach { .. }
307 | ShapeOp::RegSnap(_)
308 | ShapeOp::IfClear { .. }
309 | ShapeOp::IfOccluded { .. }
310 | ShapeOp::IfInside { .. }
311 | ShapeOp::IfTouches { .. }
312 | ShapeOp::Label(_)
313 | ShapeOp::Pick { .. }
314 | ShapeOp::Center { .. }
315 | ShapeOp::Mirror => {}
316 }
317}
318
319fn mutate_split_size<R: Rng>(size: &mut SplitSize, rng: &mut R, rate: f32) {
320 match size {
321 SplitSize::Absolute(e) => jitter_expr(rng, rate, e, 0.3, |v| v.max(0.1)),
322 SplitSize::Relative(e) => jitter_expr(rng, rate, e, 0.05, |v| v.clamp(0.01, 1.0)),
323 SplitSize::Floating(e) => jitter_expr(rng, rate, e, 0.3, |v| v.max(0.1)),
324 }
325}
326
327fn sum_split_entries<F>(entries: &[SplitEntry], kind: F) -> f64
331where
332 F: Fn(&SplitSize) -> bool,
333{
334 entries
335 .iter()
336 .filter_map(SplitEntry::as_slot)
337 .filter(|s| kind(&s.size))
338 .filter_map(|s| s.size.expr().as_lit())
339 .sum()
340}
341
342fn repair_split_entry_sums(
346 entries: &mut [SplitEntry],
347 original_abs_sum: f64,
348 original_rel_sum: f64,
349) {
350 let new_abs_sum = sum_split_entries(entries, |s| matches!(s, SplitSize::Absolute(_)));
351 if original_abs_sum > 1e-9 && new_abs_sum > original_abs_sum {
352 let scale = original_abs_sum / new_abs_sum;
353 for entry in entries.iter_mut() {
354 if let SplitEntry::Slot(slot) = entry
355 && let SplitSize::Absolute(Expr::Lit(v)) = &mut slot.size
356 {
357 *v = (*v * scale).max(0.1);
358 }
359 }
360 }
361 let new_rel_sum = sum_split_entries(entries, |s| matches!(s, SplitSize::Relative(_)));
362 if original_rel_sum > 1e-9 && new_rel_sum > original_rel_sum {
363 let scale = original_rel_sum / new_rel_sum;
364 for entry in entries.iter_mut() {
365 if let SplitEntry::Slot(slot) = entry
366 && let SplitSize::Relative(Expr::Lit(v)) = &mut slot.size
367 {
368 *v = (*v * scale).clamp(0.01, 1.0);
369 }
370 }
371 }
372}
373
374fn crossover_variant<R: Rng>(a: &RuleVariant, b: &RuleVariant, rng: &mut R) -> RuleVariant {
377 if same_structure(&a.ops, &b.ops) {
378 let ops = a
379 .ops
380 .iter()
381 .zip(b.ops.iter())
382 .map(|(ao, bo)| blend_op(ao, bo, rng))
383 .collect();
384 let selector = match (&a.selector, &b.selector) {
387 (VariantSelector::Weight(wa), VariantSelector::Weight(wb)) => {
388 VariantSelector::Weight(blx(*wa, *wb, 0.5, rng).max(0.0))
389 }
390 _ => a.selector.clone(),
391 };
392 RuleVariant { selector, ops }
393 } else {
394 if rng.random::<f32>() < 0.5 {
396 a.clone()
397 } else {
398 b.clone()
399 }
400 }
401}
402
403fn same_structure(a: &[ShapeOp], b: &[ShapeOp]) -> bool {
405 a.len() == b.len() && a.iter().zip(b.iter()).all(|(ao, bo)| same_op_kind(ao, bo))
406}
407
408fn same_op_kind(a: &ShapeOp, b: &ShapeOp) -> bool {
409 use ShapeOp::*;
410 matches!(
411 (a, b),
412 (Extrude(_), Extrude(_))
413 | (Taper(_), Taper(_))
414 | (Rotate(_), Rotate(_))
415 | (Translate(_), Translate(_))
416 | (Scale(_), Scale(_))
417 | (Split { .. }, Split { .. })
418 | (SplitArea { .. }, SplitArea { .. })
419 | (Fit { .. }, Fit { .. })
420 | (Size(_), Size(_))
421 | (Center { .. }, Center { .. })
422 | (Mirror, Mirror)
423 | (Label(_), Label(_))
424 | (IfInside { .. }, IfInside { .. })
425 | (IfTouches { .. }, IfTouches { .. })
426 | (Scatter { .. }, Scatter { .. })
427 | (Pick { .. }, Pick { .. })
428 | (ShapeL { .. }, ShapeL { .. })
429 | (ShapeU { .. }, ShapeU { .. })
430 | (Repeat { .. }, Repeat { .. })
431 | (Comp(_), Comp(_))
432 | (I(_), I(_))
433 | (Mat(_), Mat(_))
434 | (Rule(_), Rule(_))
435 | (Align { .. }, Align { .. })
436 | (Offset { .. }, Offset { .. })
437 | (Roof { .. }, Roof { .. })
438 | (Attach { .. }, Attach { .. })
439 | (Polygon(_), Polygon(_))
440 )
441}
442
443fn blx<R: Rng>(a: f64, b: f64, alpha: f64, rng: &mut R) -> f64 {
450 let lo = a.min(b);
451 let hi = a.max(b);
452 let d = (hi - lo) * alpha;
453 let lo_ext = lo - d;
454 let hi_ext = hi + d;
455 if hi_ext <= lo_ext {
456 (a + b) / 2.0
457 } else {
458 rng.random::<f64>() * (hi_ext - lo_ext) + lo_ext
459 }
460}
461
462fn blend_expr<R: Rng>(a: &Expr, b: &Expr, rng: &mut R, post: fn(f64) -> f64) -> Expr {
465 match (a.as_lit(), b.as_lit()) {
466 (Some(x), Some(y)) => Expr::Lit(post(blx(x, y, 0.5, rng))),
467 _ => a.clone(),
468 }
469}
470
471fn blend_expr3<R: Rng>(
472 a: &[Expr; 3],
473 b: &[Expr; 3],
474 rng: &mut R,
475 post: fn(f64) -> f64,
476) -> [Expr; 3] {
477 [
478 blend_expr(&a[0], &b[0], rng, post),
479 blend_expr(&a[1], &b[1], rng, post),
480 blend_expr(&a[2], &b[2], rng, post),
481 ]
482}
483
484fn blend_op<R: Rng>(a: &ShapeOp, b: &ShapeOp, rng: &mut R) -> ShapeOp {
485 match (a, b) {
486 (ShapeOp::Extrude(ha), ShapeOp::Extrude(hb)) => {
487 ShapeOp::Extrude(blend_expr(ha, hb, rng, |v| v.max(0.1)))
488 }
489 (ShapeOp::Taper(ta), ShapeOp::Taper(tb)) => {
490 ShapeOp::Taper(blend_expr(ta, tb, rng, |v| v.clamp(0.0, 1.0)))
491 }
492 (ShapeOp::Scale(va), ShapeOp::Scale(vb)) => {
493 ShapeOp::Scale(blend_expr3(va, vb, rng, |v| v.max(0.1)))
494 }
495 (ShapeOp::Translate(va), ShapeOp::Translate(vb)) => {
496 ShapeOp::Translate(blend_expr3(va, vb, rng, |v| v))
497 }
498 (
499 ShapeOp::Split {
500 axis,
501 entries: entries_a,
502 snap,
503 },
504 ShapeOp::Split {
505 entries: entries_b, ..
506 },
507 ) => {
508 let entries = if entries_a.len() == entries_b.len() {
511 entries_a
512 .iter()
513 .zip(entries_b.iter())
514 .map(|(ea, eb)| match (ea, eb) {
515 (SplitEntry::Slot(sa), SplitEntry::Slot(sb)) => {
516 SplitEntry::Slot(crate::ops::SplitSlot {
517 size: blend_split_size(&sa.size, &sb.size, rng),
518 rule: sa.rule.clone(),
519 })
520 }
521 (SplitEntry::Group(ga), SplitEntry::Group(gb)) if ga.len() == gb.len() => {
522 SplitEntry::Group(
523 ga.iter()
524 .zip(gb.iter())
525 .map(|(sa, sb)| crate::ops::SplitSlot {
526 size: blend_split_size(&sa.size, &sb.size, rng),
527 rule: sa.rule.clone(),
528 })
529 .collect(),
530 )
531 }
532 _ => ea.clone(),
533 })
534 .collect()
535 } else {
536 entries_a.clone()
537 };
538 ShapeOp::Split {
539 axis: *axis,
540 entries,
541 snap: snap.clone(),
542 }
543 }
544 (
545 ShapeOp::Repeat {
546 axis,
547 tile_sizes: tsa,
548 rule,
549 },
550 ShapeOp::Repeat {
551 tile_sizes: tsb, ..
552 },
553 ) => {
554 let blended: Vec<Expr> = if tsa.len() == tsb.len() {
555 tsa.iter()
556 .zip(tsb.iter())
557 .map(|(a, b)| blend_expr(a, b, rng, |v| v.max(0.1)))
558 .collect()
559 } else {
560 tsa.clone()
561 };
562 ShapeOp::Repeat {
563 axis: *axis,
564 tile_sizes: blended,
565 rule: rule.clone(),
566 }
567 }
568 (ShapeOp::Roof { spec: sa, cases }, ShapeOp::Roof { spec: sb, .. }) => {
569 let mut spec = sa.clone();
570 spec.pitch = blend_expr(&sa.pitch, &sb.pitch, rng, |v| v.clamp(1.0, 89.0));
571 spec.overhang = blend_expr(&sa.overhang, &sb.overhang, rng, |v| v.clamp(0.0, 2.0));
572 ShapeOp::Roof {
573 spec,
574 cases: cases.clone(),
575 }
576 }
577 _ => a.clone(),
579 }
580}
581
582fn blend_split_size<R: Rng>(a: &SplitSize, b: &SplitSize, rng: &mut R) -> SplitSize {
583 match (a, b) {
584 (SplitSize::Absolute(va), SplitSize::Absolute(vb)) => {
585 SplitSize::Absolute(blend_expr(va, vb, rng, |v| v.max(0.1)))
586 }
587 (SplitSize::Relative(va), SplitSize::Relative(vb)) => {
588 SplitSize::Relative(blend_expr(va, vb, rng, |v| v.clamp(0.01, 1.0)))
589 }
590 (SplitSize::Floating(va), SplitSize::Floating(vb)) => {
591 SplitSize::Floating(blend_expr(va, vb, rng, |v| v.max(0.1)))
592 }
593 _ => a.clone(),
595 }
596}
597
598#[cfg(test)]
601mod tests {
602 use super::*;
603 use crate::grammar::parse_ops;
604 use crate::scope::Vec3;
605 use rand::SeedableRng;
606 use rand_pcg::Pcg64;
607
608 fn build_interp() -> Interpreter {
609 let mut interp = Interpreter::new();
610 interp.add_rule(
611 "Lot",
612 parse_ops("Extrude(10) Split(Y) { 3: Floor | ~1: Top }").unwrap(),
613 );
614 interp.add_rule("Floor", parse_ops(r#"Taper(0.0) I("Floor")"#).unwrap());
615 interp.add_rule("Top", parse_ops(r#"Taper(0.8) I("Roof")"#).unwrap());
616 interp
617 }
618
619 #[test]
620 fn test_round_trip() {
621 let interp = build_interp();
622 let dna = ShapeGenotype::from_interpreter(&interp);
623 let interp2 = dna.to_interpreter();
624 let footprint = crate::scope::Scope::new(
626 Vec3::ZERO,
627 crate::scope::Quat::IDENTITY,
628 Vec3::new(10.0, 0.0, 10.0),
629 );
630 let m1 = interp.derive(footprint, "Lot").unwrap();
631 let m2 = interp2.derive(footprint, "Lot").unwrap();
632 assert_eq!(m1.len(), m2.len());
633 assert_eq!(m1.terminals[0].mesh_id, m2.terminals[0].mesh_id);
634 }
635
636 #[test]
637 fn test_mutate_preserves_validity() {
638 let interp = build_interp();
639 let mut dna = ShapeGenotype::from_interpreter(&interp);
640 let mut rng = Pcg64::seed_from_u64(7);
641 dna.mutate(&mut rng, 1.0);
643 let interp2 = dna.to_interpreter();
644 let footprint = crate::scope::Scope::new(
645 Vec3::ZERO,
646 crate::scope::Quat::IDENTITY,
647 Vec3::new(10.0, 0.0, 10.0),
648 );
649 interp2.derive(footprint, "Lot").unwrap();
651 }
652
653 #[test]
654 fn test_crossover_produces_valid_grammar() {
655 let interp_a = build_interp();
656 let mut interp_b = build_interp();
657 interp_b.add_rule(
659 "Lot",
660 parse_ops("Extrude(20) Split(Y) { 5: Floor | ~2: Top }").unwrap(),
661 );
662
663 let dna_a = ShapeGenotype::from_interpreter(&interp_a);
664 let dna_b = ShapeGenotype::from_interpreter(&interp_b);
665 let mut rng = Pcg64::seed_from_u64(99);
666 let child = dna_a.crossover(&dna_b, &mut rng);
667 let interp_child = child.to_interpreter();
668
669 let footprint = crate::scope::Scope::new(
670 Vec3::ZERO,
671 crate::scope::Quat::IDENTITY,
672 Vec3::new(10.0, 0.0, 10.0),
673 );
674 interp_child.derive(footprint, "Lot").unwrap();
675 }
676
677 #[test]
678 fn test_crossover_with_disjoint_rules() {
679 let mut interp_a = Interpreter::new();
680 interp_a.add_rule("A", parse_ops(r#"Extrude(5) I("Mesh")"#).unwrap());
681
682 let mut interp_b = Interpreter::new();
683 interp_b.add_rule("B", parse_ops(r#"Extrude(8) I("Mesh")"#).unwrap());
684
685 let dna_a = ShapeGenotype::from_interpreter(&interp_a);
686 let dna_b = ShapeGenotype::from_interpreter(&interp_b);
687 let mut rng = Pcg64::seed_from_u64(1);
688 let child = dna_a.crossover(&dna_b, &mut rng);
689 assert!(child.rules.contains_key("A"));
691 assert!(child.rules.contains_key("B"));
692 }
693
694 #[test]
695 fn test_mutate_extrude_clamp() {
696 let mut interp = Interpreter::new();
697 interp.add_rule("R", parse_ops("Extrude(0.11) I(M)").unwrap());
698 let mut dna = ShapeGenotype::from_interpreter(&interp);
699 let mut rng = Pcg64::seed_from_u64(0);
700 for _ in 0..500 {
702 dna.mutate(&mut rng, 1.0);
703 let h = match &dna.rules["R"].variants[0].ops[0] {
704 ShapeOp::Extrude(h) => h.as_lit().unwrap(),
705 _ => panic!("expected Extrude"),
706 };
707 assert!(h >= 0.1, "Extrude height {h} < 0.1");
708 }
709 }
710
711 #[test]
712 fn test_blx_same_parents() {
713 use rand::SeedableRng;
716 let mut rng = Pcg64::seed_from_u64(42);
717 let result = blx(5.0, 5.0, 0.5, &mut rng);
718 assert!((result - 5.0).abs() < 1e-9);
719 }
720
721 #[test]
725 fn test_mutate_property_1000_genotypes_all_interpret() {
726 use crate::scope::{Quat, Scope, Vec3};
727 use rand::SeedableRng;
728
729 let mut interp = Interpreter::new();
733 interp.add_rule(
734 "Lot",
735 parse_ops("Extrude(8) Split(Y) { 3: Floor | ~1: Mid | '0.2: Cap | 1.5: Top }").unwrap(),
736 );
737 interp.add_rule("Floor", parse_ops("Repeat(X, 2.0) { Bay }").unwrap());
738 interp.add_rule(
739 "Bay",
740 parse_ops(r#"Scale(0.9, 0.9, 0.9) Translate(0.1, 0, 0) I("Bay")"#).unwrap(),
741 );
742 interp.add_rule("Mid", parse_ops(r#"Taper(0.3) I("Mid")"#).unwrap());
743 interp.add_rule("Cap", parse_ops(r#"I("Cap")"#).unwrap());
744 interp.add_rule(
745 "Top",
746 parse_ops("Roof(Gable, 35) { Slope: Tile | GableEnd: Brick }").unwrap(),
747 );
748
749 let footprint = Scope::new(Vec3::ZERO, Quat::IDENTITY, Vec3::new(10.0, 0.0, 6.0));
750
751 let dna_seed = ShapeGenotype::from_interpreter(&interp);
752 let mut failures: Vec<(u64, String)> = Vec::new();
753
754 for seed in 0u64..1000 {
755 let mut dna = dna_seed.clone();
756 let mut rng = Pcg64::seed_from_u64(seed);
757 for _ in 0..3 {
760 dna.mutate(&mut rng, 1.0);
761 }
762 let interp = dna.to_interpreter();
763 match interp.derive(footprint, "Lot") {
764 Ok(_) => {}
765 Err(e) => failures.push((seed, format!("{e:?}"))),
766 }
767 }
768
769 assert!(
770 failures.is_empty(),
771 "{} of 1000 mutated genotypes failed to interpret. First failures: {:?}",
772 failures.len(),
773 failures.iter().take(5).collect::<Vec<_>>(),
774 );
775 }
776}