fix(B05 노선 추천 엔진): 원지반 기울기 참고만 · 삼각 없음 · 측점 필수 · 합치기 멈춤 노선 토공 +10 % · 토공 고르기 위 3 개만 균형 종단 (419-1)
- 못 지킨 곳 수 = R · 계류 · 측점(원지반 기울기는 목록 · grade 수에 참고) · 후보 거름 · 큰 R 판 · 합치기 · 측점 맞춤의 기울기 막기 걷음 · 탐색 링크 상한 남김 - 꺾임만 → 곡선(155° 이상도) 또는 교점 지움 · 측점 맞춤 곡선 끔 · 늦춤 단계 걷음 - 맞추기 전 선(unsnapped) 걷음 · 시종점 기울기 ends_grade_pct · ends_over_limit - 검증_sub4 첫 갈래 122.6 s → 23.8 s · 곡선 33 → 16~18 · 세 갈래 다른 선 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
@@ -3,8 +3,8 @@
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_351_2_curve_stations.py -q
|
||||
|
||||
모든 곡선 MC % 20 ≈ 0 · 이웃 MC 20 m 이상 · 곡선 수 ≤ 내부 일반측점 수 · 맞추기 전 선에서
|
||||
`ROUTE_STATION_SNAP_MAX_GAP_M`(6 m) 안 · 화면이 펴서 다시 짜도(저장 반올림) 그대로 · 추천 결과의
|
||||
기울기 넘은 곳이 맞추기 전보다 늘지 않음 · 검증_sub1 예상노선 8벌 수치.
|
||||
`ROUTE_STATION_SNAP_MAX_GAP_M`(6 m) 안 · 화면이 펴서 다시 짜도(저장 반올림) 그대로 · 추천 결과는
|
||||
측점에 맞춘 선만(맞추기 전 선 안 냄 · 419) · 검증_sub1 예상노선 8벌 수치.
|
||||
"""
|
||||
|
||||
import math
|
||||
@@ -142,11 +142,8 @@ def test_recommend_result_on_stations(hill_runs: dict, mode: str) -> None:
|
||||
if mode != "follow": # 갈래는 기울기 상한 안(언덕은 비켜 갈 수 있음)
|
||||
assert result["metrics"]["over_limit"]["total"] == 0
|
||||
assert result["metrics"]["max_grade_pct"] <= 14.0 + 1e-6
|
||||
# 측점 맞춤 끔 = 맞추기 전 선(같으면 None) — 비교값 따로
|
||||
raw = result["unsnapped"]
|
||||
if raw is not None:
|
||||
assert raw["planned"] != result["planned"] and raw["metrics"]["length_m"] > 0
|
||||
assert set(raw) == {"planned", "nodes", "curves", "off_station_curves", "metrics"}
|
||||
# 측점 맞춤 필수 — 맞추기 전 선은 안 냄(419)
|
||||
assert "unsnapped" not in result
|
||||
|
||||
|
||||
def _project_terrain():
|
||||
@@ -189,17 +186,15 @@ def test_project_modes_before_after() -> None:
|
||||
min_radius_m=12.0,
|
||||
with_profile=False,
|
||||
)
|
||||
raw = result["unsnapped"] or result
|
||||
after, before = result["metrics"], raw["metrics"]
|
||||
after = result["metrics"]
|
||||
curves = len(result["curves"])
|
||||
on_total += curves - result["off_station_curves"]
|
||||
curves_total += curves
|
||||
print(
|
||||
f"{mode}: 넘은 곳 {before['over_limit']['total']} → {after['over_limit']['total']}"
|
||||
f" · 최대 기울기 {before['max_grade_pct']:.1f} → {after['max_grade_pct']:.1f} %"
|
||||
f" · 곡선 {len(raw['curves'])} → {curves}(측점 밖 {result['off_station_curves']})"
|
||||
f"{mode}: 넘은 곳 {after['over_limit']['total']}"
|
||||
f" · 최대 기울기 {after['max_grade_pct']:.1f} %"
|
||||
f" · 곡선 {curves}(측점 밖 {result['off_station_curves']})"
|
||||
)
|
||||
assert raw["off_station_curves"] >= result["off_station_curves"]
|
||||
print(f"측점 위 곡선 {on_total}/{curves_total} = {100 * on_total / curves_total:.0f}%")
|
||||
assert on_total / curves_total >= 0.75
|
||||
|
||||
|
||||
@@ -100,7 +100,7 @@ def test_follow_nodes_equal_initial(tmp_path: Path) -> None:
|
||||
assert all(abs(a[0] - b[0]) <= 1e-3 and abs(a[1] - b[1]) <= 1e-3 for a, b in zip(nodes, saved))
|
||||
misses = [v for v in result["metrics"]["violations"] if v["kind"] == "station"]
|
||||
assert len(misses) == result["metrics"]["over_limit"]["station"] == result["off_station_curves"]
|
||||
assert result["unsnapped"] is not None # 「측점 맞춤」 끔 = 맞추기 전 선
|
||||
assert "unsnapped" not in result # 측점 맞춤 필수 — 맞추기 전 선 안 냄(419)
|
||||
|
||||
|
||||
def test_old_copy_flag_and_line_unchanged(tmp_path: Path) -> None:
|
||||
|
||||
@@ -12,11 +12,7 @@ import numpy as np
|
||||
from shapely.geometry import LineString, Point
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial import generate_initial_route
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Merge import (
|
||||
GRADE_NOISE,
|
||||
Guard,
|
||||
merge_same_side,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Merge import Guard, merge_same_side
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Radius import _flat, widen
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Search import SearchGrid, Terrain
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import _Reference
|
||||
@@ -73,16 +69,18 @@ def test_s_curve_keeps_straight() -> None:
|
||||
assert _flat(merged)[0] == _flat(_outline(S_CURVE))[0]
|
||||
|
||||
|
||||
def test_valley_grade_blocks() -> None:
|
||||
"""옛 선 = 골짜기 바닥(평평) · 합친 곡선은 옆 비탈을 올라 기울기 상한을 넘음."""
|
||||
def test_valley_earth_blocks() -> None:
|
||||
"""옛 선 = 골짜기 바닥 · 합친 곡선은 옆 비탈을 올라 토공 가늠이 늘어 막힘(419 — 원지반 기울기는
|
||||
참고만이라 기울기로는 안 막음 · 넘는 만큼은 잼에 남음)."""
|
||||
distance = _distance(_outline(SAME))
|
||||
guard = _guard(SAME, z=lambda xx, yy: 3.0 * distance(xx, yy))
|
||||
assert len(_merge(SAME, guard).curves) == 2
|
||||
# 막은 까닭 = 기울기 — 합친 꼴 조각이 옛 자리보다 상한을 더 넘음
|
||||
cross = _line_intersection(*SAME)
|
||||
piece = guard.piece(SAME[0], cross, SAME[-1], 40.0)
|
||||
window = _Reference(_outline(SAME).vertices).window(piece)
|
||||
assert guard._look(piece)[0] > guard._look(window)[0] + GRADE_NOISE
|
||||
passed, (out, along, earth) = guard.judge(piece, window)
|
||||
assert not passed and not out and along == 0.0 and earth > 0.0
|
||||
assert guard._look(piece)[0] > guard._look(window)[0] # 기울기는 참고로 잼
|
||||
|
||||
|
||||
def test_outside_corridor_blocks() -> None:
|
||||
@@ -111,9 +109,12 @@ def test_stream_buffer_blocks() -> None:
|
||||
|
||||
|
||||
def test_route_yardstick_blocks() -> None:
|
||||
"""노선 전체 잣대(넘은 곳 · 최대 기울기 · 절성토)가 합치기 전보다 나빠지면 안 합침."""
|
||||
worse = {**FLAT, "max_grade_pct": 5.0}
|
||||
assert len(_merge(SAME, _guard(SAME), measure=lambda line: worse).curves) == 2
|
||||
"""노선 전체 잣대(못 지킨 곳 · 절성토 +10 %)가 합치기 전보다 나빠지면 안 합침 · 최대 기울기는
|
||||
참고만(419)."""
|
||||
steeper = {**FLAT, "max_grade_pct": 5.0}
|
||||
assert len(_merge(SAME, _guard(SAME), measure=lambda line: steeper).curves) == 1
|
||||
over = {**FLAT, "over_limit": {"total": 1, "grade": 0}}
|
||||
assert len(_merge(SAME, _guard(SAME), measure=lambda line: over).curves) == 2
|
||||
|
||||
|
||||
def test_widen_guard_larger_than_gap_limit() -> None:
|
||||
|
||||
@@ -0,0 +1,203 @@
|
||||
"""419 노선 추천 엔진 — 원지반 기울기 참고만 · 삼각 없음 · 측점 필수 · 합치기 멈춤 = 노선 토공.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_419_1_recommend_engine.py -q
|
||||
|
||||
· 못 지킨 곳 수 = R · 계류 · 측점(원지반 기울기는 목록 · `grade` 수에 참고로만).
|
||||
· 꺾임만(R 없는 교점) → 곡선(155° 이상도) 또는 교점 지움 · 측점 맞춤(직접 잼)도 꺾임만을 안 냄.
|
||||
· 합치기 멈춤 = 노선 토공 +10 % — 바뀐 측점 횡단 차이(`cross_earthwork_delta`)를 주면 그 값으로.
|
||||
· 종단 반영 켬 토공 고르기 = 줄 세운 위 3 개만 균형 종단 · 시종점 기울기 알림 · 맞추기 전 선 안 냄.
|
||||
"""
|
||||
|
||||
import math
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial import ends_grade, generate_initial_route
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Merge import (
|
||||
TOTAL_EARTH_TOL,
|
||||
Guard,
|
||||
merge_same_side,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import SHORTLIST, choose, hard_limits
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Radius import _flat, curve_every_kink
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Search import SearchGrid, Terrain
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Station import (
|
||||
off_station_count,
|
||||
snap_curves_to_stations,
|
||||
)
|
||||
from common_util.common_util_route_polyline import build_planned_polyline
|
||||
|
||||
RADIUS = 12.0
|
||||
SAME = [(20.0, 20.0), (150.0, 20.0), (230.0, 70.0), (260.0, 180.0)]
|
||||
|
||||
|
||||
def _kinks(outline) -> int:
|
||||
return sum(1 for flag in _flat(outline)[1][1:-1] if not flag)
|
||||
|
||||
|
||||
def _flat_terrain(width=802.0, height=402.0) -> Terrain:
|
||||
x = np.arange(0.0, width, 2.0)
|
||||
y = np.arange(0.0, height, 2.0)
|
||||
xx, _ = np.meshgrid(x, y)
|
||||
return Terrain(x, y, np.zeros(xx.shape), np.ones(xx.shape, dtype=bool))
|
||||
|
||||
|
||||
def test_hard_limits_leave_grade_as_reference() -> None:
|
||||
metrics = {
|
||||
"over_limit": {"grade": 3, "radius": 1, "stream": 1, "total": 5},
|
||||
"violations": [{"kind": "grade"}] * 3 + [{"kind": "radius"}, {"kind": "stream"}],
|
||||
}
|
||||
hard = hard_limits(metrics)
|
||||
assert hard["over_limit"] == {"grade": 3, "radius": 1, "stream": 1, "total": 2}
|
||||
assert hard["violations"] == metrics["violations"] # 목록엔 참고로 남음
|
||||
|
||||
|
||||
def test_kink_becomes_curve_even_gentle() -> None:
|
||||
"""내각 160° 꺾임만(곡선 끔) → 곡선(최소 R) · 155° 이상도 곡선 둠."""
|
||||
points = [(0.0, 0.0), (100.0, 0.0), (200.0, 100.0 * math.tan(math.radians(20.0)))]
|
||||
kinked = build_planned_polyline(
|
||||
points, min_radius_m=RADIUS, simplify=False, curve_flags=[True, False, True]
|
||||
)
|
||||
assert _kinks(kinked) == 1
|
||||
fixed = curve_every_kink(kinked, RADIUS)
|
||||
assert _kinks(fixed) == 0 and len(fixed.curves) == 1
|
||||
assert fixed.curves[0].radius_m >= RADIUS - 1e-6 and not fixed.curves[0].violations
|
||||
|
||||
|
||||
def test_kink_without_room_is_removed() -> None:
|
||||
"""앞뒤 직선이 짧아 최소 R 이 안 드는 꺾임만 → 교점 지움(곧게)."""
|
||||
points = [(0.0, 0.0), (100.0, 0.0), (103.0, 2.0), (200.0, 2.0)]
|
||||
kinked = build_planned_polyline(
|
||||
points, min_radius_m=RADIUS, simplify=False, curve_flags=[True, True, False, True]
|
||||
)
|
||||
fixed = curve_every_kink(kinked, RADIUS)
|
||||
assert _kinks(fixed) == 0
|
||||
assert len(_flat(fixed)[0]) == 3 # 짧은 쪽 교점 하나 빠짐
|
||||
|
||||
|
||||
def _zigzag(seed: int) -> list[tuple[float, float]]:
|
||||
rng = np.random.default_rng(seed)
|
||||
points, heading = [(60.0, 200.0)], 0.0
|
||||
for _ in range(10):
|
||||
heading += float(rng.uniform(-0.6, 0.6))
|
||||
step = float(rng.uniform(40.0, 80.0))
|
||||
x, y = points[-1]
|
||||
points.append((x + step * math.cos(heading), y + step * math.sin(heading)))
|
||||
return points
|
||||
|
||||
|
||||
@pytest.mark.parametrize("seed", range(6))
|
||||
def test_guard_snap_no_kinks_all_on_station(seed: int) -> None:
|
||||
"""직접 잼 측점 맞춤(추천 후보) — 평지 · 꺾임만 섞인 선 → 꺾임만 0 · 측점 밖 0."""
|
||||
points = _zigzag(seed)
|
||||
flags = [True] + [i % 3 != 1 for i in range(1, len(points) - 1)] + [True]
|
||||
before = build_planned_polyline(points, min_radius_m=RADIUS, simplify=False, curve_flags=flags)
|
||||
lo = np.min(points, axis=0) - 150.0
|
||||
hi = np.max(points, axis=0) + 150.0
|
||||
x = np.arange(lo[0], hi[0], 2.0)
|
||||
y = np.arange(lo[1], hi[1], 2.0)
|
||||
terrain = Terrain(x, y, np.zeros((len(y), len(x))), np.ones((len(y), len(x)), dtype=bool))
|
||||
guard = Guard(SearchGrid(points, terrain, [], stream_offset_m=100.0), 0.14)
|
||||
after = snap_curves_to_stations(before, RADIUS, guard=guard)
|
||||
assert after is not None
|
||||
assert _kinks(after) == 0
|
||||
assert off_station_count(after) == 0
|
||||
|
||||
|
||||
FLAT = {"over_limit": {"total": 0, "grade": 0}, "max_grade_pct": 0.0}
|
||||
|
||||
|
||||
def _merge(earth_base: float, delta: float):
|
||||
"""평지 같은 쪽 두 꺾임 — 노선 토공 `earth_base` · 합친 선 차이 `delta`(가짜 횡단 함수)."""
|
||||
outline = build_planned_polyline(
|
||||
SAME, min_radius_m=RADIUS, simplify=False, curve_flags=[True] * len(SAME)
|
||||
)
|
||||
base = {**FLAT, "cut_m3": earth_base, "fill_m3": 0.0}
|
||||
calls = []
|
||||
|
||||
def cross(before, after, window):
|
||||
calls.append(window)
|
||||
return delta
|
||||
|
||||
guard = Guard(SearchGrid(SAME, _flat_terrain(402.0, 262.0), [], stream_offset_m=100.0), 0.14)
|
||||
merged = merge_same_side((outline, base), guard, RADIUS, lambda line: base, cross)[0]
|
||||
return merged, calls
|
||||
|
||||
|
||||
def test_merge_stops_at_route_earth_tolerance() -> None:
|
||||
"""합치기 판정 = 바뀐 측점 횡단 차이 — +10 % 안이면 합치고 넘으면 안 합침(엔진 상수)."""
|
||||
assert TOTAL_EARTH_TOL == pytest.approx(0.10)
|
||||
merged, calls = _merge(1000.0, 90.0)
|
||||
assert len(merged.curves) == 1 and calls
|
||||
s0, s1 = calls[0]
|
||||
assert 0.0 <= s0 < s1 # 바뀐 자리(앞 선 누가거리)만 넘김
|
||||
blocked, calls = _merge(1000.0, 110.0)
|
||||
assert len(blocked.curves) == 2 and calls
|
||||
|
||||
|
||||
def _route(name: str, earth: float, station: int = 0):
|
||||
metrics = {
|
||||
"name": name,
|
||||
"cut_m3": earth,
|
||||
"fill_m3": 0.0,
|
||||
"cost_index": earth,
|
||||
"over_limit": {"total": station, "station": station},
|
||||
}
|
||||
return (object(), metrics)
|
||||
|
||||
|
||||
def test_choose_profiles_only_shortlist() -> None:
|
||||
"""종단 반영 켬 토공 = 상한 누른 계획선 토공 위 3 개만 균형 종단 · 그 사이에서 고름."""
|
||||
routes = [_route(f"r{i}", 100.0 + 10 * i) for i in range(8)] + [_route("off", 1.0, 1)]
|
||||
profiled = []
|
||||
|
||||
def profile(route):
|
||||
profiled.append(route[1]["name"])
|
||||
# 균형 종단 토공은 순서가 바뀜 — 셋째가 가장 작음
|
||||
earth = {"r0": 900.0, "r1": 800.0, "r2": 300.0}.get(route[1]["name"], 50.0)
|
||||
route[1]["profile"] = {"cut_m3": earth, "fill_m3": 0, "borrow_m3": 0, "spoil_m3": 0}
|
||||
|
||||
chosen = choose("earthwork", routes, True, profile)
|
||||
assert profiled == ["r0", "r1", "r2"] and len(profiled) == SHORTLIST
|
||||
assert chosen[1]["name"] == "r2"
|
||||
# 종단 끔 = 줄 세우기 값 그대로 · 종단 안 붙임
|
||||
profiled.clear()
|
||||
assert choose("earthwork", routes, False, profile)[1]["name"] == "r0" and not profiled
|
||||
|
||||
|
||||
def test_ends_grade() -> None:
|
||||
x = np.arange(0.0, 402.0, 2.0)
|
||||
y = np.arange(0.0, 102.0, 2.0)
|
||||
xx, _ = np.meshgrid(x, y)
|
||||
slope = Terrain(x, y, 0.3 * xx, np.ones(xx.shape, dtype=bool))
|
||||
assert ends_grade([(50.0, 50.0), (250.0, 50.0)], slope, 200.0) == pytest.approx(30.0)
|
||||
assert ends_grade([(50.0, 50.0), (250.0, 50.0)], slope, 600.0) == pytest.approx(10.0)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def hill_runs() -> dict:
|
||||
x = np.arange(0.0, 802.0, 2.0)
|
||||
y = np.arange(0.0, 402.0, 2.0)
|
||||
xx, yy = np.meshgrid(x, y)
|
||||
hill = Terrain(x, y, 40.0 * np.exp(-((xx - 400.0) ** 2 + (yy - 200.0) ** 2) / 90.0**2), xx > -1)
|
||||
expected = [(float(v), 200.0 + 40.0 * math.sin(v / 60.0)) for v in np.arange(50.0, 752.0, 4.0)]
|
||||
return {
|
||||
mode: generate_initial_route(
|
||||
expected, hill, [], mode=mode, weights=None, max_grade=0.14, min_radius_m=RADIUS
|
||||
)
|
||||
for mode in ("gradeline", "earthwork", "gentle", "weighted")
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mode", ["gradeline", "earthwork", "gentle", "weighted"])
|
||||
def test_hill_branches_rules(hill_runs: dict, mode: str) -> None:
|
||||
"""언덕 — 꺾임만 0 · 측점 밖 0 · 못 지킨 곳 수 = R · 계류 · 측점 · 맞추기 전 선 없음."""
|
||||
result = hill_runs[mode]
|
||||
interior = result["nodes"][1:-1]
|
||||
assert interior and all(node["radius_m"] is not None for node in interior) # 삼각 0
|
||||
assert result["off_station_curves"] == 0
|
||||
over = result["metrics"]["over_limit"]
|
||||
assert over["total"] == over["radius"] + over["stream"] + over.get("station", 0)
|
||||
assert "unsnapped" not in result
|
||||
assert result["ends_over_limit"] is False and result["ends_grade_pct"] < 14.0
|
||||
@@ -2,8 +2,9 @@
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_route_initial.py -q
|
||||
|
||||
공통 제약(종단기울기 상한 · 최소 곡선반지름 · 계류 이격)은 넘지 못함 — 지형상 불가능할 때만
|
||||
`metrics.violations` 에 측점 · 값 · 사유로 뜨고 `over_limit.total` 은 그 수와 같다.
|
||||
공통 제약(최소 곡선반지름 · 계류 이격)은 넘지 못함 — 지형상 불가능할 때만 `metrics.violations` 에
|
||||
측점 · 값 · 사유로 뜨고 `over_limit.total` 은 그 수와 같다. 원지반 기울기(탐색 링크 상한은 막음)는
|
||||
참고만 — 목록엔 뜨되 `total` 에 안 듦(419).
|
||||
예상노선 추적(follow)은 전처리 초기값과 같은 선 — 제약 탐색 없이 넘은 곳만 목록(PLAN 23-1).
|
||||
갈래는 제 목표 비교값이 가장 좋은 후보 — 실프로젝트(랩탑_보조 · 읽기만)에서 갈래가 갈림.
|
||||
"""
|
||||
@@ -32,8 +33,8 @@ from B05_Profile.B05_Profile_Engine_RouteInitial_Metrics import (
|
||||
grade_line,
|
||||
station_label,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import GOALS
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Profile import profile_earth
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import GOALS, SHORTLIST
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Profile import plain_metrics, profile_earth
|
||||
from common_util.common_util_route_polyline import build_planned_polyline
|
||||
|
||||
SEARCHED = [mode for mode in MODES if mode != "follow"]
|
||||
@@ -68,9 +69,10 @@ def _run(terrain: Terrain, mode: str, streams=(), weights=None) -> dict:
|
||||
|
||||
|
||||
def _kept(result: dict) -> None:
|
||||
"""제약을 지켰거나, 못 지킨 곳이 목록에 있고 수가 같음."""
|
||||
"""제약을 지켰거나, 못 지킨 곳이 목록에 있고 수가 같음(원지반 기울기는 참고 · 수에 안 듦)."""
|
||||
metrics = result["metrics"]
|
||||
assert metrics["over_limit"]["total"] == len(metrics["violations"])
|
||||
hard = [v for v in metrics["violations"] if v["kind"] != "grade"]
|
||||
assert metrics["over_limit"]["total"] == len(hard)
|
||||
if not metrics["violations"]:
|
||||
assert metrics["max_grade_pct"] <= GRADE * 100.0 + 1e-6
|
||||
assert result["relaxed"] is False
|
||||
@@ -109,7 +111,7 @@ def test_follow_is_initial_polyline_and_lists_violations(hill: Terrain) -> None:
|
||||
assert result["relaxed"] is False and result["terrain_bound_pct"] is None
|
||||
grades = [v for v in result["metrics"]["violations"] if v["kind"] == "grade"]
|
||||
assert grades and all("예상노선 추적" in v["reason"] for v in grades)
|
||||
assert result["metrics"]["over_limit"]["total"] == len(result["metrics"]["violations"])
|
||||
_kept(result)
|
||||
|
||||
|
||||
def _earth(result: dict) -> float:
|
||||
@@ -123,13 +125,19 @@ def _earth(result: dict) -> float:
|
||||
def _check_branch_goals(runs: dict) -> None:
|
||||
"""갈래마다 제 목표 비교값(`GOALS` — 절성토 · 최대 기울기 · 목표 기울기 편차 · 위험 길이 ·
|
||||
계류 횡단 · 공사비)이 갈래(같은 후보 묶음) 가운데 1 등 · 새 비교값이 서로 맞음(PLAN 23-5).
|
||||
고르기는 측점에 맞춘 선 · 측점 밖 곡선 없는 후보 먼저(383-1)."""
|
||||
고르기는 측점에 맞춘 선 · 측점 밖 곡선 없는 후보 먼저(383-1). 토공 · 공사비는 상한 누른 계획선
|
||||
토공(종단 전 값) 위 `SHORTLIST` 안에서 균형 종단으로 고름(419) — 종단 전 값으로 위 3 안."""
|
||||
branches = [runs[mode]["metrics"] for mode in BRANCHES]
|
||||
|
||||
def rank(goal, m: dict) -> tuple:
|
||||
return (m["over_limit"].get("station", 0) > 0, *goal(m))
|
||||
|
||||
for mode, goal in GOALS.items():
|
||||
if mode in ("earthwork", "cost"):
|
||||
plain = sorted(rank(goal, plain_metrics(m)) for m in branches)
|
||||
own = rank(goal, plain_metrics(runs[mode]["metrics"]))
|
||||
assert own <= plain[min(SHORTLIST, len(plain)) - 1], mode
|
||||
continue
|
||||
assert all(rank(goal, runs[mode]["metrics"]) <= rank(goal, m) for m in branches), mode
|
||||
for result in runs.values():
|
||||
m = result["metrics"]
|
||||
|
||||
Reference in New Issue
Block a user