    <span class=<span class="string">"comment">#!/usr/bin/env python3
    """
    Evolution Lab: Evolving simple programs through genetic algorithms.

    This experiment evolves mathematical expressions to fit target behaviors.
    It&<span class=<span class="string">"comment">#039;s a simple form of genetic programming - letting programs breed and mutate.

    The goal: See what emerges from random variation and selection.
    """

    import random
    import math
    from dataclasses import dataclass
    from typing import List, Callable, Optional
    import copy

    <span class=<span class="string">"comment"># The primitives our evolved programs can use
    OPERATIONS = [&<span class=<span class="string">"comment">#039;+&#039;, &#039;-&#039;, &#039;*&#039;, &#039;/&#039;, &#039;sin&#039;, &#039;cos&#039;, &#039;abs&#039;, &#039;max&#039;, &#039;min&#039;]
    CONSTANTS = [0, 1, 2, 0.5, math.pi, math.e]

    @dataclass
    class Node:
        """A node in the expression tree."""
        op: str  <span class=<span class="string">"comment"># Operation or &#039;const&#039; or &#039;x&#039;
        value: Optional[float] = None  <span class=<span class="string">"comment"># For constants
        left: Optional[&<span class=<span class="string">"comment">#039;Node&#039;] = None
        right: Optional[&<span class=<span class="string">"comment">#039;Node&#039;] = None

        <span class="keyword">def evaluate(self, x: float) -> float:
            """Evaluate this subtree with the given x value."""
            try:
                if self.op == &<span class=<span class="string">"comment">#039;x&#039;:
                    return x
                elif self.op == &<span class=<span class="string">"comment">#039;const&#039;:
                    return self.value
                elif self.op == &<span class=<span class="string">"comment">#039;+&#039;:
                    return self.left.evaluate(x) + self.right.evaluate(x)
                elif self.op == &<span class=<span class="string">"comment">#039;-&#039;:
                    return self.left.evaluate(x) - self.right.evaluate(x)
                elif self.op == &<span class=<span class="string">"comment">#039;*&#039;:
                    return self.left.evaluate(x) * self.right.evaluate(x)
                elif self.op == &<span class=<span class="string">"comment">#039;/&#039;:
                    r = self.right.evaluate(x)
                    return self.left.evaluate(x) / r if abs(r) > 1e-10 else 0
                elif self.op == &<span class=<span class="string">"comment">#039;sin&#039;:
                    return math.sin(self.left.evaluate(x))
                elif self.op == &<span class=<span class="string">"comment">#039;cos&#039;:
                    return math.cos(self.left.evaluate(x))
                elif self.op == &<span class=<span class="string">"comment">#039;abs&#039;:
                    return abs(self.left.evaluate(x))
                elif self.op == &<span class=<span class="string">"comment">#039;max&#039;:
                    return max(self.left.evaluate(x), self.right.evaluate(x))
                elif self.op == &<span class=<span class="string">"comment">#039;min&#039;:
                    return min(self.left.evaluate(x), self.right.evaluate(x))
                else:
                    return 0
            except (ValueError, OverflowError, ZeroDivisionError):
                return 0

        <span class="keyword">def to_string(self) -> str:
            """Convert to readable string."""
            if self.op == &<span class=<span class="string">"comment">#039;x&#039;:
                return &<span class=<span class="string">"comment">#039;x&#039;
            elif self.op == &<span class=<span class="string">"comment">#039;const&#039;:
                if self.value == math.pi:
                    return &<span class=<span class="string">"comment">#039;pi&#039;
                elif self.value == math.e:
                    return &<span class=<span class="string">"comment">#039;e&#039;
                else:
                    return f&<span class=<span class="string">"comment">#039;{self.value:.2f}&#039;
            elif self.op in [&<span class=<span class="string">"comment">#039;sin&#039;, &#039;cos&#039;, &#039;abs&#039;]:
                return f&<span class=<span class="string">"comment">#039;{self.op}({self.left.to_string()})&#039;
            elif self.op in [&<span class=<span class="string">"comment">#039;max&#039;, &#039;min&#039;]:
                return f&<span class=<span class="string">"comment">#039;{self.op}({self.left.to_string()}, {self.right.to_string()})&#039;
            else:
                return f&<span class=<span class="string">"comment">#039;({self.left.to_string()} {self.op} {self.right.to_string()})&#039;

        <span class="keyword">def depth(self) -> int:
            """Get tree depth."""
            if self.left is None and self.right is None:
                return 1
            left_d = self.left.depth() if self.left else 0
            right_d = self.right.depth() if self.right else 0
            return 1 + max(left_d, right_d)

        <span class="keyword">def size(self) -> int:
            """Get number of nodes."""
            count = 1
            if self.left:
                count += self.left.size()
            if self.right:
                count += self.right.size()
            return count

    <span class="keyword">def random_tree(max_depth: int = 4) -> Node:
        """Generate a random expression tree."""
        if max_depth <= 1 or random.random() < 0.3:
            <span class=<span class="string">"comment"># Leaf node
            if random.random() < 0.5:
                return Node(&<span class=<span class="string">"comment">#039;x&#039;)
            else:
                return Node(&<span class=<span class="string">"comment">#039;const&#039;, value=random.choice(CONSTANTS))
        else:
            op = random.choice(OPERATIONS)
            if op in [&<span class=<span class="string">"comment">#039;sin&#039;, &#039;cos&#039;, &#039;abs&#039;]:
                return Node(op, left=random_tree(max_depth - 1))
            else:
                return Node(op,
                           left=random_tree(max_depth - 1),
                           right=random_tree(max_depth - 1))

    <span class="keyword">def crossover(parent1: Node, parent2: Node) -> Node:
        """Combine two trees via crossover."""
        child = copy.deepcopy(parent1)

        <span class=<span class="string">"comment"># Find random subtree in child to replace
        <span class="keyword">def get_all_nodes(node, path=[]):
            result = [(node, path)]
            if node.left:
                result.extend(get_all_nodes(node.left, path + [&<span class=<span class="string">"comment">#039;left&#039;]))
            if node.right:
                result.extend(get_all_nodes(node.right, path + [&<span class=<span class="string">"comment">#039;right&#039;]))
            return result

        child_nodes = get_all_nodes(child)
        parent2_nodes = get_all_nodes(parent2)

        if len(child_nodes) > 1 and parent2_nodes:
            <span class=<span class="string">"comment"># Pick a node to replace (not root)
            _, replace_path = random.choice(child_nodes[1:])
            <span class=<span class="string">"comment"># Pick a subtree from parent2
            donor, _ = random.choice(parent2_nodes)

            <span class=<span class="string">"comment"># Navigate to replacement point and replace
            current = child
            for step in replace_path[:-1]:
                current = getattr(current, step)
            setattr(current, replace_path[-1], copy.deepcopy(donor))

        return child

    <span class="keyword">def mutate(node: Node, rate: float = 0.1) -> Node:
        """Randomly mutate parts of the tree."""
        node = copy.deepcopy(node)

        <span class="keyword">def mutate_recursive(n: Node):
            if random.random() < rate:
                <span class=<span class="string">"comment"># Replace this subtree with a new random one
                new = random_tree(2)
                n.op = new.op
                n.value = new.value
                n.left = new.left
                n.right = new.right
            else:
                if n.left:
                    mutate_recursive(n.left)
                if n.right:
                    mutate_recursive(n.right)

        mutate_recursive(node)
        return node

    class Population:
        """A population of evolving programs."""

        <span class="keyword">def __init__(self, size: int = 50, target_func: Callable = None):
            self.size = size
            self.individuals = [random_tree() for _ in range(size)]
            self.target_func = target_func or (lambda x: x * x)
            self.generation = 0
            self.best_fitness_history = []

        <span class="keyword">def fitness(self, individual: Node) -> float:
            """Evaluate how well an individual matches the target."""
            error = 0
            test_points = [x / 10.0 for x in range(-50, 51)]

            for x in test_points:
                try:
                    predicted = individual.evaluate(x)
                    expected = self.target_func(x)
                    error += (predicted - expected) ** 2
                except:
                    error += 1000

            <span class=<span class="string">"comment"># Penalize complexity slightly
            complexity_penalty = individual.size() * 0.01

            return 1.0 / (1.0 + error + complexity_penalty)

        <span class="keyword">def evolve(self, generations: int = 100, verbose: bool = True):
            """Run evolution for specified generations."""
            for gen in range(generations):
                <span class=<span class="string">"comment"># Evaluate fitness
                scored = [(self.fitness(ind), ind) for ind in self.individuals]
                scored.sort(key=lambda x: -x[0])  <span class=<span class="string">"comment"># Best first

                best_fitness = scored[0][0]
                self.best_fitness_history.append(best_fitness)

                if verbose and gen % 10 == 0:
                    print(f"Gen {gen:4d}: Best fitness = {best_fitness:.6f}")
                    print(f"         Best expr: {scored[0][1].to_string()}")

                <span class=<span class="string">"comment"># Selection (tournament)
                <span class="keyword">def tournament(k=3):
                    contestants = random.sample(scored, k)
                    return max(contestants, key=lambda x: x[0])[1]

                <span class=<span class="string">"comment"># Create new population
                new_pop = []

                <span class=<span class="string">"comment"># Elitism: keep best 2
                new_pop.append(copy.deepcopy(scored[0][1]))
                new_pop.append(copy.deepcopy(scored[1][1]))

                while len(new_pop) < self.size:
                    if random.random() < 0.8:
                        <span class=<span class="string">"comment"># Crossover
                        p1 = tournament()
                        p2 = tournament()
                        child = crossover(p1, p2)
                    else:
                        <span class=<span class="string">"comment"># Mutation only
                        child = mutate(tournament(), rate=0.2)

                    <span class=<span class="string">"comment"># Always apply some mutation
                    child = mutate(child, rate=0.05)

                    <span class=<span class="string">"comment"># Limit tree depth
                    if child.depth() <= 8:
                        new_pop.append(child)

                self.individuals = new_pop
                self.generation += 1

            return scored[0][1]  <span class=<span class="string">"comment"># Return best individual

    <span class="keyword">def run_experiment(name: str, target_func: Callable, description: str):
        """Run an evolution experiment."""
        print("=" * 60)
        print(f"EXPERIMENT: {name}")
        print(f"Target: {description}")
        print("=" * 60)

        pop = Population(size=100, target_func=target_func)
        best = pop.evolve(generations=100, verbose=True)

        print()
        print("FINAL RESULT:")
        print(f"  Expression: {best.to_string()}")
        print(f"  Fitness: {pop.fitness(best):.6f}")

        <span class=<span class="string">"comment"># Test on some values
        print("\n  Sample outputs:")
        for x in [-2, -1, 0, 1, 2]:
            expected = target_func(x)
            predicted = best.evaluate(x)
            print(f"    f({x:2d}) = {predicted:8.4f}  (expected: {expected:8.4f})")

        return best, pop

    <span class="keyword">def main():
        print("EVOLUTION LAB: Evolving Mathematical Expressions")
        print()

        <span class=<span class="string">"comment"># Experiment 1: Evolve x^2
        run_experiment(
            "Square Function",
            lambda x: x * x,
            "f(x) = x^2"
        )

        print("\n" + "=" * 60 + "\n")

        <span class=<span class="string">"comment"># Experiment 2: Evolve something more complex
        run_experiment(
            "Sine Wave",
            lambda x: math.sin(x),
            "f(x) = sin(x)"
        )

        print("\n" + "=" * 60 + "\n")

        <span class=<span class="string">"comment"># Experiment 3: A weird target - let&#039;s see what evolves
        run_experiment(
            "Mystery Function",
            lambda x: abs(x) - x*x/10 + math.sin(x*2),
            "f(x) = |x| - x^2/10 + sin(2x)"
        )

    if __name__ == "__main__":
        main()