feat(B05 노선 추천 엔진): 합치기 판정 = 바뀐 측점 횡단 차이 · 후보 줄 세움 = 노선 횡단 토공 · 균형 종단 위 5 개 (419-1)
- 420 cross_earthwork · cross_earthwork_delta 이음 — 합치기 멈춤 = 계획고 지반고 노선 횡단 +10 % - 토공 · 공사비 줄 세움 = 상한 누른 계획선 횡단(종단 켬) · 지반고 횡단(종단 끔) - 위 후보 수 3 → 5(검증_sub4 균형 종단 토공 최소 = 줄 세움 5 위) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014utrjfoW831Vc5wJUd13kL
This commit is contained in:
@@ -54,11 +54,18 @@ from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import numpy as np
|
||||
from shapely.geometry import LineString
|
||||
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_CrossEarth import (
|
||||
cross_earthwork,
|
||||
cross_earthwork_delta,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Merge import RouteEarth
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Metrics import GRADE_SMOOTH_M, route_metrics
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import (
|
||||
Pool,
|
||||
@@ -70,6 +77,8 @@ from B05_Profile.B05_Profile_Engine_RouteInitial_Pool import (
|
||||
weighted,
|
||||
)
|
||||
from B05_Profile.B05_Profile_Engine_RouteInitial_Profile import (
|
||||
STATION_M,
|
||||
_reference,
|
||||
attach_profile,
|
||||
cover_shortfall,
|
||||
plain_metrics,
|
||||
@@ -300,9 +309,10 @@ def generate_initial_route(
|
||||
후보 묶음(`..._Pool`)에서 제 목표 비교값이 가장 좋은 것. `stream_offset_m`·`grade_smooth_m` 을
|
||||
안 주면(None) 이 머리의 기본값을 쓴다 — 화면에서 프로젝트마다 겹쳐 쓸 수 있다(PLAN 20장 확정).
|
||||
|
||||
후보마다 균형 종단(`..._Profile` · PLAN 63-4)을 붙여 토공 점수를 그것으로 고른다 — 편집 창
|
||||
추천 점수 · 미리 보기에만 쓰고 확정 종단과 무관. `pipes`(x, y, 덮개 여유)는 덮개 부족 수만 센다.
|
||||
`with_profile` 끄면(종단 반영 끔) 종단 없이 63-4 전 점수(원지반 가늠)로 고른다.
|
||||
토공 · 공사비 갈래는 후보를 노선 횡단 토공(`..._CrossEarth` · 상한 누른 계획선)으로 줄 세워 위
|
||||
몇 개에만 균형 종단(`..._Profile` · PLAN 63-4)을 붙여 고른다(419) — 편집 창 추천 점수 · 미리
|
||||
보기에만 쓰고 확정 종단과 무관. `pipes`(x, y, 덮개 여유)는 덮개 부족 수만 센다.
|
||||
`with_profile` 끄면(종단 반영 끔) 종단 없이 계획고 지반고 횡단 토공으로 고른다.
|
||||
"""
|
||||
if mode not in MODES:
|
||||
raise ValueError(f"모르는 갈래: {mode}")
|
||||
@@ -365,6 +375,58 @@ def generate_initial_route(
|
||||
def quick(route: Any) -> None:
|
||||
profiled(route, exact=False)
|
||||
|
||||
def cross(line: Any, at: Any = None, design_z: Any = None) -> dict[str, Any]:
|
||||
return cross_earthwork(
|
||||
line.vertices,
|
||||
at,
|
||||
design_z,
|
||||
terrain,
|
||||
standard,
|
||||
design_curves=[curve.as_dict() for curve in line.curves],
|
||||
)
|
||||
|
||||
def route_earth(line: Any) -> float:
|
||||
# 합치기 멈춤 잣대 = 계획고 지반고 횡단(1차 종단 규칙 · 종단 끔과 같음)
|
||||
try:
|
||||
found = cross(line)
|
||||
except ValueError:
|
||||
return math.inf
|
||||
return found["cut"] + found["fill"]
|
||||
|
||||
def merge_delta(before: Any, after: Any, window: tuple[float, float]) -> float:
|
||||
# 바뀐 자리 측점만 — 뒤 측점은 같은 선을 따라 밀린 것뿐(옛 선 끝 s1 → 새 선 s1 + 연장 차)
|
||||
shift = LineString(after.vertices).length - LineString(before.vertices).length
|
||||
first = math.ceil(window[0] / STATION_M) * STATION_M
|
||||
try:
|
||||
return cross_earthwork_delta(
|
||||
before.vertices,
|
||||
after.vertices,
|
||||
np.arange(first, window[1] + shift + 1e-9, STATION_M),
|
||||
terrain,
|
||||
standard,
|
||||
before_chainages=np.arange(first, window[1] + 1e-9, STATION_M),
|
||||
design_curves_before=[curve.as_dict() for curve in before.curves],
|
||||
design_curves_after=[curve.as_dict() for curve in after.curves],
|
||||
)["earth"]
|
||||
except ValueError:
|
||||
return math.inf
|
||||
|
||||
def cross_score(route: Any, capped: bool) -> float:
|
||||
# 후보 줄 세움(419) — 켬 = 상한 누른 계획선(`_reference`) 횡단 · 끔 = 계획고 지반고 횡단
|
||||
held = pool.scores if pool is not None else {}
|
||||
key = (id(route[1]), capped)
|
||||
if key not in held:
|
||||
try:
|
||||
found = cross(route[0])
|
||||
if capped:
|
||||
at = np.asarray([row["chainage_m"] for row in found["per_station"]])
|
||||
ground = np.asarray([row["ground_m"] for row in found["per_station"]])
|
||||
found = cross(route[0], at, _reference(ground, np.diff(at), max_grade))
|
||||
held[key] = found["cut"] + found["fill"]
|
||||
except ValueError:
|
||||
held[key] = math.inf
|
||||
return held[key]
|
||||
|
||||
bound = None
|
||||
pool: Pool | None = None
|
||||
target = _weighted_end(used) if mode == "weighted" else mode
|
||||
@@ -387,17 +449,24 @@ def generate_initial_route(
|
||||
graph = RouteGraph(grid, expected, limit=max_grade, min_radius_m=min_radius_m)
|
||||
while len(_POOLS) >= POOL_CACHE_SIZE:
|
||||
_POOLS.pop(next(iter(_POOLS)))
|
||||
# 합치기 멈춤 토공 — 420 `cross_earthwork_delta`(바뀐 측점 횡단 차이)가 오면 `earth_delta`
|
||||
# 로 이음 · 그 전엔 노선 잣대 토공(띠 가늠 · `..._Merge.merge_same_side`)
|
||||
_POOLS[key] = build_pool(graph, follow_line(), max_grade, min_radius_m, measure, fit)
|
||||
# 합치기 멈춤 토공 = 노선 횡단 + 바뀐 측점 횡단 차이(420 `..._CrossEarth` · 419)
|
||||
_POOLS[key] = build_pool(
|
||||
graph,
|
||||
follow_line(),
|
||||
max_grade,
|
||||
min_radius_m,
|
||||
measure,
|
||||
fit,
|
||||
cross=RouteEarth(route_earth, merge_delta),
|
||||
)
|
||||
pool = _POOLS[key]
|
||||
bound = pool.bound
|
||||
if target == "weighted":
|
||||
outline, metrics = weighted(pool, used, min_radius_m, measure)
|
||||
else:
|
||||
# 종단 반영 켬 토공 · 공사비 = 줄 세운 위 후보만 균형 종단(빠른 판) · 고른 하나만
|
||||
# 아래에서 바로 셈(419)
|
||||
outline, metrics = choose(target, pool.candidates, with_profile, quick)
|
||||
# 토공 · 공사비 = 횡단 토공으로 줄 세움 · 종단 반영 켬이면 위 후보만 균형 종단(빠른
|
||||
# 판) · 고른 하나만 아래에서 바로 셈(419)
|
||||
outline, metrics = choose(target, pool.candidates, with_profile, quick, cross_score)
|
||||
# 후보는 이미 측점에 맞춘 선(383-1 · `..._Pool.settle` · 맞추기 전 선은 안 냄 · 419)
|
||||
snapped = (outline, metrics)
|
||||
|
||||
@@ -432,7 +501,9 @@ def generate_initial_route(
|
||||
"ends_grade_pct": ends,
|
||||
"ends_over_limit": ends > max_grade * 100.0 + 1e-6,
|
||||
# 10 m 안인 다른 갈래(같은 노선 알림) · 제약 안 띠 폭 어림 — 추적은 묶음이 이미 있을 때만
|
||||
"similar": None if pool is None else similar(pool, target, outline, with_profile, quick),
|
||||
"similar": None
|
||||
if pool is None
|
||||
else similar(pool, target, outline, with_profile, quick, cross_score),
|
||||
"with_profile": with_profile,
|
||||
"band_width_m": None if pool is None else round(pool.band_width_m, 1),
|
||||
"criteria": {
|
||||
|
||||
Reference in New Issue
Block a user