T28 — Ld4 Stage E: MacPherson strut analyzer¶
목적¶
Ld4 의 두 번째 suspension type 추가. DW 와 schema / solver 패턴은 같지만 strut 의 kinematic 모델이 본질적으로 다름 — strut tube 가 knuckle 에 강체 결합 + chassis 측에 spherical top mount + 내부 telescoping(spring 압축).
Stage E 의 핵심 검증:
1. YAML schema 가 type-dispatch 로 깨끗하게 분리됨 (type: macpherson)
2. C++ side 의 ISuspensionKinematics 인터페이스는 변경 없이 동작 — sweep CSV format 만 일치하면 됨
3. 다른 suspension type 도 같은 lookup 경로로 Ld3 에 attach 가능
구조적 차이 vs DW¶
| 항목 | Double wishbone | MacPherson |
|---|---|---|
| Upper anchor | UCA (chassis_front-rear axis) — UCA rotates about axis | Strut tube (강체 on knuckle) + chassis 측 spherical top mount |
| Upper-knuckle 운동 | UCA axis 주위 1-D arc | tube axis 가 chassis ST 통과해야 (cylindrical joint coaxial 제약) |
| Strut 길이 | (없음, 두 arm 의 rigid bar) | 가변 (spring telescoping) |
| Knuckle 자유도 닫힘 | UCA + tie rod = 0 free DOF | tube-axis cylindrical 제약 (2 scalar) + tie rod (1 scalar) = 0 free DOF |
| Solve | Sequential: LCA θ → UCA θ → trilaterate TK | Single LM (3 cross-product residuals + 1 tie rod, 1 redundant) |
첫 시도의 결함과 수정¶
초기 구현은 strut top 을 단일 ball-joint sphere 로 모델 (|SK − ST| = L_strut, 1 scalar). 이는 strut 의 길이만 고정하고 방향은 자유롭게 두는 model — 실차의 cylindrical joint(축 고정, 길이 가변) 와 정반대. 결과적으로 knuckle 의 1 rotational DOF 가 미닫혀 numerical 발산 / regularization hack 필요.
올바른 제약: tube 가 chassis 측 shaft 와 coaxial 이어야 하므로, strut compression 길이는 자유 이지만 tube axis 의 line 은 chassis ST 를 통과 해야 함.
Tube 축 방향은 body frame 에 고정 (tube_axis_body = (ST_static − SK_static).normalized). 회전 후:
SK_world - ST_chassis ∥ R @ tube_axis_body
⟺ (SK_world - ST_chassis) × (R @ tube_axis_body) = 0 (3 scalar, 1 redundant)
구현 (tools/kinematics/mp_3d_solver.py)¶
def residuals(axis_angle_v):
R = axis_angle_to_R(v)
SK = LK + R @ off_SK
TK = LK + R @ off_TK
tube_axis_world = R @ tube_axis_body
cross = np.cross(SK - ST_chassis, tube_axis_world) # 3 scalars
r_tie = |TK - TR_inner| - L_tr # 1 scalar
return [cross[0], cross[1], cross[2], r_tie]
# 4 residuals, 3 R DOF, 1 dependency in cross — net 3 indep × 3 DOF = 0 free
Regularization 도 continuation 도 불필요 — 단발 LM 으로 unique solution 수렴.
검증¶
정적 (travel=0, steer=0)¶
| 항목 | 결과 |
|---|---|
| camber | 0.000° |
| toe | 0.000° |
| track_change | 0.000° |
| caster | 0.000° (hardpoint 의 ST/SK 둘 다 x=−0.04 라 strut 가 y-z 평면) |
17×5 (travel × steer) sweep¶
sweep_3d.csv— 85 points, all validsweep_3d.png— 4 panel plot- 곡선 부드럽고 monotone, 단발 LM 으로 수렴 (continuation 불필요)
| 메트릭 | 값 (수정 후) |
|---|---|
| Camber gain | 0.034 °/mm — 실차 sedan MacPherson typical (0.02–0.06 °/mm) ✓ |
| Bump steer (toe gain) | ~0.02 °/mm — kinematic bump steer 의 정상치 |
| Track narrowing | ~0.15 mm/mm — 일반적 |
| Strut compression | ≈ 1:1 with wheel travel (LCA 가 거의 수평일 때 예상) |
| Caster (이 sample) | 0° (ST/SK 모두 x=−0.04 → 측면 정렬) |
C++ lookup 호환¶
- Same CSV schema as DW (
wheel_travel, steer_rack_dy, camber, toe, track_change, caster, valid) vdsim.create_lookup_kinematics(csv)가 그대로 로드vdsim.attach_front_kinematics(dyn, csv)가 Ld3 에 attach OK
의미¶
C++ side 는 suspension type 에 종속되지 않음 — offline solver 만 type별로 구현하면 됨. 향후 5-link, trailing arm, twist beam 등도 같은 패턴으로 추가. 이게 사용자가 원하는 "Adams Car 같은" workflow 의 기본 구조:
YAML hardpoints (type별 schema)
↓ offline solver (type별)
sweep CSV (universal format)
↓ create_lookup_kinematics (C++)
ISuspensionKinematics (type-agnostic runtime)
↓ attach to Ld3
실시간 시뮬레이션
다음 단계¶
- 5-link rear analyzer (가장 복잡, 5개 link 의 5개 length constraints)
- Trailing arm rear (2-3 hardpoints 만 — 가장 단순한 multilink)
- Twist beam rear (axle-coupled, special handling)
- Adams-compatible YAML import (Adams Car 의 hardpoint 파일 포맷)
- 본격 multibody compliance (Ld5) — 부싱 stiffness, force-dependent deflection