Files
Aislo/E03_StabSection/E03_StabSection_Engine_Describe.py
T
eomsangdonandClaude Opus 5.5 f25dac9000 feat(E03): 네일 · 앵커 보강력 2D 한계평형 — 절편 바닥 점하중 · 능동/수동(Slide2 같은 꼴) · 진입 탐색 넓힘 · support_forces (536-3 뒷부분 · 요구 345 · 351)
- 새 Engine_Support: 첫 교점 · min(인장 · 인발 · 벗겨짐) / 간격 · 앵커 정착장만 부착 · 프리스트레스 앵커 = 능동(min(프리스트레스, 한도)) · 네일 = 수동(힘 / F)
- LEM · GLE: 능동은 연직 · 수평 하중 칸 · 수동은 c' 칸(1/F) · 전체 평형 수동 항은 분자로(같은 근) · Fellenius 수동은 고정점
- Search: 보강 있으면 진입 한계만 보강재 끝 뒤까지(진출 = 원래 한계) · 판정 임계면 support_forces
- Station: analyze_station(supports=None) · side 칸 · 없으면 머리 x 쪽 비탈 · supports 없으면 결과 불변
- 시험 test_536_3_stab_supports 10(쐐기 닫힌 꼴 · 힘 꼴 손계산 · Slide2 #47 · #85 · #86 · 탐색 넓힘 · 불변 · 측점 꼴)

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

141 lines
6.2 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 import E03_StabSection_Engine_Support as support
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])
if prep.supports is not None:
out["support_forces"] = (
support.forces(sl, 0, F, prep.supports["active"], s) if np.isfinite(F) else []
)
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}