Source code for build_tools.syllable_walk_web.services.walk_generator

"""
Walk generation service for the web application.

Generates syllable walks using the existing SyllableWalker.
"""

from __future__ import annotations

from typing import Any


[docs] def generate_walks( walker: Any, # SyllableWalker *, profile: str | None = None, steps: int = 5, count: int = 2, max_flips: int = 2, temperature: float = 0.7, frequency_weight: float = 0.0, neighbor_limit: int | None = 10, min_length: int | None = 2, max_length: int | None = 5, seed: int | None = None, ) -> list[dict[str, Any]]: """Generate walks using the SyllableWalker. If *profile* is given, uses ``walk_from_profile``. Otherwise uses explicit parameters via ``walk``. Args: walker: Initialised SyllableWalker instance. profile: Named profile (clerical/dialect/goblin/ritual) or None. steps: Number of walk steps (name length = steps + 1). count: Number of walks to generate. max_flips: Maximum feature flips per step. temperature: Exploration temperature (0.1–5.0). frequency_weight: Frequency bias (-2.0 to 2.0). neighbor_limit: Max neighbors to consider per step; ``None`` disables cap. min_length: Minimum syllable length filter; ``None`` disables bound. max_length: Maximum syllable length filter; ``None`` disables bound. seed: Optional seed for determinism. Returns: List of walk result dicts, each with keys: ``formatted`` (str), ``syllables`` (list[str]), ``steps`` (list[dict]). Raises: ValueError: If requested constraints are invalid. """ if neighbor_limit is not None and neighbor_limit < 1: raise ValueError(f"neighbor_limit must be >= 1, got {neighbor_limit}") if min_length is not None and min_length < 1: raise ValueError(f"min_length must be >= 1, got {min_length}") if max_length is not None and max_length < 1: raise ValueError(f"max_length must be >= 1, got {max_length}") if min_length is not None and max_length is not None and min_length > max_length: raise ValueError(f"min_length ({min_length}) must be <= max_length ({max_length})") results = [] for i in range(count): # Each walk in a batch gets seed+i so they produce different results # while remaining deterministic: the same (seed, count) pair always # produces the same set of walks. walk_seed = (seed + i) if seed is not None else None start = walker.get_random_syllable( seed=walk_seed, min_length=min_length, max_length=max_length, ) # walk_from_profile uses pre-tuned parameter sets (temperature, # max_flips, etc.); "custom" is a sentinel meaning "use explicit # parameters from the request". if profile and profile != "custom": walk = walker.walk_from_profile( start=start, profile=profile, steps=steps, neighbor_limit=neighbor_limit, min_length=min_length, max_length=max_length, seed=walk_seed, ) else: walk = walker.walk( start=start, steps=steps, max_flips=max_flips, temperature=temperature, frequency_weight=frequency_weight, neighbor_limit=neighbor_limit, min_length=min_length, max_length=max_length, seed=walk_seed, ) # Format the walk syllable_texts = [step["syllable"] for step in walk] # Middle dot (·) is the project-wide syllable boundary convention, # matching the TUI and combiner output formats. formatted = "\u00b7".join(syllable_texts) results.append( { "formatted": formatted, "syllables": syllable_texts, "steps": walk, } ) return results