285 lines
7.5 KiB
Python
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
|