diff --git a/B05_Profile/B05_Profile_Engine_RouteInitial_Profile.py b/B05_Profile/B05_Profile_Engine_RouteInitial_Profile.py index aa0d43615..8b58087b3 100644 --- a/B05_Profile/B05_Profile_Engine_RouteInitial_Profile.py +++ b/B05_Profile/B05_Profile_Engine_RouteInitial_Profile.py @@ -63,6 +63,8 @@ LAMBDA_STEPS = 14 EXACT_STEPS = 6 # 고른 계획선의 실제 누가토량으로 λ 를 더 좁히는 횟수 PIPE_SNAP_M = 5.0 # 관 자리가 노선에서 이 안이면 그 노선의 관으로 봄 GRADE_EPS = 1e-6 +# [적용] 종단 꼭짓점 표고 반올림(0.1 mm) 두 끝 몫 — 직선 기울기 누를 때 남기는 여유(m · 419-3) +ROUND_SLACK_M = 1e-4 def _sample( @@ -251,10 +253,11 @@ def _reference(ground: np.ndarray, spans: np.ndarray, max_grade: float) -> np.nd def _hold(s: np.ndarray, z: np.ndarray, limit: float) -> np.ndarray: """변화점 사이 직선 기울기를 `limit` 안으로 — 시종점은 그대로 두고 앞으로 · 뒤로 누름(시종점 - 사이가 상한 안이면 몇 번 안에 둘 다 맞음).""" + 사이가 상한 안이면 몇 번 안에 둘 다 맞음). 마디마다 `ROUND_SLACK_M` 남김 — 종단 정본이 표고를 + 0.1 mm 로 반올림해도 상한 안(검증_sub4 14.001 %).""" z = z.copy() start, end = z[0], z[-1] - rise = limit * np.diff(s) + rise = np.maximum(limit * np.diff(s) - ROUND_SLACK_M, 0.0) for _ in range(4): z[0] = start for i in range(1, len(z)): @@ -278,7 +281,10 @@ def held_pass_through( from B05_Profile.B05_Profile_Engine_Grade_Alignment import build_curves, evaluate stations_a, plan_a = np.asarray(stations, float), np.asarray(plan, float) - limit = float(np.abs(np.diff(plan_a) / np.diff(stations_a)).max()) + GRADE_EPS + limit = float(np.abs(np.diff(plan_a) / np.diff(stations_a)).max()) + # 계획고 1 mm 반올림(`plan_m`)으로 상한을 살짝 넘은 몫은 상한으로 + legal = policy.max_grade_pct / 100.0 + limit = legal if legal <= limit <= legal + 1e-4 else limit + GRADE_EPS target = np.interp(base_s, stations_a, plan_a) base_z = _hold(base_s, target, limit) for _ in range(12): diff --git a/resources/tester/test_419_1_recommend_engine.py b/resources/tester/test_419_1_recommend_engine.py index dbaa0e4a4..89ca5bceb 100644 --- a/resources/tester/test_419_1_recommend_engine.py +++ b/resources/tester/test_419_1_recommend_engine.py @@ -274,4 +274,5 @@ def test_419_3_applied_profile_holds_grade() -> None: assert steepest(pass_through(s, plan, policy)) > 0.15 # 옛 맞춤 = 상한 넘음(재현) held = held_pass_through(s, list(s), list(plan), policy) assert steepest(held) <= 0.14 + 1e-5 + assert steepest(np.round(held, 4)) <= 0.14 # 정본 0.1 mm 반올림 뒤도 상한 안 assert held[0] == pytest.approx(plan[0]) and held[-1] == pytest.approx(plan[-1]) diff --git a/resources/tester/test_74_route_confirm_initial.py b/resources/tester/test_74_route_confirm_initial.py index 1c79e715b..089e1dbfa 100644 --- a/resources/tester/test_74_route_confirm_initial.py +++ b/resources/tester/test_74_route_confirm_initial.py @@ -6,6 +6,8 @@ import json from pathlib import Path +import pytest + from B05_Profile.B05_Profile_Router_Replan import RouteReplanRequest from B05_Profile.B05_Profile_Router_RouteInitial import _set_base_pvi from B06_Section.B06_Section_Router_Design import CARRY_INPUT_KEYS @@ -47,7 +49,8 @@ def test_변화점_계획고를_균형_종단으로(tmp_path: Path) -> None: p["elevation_m"] for p in json.loads(path.read_text("utf-8"))["profile_alignment"]["base_pvi"] ] - assert got == [10.0, 12.0, 13.0, 14.0] # 구조물 측점(30 m)은 직선 보간 + # 구조물 측점(30 m)은 직선 보간 · 상한 누를 때 반올림 여유 0.1 mm 몫(419-3) + assert got == pytest.approx([10.0, 12.0, 13.0, 14.0], abs=5e-4) def test_확인_요청_기본은_끔() -> None: