1"""Refresh the run-dependent parts of cymsimweb's Python reference fixture.
3Usage: python tools/make_web_fixture.py ../cymsimweb/tests/fixtures/python_reference.json
5The fixture's configuration (a coarse, dent-free profile) is kept; the ``config``, ``timestep``,
6``run`` and ``run_linear`` entries are rewritten from a fresh run of this package, including the
7recorded normal acceleration of a few nodes and a sample of the decimation taps, which the web
8tests compare against their own implementation. The mesh, shell, DKT and patch entries do not
9depend on the time loop and are left as they are.
10"""
11from __future__ import annotations
13import dataclasses
14import json
15import sys
17import numpy as np
19from cymsim.config import SimulationConfig
20from cymsim.simulate import Simulator
22ACC_NODES = [0, 50, 120]
25def run_entry(cfg: SimulationConfig) -> dict:
26 sim = Simulator(cfg)
27 res = sim.run()
28 a = res.acceleration.as_float()
29 return {
30 "hits": [dataclasses.asdict(h) for h in res.hits],
31 "energy_log": [[e.time, e.kinetic, e.membrane, e.bending, e.exciters, e.max_w_over_h] for e in res.energy_log],
32 "pressure_rayleigh": res.pressure_rayleigh.tolist(),
33 "pressure_unbaffled": res.pressure_unbaffled.tolist(),
34 "fs": res.fs,
35 "acceleration_nodes": ACC_NODES,
36 "acceleration": [a[:, i].astype(float).tolist() for i in ACC_NODES],
37 "n_steps": sim.n_steps,
38 }, sim
41def main(path: str) -> None:
42 with open(path) as f:
43 fx = json.load(f)
44 cfg = SimulationConfig.from_dict(fx["config"])
45 cfg.output.record_acceleration = True
46 cfg.output.displacement_rate = 0.0
47 sim0 = Simulator(cfg)
48 # whole blocks must end on an audio sample so that the web loop can stop at the same time
49 m = sim0.substeps
50 if cfg.numerics.steps_per_block % m:
51 cfg.numerics.steps_per_block = m * max(1, cfg.numerics.steps_per_block // m)
52 fx["config"] = cfg.to_dict()
53 run, sim = run_entry(cfg)
54 lin_cfg = SimulationConfig.from_dict(cfg.to_dict())
55 lin_cfg.numerics.nonlinear = False
56 run_linear, _ = run_entry(lin_cfg)
57 fx["timestep"] = {"dt": sim.dt, "substeps": sim.substeps}
58 fx["run"], fx["run_linear"] = run, run_linear
59 taps = sim.taps
60 fx["decimation"] = {"span": taps.shape[0], "substeps": taps.shape[1],
61 "taps_sample": [[int(w), int(k), float(taps[w, k])] for w, k in
62 [(0, 0), (15, 0), (15, m - 1), (16, 0), (31, m - 1), (7, m // 2)]],
63 "normal_first3": sim.shell.normal_node[:3].tolist()}
64 with open(path, "w") as f:
65 json.dump(fx, f)
66 print(f"wrote {path}: {len(run['pressure_unbaffled'])} samples, {sim.n_steps} steps, m = {m}")
69if __name__ == "__main__":
70 main(sys.argv[1])