perf(배수유역): 342-3 빠른 길 — 흐름 방향 큰 격자 한 번 · 도로 닿음 판정 한꺼번에
- 하강 방향장 = 해석 격자 둘레 400 m 큰 격자로 한 번 짓고 회차마다 잘라 씀(crop_descent) · 넘으면 그때 다시 지음 - classify_flow 한 칸씩 걷기 → 포인터 더블링(_reach_by_doubling) · 기록용 출발 셀 수는 최외곽 · 나머지 셀 수로 - 거울 시험 test_342_3_watershed_fast(옛 걷기와 칸마다 같음 · 잘라 씀 안쪽 같음) · 실측 bench_342_watershed - 검증_sub3 실측: 적색 차이 0칸 · 도로 귀속 · 강도 · 관 자리 같음 · 108 → 27 s(붐빈 랩탑 · 340 기준 약 15 s) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XWh5wNNycMkbwMpACz1rte
This commit is contained in:
@@ -278,8 +278,8 @@ class FlowClassification:
|
||||
direction: np.ndarray # (R*C,) int16 — 32방위 코드(32=제자리, 33=무효)
|
||||
reaches_road: np.ndarray # (R*C,) bool — 물길을 따라가면 도로에 닿는가
|
||||
analyzed: np.ndarray # (R*C,) bool — 실제로 판정한 셀
|
||||
outer_seeds: int # 최외곽에서 출발해 판정한 셀 수
|
||||
interior_seeds: int # 최외곽 추적에 안 걸려 따로 출발시킨 내부 셀 수
|
||||
outer_seeds: int # 최외곽 셀 수(기록용 · 세류 셀 뺌)
|
||||
interior_seeds: int # 그 밖 판정 셀 수(기록용 · 세류 셀 뺌)
|
||||
burned: np.ndarray | None = None # (R*C,) bool — 세류망을 따라 흐름을 새긴 셀
|
||||
|
||||
|
||||
@@ -321,6 +321,8 @@ def classify_flow(
|
||||
다시 분석하지 않는다.
|
||||
④ 최외곽 추적에 안 걸린 내부 셀을 그다음에 따로 출발시킨다.
|
||||
|
||||
판정 값은 이 차례로 걷는 것과 칸마다 같다 — 계산은 한꺼번에 한다(`_reach_by_doubling`).
|
||||
|
||||
`azimuth`를 주면 그 32방위 코드를 그대로 화살표로 쓴다(등고선 하강 방향). 주지 않으면
|
||||
지표면 기울기에서 뽑는다. **화살표는 실제 수신 셀과 같은 방향이어야 한다** — 어긋나면
|
||||
화살표로 사슬을 따라가는 눈 검증이 성립하지 않는다.
|
||||
@@ -336,21 +338,13 @@ def classify_flow(
|
||||
direction = descent_azimuth(spec, terrain.elevation, terrain.valid, receiver, burned)
|
||||
else:
|
||||
direction = _azimuth_with_burn(spec, azimuth, receiver, burned)
|
||||
reaches = np.zeros(spec.size, dtype=bool)
|
||||
# 0=미방문, 1=경로에 올라 있음, 2=판정 완료
|
||||
state = np.zeros(spec.size, dtype=np.int8)
|
||||
|
||||
# ⓪ 세류선과 겹치는 셀을 먼저 적색으로 못박는다.
|
||||
reaches[stream_cells] = True
|
||||
state[stream_cells] = 2
|
||||
reaches = _reach_by_doubling(receiver, valid, absorbing, stream_cells)
|
||||
analyzed = valid | stream_cells
|
||||
stream_done = int(stream_cells.sum())
|
||||
# 기록용 숫자 — 한꺼번에 판정하므로 「출발」 구분이 없다. 최외곽 셀 수 · 나머지 셀 수만 남긴다.
|
||||
outer_done = int((outermost_cells(terrain.valid).reshape(-1) & ~stream_cells).sum())
|
||||
interior_done = int((valid & ~stream_cells).sum()) - outer_done
|
||||
|
||||
outer = np.flatnonzero(outermost_cells(terrain.valid))
|
||||
remaining = np.flatnonzero(valid)
|
||||
outer_done = _walk_from(outer, receiver, valid, absorbing, state, reaches)
|
||||
interior_done = _walk_from(remaining, receiver, valid, absorbing, state, reaches)
|
||||
|
||||
analyzed = state == 2
|
||||
logger.info(
|
||||
"배수유역: 흐름 판정 %d셀 (세류 선확정 %d / 최외곽 출발 %d / 내부 보충 %d) — "
|
||||
"도로 도달 %d, 미도달 %d",
|
||||
@@ -391,52 +385,32 @@ def _azimuth_with_burn(
|
||||
return direction
|
||||
|
||||
|
||||
def _walk_from(
|
||||
starts: np.ndarray,
|
||||
def _reach_by_doubling(
|
||||
receiver: np.ndarray,
|
||||
valid: np.ndarray,
|
||||
absorbing: np.ndarray,
|
||||
state: np.ndarray,
|
||||
reaches: np.ndarray,
|
||||
) -> int:
|
||||
"""출발 셀에서 물길 사슬을 따라가며 판정하고, 사슬 전체에 같은 색을 적는다.
|
||||
stream_cells: np.ndarray,
|
||||
) -> np.ndarray:
|
||||
"""물길 사슬을 끝까지 따라간 종착 셀로 셀마다 도로 도달을 한꺼번에 판정한다.
|
||||
|
||||
`absorbing`은 도로 셀과 확정된 세류망 셀 — 여기 닿으면 사슬 전체가 도로 도달이다.
|
||||
새로 판정한 셀 수를 돌려준다.
|
||||
사슬을 한 칸씩 걷던 판정(최외곽 출발 → 내부 보충 · 이미 판정된 셀의 색 물려받기)과
|
||||
**칸마다 같은 값**이다 — 순환이 없으면 어느 셀에서 출발하든 같은 종착에 닿기 때문이다.
|
||||
종착 = 도로 · 세류 셀(적색) · 싱크 · 해석 영역 밖으로 나가는 셀(미도달). 세류 셀은 표고가
|
||||
없어도 따라간다. 포인터 더블링(`trace_flow` 와 같은 방식)이라 파이썬 반복이 없다.
|
||||
"""
|
||||
resolved = 0
|
||||
path: list[int] = []
|
||||
for start in starts.tolist():
|
||||
if state[start] == 2:
|
||||
continue
|
||||
path.clear()
|
||||
node = start
|
||||
while True:
|
||||
if state[node] == 2:
|
||||
verdict = bool(reaches[node]) # 이미 색이 정해진 셀 — 그 색을 물려받는다
|
||||
break
|
||||
if state[node] == 1: # 방어: 채움·평탄해소 후에는 순환이 없어야 한다
|
||||
verdict = False
|
||||
break
|
||||
state[node] = 1
|
||||
path.append(node)
|
||||
if absorbing[node]:
|
||||
verdict = True # 도로 또는 세류망에 합류 — 여기서 하류로 빠진다
|
||||
break
|
||||
following = int(receiver[node])
|
||||
if following == node:
|
||||
verdict = False # 싱크에 갇힘
|
||||
break
|
||||
if not valid[following] and state[following] != 2:
|
||||
# 해석 영역 밖으로 빠짐. 단, 이미 색이 정해진 셀(세류 선확정 등)이면 따라간다.
|
||||
verdict = False
|
||||
break
|
||||
node = following
|
||||
for visited in path:
|
||||
reaches[visited] = verdict
|
||||
state[visited] = 2
|
||||
resolved += len(path)
|
||||
return resolved
|
||||
index = np.arange(receiver.size, dtype=np.int64)
|
||||
following = receiver.astype(np.int64)
|
||||
terminal = absorbing | (following == index) | ~(valid[following] | stream_cells[following])
|
||||
jump = np.where(terminal, index, following)
|
||||
for _ in range(_MAX_DOUBLING_ROUNDS):
|
||||
next_jump = jump[jump]
|
||||
if np.array_equal(next_jump, jump):
|
||||
break
|
||||
jump = next_jump
|
||||
# 순환에 갇힌 사슬은 종착에 못 닿는다 — 걷던 판정처럼 미도달.
|
||||
reaches = absorbing[jump] & terminal[jump] & (valid | stream_cells)
|
||||
reaches[stream_cells] = True
|
||||
return reaches
|
||||
|
||||
|
||||
# ── 격자 확장 (별도 단계) ───────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user