Files
Aislo/E03_StabSection/E03_StabSection_Engine_Describe.py
T
eomsangdonandClaude Opus 5.5 f3cc133297 fix(E03): Spencer · M-P 해 없음 = 「수렴 안 됨」 — 넓은 λ 다시 찾기 · 수렴 정보 칸 (532-2 덧 3 · 요구 351)
- 좁은 λ 에 해 없으면 넓은 λ(Spencer −2 ~ 2 · M-P ±π · 41 점 거친 훑기 → 뉴턴) 다시 찾음
- Bishop 임계 원에서도 해 없음 + 찾은 최소 > Bishop = 그 방법 원호 「해 없음」(F None · status · reason) · 최소 F 후보에서 빠짐
- 수렴 정보 칸 residual · lambda_range · status(converged · no_solution · rejected) 덧붙임 · 해 없음 때도 F_m · F_f–λ 곡선(넓은 범위)
- E03 방법 표 빈 값 = 「수렴 안 됨」(View no_solution 칸 · 옛 저장본은 없음)
- 시험 test_532_2_stab_nosolution 새로 · 검증_sub4 59 측점 Bishop · Fellenius · Janbu 값 차 0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 04:00:59 +09:00

136 lines
6.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""E03 결과 꾸미기 — 임계면 글상자 · 절편 자유물체도 · F–λ 곡선 · Safety Map 상위 면.
좌표는 원래 방향(x = B06 offset · 좌 +)으로 되돌려 냄. 출력 칸은 덧붙이기만(E03 화면 · E05 보고서가 읽음).
"""
import numpy as np
from E03_StabSection import E03_StabSection_Engine_GLE as gle
from E03_StabSection import E03_StabSection_Engine_LEM as lem
from E03_StabSection.E03_StabSection_Engine_Surface import (
circle_slices,
evaluate,
poly_slices,
three_point_center,
)
#: Safety Map 면 수 · 면마다 꼭짓점 수(원호)
SAFETY_MAP_TOP = 50
_MAP_POINTS = 25
def _curve(sl, center, method, f=None, wide=False) -> dict:
"""F_m · F_f 대 λ (화면 그래프) — Spencer = 상수 f · M-P = 반정현 · wide = 넓은 범위(해 없음이 보이게)."""
if method == "spencer":
rng, n = (
(lem.LAMBDA_WIDE, lem.WIDE_POINTS) if wide else (lem.LAMBDA_RANGE, gle.CURVE_POINTS)
)
grid, fm, ff = lem.lambda_curve(sl, center, np.linspace(*rng, n))
fm, ff = fm[:, 0], ff[:, 0]
else:
grid = np.linspace(*gle.LAMBDA_WIDE, lem.WIDE_POINTS) if wide else None
grid, fm, ff = gle.lambda_curves(sl, center, f, grid)
fm, ff = fm[0], ff[0]
num = lambda v: [float(x) if np.isfinite(x) else None for x in v] # noqa: E731
return {"lambda": num(grid), "Fm": num(fm), "Ff": num(ff)}
def describe(prep, kind, geom, method, n, min_depth, seed_F=None) -> dict:
"""임계면 하나 → 안전율 · 좌표 · 절편표(자유물체도 힘) · 수렴 · (Spencer · M-P) λ · F–λ 곡선."""
s = prep.direction
if kind == "circle":
xc, zc, R, xE, xX = geom
sl = circle_slices(prep, [xc], [zc], [R], [xE], [xX], n, min_depth)
center = np.array([[xc, zc]])
pts = np.column_stack([sl["xe"][0], sl["ze"][0]])
else:
vx, vz = geom
sl = poly_slices(prep, vx[None, :], vz[None, :], n, min_depth)
center = three_point_center(vx[None, :], vz[None, :])
pts = np.column_stack([vx, vz])
seed = None if seed_F is None else np.array([seed_F], float)
res = evaluate(sl, center, (method,), mp_seed=seed)[method]
F = float(res["F"][0])
lam = float(res["lambda"][0]) if method in ("spencer", "morgenstern_price") else 0.0
f = gle.interslice_function(sl["xe"]) if method == "morgenstern_price" else None
rows = lem.slice_table(sl, 0, F, lam, method, f) if np.isfinite(F) else []
for r in rows:
r["material"] = (
prep.materials[r["material"]]["id"] if r["material"] < len(prep.materials) else None
)
# 행 순서 = 머리 → 끝 · E · X 의 left = 머리 쪽 경계 · right = 끝 쪽 경계(해석 틀 그대로)
r["x_left"], r["x_right"] = sorted((s * r["x_left"], s * r["x_right"]))
r["x_mid"] = s * r["x_mid"]
out = {
"F": F if np.isfinite(F) else None,
"kind": kind,
"points": [[float(s * x), float(z)] for x, z in (pts if s > 0 else pts[::-1])],
"entry": [float(s * sl["xe"][0, 0]), float(sl["ze"][0, 0])],
"exit": [float(s * sl["xe"][0, -1]), float(sl["ze"][0, -1])],
"iterations": int(res["iterations"][0]),
"converged": bool(res["converged"][0]),
"slices": rows,
}
if kind == "circle":
out["center"] = [float(s * center[0, 0]), float(center[0, 1])]
out["radius"] = float(geom[2])
else:
out["moment_center"] = [float(s * center[0, 0]), float(center[0, 1])]
if method in ("spencer", "morgenstern_price"):
# 수렴 정보 — λ · 반복 수 · 잔차(max |F_m − F|, |F_f − F|) · 상태 · 해 없음 까닭
solved = np.isfinite(F)
wide = bool(res["wide"][0])
rng = (
(lem.LAMBDA_WIDE if wide else lem.LAMBDA_RANGE)
if method == "spencer"
else (gle.LAMBDA_WIDE if wide else gle.LAMBDA_RANGE)
)
out["lambda"] = lam if solved else None
out["theta_deg"] = float(np.degrees(np.arctan(lam))) if solved else None
r = float(res["residual"][0])
out["residual"] = r if np.isfinite(r) else None
out["lambda_range"] = [float(rng[0]), float(rng[1])]
crossed = bool(res["converged"][0])
out["status"] = "converged" if solved else ("rejected" if crossed else "no_solution")
out["reason"] = (
None
if solved
else "해는 있으나 버림(m_α < 0.2 · 저항 없음 · F 하한)"
if crossed
else f"F_m · F_f 가 λ {rng[0]:.2f} ~ {rng[1]:.2f} 안에서 안 만남(해 없음)"
)
out["lambda_curve"] = _curve(sl, center, method, f, wide or not solved)
if method == "morgenstern_price":
out["interslice_function"] = "half_sine"
if method == "janbu":
out["F0"] = float(res["F0"][0]) if np.isfinite(res["F0"][0]) else None
out["f0"] = float(res["f0"][0])
if prep.crack_depth > 0:
out["crack_water_force"] = float(sl["Pw"][0])
return out
def safety_map(prep, circles, F, method, top=SAFETY_MAP_TOP) -> dict:
"""Safety Map — 거친 탐색 원호 중 안전율 낮은 top 개(F · 중심 · 반지름 · 진입 · 진출 · 꼭짓점)."""
s = prep.direction
xc, zc, R, xE, xX = circles
order = np.argsort(np.where(np.isfinite(F), F, np.inf))[:top]
order = order[np.isfinite(F[order])]
t = np.linspace(0.0, 1.0, _MAP_POINTS)
out = []
for k in order:
x = xE[k] + (xX[k] - xE[k]) * t
z = zc[k] - np.sqrt(np.clip(R[k] ** 2 - (x - xc[k]) ** 2, 0, None))
pts = np.column_stack([s * x, z])
out.append(
{
"F": float(F[k]),
"center": [float(s * xc[k]), float(zc[k])],
"radius": float(R[k]),
"entry": [float(s * xE[k]), float(z[0])],
"exit": [float(s * xX[k]), float(z[-1])],
"points": [[float(a), float(b)] for a, b in (pts if s > 0 else pts[::-1])],
}
)
return {"method": method, "surfaces": out}