Files
impakt/tests/conftest.py
2026-04-10 14:37:34 -04:00

285 lines
7.5 KiB
Python

"""Shared test fixtures.
Generates synthetic MME test data that mimics a realistic frontal crash test.
"""
from __future__ import annotations
from pathlib import Path
import numpy as np
import pytest
from impakt.channel.code import ChannelCode
from impakt.channel.model import (
Channel,
ChannelGroup,
DummyInfo,
ImpactConfig,
TestData,
TestMetadata,
VehicleInfo,
)
@pytest.fixture
def sample_rate() -> float:
return 20000.0
@pytest.fixture
def duration() -> float:
return 0.2 # 200 ms
@pytest.fixture
def time_array(sample_rate: float, duration: float) -> np.ndarray:
"""Time array: -0.01s to 0.19s (10 ms pre-trigger + 190 ms event)."""
n = int(duration * sample_rate)
pre = int(0.01 * sample_rate)
return np.arange(n, dtype=np.float64) / sample_rate - pre / sample_rate
@pytest.fixture
def head_accel_x(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic head X acceleration: half-sine pulse peaking at ~40g."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0) & (t <= 0.1)
data[mask] = 40.0 * np.sin(np.pi * t[mask] / 0.1)
# Add some noise
data += np.random.default_rng(42).normal(0, 0.5, len(data))
return Channel(
name="11HEAD0000ACXA",
code=ChannelCode.parse("11HEAD0000ACXA"),
data=data,
time=t,
unit="g",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def head_accel_y(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic head Y acceleration: smaller lateral component."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0) & (t <= 0.1)
data[mask] = 8.0 * np.sin(np.pi * t[mask] / 0.1) * np.cos(3 * np.pi * t[mask] / 0.1)
data += np.random.default_rng(43).normal(0, 0.3, len(data))
return Channel(
name="11HEAD0000ACYA",
code=ChannelCode.parse("11HEAD0000ACYA"),
data=data,
time=t,
unit="g",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def head_accel_z(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic head Z acceleration."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0) & (t <= 0.1)
data[mask] = 15.0 * np.sin(np.pi * t[mask] / 0.1)
data += np.random.default_rng(44).normal(0, 0.3, len(data))
return Channel(
name="11HEAD0000ACZA",
code=ChannelCode.parse("11HEAD0000ACZA"),
data=data,
time=t,
unit="g",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def head_group(head_accel_x: Channel, head_accel_y: Channel, head_accel_z: Channel) -> ChannelGroup:
return ChannelGroup(
key="11HEAD0000AC_A",
x=head_accel_x,
y=head_accel_y,
z=head_accel_z,
)
@pytest.fixture
def chest_deflection_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic chest deflection: ramps to 35mm peak."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0.01) & (t <= 0.08)
t_event = t[mask] - 0.01
data[mask] = 35.0 * np.sin(np.pi * t_event / 0.07)
return Channel(
name="11CHST0000DCXA",
code=ChannelCode.parse("11CHST0000DCXA"),
data=data,
time=t,
unit="mm",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def neck_fz_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic neck Fz: tension pulse peaking at ~3000N."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0.005) & (t <= 0.06)
t_event = t[mask] - 0.005
data[mask] = 3000.0 * np.sin(np.pi * t_event / 0.055)
return Channel(
name="11NECKUP00FOZA",
code=ChannelCode.parse("11NECKUP00FOZA"),
data=data,
time=t,
unit="N",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def neck_my_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic neck My: flexion moment peaking at ~80 Nm."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0.01) & (t <= 0.07)
t_event = t[mask] - 0.01
data[mask] = 80.0 * np.sin(np.pi * t_event / 0.06)
return Channel(
name="11NECKUP00MOYA",
code=ChannelCode.parse("11NECKUP00MOYA"),
data=data,
time=t,
unit="N·m",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def femur_left_channel(time_array: np.ndarray, sample_rate: float) -> Channel:
"""Synthetic left femur load: compressive pulse peaking at ~4500N."""
t = time_array
data = np.zeros_like(t)
mask = (t >= 0.02) & (t <= 0.09)
t_event = t[mask] - 0.02
data[mask] = -4500.0 * np.sin(np.pi * t_event / 0.07)
return Channel(
name="11FEMRLE00FOZA",
code=ChannelCode.parse("11FEMRLE00FOZA"),
data=data,
time=t,
unit="N",
sample_rate=sample_rate,
source_test_id="TEST_001",
)
@pytest.fixture
def sample_metadata() -> TestMetadata:
return TestMetadata(
test_number="TEST_001",
vehicle=VehicleInfo(make="Toyota", model="Camry", year=2024, mass_kg=1523.0),
dummy=DummyInfo(dummy_type="H3-50M", position="Driver", mass_kg=78.0),
impact=ImpactConfig(
test_type="Full Frontal",
speed_kmh=56.3,
barrier_type="Rigid",
overlap_percent=100.0,
),
)
@pytest.fixture
def sample_test_data(
sample_metadata: TestMetadata,
head_accel_x: Channel,
head_accel_y: Channel,
head_accel_z: Channel,
chest_deflection_channel: Channel,
neck_fz_channel: Channel,
neck_my_channel: Channel,
femur_left_channel: Channel,
) -> TestData:
"""Full synthetic test data with multiple channels."""
channels = {
head_accel_x.name: head_accel_x,
head_accel_y.name: head_accel_y,
head_accel_z.name: head_accel_z,
chest_deflection_channel.name: chest_deflection_channel,
neck_fz_channel.name: neck_fz_channel,
neck_my_channel.name: neck_my_channel,
femur_left_channel.name: femur_left_channel,
}
return TestData(
test_id="TEST_001",
metadata=sample_metadata,
channels=channels,
)
@pytest.fixture
def sample_mme_dir(tmp_path: Path, head_accel_x: Channel) -> Path:
"""Create a minimal MME directory structure for reader tests."""
test_dir = tmp_path / "test_001"
test_dir.mkdir()
# MME.ini
ini = test_dir / "MME.ini"
ini.write_text(
"[Test]\n"
"test_number = TEST_001\n"
"test_type = Full Frontal\n"
"test_speed = 56.3\n"
"\n"
"[Vehicle]\n"
"vehicle_make = Toyota\n"
"vehicle_model = Camry\n"
"vehicle_year = 2024\n"
"\n"
"[Dummy]\n"
"dummy_type = H3-50M\n"
"dummy_position = Driver\n",
encoding="utf-8",
)
# Channel directory
ch_dir = test_dir / "channels"
ch_dir.mkdir()
# .chn file
chn = ch_dir / "11HEAD0000ACXA.chn"
chn.write_text(
"[Channel]\n"
"channel_code = 11HEAD0000ACXA\n"
"unit = g\n"
"sample_rate = 20000\n"
"num_samples = 4000\n"
"pre_trigger = 200\n"
"cfc = 1000\n"
"data_format = ascii\n",
encoding="utf-8",
)
# .dat file (ASCII, one value per line)
dat = ch_dir / "11HEAD0000ACXA.dat"
np.savetxt(str(dat), head_accel_x.data, fmt="%.6f")
return test_dir