- 새 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>
153 lines
7.6 KiB
Python
153 lines
7.6 KiB
Python
"""E03 보강재(네일 · 앵커) — 활동면을 지나는 보강력을 절편 바닥 점하중으로(Slide2 Support 와 같은 꼴).
|
||
|
||
입력 꼴(E03 보강 검토 화면과 한 벌) supports = [{
|
||
"type": "nail" | "anchor", "x", "z" (머리 · 모형 좌표 m), "angle_deg" (수평 아래로 +), "length_m",
|
||
"spacing_m" (종방향 간격 · 단위 폭 힘 = 힘 / 간격), "tensile_kN" (강재 인장 한도),
|
||
"bond_kPa" (그라우트 · 지반 부착 · 네일 = 전 길이 · 앵커 = 끝 정착장만), "bond_length_m" (앵커 정착장),
|
||
"hole_dia_mm", "prestress_kN" (앵커만 · 0 이면 수동), "plate_kN"? (머리 판 · 없으면 tensile_kN),
|
||
"side"? ("left" | "right" · 고르는 것은 `Engine_Station`)}]
|
||
힘 크기 = 지점마다 Slide2 Force Diagram 의 최소 — 인장(tensile) · 인발(pullout = 부착 × 활동면 밖 부착 길이) ·
|
||
벗겨짐(stripping = 판 + 부착 × 활동면 안 부착 길이) · 부착 = π·D·bond_kPa (kN/m) · 자유장은 부착 없음.
|
||
인발 안전율은 입력 부착값에 넣어 둠(Slide2 와 같음 — 엔진은 나누지 않음).
|
||
방향 = 보강재와 나란히(Slide2 Parallel to Reinforcement) · 비탈 속으로(활동 방향 반대 = −x) 아래로 angle.
|
||
능동 · 수동 = Slide2 Active/Passive Force Application:
|
||
능동(프리스트레스 앵커) = 힘 그대로 — 활동력을 줄임 F = R / (D − T) · 힘 = min(프리스트레스, 한도)
|
||
수동(네일 · 프리스트레스 0 앵커) = 힘 / F — 저항을 늘림 F = (R + T) / D
|
||
머리가 활동 덩이 밖이면(활동면이 보강재를 안 지나거나 덩이 밖에서 드나듦) 0 = not_crossing.
|
||
절편 칸(꼴 (S, n) · 활동 방향 +x 틀): sup_va · sup_ha = 능동 연직(아래 +) · 수평(+x) · sup_vp · sup_hp = 수동(× 1/F 전)
|
||
· sup_xva · sup_zha · sup_xvp · sup_zhp = 모멘트용 x·연직 · z·수평. 보강재 칸(꼴 (S, K)) sup_T · sup_mode · sup_x · sup_z.
|
||
"""
|
||
|
||
import numpy as np
|
||
|
||
MODES = ("tensile", "pullout", "stripping", "not_crossing", "prestress")
|
||
_NOT, _PRE = 3, 4
|
||
_FIELDS = ("va", "ha", "vp", "hp", "xva", "zha", "xvp", "zhp")
|
||
|
||
|
||
def prepare(raw: list[dict] | None, direction: int) -> dict | None:
|
||
"""입력 꼴 → 배열(활동 방향 +x 틀 · x 에 direction 곱함) · 없으면 None."""
|
||
if not raw:
|
||
return None
|
||
s = 1.0 if direction >= 0 else -1.0
|
||
cols = {k: [] for k in ("hx", "hz", "dx", "dz", "L", "free", "q", "tens", "plate", "sp", "pre")}
|
||
active = []
|
||
for r in raw:
|
||
kind = r.get("type", "nail")
|
||
if kind not in ("nail", "anchor"):
|
||
raise ValueError(f"보강재 종류 모름: {kind}")
|
||
L, sp = float(r["length_m"]), float(r["spacing_m"])
|
||
tens, bond = float(r["tensile_kN"]), float(r["bond_kPa"])
|
||
dia = float(r["hole_dia_mm"]) / 1000.0
|
||
lb = float(r.get("bond_length_m") or 0.0) if kind == "anchor" else L
|
||
pre = float(r.get("prestress_kN") or 0.0) if kind == "anchor" else 0.0
|
||
plate = float(r["plate_kN"]) if r.get("plate_kN") is not None else tens
|
||
if L <= 0 or sp <= 0 or dia <= 0 or tens < 0 or bond < 0 or plate < 0 or pre < 0:
|
||
raise ValueError(f"보강재 값 범위 밖: {r}")
|
||
if not 0 < lb <= L:
|
||
raise ValueError(f"앵커 정착장은 0 초과 · 길이 이하: {r}")
|
||
a = np.radians(float(r["angle_deg"]))
|
||
for k, v in (
|
||
("hx", s * float(r["x"])),
|
||
("hz", float(r["z"])),
|
||
("dx", -np.cos(a)),
|
||
("dz", -np.sin(a)),
|
||
("L", L),
|
||
("free", L - lb),
|
||
("q", np.pi * dia * bond),
|
||
("tens", tens),
|
||
("plate", plate),
|
||
("sp", sp),
|
||
("pre", pre),
|
||
):
|
||
cols[k].append(v)
|
||
active.append(pre > 0)
|
||
out = {k: np.array(v, float) for k, v in cols.items()}
|
||
out["active"] = np.array(active, bool)
|
||
return out
|
||
|
||
|
||
def tips(sup: dict) -> np.ndarray:
|
||
"""보강재 끝 x (활동 방향 +x 틀) — 탐색 한계를 끝 뒤까지 넓힘."""
|
||
return sup["hx"] + sup["L"] * sup["dx"]
|
||
|
||
|
||
def apply(sl: dict, sup: dict) -> None:
|
||
"""절편 묶음(sl)에 보강력 칸을 덧붙임 — 면마다 보강재마다 첫 교점 · 힘 · 파괴 꼴."""
|
||
xe, ze = sl["xe"], sl["ze"]
|
||
S, n = sl["b"].shape
|
||
hx, hz, dx, dz = (sup[k][None, :, None] for k in ("hx", "hz", "dx", "dz"))
|
||
ax, az = xe[:, None, :-1], ze[:, None, :-1]
|
||
ex, ez = np.diff(xe, axis=1)[:, None, :], np.diff(ze, axis=1)[:, None, :]
|
||
den = dx * ez - dz * ex
|
||
safe = np.where(np.abs(den) < 1e-12, np.nan, den)
|
||
wx, wz = ax - hx, az - hz
|
||
t = (wx * ez - wz * ex) / safe # 보강재 위 머리에서 거리
|
||
u = (wx * dz - wz * dx) / safe # 활동면 마디 위 비율
|
||
hit = (u >= 0) & (u < 1) & (t > 0) & (t <= sup["L"][None, :, None])
|
||
tt = np.where(hit, t, np.inf)
|
||
j = np.argmin(tt, axis=-1) # (S, K) 첫 교점 절편
|
||
tc = np.take_along_axis(tt, j[..., None], -1)[..., 0]
|
||
# 머리가 활동 덩이 안(진입 ~ 진출 사이 · 활동면 위)일 때만 — 첫 교점 = 덩이에서 나가는 점
|
||
k_head = np.clip((xe[:, None, :] <= sup["hx"][None, :, None]).sum(-1) - 1, 0, n - 1)
|
||
x0 = np.take_along_axis(xe, k_head, 1)
|
||
x1 = np.take_along_axis(xe, k_head + 1, 1)
|
||
z0 = np.take_along_axis(ze, k_head, 1)
|
||
z1 = np.take_along_axis(ze, k_head + 1, 1)
|
||
zslip = z0 + (sup["hx"][None, :] - x0) * (z1 - z0) / np.maximum(x1 - x0, 1e-12)
|
||
inside = (sup["hx"] >= xe[:, :1]) & (sup["hx"] <= xe[:, -1:]) & (sup["hz"] > zslip)
|
||
cross = inside & np.isfinite(tc) & sl["valid"][:, None]
|
||
tc = np.where(cross, tc, 0.0)
|
||
lb = sup["L"] - sup["free"]
|
||
bond_in = np.clip(tc - sup["free"], 0.0, lb)
|
||
caps = np.stack(
|
||
[
|
||
np.broadcast_to(sup["tens"], tc.shape),
|
||
sup["q"] * (lb - bond_in),
|
||
sup["plate"] + sup["q"] * bond_in,
|
||
],
|
||
axis=-1,
|
||
)
|
||
mode = np.argmin(caps, axis=-1)
|
||
T = np.min(caps, axis=-1)
|
||
pre_rules = sup["active"] & (sup["pre"] < T)
|
||
T = np.where(pre_rules, sup["pre"], T)
|
||
mode = np.where(pre_rules, _PRE, mode)
|
||
T = np.where(cross, T, 0.0) / sup["sp"]
|
||
mode = np.where(cross, mode, _NOT)
|
||
xs = sup["hx"] + tc * sup["dx"]
|
||
zs = sup["hz"] + tc * sup["dz"]
|
||
acc = {f: np.zeros((S, n)) for f in _FIELDS}
|
||
rows = np.arange(S)
|
||
for k in range(len(sup["L"])):
|
||
v, h = -T[:, k] * sup["dz"][k], T[:, k] * sup["dx"][k] # 아래 + · +x(비탈 속 = −)
|
||
tag = "a" if sup["active"][k] else "p"
|
||
for f, val in ((f"v{tag}", v), (f"h{tag}", h), (f"xv{tag}", xs[:, k] * v)):
|
||
np.add.at(acc[f], (rows, j[:, k]), val)
|
||
np.add.at(acc[f"zh{tag}"], (rows, j[:, k]), zs[:, k] * h)
|
||
for f in _FIELDS:
|
||
sl[f"sup_{f}"] = acc[f]
|
||
sl["sup_T"], sl["sup_mode"], sl["sup_x"], sl["sup_z"] = T, mode, xs, zs
|
||
|
||
|
||
def forces(sl: dict, k: int, F: float, active: np.ndarray, direction: int) -> list[dict]:
|
||
"""면 k 의 보강재별 결과 칸(support_forces) — 한도(단위 폭) · 실제 넣은 힘(수동 = / F) · 파괴 꼴 · 교점."""
|
||
out = []
|
||
for i, (T, m) in enumerate(zip(sl["sup_T"][k], sl["sup_mode"][k])):
|
||
crossing = int(m) != _NOT
|
||
out.append(
|
||
{
|
||
"index": i,
|
||
"mobilized_kN_per_m": float(T),
|
||
"applied_kN_per_m": float(T if active[i] else T / F),
|
||
"mode": MODES[int(m)],
|
||
"force_application": "active" if active[i] else "passive",
|
||
"point": (
|
||
[float(direction * sl["sup_x"][k, i]), float(sl["sup_z"][k, i])]
|
||
if crossing
|
||
else None
|
||
),
|
||
}
|
||
)
|
||
return out
|