Files
boxsim-fileformat-rev/tests/test_model.py
T

92 lines
3.3 KiB
Python
Raw Normal View History

from __future__ import annotations
import pytest
from boxsim import model
from boxsim.model import COLS, LINK_BASE, ROWS, TOTAL_SLOTS, Orientation, PartType
def test_slot_partition_is_exact():
"""The three slot ranges must tile 1..375 with no gap or overlap."""
seen = {}
for slot in range(1, TOTAL_SLOTS + 1):
orientation, row, col = model.slot_geometry(slot)
if orientation == Orientation.LINK:
continue
key = (orientation, row, col)
assert key not in seen, f"slot {slot} collides with {seen[key]}"
seen[key] = slot
horizontal = sum(1 for k in seen if k[0] == Orientation.HORIZONTAL)
vertical = sum(1 for k in seen if k[0] == Orientation.VERTICAL)
assert horizontal == (COLS - 1) * ROWS == 180
assert vertical == COLS * (ROWS - 1) == 187
assert LINK_BASE == 368 and TOTAL_SLOTS == 375
def test_slot_geometry_bounds():
with pytest.raises(model.ModelError):
model.slot_geometry(0)
with pytest.raises(model.ModelError):
model.slot_geometry(TOTAL_SLOTS + 1)
def test_slot_endpoints_are_adjacent_nodes():
orientation, row, col = model.slot_geometry(1)
assert (orientation, row, col) == (Orientation.HORIZONTAL, 0, 0)
orientation, row, col = model.slot_geometry(181)
assert (orientation, row, col) == (Orientation.VERTICAL, 0, 0)
# The last vertical slot must sit on the bottom-but-one row.
orientation, row, col = model.slot_geometry(367)
assert (orientation, row, col) == (Orientation.VERTICAL, ROWS - 2, COLS - 1)
def test_default_project_shape(default_project):
p = default_project
assert p.version == 2000
assert p.amp_count == 1
assert p.chassis_count == 3
assert [c.name for c in p.chassis] == [
"GF 200 - 8 Ohm",
"B 100 - 6 Ohm",
"G 20 SC - 8 Ohm",
]
def test_default_netlist_is_closed(default_project):
"""The published Default project must have no floating terminal."""
xo = default_project.crossovers[0]
netlist = xo.build_netlist()
assert len(xo.components) == 22
assert netlist.net_count == 14
assert netlist.dangling() == []
def test_drivers_and_amp_present(default_project):
xo = default_project.crossovers[0]
drivers = [p for p in xo.components if p.typ == PartType.DRIVER]
amps = [p for p in xo.components if p.typ == PartType.AMP_INPUT]
assert len(amps) == 1
assert sorted(d.chassis_index for d in drivers) == [0, 1, 2]
def test_bounding_box(default_project):
assert default_project.crossovers[0].bounding_box() == (0, 0, 8, 9)
def test_crossover_corner_frequencies(default_project):
"""Sanity-check the reconstruction against loudspeaker physics.
A wrong slot-to-node mapping would still produce a netlist, but not one
whose series/shunt pairs land on sensible crossover points.
"""
import math
xo = default_project.crossovers[0]
by_slot = {p.slot: p for p in xo.components}
# Woofer low-pass: 4.7 mH series with the 15 uF shunt.
fc = 1 / (2 * math.pi * math.sqrt(by_slot[5].value * by_slot[198].value))
assert 550 < fc < 650
# Tweeter high-pass: two series caps into the 0.22 mH shunt.
cser = 1 / (1 / by_slot[65].value + 1 / by_slot[67].value)
fc = 1 / (2 * math.pi * math.sqrt(by_slot[252].value * cser))
assert 5000 < fc < 6500