feat(B05): 추천 갈래 다시 — 영선 · 안전 우선 · 계류 횡단 최소 · 공사비 어림 최소(PLAN 23-5a)
- 갈래 short · balance 뺌(요청 422) · gradeline · safe · crossings · cost 더함 — 각 목표로 제약 탐색한 후보를 묶음에 넣고 제 비교값으로 고름 - 탐색 목표 셋 더함: 목표 기울기 편차(영선 · 등경사선) · 위험(옆경사 35° 이상 · 계류 버퍼 안 통과 길이) · 계류 횡단 링크 · 공사비는 상대 단가 그대로 - 비교값 더함: side_slope_avg_pct · side_slope_max_pct · hazard_m · stream_crossings · stream_offset_avg_m · grade_dev_pct · cost_index - 공사비 상대 단가(절성토 1 ㎥ = 1 · 연장 1 m = 2 · 계류 횡단 1 곳 = 400)는 임시 상대값 · 브레인 임의 판정 - 요청 criteria.target_grade_pct(영선 목표 기울기 · 없으면 상한) · 응답 criteria.target_grade_pct - 시험 test_route_initial 31 · 왕복 시험은 화면이 아는 갈래만(새 넷은 23-5b 뒤) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ACSGaHdLgnkEkoA4LDtMU
This commit is contained in:
@@ -19,7 +19,11 @@ from scipy.interpolate import RegularGridInterpolator
|
||||
from scipy.sparse import csr_matrix
|
||||
from scipy.sparse.csgraph import breadth_first_order, connected_components, dijkstra
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Metrics import GRADE_SMOOTH_M, smoothed_ground
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Metrics import (
|
||||
GRADE_SMOOTH_M,
|
||||
SAFE_SIDE_SLOPE,
|
||||
smoothed_ground,
|
||||
)
|
||||
|
||||
# 토공 가늠 폭(m) = 유효너비 3 + 길어깨 0.5 × 2(별표2 간선).
|
||||
ROAD_WIDTH_M = 4.0
|
||||
@@ -31,7 +35,10 @@ TURN_COST = 1.0 # 꺾을 때 벌점 = 최소 곡선반지름 원호 길이(R·
|
||||
SOFT_STREAM_COST = 20.0 # 계류 버퍼 안 나란히 달리기(지형상 불가능할 때만 · 1 m 당)
|
||||
BOUND_BISECT = 7 # 지형상 불가능할 때 막는 기울기를 찾는 이분 탐색 횟수(구간 1/128 까지)
|
||||
STREAM_CROSS_COS = math.cos(math.radians(45.0)) # 버퍼 안 이동 방향 한도(계류 쪽 · 반대쪽 ±45°)
|
||||
OBJECTIVES = ("expected", "earthwork", "grade")
|
||||
# 탐색 목표(1 m 당) — 이격 · 토공 가늠 · 기울기 · 목표 기울기 편차(영선) · 위험(급한 옆경사 · 계류
|
||||
# 가까움) · 계류 횡단(계류 선 위 링크) — PLAN 23-5
|
||||
OBJECTIVES = ("expected", "earthwork", "grade", "gradeline", "risk", "crossing")
|
||||
CROSSING_BAND = 0.75 # 계류 횡단 링크 = 링크 가운데가 계류에서 칸 × 이 값 안
|
||||
# 16 방향(행, 열) — +x 에서 반시계. 나이트 걸음(1, 2) 등을 넣어 22.5° 안팎 방향을 곧게 달림.
|
||||
DIRECTIONS = (
|
||||
(0, 1),
|
||||
@@ -101,8 +108,10 @@ class SearchGrid:
|
||||
*,
|
||||
stream_offset_m: float,
|
||||
grade_smooth_m: float = GRADE_SMOOTH_M,
|
||||
target_grade: float | None = None,
|
||||
) -> None:
|
||||
step = max(1, int(round(SEARCH_CELL_M / _cell_size(terrain.x))))
|
||||
self.target_grade = target_grade # 영선 노선 목표 기울기(비율) · 없으면 링크 상한
|
||||
self.xs, self.ys = terrain.x[::step], terrain.y[::step]
|
||||
# 기울기 · 옆경사는 비교값과 같은 고른 지반에서 잰다(`smoothed_ground`)
|
||||
self.z = smoothed_ground(terrain, grade_smooth_m)[::step, ::step]
|
||||
@@ -154,7 +163,17 @@ class SearchGrid:
|
||||
# 반 간격(10 m) 동안 쌓는 높이 차 단면(지형상 불가능할 때만 0 이 아님)
|
||||
earth = ROAD_WIDTH_M**2 * cross / 4.0 + ROAD_WIDTH_M * over * 10.0
|
||||
offset = 0.5 * (self.d_expected[first][ok] + self.d_expected[second][ok])
|
||||
terms = np.stack([offset, earth, (grade / limit) ** 2])
|
||||
target = limit if self.target_grade is None else self.target_grade
|
||||
stream_mid = 0.5 * (self.d_stream[first][ok] + self.d_stream[second][ok])
|
||||
# 위험 = 비교값(`hazard_m`)과 같은 뜻 — 옆경사 35° 이상 · 계류 버퍼 안을 지나는 길이(칸
|
||||
# 두 끝 가운데 급한 쪽) · 옆경사 몫을 조금 더해 같은 길이면 덜 급한 쪽
|
||||
risk = ((cross >= SAFE_SIDE_SLOPE) | (stream_mid < self.stream_offset_m)).astype(
|
||||
np.float64
|
||||
) + 0.1 * (cross / SAFE_SIDE_SLOPE) ** 2
|
||||
crossing = (stream_mid < CROSSING_BAND * self.cell).astype(np.float64)
|
||||
terms = np.stack(
|
||||
[offset, earth, (grade / limit) ** 2, ((grade - target) / limit) ** 2, risk, crossing]
|
||||
)
|
||||
return a[ok], b[ok], self.cell * norm, terms, over, along, grade
|
||||
|
||||
def objectives(
|
||||
@@ -328,8 +347,9 @@ class RouteGraph:
|
||||
) -> list[tuple[float, float]]:
|
||||
"""노드(꺾임점) 목록 — 첫 · 끝은 예상노선 양 끝 그대로.
|
||||
|
||||
세 목표는 `scale`(1 m 당 크기)로 나눈 뒤 비중을 곱한다 — 안 주면 쓸 수 있는 링크 평균이
|
||||
1 이 되게(길이 가중). `grade_cost` = 상한을 넘은 기울기 벌점(1 m 당 × 넘은 비율 / 상한) ·
|
||||
목표(`OBJECTIVES`)는 `scale`(1 m 당 크기)로 나눈 뒤 비중을 곱한다 — 안 주면 쓸 수 있는
|
||||
링크 평균이 1 이 되게(길이 가중). `grade_cost` = 상한을 넘은 기울기 벌점(1 m 당 × 넘은
|
||||
비율 / 상한) ·
|
||||
`stream_cost` = 계류 버퍼 안 나란히 벌점(엄격하지 않을 때만) ·
|
||||
`length_cost` = 1 m 당 거리 비용.
|
||||
"""
|
||||
@@ -339,7 +359,8 @@ class RouteGraph:
|
||||
if scale is None:
|
||||
length = self.length[usable]
|
||||
scale = (self.terms[:, usable] * length).sum(axis=1) / max(length.sum(), 1e-9)
|
||||
share = np.array([weights[key] for key in OBJECTIVES]) / max(sum(weights.values()), 1e-9)
|
||||
share = np.array([weights.get(key, 0.0) for key in OBJECTIVES])
|
||||
share = share / max(sum(weights.values()), 1e-9)
|
||||
factor = np.divide(share, scale, out=np.zeros_like(share), where=scale > 1e-12)
|
||||
penalty = grade_cost * self.over / self.limit
|
||||
if not strict_stream:
|
||||
|
||||
Reference in New Issue
Block a user