"""Shared helpers for the example solvers: load the challenge, write wavs, write submission JSON.""" from __future__ import annotations import json import platform from pathlib import Path import numpy as np from scipy.io import wavfile def load_challenge(path: Path) -> dict: with open(path) as f: return json.load(f) def write_wav(path: Path, samples_si: np.ndarray, challenge: dict) -> None: """Write pointwise samples of the SI signal, divided by signal_scale, as float32.""" out = challenge["output"] scaled = np.asarray(samples_si, dtype=np.float64) / float(out["signal_scale"]) path.parent.mkdir(parents=True, exist_ok=True) wavfile.write(path, int(out["sample_rate_hz"]), scaled.astype(np.float32)) def hardware_string() -> str: cpu = platform.processor() or platform.machine() try: with open("/proc/cpuinfo") as f: for line in f: if line.startswith("model name"): cpu = line.split(":", 1)[1].strip() break except OSError: pass return f"{cpu}, single thread, Python {platform.python_version()} + numpy {np.__version__}" def pluck_load_q(p: dict) -> float: """Total load Q (a slope, force over tension) giving pluck height h at x_p.""" return p["h"] / (p["x_p"] * (p["L"] - p["x_p"]) / p["L"] - p["w"] * (0.25 - 1.0 / np.pi**2)) def initial_shape(x: np.ndarray, p: dict) -> np.ndarray: """u_0(x): static equilibrium under the raised-cosine finger load, from the challenge spec.""" L, xp, w = p["L"], p["x_p"], p["w"] Q = pluck_load_q(p) tri = np.where(x <= xp, x * (L - xp) / L, xp * (L - x) / L) y = x - xp cap = np.zeros_like(x) m = np.abs(y) <= w ym = y[m] cap[m] = ym * (ym > 0) - (1.0 / (2.0 * w)) * ((ym + w) ** 2 / 2.0 - (w**2 / np.pi**2) * (1.0 + np.cos(np.pi * ym / w))) return Q * (tri + cap)