| |
| """Generate a planet name with the single-step ONNX model.""" |
|
|
| from __future__ import annotations |
|
|
| import argparse |
| import json |
| from pathlib import Path |
|
|
| import numpy as np |
| import onnxruntime as ort |
|
|
|
|
| ROOT = Path(__file__).resolve().parent |
| DEFAULT_MODEL = ROOT / "planet_namer_fp16.onnx" |
| DEFAULT_VOCAB = ROOT / "vocab.json" |
|
|
|
|
| def generate_name( |
| stats: list[float], |
| *, |
| model_path: Path = DEFAULT_MODEL, |
| vocab_path: Path = DEFAULT_VOCAB, |
| temperature: float = 0.8, |
| seed: int | None = None, |
| ) -> str: |
| """Generate one name from seven normalized stats in vocab stat order.""" |
| with vocab_path.open(encoding="utf-8") as handle: |
| vocab = json.load(handle) |
|
|
| stat_order = vocab["stat_order"] |
| if len(stats) != len(stat_order): |
| raise ValueError( |
| f"expected {len(stat_order)} stats ({', '.join(stat_order)}), " |
| f"received {len(stats)}" |
| ) |
| if any(not 0.0 <= value <= 1.0 for value in stats): |
| raise ValueError("every stat must be between 0 and 1 inclusive") |
| if temperature < 0: |
| raise ValueError("temperature must be non-negative") |
|
|
| idx_to_char = {int(key): value for key, value in vocab["idx_to_char"].items()} |
| hidden_size = int(vocab.get("hidden_size", 192)) |
| max_len = int(vocab.get("max_len", 20)) |
| pad_idx = int(vocab.get("pad_idx", 0)) |
| sos_idx = int(vocab.get("sos_idx", 1)) |
| eos_idx = int(vocab.get("eos_idx", 2)) |
|
|
| session = ort.InferenceSession( |
| str(model_path), providers=["CPUExecutionProvider"] |
| ) |
| stats_array = np.asarray([stats], dtype=np.float32) |
| zero_stats = np.zeros_like(stats_array) |
| char_in = np.asarray([[sos_idx]], dtype=np.int64) |
| h_in = np.zeros((1, 1, hidden_size), dtype=np.float32) |
| c_in = np.zeros((1, 1, hidden_size), dtype=np.float32) |
| rng = np.random.default_rng(seed) |
| characters: list[str] = [] |
|
|
| for step in range(max_len): |
| logits, h_in, c_in = session.run( |
| None, |
| { |
| "stats_init": stats_array if step == 0 else zero_stats, |
| "stats": stats_array, |
| "char_in": char_in, |
| "h_in": h_in, |
| "c_in": c_in, |
| }, |
| ) |
|
|
| if temperature < 1e-6: |
| char_idx = int(np.argmax(logits[0])) |
| else: |
| scaled = logits[0].astype(np.float64) / temperature |
| scaled -= scaled.max() |
| probabilities = np.exp(scaled) |
| probabilities /= probabilities.sum() |
| char_idx = int(rng.choice(len(probabilities), p=probabilities)) |
|
|
| if char_idx in (pad_idx, eos_idx): |
| break |
| if char_idx != sos_idx: |
| character = idx_to_char.get(char_idx) |
| if character and not character.startswith("<"): |
| characters.append(character) |
| char_in = np.asarray([[char_idx]], dtype=np.int64) |
|
|
| return "".join(characters) |
|
|
|
|
| def parse_args() -> argparse.Namespace: |
| parser = argparse.ArgumentParser(description=__doc__) |
| parser.add_argument( |
| "--stats", |
| nargs=7, |
| required=True, |
| type=float, |
| metavar=("ATM", "GRAV", "RES", "LIFE", "TEMP", "WATER", "RAD"), |
| help="seven normalized values in the documented order", |
| ) |
| parser.add_argument("--temperature", type=float, default=0.8) |
| parser.add_argument("--seed", type=int) |
| parser.add_argument("--model", type=Path, default=DEFAULT_MODEL) |
| parser.add_argument("--vocab", type=Path, default=DEFAULT_VOCAB) |
| return parser.parse_args() |
|
|
|
|
| def main() -> None: |
| args = parse_args() |
| print( |
| generate_name( |
| args.stats, |
| model_path=args.model, |
| vocab_path=args.vocab, |
| temperature=args.temperature, |
| seed=args.seed, |
| ) |
| ) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|