"""Real config loading and pure structural update for the frozen development case.""" import importlib.util import math from pathlib import Path import numpy as np import pytest path = Path(__file__).resolve().parents[2] / 'research/steady_pinball/run.py' spec = importlib.util.spec_from_file_location('steady_pinball_run', path) runner = importlib.util.module_from_spec(spec) spec.loader.exec_module(runner) def test_actual_loader_and_paper_geometry(tmp_path): import json from CelerisLab.config import load_lbm_config p = tmp_path/'config.json' p.write_text(json.dumps(runner.config())) cfg = load_lbm_config(str(p)) cfg.validate() assert (cfg.nx, cfg.ny, cfg.viscosity, cfg.velocity) == (1280, 512, .004, .01) assert cfg.collision == 'MRT' and cfg.y_wall_bc == 'bounce_back' assert runner.U * runner.D / runner.NU == 50. assert runner.CENTRES == ((600., 255.5), (626., 270.5), (626., 240.5)) def test_structure_rest_equilibrium_and_energy_decay(): s = runner.Structure(zeta=.01) assert s.candidate(0.) == (0., 0.) assert s.mass == pytest.approx(10*3*math.pi*100) s.y, s.v = .2, 0. # One damped period: independent continuous solution at total elapsed time. for _ in range(runner.PERIOD): s.y, s.v = s.candidate(0.) t = runner.PERIOD a = s.zeta*s.omega w = s.omega*math.sqrt(1-s.zeta**2) expected = .2*math.exp(-a*t)*(math.cos(w*t)+a/w*math.sin(w*t)) assert s.y == pytest.approx(expected, abs=2e-12) assert .5*s.mass*s.v*s.v+.5*s.stiffness*s.y*s.y < .5*s.stiffness*.2**2 s.y, s.v = .3, 0. assert s.candidate(s.stiffness*.3) == pytest.approx((.3, 0.), abs=1e-15) def test_report_uses_completed_rows_and_net_force(): rows = np.zeros((4, 15)) rows[:, 0] = np.arange(1, 5) rows[:, 1] = 2. rows[:, 3:9] = 1. got = runner.summarize(rows) assert got[0]['end_step'] == 4 assert got[0]['y_rms_D'] == .1 assert got[0]['fy_rms'] == 6. @pytest.mark.parametrize('case', ['Z0', 'M2', 'R2']) def test_zero_damping_cases_conserve_unforced_energy_and_match_forced_solution(case): s = runner.Structure(*runner.STRUCTURES[case]) assert s.zeta == s.damping == 0. s.y, s.v = .2, 1.e-5 y0, v0 = s.y, s.v energy = .5*s.mass*v0**2 + .5*s.stiffness*y0**2 for _ in range(12345): s.y, s.v = s.candidate(0.) assert .5*s.mass*s.v**2 + .5*s.stiffness*s.y**2 == pytest.approx(energy, rel=2e-11) force, elapsed = 1.e-4, 3210 s.y, s.v = y0, v0 for _ in range(elapsed): s.y, s.v = s.candidate(force) phase = s.omega*elapsed equilibrium = force/s.stiffness expected_y = equilibrium+(y0-equilibrium)*math.cos(phase)+v0*math.sin(phase)/s.omega expected_v = -(y0-equilibrium)*s.omega*math.sin(phase)+v0*math.cos(phase) assert (s.y, s.v) == pytest.approx((expected_y, expected_v), abs=2e-11) def test_predeclared_comparisons_change_structure_only(): z, m, r = [runner.Structure(*runner.STRUCTURES[k]) for k in ['Z0', 'M2', 'R2']] assert m.mass/z.mass == pytest.approx(.2) assert m.stiffness/z.stiffness == pytest.approx(.2) assert r.mass == m.mass assert r.stiffness/m.stiffness == pytest.approx(.36) assert [runner.STEPS*s.frequency for s in [z, m, r]] == pytest.approx([60., 60., 36.])