129 lines
5.3 KiB
Python
129 lines
5.3 KiB
Python
"""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.])
|
|
|
|
|
|
@pytest.mark.parametrize('diameter', [20, 40, 60])
|
|
def test_diffusive_scaling_preserves_physical_geometry_and_duration(tmp_path, diameter):
|
|
import json
|
|
from CelerisLab.config import load_lbm_config
|
|
scale = diameter/20
|
|
raw = runner.config(diameter)
|
|
p = tmp_path/'scaled.json'
|
|
p.write_text(json.dumps(raw))
|
|
cfg = load_lbm_config(str(p))
|
|
cfg.validate()
|
|
assert cfg.velocity*diameter/cfg.viscosity == pytest.approx(50.)
|
|
assert (cfg.nx-1)/diameter == 1279/20
|
|
assert (cfg.ny-2)/diameter == 510/20
|
|
for original, mapped in zip(runner.CENTRES+runner.PROBES,
|
|
runner.scaled_points(runner.CENTRES+runner.PROBES, scale)):
|
|
assert mapped[0]/diameter == pytest.approx(original[0]/20)
|
|
assert (mapped[1]-.5)/diameter == pytest.approx((original[1]-.5)/20)
|
|
old = runner.Structure(*runner.STRUCTURES['M2'])
|
|
new = runner.Structure(2., old.frequency/scale**2, diameter=diameter)
|
|
assert new.mass/old.mass == pytest.approx(scale**2)
|
|
assert new.stiffness/old.stiffness == pytest.approx(1/scale**2)
|
|
assert new.damping == 0.
|
|
assert cfg.velocity/(new.frequency*diameter) == pytest.approx(5.)
|
|
assert runner.STEPS*scale**2*cfg.velocity/diameter == pytest.approx(300.)
|
|
assert runner.WARMUP*scale**2*cfg.velocity/diameter == pytest.approx(256.)
|
|
# A physical held force maps as F_fine=F_coarse/scale in two dimensions.
|
|
old.y, old.v = .3, .0002
|
|
new.y, new.v = scale*old.y, old.v/scale
|
|
expected = old.candidate(.0001)
|
|
for _ in range(round(scale**2)):
|
|
new.y, new.v = new.candidate(.0001/scale)
|
|
assert (new.y/scale, new.v*scale) == pytest.approx(expected, abs=2e-12)
|
|
|
|
|
|
def test_scaled_summary_uses_requested_units_and_completed_rows():
|
|
rows = np.zeros((5, 15))
|
|
rows[:, 0] = np.arange(1, 6)
|
|
rows[:, 1] = 4.
|
|
got = runner.summarize(rows, diameter=40, block_steps=3)
|
|
assert [(x['start_step'], x['end_step']) for x in got] == [(1, 3), (4, 5)]
|
|
assert all(x['y_rms_D'] == .1 for x in got)
|
|
assert runner.summarize(rows[:0], diameter=40) == []
|