perf(E03): 2D LEM 절편 · 반복 계산 줄임 — 결과 같음 (532-2 2차 ①)

- 지층 칸: 어느 띠에도 안 쓰인 빈 구간 칸을 잘라 절편 폭 22 → 3
- Bishop · Janbu 반복: 끝난 면은 빼고 셈(남은 면이 절반 아래면 묶음 줄임)
- λ = 0 길에서 λ 칸 계산 뺌 · Spencer 뉴턴 상한 40 → 25(수렴 면은 모두 27 회 안 · 넘는 면은 끝내 안 풀림)
- 검증_sub4 59 측점 전후 1648 값 차 0 % · 18 프로세스 벽시계 73.4 → 44.9 s

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-26 03:39:51 +09:00
co-authored by Claude Opus 5.5
parent f341e0465f
commit a39804bd8f
+41 -22
View File
@@ -104,6 +104,11 @@ class Prepared:
self.bot_a, self.bot_b = (np.take_along_axis(bot[k], order, 1) for k in _halves(half))
self.top_a, self.top_b = (np.take_along_axis(top[k], order, 1) for k in _halves(half))
self.mat = np.take_along_axis(mat[:half], order, 1)
# 빈 구간은 정렬 뒤 끝으로 — 어느 띠에도 안 쓰인 끝 칸을 잘라 절편 계산 폭을 줄임(값 같음)
used = ~(np.isnan(self.bot_a) & np.isnan(self.bot_b))
k = int(np.max(np.nonzero(used.any(0))[0], initial=0)) + 1
for name in ("bot_a", "bot_b", "top_a", "top_b", "mat"):
setattr(self, name, getattr(self, name)[:, :k])
# 지표면 꺾은선(정확) — 띠 양 끝으로 늘린 맨 윗면 · 연직 면은 같은 x 두 점
ta = np.nanmax(self.top_a, axis=1)
tb = np.nanmax(self.top_b, axis=1)
@@ -295,11 +300,17 @@ def _normal(sl, F, lam, grad=0):
p = _pre(sl)
g = 1.0 / np.asarray(F, float)[:, None]
L = np.asarray(lam, float)[:, None]
P1 = p["cusa"] + L * p["cuca"]
Q1 = p["sat"] + L * p["tca"]
den = sl["ca"] - L * sl["sa"] + g * Q1
if L.any():
P1 = p["cusa"] + L * p["cuca"]
Q1 = p["sat"] + L * p["tca"]
den = sl["ca"] - L * sl["sa"] + g * Q1
top = sl["W"] + L * p["lw"]
else: # λ = 0(Bishop · Janbu) — 같은 식에서 λ 칸만 뺌
P1, Q1 = p["cusa"], p["sat"]
den = sl["ca"] + g * Q1
top = sl["W"]
safe = np.where(np.abs(den) < _TINY, _TINY, den)
N = (sl["W"] + L * p["lw"] - g * P1) / safe
N = (top - g * P1) / safe
if not grad:
return N, den
dg = -(P1 + N * Q1) / safe
@@ -360,29 +371,37 @@ def _ff(sl, F, lam, grad=0, nd=None):
return (*_balance(p["tca"], sl["sa"], p["rc0"], p["fd0"], nd, F, False), nd[1])
def _iterate(fun, F0):
"""F = fun(F) 풀이(묶음 전체) — 해석 도함수 뉴턴 · 안 되면 반쯤 섞은 고정점 걸음.
def _iterate(sl, center, lam, F0, moment=True):
"""F = f(F) 풀이 — f = 모멘트(F_m) 또는 힘(F_f) 평형 · 해석 도함수 뉴턴 · 안 되면 반쯤 섞은 고정점 걸음.
fun(F) → (f, df/dF, …). 단순 고정점은 급한 머리 절편에서 진동해 느림(도함수 ≈ −1) — 뉴턴은 몇 번에 끝남.
단순 고정점은 급한 머리 절편에서 진동해 느림(도함수 ≈ −1) — 뉴턴은 몇 번에 끝남.
끝난 면은 빼고 셈(남은 면이 절반 아래면 묶음을 줄임 · 면마다 따로 셈이라 값 같음).
"""
F = np.asarray(F0, float).copy()
done = np.zeros(len(F), bool)
iters = np.zeros(len(F), int)
idx, w, wc, wl = np.arange(len(F)), sl, center, lam
for _ in range(MAX_ITER):
f1, df = fun(F)[:2]
h1 = f1 - F
Fi, di = F[idx], done[idx]
f1, df = (_fm(w, wc, Fi, wl, 1) if moment else _ff(w, Fi, wl, 1))[:2]
h1 = f1 - Fi
dh = df - 1.0
newton = F - h1 / np.where(np.abs(dh) < 1e-12, -1.0, dh)
relaxed = 0.5 * (F + f1)
use = np.isfinite(newton) & (dh < -1e-9) & (newton > 0.5 * F) & (newton < 2.0 * F)
newton = Fi - h1 / np.where(np.abs(dh) < 1e-12, -1.0, dh)
relaxed = 0.5 * (Fi + f1)
use = np.isfinite(newton) & (dh < -1e-9) & (newton > 0.5 * Fi) & (newton < 2.0 * Fi)
Fn = np.where(use, newton, relaxed)
Fn = np.where(np.isfinite(Fn) & (Fn > 0), Fn, np.nan)
conv = np.abs(h1) < TOL * np.maximum(1.0, np.abs(F))
F = np.where(done | conv, F, Fn)
iters += ~done
done |= conv | ~np.isfinite(F)
if done.all():
conv = np.abs(h1) < TOL * np.maximum(1.0, np.abs(Fi))
F[idx] = np.where(di | conv, Fi, Fn)
iters[idx] += ~di
done[idx] = di | conv | ~np.isfinite(F[idx])
act = ~done[idx]
if not act.any():
break
if act.sum() < 0.5 * len(idx):
idx, w = idx[act], _subset(w, act)
wc = None if wc is None else wc[act]
wl = wl[act]
return F, iters, done & np.isfinite(F)
@@ -406,7 +425,7 @@ def ordinary(sl: dict, center: np.ndarray) -> dict:
def bishop(sl: dict, center: np.ndarray, F0=None) -> dict:
F0 = np.full(len(sl["W"]), 1.5) if F0 is None else F0
lam = np.zeros(len(F0))
F, it, conv = _iterate(lambda f: _fm(sl, center, f, lam, 1), F0)
F, it, conv = _iterate(sl, center, lam, F0)
return _finish(sl, F, it, conv, lam)
@@ -414,7 +433,7 @@ def janbu(sl: dict, F0=None) -> dict:
"""Janbu 간편(F_0) + 보정 f_0 — 지식DB 02 §2.3 · b1 = 0.31(c=0) · 0.69(φ=0) · 0.50."""
F0 = np.full(len(sl["W"]), 1.5) if F0 is None else F0
lam = np.zeros(len(F0))
F, it, conv = _iterate(lambda f: _ff(sl, f, lam, 1), F0)
F, it, conv = _iterate(sl, None, lam, F0, moment=False)
out = _finish(sl, F, it, conv, lam)
out["F0"] = out["F"]
out["f0"] = janbu_f0(sl)
@@ -461,7 +480,7 @@ def spencer(sl: dict, center: np.ndarray, F0=None, scan: bool = True) -> dict:
return out
def _spencer_newton(sl, center, F, lam, iters=40):
def _spencer_newton(sl, center, F, lam, iters=25):
"""(F_m − F, F_f − F) = 0 뉴턴 — 걸음 크기 제한(F 절반 · λ 0.3) · 수렴 = 두 잔차 < 1e-4 · λ 범위 안.
끝난 면은 빼고(남은 면이 절반 아래면 묶음을 줄임) · F < F_MIN 으로 빠진 면은 버림(어차피 `_finish` 가 버림).
@@ -514,8 +533,8 @@ def lambda_curve(sl, center, grid=None):
F0 = np.full(S, 1.5)
for k, g in enumerate(grid):
lam = np.full(S, g)
fm_all[k] = _iterate(lambda f: _fm(sl, center, f, lam, 1), F0)[0]
ff_all[k] = _iterate(lambda f: _ff(sl, f, lam, 1), F0)[0]
fm_all[k] = _iterate(sl, center, lam, F0)[0]
ff_all[k] = _iterate(sl, None, lam, F0, moment=False)[0]
return grid, fm_all, ff_all