Merge pull request 'fix: feat: Push3 evolution — elitism (top N survive unchanged) (#640)' (#643) from fix/issue-640 into master
This commit is contained in:
commit
f1ed0e4fdc
1 changed files with 57 additions and 6 deletions
|
|
@ -10,6 +10,7 @@
|
|||
# --population 10 \
|
||||
# --generations 5 \
|
||||
# --mutation-rate 2 \
|
||||
# --elites 2 \
|
||||
# --output evolved/
|
||||
#
|
||||
# Algorithm:
|
||||
|
|
@ -18,7 +19,8 @@
|
|||
# a. Score all candidates via fitness.sh
|
||||
# b. Log generation stats (min/max/mean fitness, best candidate)
|
||||
# c. Select k survivors via tournament selection (k = population/2)
|
||||
# d. Generate next population: mutate survivors + crossover pairs
|
||||
# d. Elitism: copy top N candidates unchanged into next generation
|
||||
# e. Generate next population: mutate survivors + crossover pairs
|
||||
# 3. Output best candidate as Push3 file.
|
||||
# 4. Show diff: original vs evolved (which constants changed, by how much).
|
||||
#
|
||||
|
|
@ -61,6 +63,7 @@ SEED=""
|
|||
POPULATION=10
|
||||
GENERATIONS=5
|
||||
MUTATION_RATE=2
|
||||
ELITES=2
|
||||
OUTPUT_DIR=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
|
|
@ -69,6 +72,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--population) POPULATION="$2"; shift 2 ;;
|
||||
--generations) GENERATIONS="$2"; shift 2 ;;
|
||||
--mutation-rate) MUTATION_RATE="$2"; shift 2 ;;
|
||||
--elites) ELITES="$2"; shift 2 ;;
|
||||
--output) OUTPUT_DIR="$2"; shift 2 ;;
|
||||
*) echo "Unknown option: $1" >&2; exit 2 ;;
|
||||
esac
|
||||
|
|
@ -88,6 +92,11 @@ for _name_val in "population:$POPULATION" "generations:$GENERATIONS" "mutation-r
|
|||
fi
|
||||
done
|
||||
|
||||
if ! [[ "$ELITES" =~ ^[0-9]+$ ]]; then
|
||||
echo "Error: --elites must be a non-negative integer (got: $ELITES)" >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# Canonicalize paths
|
||||
SEED="$(cd "$(dirname "$SEED")" && pwd)/$(basename "$SEED")"
|
||||
mkdir -p "$OUTPUT_DIR"
|
||||
|
|
@ -142,6 +151,25 @@ print(min(nums), max(nums), round(sum(nums) / len(nums)))
|
|||
PYEOF
|
||||
}
|
||||
|
||||
# Top-N selection: return filepaths of the N highest-scoring candidates (descending).
|
||||
py_top_n() {
|
||||
local n="$1"
|
||||
local scores_file="$2"
|
||||
python3 - "$n" "$scores_file" <<'PYEOF'
|
||||
import sys
|
||||
n = int(sys.argv[1])
|
||||
entries = []
|
||||
with open(sys.argv[2]) as f:
|
||||
for line in f:
|
||||
parts = line.rstrip('\n').split('\t')
|
||||
if len(parts) >= 3:
|
||||
entries.append((int(parts[1]), parts[2]))
|
||||
entries.sort(key=lambda x: x[0], reverse=True)
|
||||
for _, path in entries[:n]:
|
||||
print(path)
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# Tournament selection: given a scores file (one "idx score filepath" per line),
|
||||
# run k tournaments of size 2 and return winner filepaths (one per line).
|
||||
py_tournament() {
|
||||
|
|
@ -208,6 +236,7 @@ log " Seed: $SEED"
|
|||
log " Population: $POPULATION"
|
||||
log " Generations: $GENERATIONS"
|
||||
log " Mutation rate: $MUTATION_RATE"
|
||||
log " Elites: $ELITES"
|
||||
log " Output: $OUTPUT_DIR"
|
||||
log " TSX: $TSX_CMD"
|
||||
log " Eval mode: $EVAL_MODE"
|
||||
|
|
@ -388,15 +417,37 @@ PYEOF
|
|||
|
||||
log " Selected ${#SURVIVOR_FILES[@]} survivors via tournament"
|
||||
|
||||
# --- d. Generate next population ---
|
||||
# --- d/e. Generate next population (elitism + offspring) ---
|
||||
|
||||
NEXT_GEN_DIR="$WORK_DIR/gen_$((gen + 1))"
|
||||
mkdir -p "$NEXT_GEN_DIR"
|
||||
|
||||
NEXT_IDX=0
|
||||
HALF=$((POPULATION / 2))
|
||||
|
||||
# First half: mutate random survivors
|
||||
# --- d. Elitism: copy top ELITES candidates unchanged ---
|
||||
|
||||
if [ "$ELITES" -gt 0 ]; then
|
||||
ELITE_FILES=()
|
||||
while IFS= read -r ELITE_FILE; do
|
||||
[ -f "$ELITE_FILE" ] && ELITE_FILES+=("$ELITE_FILE")
|
||||
done < <(py_top_n "$ELITES" "$SCORES_FILE")
|
||||
|
||||
for ELITE_FILE in "${ELITE_FILES[@]}"; do
|
||||
DEST="$NEXT_GEN_DIR/candidate_$(printf '%03d' $NEXT_IDX).push3"
|
||||
cp "$ELITE_FILE" "$DEST"
|
||||
printf '0\n' > "${DEST%.push3}.ops"
|
||||
NEXT_IDX=$((NEXT_IDX + 1))
|
||||
done
|
||||
|
||||
log " Elitism: carried over ${#ELITE_FILES[@]} top candidate(s) unchanged"
|
||||
fi
|
||||
|
||||
# --- e. Fill remaining slots with mutation and crossover offspring ---
|
||||
|
||||
NON_ELITE=$((POPULATION - NEXT_IDX))
|
||||
HALF=$((NON_ELITE / 2))
|
||||
|
||||
# First half of remaining: mutate random survivors
|
||||
for _i in $(seq 1 $HALF); do
|
||||
SUR="${SURVIVOR_FILES[$((RANDOM % ${#SURVIVOR_FILES[@]}))]}"
|
||||
DEST="$NEXT_GEN_DIR/candidate_$(printf '%03d' $NEXT_IDX).push3"
|
||||
|
|
@ -411,8 +462,8 @@ PYEOF
|
|||
NEXT_IDX=$((NEXT_IDX + 1))
|
||||
done
|
||||
|
||||
# Second half: crossover random survivor pairs
|
||||
REMAINING=$((POPULATION - HALF))
|
||||
# Second half of remaining: crossover random survivor pairs
|
||||
REMAINING=$((POPULATION - NEXT_IDX))
|
||||
for _i in $(seq 1 $REMAINING); do
|
||||
SUR_A="${SURVIVOR_FILES[$((RANDOM % ${#SURVIVOR_FILES[@]}))]}"
|
||||
SUR_B="${SURVIVOR_FILES[$((RANDOM % ${#SURVIVOR_FILES[@]}))]}"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue