nekomata / selfmod /evolve.lisp
SNAPKITTYWEST's picture
push from SNAPKITTYWEST/nekomata
3b70664 verified
Raw
History Blame Contribute Delete
4.42 kB
(in-package #:nekod.selfmod)
;; Evolutionary optimizer: genetic algorithm over container spec populations
;; Selection -> Crossover -> Mutation -> Evaluation -> next generation
;; ---- Crossover operators ----------------------------------------------------
(defun crossover-specs (spec-a spec-b)
"Single-point crossover between two container specs.
Returns two children specs."
(let ((child-a (copy-structure spec-a))
(child-b (copy-structure spec-b)))
;; Swap restart policies
(rotatef (container-spec-restart-policy child-a)
(container-spec-restart-policy child-b))
;; Mix env vars: child-a gets first half of A env + second half of B env
(let* ((env-a (container-spec-env spec-a))
(env-b (container-spec-env spec-b))
(mid-a (ceiling (length env-a) 2))
(mid-b (ceiling (length env-b) 2)))
(setf (container-spec-env child-a)
(append (subseq env-a 0 mid-a)
(subseq env-b mid-b)))
(setf (container-spec-env child-b)
(append (subseq env-b 0 mid-b)
(subseq env-a mid-a))))
(values child-a child-b)))
;; ---- Selection operators ----------------------------------------------------
(defun tournament-select (population scores &optional (k 3))
"Tournament selection: pick K random candidates, return the fittest."
(let* ((n (length population))
(candidates (loop repeat k collect (random n)))
(best-idx (first (sort candidates #'>
:key (lambda (i) (nth i scores))))))
(nth best-idx population)))
(defun roulette-select (population scores)
"Fitness-proportionate (roulette wheel) selection."
(let* ((total (reduce #'+ scores))
(pick (random (max total 0.001)))
(running 0.0))
(loop for spec in population
for score in scores
do (incf running score)
when (>= running pick)
return spec
finally (return (car (last population))))))
;; ---- Diversity measure ------------------------------------------------------
(defun population-diversity (population)
"Measure diversity as fraction of specs with unique restart policies."
(let ((policies (mapcar #'container-spec-restart-policy population)))
(/ (length (remove-duplicates policies)) (max (length population) 1))))
;; ---- Full evolutionary step -------------------------------------------------
(defun evolutionary-step (population telemetry
&key (mutation-rate 0.15)
(crossover-rate 0.6)
(elitism 2))
"Full evolutionary step: selection, crossover, mutation, elitism.
Returns next-generation population of same size."
(let* ((n (length population))
(scores (mapcar (lambda (s) (fitness s telemetry)) population))
;; Elites survive unchanged
(sorted-pairs (sort (mapcar #'cons population scores) #'> :key #'cdr))
(elites (mapcar #'car (subseq sorted-pairs 0 (min elitism n))))
(next (copy-list elites)))
;; Fill rest via crossover + mutation
(loop while (< (length next) n)
do (let ((parent-a (tournament-select population scores))
(parent-b (tournament-select population scores)))
(if (< (random 1.0) crossover-rate)
(multiple-value-bind (child-a child-b)
(crossover-specs parent-a parent-b)
(push (or (mutate-spec child-a mutation-rate) child-a) next)
(when (< (length next) n)
(push (or (mutate-spec child-b mutation-rate) child-b) next)))
(push (or (mutate-spec parent-a mutation-rate) parent-a) next))))
(subseq next 0 n)))
;; ---- Convergence check ------------------------------------------------------
(defun converged-p (population telemetry &optional (threshold 0.01))
"Check if population has converged (fitness variance < threshold)."
(let* ((scores (mapcar (lambda (s) (fitness s telemetry)) population))
(mean (/ (reduce #'+ scores) (max (length scores) 1)))
(var (/ (reduce #'+ (mapcar (lambda (s) (expt (- s mean) 2)) scores))
(max (length scores) 1))))
(< var threshold)))