Add random program generation and mutation.
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
use rand::Rng;
|
||||
use crate::compiler::ast::{UntypedAst, OpCode};
|
||||
|
||||
pub fn random_sublist_ast(rng: &mut impl Rng, max_depth: usize) -> UntypedAst {
|
||||
// 1) Choose how many children the top-level `Sublist` will have.
|
||||
let len = rng.gen_range(1..=3);
|
||||
|
||||
// 2) Build a vector of sub-ASTs by calling `random_ast` for each child
|
||||
let mut children = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
// We start at `depth=1` because the top-level sublist itself is `depth=0`.
|
||||
children.push(random_ast(rng, 1, max_depth));
|
||||
}
|
||||
|
||||
// 3) Return a `Sublist` as the root node
|
||||
UntypedAst::Sublist(children)
|
||||
}
|
||||
|
||||
/// Generate a random `UntypedAst` by recursively building sub-trees,
|
||||
/// int literals, or instructions.
|
||||
///
|
||||
/// - `depth` tracks how deep we are in the tree.
|
||||
/// - `max_depth` is the maximum allowed depth to prevent infinite recursion.
|
||||
pub fn random_ast(rng: &mut impl Rng, depth: usize, max_depth: usize) -> UntypedAst {
|
||||
if depth >= max_depth {
|
||||
// Return something "terminal,"
|
||||
// e.g. an IntLiteral or a single Instruction
|
||||
random_terminal(rng)
|
||||
} else {
|
||||
// Weighted choice:
|
||||
// 0 => IntLiteral,
|
||||
// 1 => single Instruction,
|
||||
// 2 => Sublist with children
|
||||
let choice = rng.gen_range(0..3);
|
||||
match choice {
|
||||
0 => UntypedAst::IntLiteral(rng.gen_range(-10..10)),
|
||||
1 => UntypedAst::Instruction(random_opcode(rng)),
|
||||
2 => {
|
||||
// Make a sublist with 1..=3 children
|
||||
let len = rng.gen_range(1..=3);
|
||||
let mut children = Vec::with_capacity(len);
|
||||
for _ in 0..len {
|
||||
children.push(random_ast(rng, depth + 1, max_depth));
|
||||
}
|
||||
UntypedAst::Sublist(children)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random "terminal" node.
|
||||
/// Typically either an int literal or a single instruction.
|
||||
fn random_terminal(rng: &mut impl Rng) -> UntypedAst {
|
||||
// Weighted choice:
|
||||
// 0 => IntLiteral
|
||||
// 1 => single Instruction
|
||||
let choice = rng.gen_range(0..2);
|
||||
match choice {
|
||||
0 => UntypedAst::IntLiteral(rng.gen_range(-10..10)),
|
||||
1 => UntypedAst::Instruction(random_opcode(rng)),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a random `OpCode` from your known set of instructions.
|
||||
pub fn random_opcode(rng: &mut impl Rng) -> OpCode {
|
||||
let choice = rng.gen_range(0..6);
|
||||
// because we have 6 variants: Noop, Plus, Minus, Mult, Dup, Pop
|
||||
match choice {
|
||||
0 => OpCode::Noop,
|
||||
1 => OpCode::Plus,
|
||||
2 => OpCode::Minus,
|
||||
3 => OpCode::Mult,
|
||||
4 => OpCode::Dup,
|
||||
5 => OpCode::Pop,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,64 @@
|
||||
// use crate::compiler::ast::{Expr};
|
||||
use rand::Rng;
|
||||
use crate::compiler::ast::{UntypedAst, OpCode};
|
||||
use crate::gp::generate::{random_ast, random_opcode};
|
||||
|
||||
// pub fn crossover(a: &Expr, b: &Expr) -> (Expr, Expr) {
|
||||
// // stub
|
||||
// (a.clone(), b.clone())
|
||||
// }
|
||||
/// Returns a new `UntypedAst` that is a mutated version of `original`.
|
||||
/// We do not modify `original`; we clone or build new pieces as needed.
|
||||
///
|
||||
/// - `depth`: current depth of this node,
|
||||
/// - `max_depth`: max allowed depth (to prevent infinite recursion).
|
||||
pub fn mutated_ast(original: &UntypedAst, rng: &mut impl Rng, depth: usize, max_depth: usize) -> UntypedAst {
|
||||
// 10% chance: completely replace this node with a fresh random subtree
|
||||
if rng.gen_range(0..100) < 10 {
|
||||
return random_ast(rng, depth, max_depth);
|
||||
}
|
||||
|
||||
// pub fn mutate(expr: &mut Expr) {
|
||||
// // stub
|
||||
// }
|
||||
match original {
|
||||
UntypedAst::IntLiteral(val) => {
|
||||
// Another small chance to replace the entire node with random subtree
|
||||
if rng.gen_range(0..100) < 10 {
|
||||
return random_ast(rng, depth, max_depth);
|
||||
}
|
||||
|
||||
// Otherwise, "tweak" the int by a small offset
|
||||
let delta = rng.gen_range(-2..=2);
|
||||
let new_val = val.saturating_add(delta);
|
||||
UntypedAst::IntLiteral(new_val)
|
||||
}
|
||||
UntypedAst::Instruction(op) => {
|
||||
// 10% chance to replace node
|
||||
if rng.gen_range(0..100) < 10 {
|
||||
return random_ast(rng, depth, max_depth);
|
||||
}
|
||||
|
||||
// 50% chance to pick a new opcode
|
||||
// or else keep the same
|
||||
if rng.gen_bool(0.5) {
|
||||
UntypedAst::Instruction(random_opcode(rng))
|
||||
} else {
|
||||
// Just clone the original
|
||||
UntypedAst::Instruction(op.clone())
|
||||
}
|
||||
}
|
||||
UntypedAst::Sublist(children) => {
|
||||
// 10% chance to replace this sublist entirely
|
||||
if rng.gen_range(0..100) < 10 {
|
||||
return random_ast(rng, depth, max_depth);
|
||||
}
|
||||
|
||||
// Otherwise, mutate zero or more children
|
||||
let mut new_children = children.clone();
|
||||
if !new_children.is_empty() {
|
||||
// Choose how many kids to mutate:
|
||||
// e.g., 1..=min(2, children.len())
|
||||
let mut_count = rng.gen_range(1..=new_children.len().min(2));
|
||||
|
||||
for _ in 0..mut_count {
|
||||
let idx = rng.gen_range(0..new_children.len());
|
||||
new_children[idx] = mutated_ast(&new_children[idx], rng, depth+1, max_depth);
|
||||
}
|
||||
}
|
||||
UntypedAst::Sublist(new_children)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-11
@@ -1,13 +1,3 @@
|
||||
pub mod population;
|
||||
pub mod genetic_ops;
|
||||
// pub mod fitness;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_dummy() {
|
||||
assert_eq!(2+2, 4);
|
||||
}
|
||||
}
|
||||
pub mod generate;
|
||||
|
||||
Reference in New Issue
Block a user