1"""Modal series solution of string-pluck-v1 (see the appendix of the challenge README).
3Usage: python modal_string.py <challenge.json> <out-dir> [N ...]
5Writes modal-n<N>.wav for each truncation order N and prints a JSON list of per-solution
6metadata (timing, parameters) to stdout.
7"""
8from __future__ import annotations
10import json
11import sys
12import time
13from pathlib import Path
15import numpy as np
17sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
18from common import hardware_string, load_challenge, write_wav # noqa: E402
21def modal_coefficients(p: dict, n_modes: int):
22 n = np.arange(1, n_modes + 1)
23 k = n * np.pi / p["L"]
24 q = np.pi / p["w"]
25 A = (2.0 * p["h"] / p["L"]) * np.sin(k * p["x_p"]) * np.sin(k * p["w"]) * q**2 / (k * (q**2 - k**2))
26 alpha = p["sigma0"] + 0.5 * p["kappa"] * k**2
27 omega2 = (p["c"] * k) ** 2 - alpha**2
28 if np.any(omega2 <= 0):
29 raise ValueError("an overdamped mode is within the requested truncation; not handled")
30 omega = np.sqrt(omega2)
31 return k, A, alpha, omega
34def pickup_velocity(p: dict, n_modes: int, t: np.ndarray) -> np.ndarray:
35 k, A, alpha, omega = modal_coefficients(p, n_modes)
36 coeff = -A * (p["c"] * k) ** 2 / omega * np.sin(k * p["x_m"]) # per-mode amplitude of a_n'(t) sin(k_n x_m)
37 s = np.zeros_like(t)
38 block = 64 # modes per block, to bound memory for large N
39 for i in range(0, n_modes, block):
40 sl = slice(i, i + block)
41 e = np.exp(-np.outer(t, alpha[sl]))
42 s += (e * np.sin(np.outer(t, omega[sl]))) @ coeff[sl]
43 return s
46def main():
47 challenge = load_challenge(Path(sys.argv[1]))
48 out_dir = Path(sys.argv[2])
49 orders = [int(a) for a in sys.argv[3:]] or [4, 8, 16, 32, 64, 256, 1024]
50 p = challenge["parameters"]
51 out = challenge["output"]
52 fs = out["sample_rate_hz"]
53 t = np.arange(out["num_samples"]) / fs
54 meta = []
55 for n in orders:
56 pickup_velocity(p, min(n, 8), t[:16]) # warm up
57 t0 = time.perf_counter()
58 s = pickup_velocity(p, n, t)
59 dt = time.perf_counter() - t0
60 write_wav(out_dir / f"modal-n{n}.wav", s, challenge)
61 meta.append({
62 "solution_id": f"modal-n{n}",
63 "output": f"{out_dir.name}/modal-n{n}.wav",
64 "method": "modal series, truncated",
65 "parameters": {"n_modes": n},
66 "timing_seconds": round(dt, 4),
67 "hardware": hardware_string(),
68 "peak_si": float(np.max(np.abs(s))),
69 })
70 print(f"N={n:5d} peak={np.max(np.abs(s)):.4f} m/s t={dt:.3f}s", file=sys.stderr)
71 print(json.dumps(meta, indent=1))
74if __name__ == "__main__":
75 main()