veil-pgd / src /veil_pgd /optimizer /search_space.py
Klaus Clawd
Initial public release: VEIL-PGD v0.1
c793f45
Raw
History Blame Contribute Delete
2.39 kB
"""Defines and samples the render-spec search space."""
from __future__ import annotations
import random
from dataclasses import replace
from veil_pgd.config import OptimizerConfig
from veil_pgd.types import Position, RenderSpec
_POSITIONS: list[Position] = [
"bottom_middle", "top_middle", "top_right", "bottom_right",
"top_left", "bottom_left", "center",
]
_COLOR_STRATEGIES = ["bg_mean_offset", "near_bg_dark", "near_bg_light"]
def grid_specs(
decoys: list[str], positions: list[Position], cfg: OptimizerConfig
) -> list[RenderSpec]:
"""Coarse grid over structural knobs at a fixed mid alpha.
Caps positions/colors to `cfg.grid_positions`/`cfg.grid_colors` so grid size
stays bounded; the evolutionary phase explores the rest of the space.
"""
positions = positions[: cfg.grid_positions]
colors = _COLOR_STRATEGIES[: cfg.grid_colors]
specs: list[RenderSpec] = []
for text in decoys:
for pos in positions:
for color in colors:
for px in cfg.grid_font_px:
specs.append(
RenderSpec(
text=text,
font_px=px,
alpha=cfg.grid_alpha,
color_strategy=color, # type: ignore[arg-type]
position=pos,
)
)
return specs
def mutate(spec: RenderSpec, cfg: OptimizerConfig, rng: random.Random) -> RenderSpec:
"""Perturb the continuous/fine knobs for the evolutionary phase."""
lo, hi = cfg.alpha_range
flo, fhi = cfg.font_px_range
choice = rng.choice(["alpha", "font", "offset", "rep", "color"])
if choice == "alpha":
return replace(spec, alpha=min(hi, max(lo, spec.alpha + rng.uniform(-0.05, 0.05))))
if choice == "font":
return replace(spec, font_px=min(fhi, max(flo, spec.font_px + rng.choice([-2, -1, 1, 2]))))
if choice == "offset":
return replace(spec, brightness_offset=min(30, max(5, spec.brightness_offset + rng.choice([-5, 5]))))
if choice == "rep":
return replace(spec, repetition=min(4, max(1, spec.repetition + rng.choice([-1, 1]))))
return replace(spec, color_strategy=rng.choice(_COLOR_STRATEGIES)) # type: ignore[arg-type]
def default_positions() -> list[Position]:
return list(_POSITIONS)