- E03_StabSection_WallCheck: Coulomb · Rankine · M-O 토압 · 상재하중 · 수압 · 양압력 · Meyerhof 지지력 · 평상시 / 지진 시 판정 - E03_StabSection_WhatIf: 한 쪽 비탈 1:n(+ 소단) 다시 그림 · 원지반 만나는 가장 완만한 n · 성긴 걸음 + 이분 최소 완화(완만할수록 늘 안전하지 않음) - E03_StabSection_Router_WhatIf: POST whatif · wallcheck · GET whatif · 저장본 _derived/stab2d_whatif(+ _view) · 원본 측점 · 요약 그대로 - E05: s8 보강검토(양식 · 자리표) · 저장본 없으면 시트 뺌 · 갱신 때 사라진 프로그램 몫 뺌 - 시험 test_536_3 12 · 534-3 양식 시험 시트 8(보강검토 빈 판 1쪽) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
221 lines
9.3 KiB
Python
221 lines
9.3 KiB
Python
"""E03 벽(옹벽 · 석축) 외적 안정 — 활동 · 전도 · 지지력 (요구 345 · 351 · PLAN 536-3).
|
||
|
||
storage 를 모르는 순수 함수. 벽 폴리곤(2D 모형 `walls` · x = B06 offset · z = 표고) + 배면 · 기초 물성
|
||
→ 하중 · 안전율 · 판정. 기준 = KDS 11 80 05(지식DB `구조물_안정계산.md` §2 · `original/안정성평가/04`).
|
||
|
||
- 토압 = Coulomb(벽 배면 = 뒷굽 바닥 ~ 배면 꼭대기 가상면 · 벽마찰 δ) 또는 Rankine(뒷굽 연직 가상면 ·
|
||
δ = β) · 지진 시 = Mononobe-Okabe(정적 H/3 + 증분 0.6H · Seed-Whitman) + 벽 관성 kh·W.
|
||
- 가상면과 벽 사이 흙 = 벽 무게에 더함 · 배면 점착 = 0(보수 쪽).
|
||
- 상재하중 q = 배면 등분포(노면 하중) · 수압 = 배면 수위 hw(바닥부터) 정수압 + 바닥 삼각 양압력 ·
|
||
수위 아래 토압 = 수중 단위중량.
|
||
- 활동 = (V·μ + c_b·B) / ΣH(수동토압 빼고) · 전도 = 앞굽 기준 저항 / 전도 모멘트 ·
|
||
지지력 = Meyerhof 극한(유효폭 B' · 경사 계수) / 최대 지반반력(사다리꼴 · 삼각).
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import math
|
||
from typing import Any
|
||
|
||
import numpy as np
|
||
from shapely.geometry import LineString, Polygon, box
|
||
from shapely.ops import unary_union
|
||
|
||
GAMMA_W = 9.81
|
||
#: KDS 11 80 05 기준안전율 — 평상시 · 지진 시(활동은 수동토압 뺀 값)
|
||
REQUIRED = {
|
||
"normal": {"sliding": 1.5, "overturning": 2.0, "bearing": 3.0},
|
||
"seismic": {"sliding": 1.2, "overturning": 1.5, "bearing": 2.0},
|
||
}
|
||
#: 벽 단위중량(kN/m³) — 콘크리트 · 찰쌓기 23 · 메쌓기(공극) 20
|
||
WALL_GAMMA = {"default": 23.0, "dry_masonry": 20.0}
|
||
_N = 400 # 토압 적분 칸
|
||
|
||
|
||
def ka_coulomb(phi: float, delta: float, theta: float, beta: float, psi: float = 0.0) -> float:
|
||
"""Coulomb 주동토압계수(ψ > 0 = Mononobe-Okabe KAE · kv = 0). 각 = 도.
|
||
|
||
θ = 배면이 연직에서 앞쪽(벽 쪽)으로 누운 각(쐐기가 커지는 쪽 +) · β = 배면 흙 경사 · δ = 벽마찰."""
|
||
f, d, t, b, p = (math.radians(v) for v in (phi, delta, theta, beta, psi))
|
||
under = math.sin(f + d) * math.sin(f - b - p) / (math.cos(d + t + p) * math.cos(b - t))
|
||
root = math.sqrt(max(under, 0.0))
|
||
return math.cos(f - t - p) ** 2 / (
|
||
math.cos(p) * math.cos(t) ** 2 * math.cos(d + t + p) * (1.0 + root) ** 2
|
||
)
|
||
|
||
|
||
def ka_rankine(phi: float, beta: float = 0.0) -> float:
|
||
"""Rankine 주동토압계수(경사 배면 β · 합력은 β 로 기욺)."""
|
||
f, b = math.radians(phi), math.radians(beta)
|
||
root = math.sqrt(max(math.cos(b) ** 2 - math.cos(f) ** 2, 0.0))
|
||
return math.cos(b) * (math.cos(b) - root) / (math.cos(b) + root)
|
||
|
||
|
||
def bearing_factors(phi: float) -> tuple[float, float, float]:
|
||
"""Meyerhof 지지력 계수(Nc · Nq · Nγ) — φ 도."""
|
||
f = math.radians(phi)
|
||
if f < 1e-9:
|
||
return 5.14, 1.0, 0.0
|
||
nq = math.exp(math.pi * math.tan(f)) * math.tan(math.pi / 4 + f / 2) ** 2
|
||
return (nq - 1.0) / math.tan(f), nq, (nq - 1.0) * math.tan(1.4 * f)
|
||
|
||
|
||
def _pressure(
|
||
ka: float, H: float, gamma: float, gamma_sat: float, q: float, hw: float
|
||
) -> tuple[float, float]:
|
||
"""연직 높이 H 가상면 토압 합력 · 바닥에서 작용 높이 — σ' = 수위 위 γ · 아래 γ' 누적 + q."""
|
||
z = np.linspace(0.0, H, _N + 1) # 꼭대기에서 아래로
|
||
dry = np.minimum(z, H - hw)
|
||
wet = np.maximum(z - (H - hw), 0.0)
|
||
p = ka * (gamma * dry + (gamma_sat - GAMMA_W) * wet + q)
|
||
total = float(np.trapz(p, z))
|
||
arm = float(np.trapz(p * (H - z), z)) / total if total > 0 else H / 3
|
||
return total, arm
|
||
|
||
|
||
def check_wall(
|
||
polygon: list,
|
||
back: int,
|
||
*,
|
||
backfill: dict[str, float],
|
||
foundation: dict[str, float],
|
||
method: str = "coulomb",
|
||
delta_ratio: float = 2.0 / 3.0,
|
||
beta_deg: float = 0.0,
|
||
surcharge_kpa: float = 10.0,
|
||
water_ratio: float = 0.0,
|
||
kh: float = 0.0,
|
||
wall_gamma: float = WALL_GAMMA["default"],
|
||
base_friction: float | None = None,
|
||
base_adhesion: float = 0.0,
|
||
embed_m: float = 0.0,
|
||
) -> dict[str, Any]:
|
||
"""벽 하나 외적 안정. back = 배면(흙) 쪽 x 방향(+1 · −1). 물성 = {gamma_t, gamma_sat, c, phi}.
|
||
|
||
kh > 0 이면 지진 시(기준 `REQUIRED["seismic"]`) · 아니면 평상시. 돌려줌 = 하중 · 안전율 · 판정."""
|
||
wall = Polygon(polygon)
|
||
xs = np.array([p[0] for p in polygon], dtype=float)
|
||
zs = np.array([p[1] for p in polygon], dtype=float)
|
||
zb, zt = float(zs.min()), float(zs.max())
|
||
H = zt - zb
|
||
base = xs[zs <= zb + 1e-3]
|
||
heel = float(base.max() if back > 0 else base.min())
|
||
toe = float(base.min() if back > 0 else base.max())
|
||
B = abs(heel - toe)
|
||
phi, gamma = float(backfill["phi"]), float(backfill["gamma_t"])
|
||
gamma_sat = float(backfill.get("gamma_sat") or gamma)
|
||
hw = min(max(water_ratio, 0.0), 1.0) * H
|
||
|
||
if method == "rankine":
|
||
x_top, theta, delta = heel, 0.0, beta_deg
|
||
else:
|
||
top = xs[zs >= zt - 1e-3]
|
||
x_top = float(top.max() if back > 0 else top.min()) # 배면 꼭대기 모서리
|
||
theta = math.degrees(math.atan2(-(x_top - heel) * back, H))
|
||
delta = delta_ratio * phi
|
||
# 가상면과 벽 사이 흙 — 벽 쪽 반평면 ∩ 벽 상자 − 벽 · 가상면에 붙은 조각만(앞면 앞 흙 빼고)
|
||
reach = 10.0 * (H + B + 1.0)
|
||
side = [(heel, zb), (x_top, zt), (x_top - back * reach, zt), (heel - back * reach, zb)]
|
||
plane = LineString([(heel, zb), (x_top, zt)])
|
||
rest = Polygon(side).intersection(box(*wall.bounds)).difference(wall)
|
||
parts = [g for g in getattr(rest, "geoms", [rest]) if isinstance(g, Polygon)]
|
||
soil = unary_union([g for g in parts if g.area > 1e-9 and g.distance(plane) < 1e-6])
|
||
soil_w = soil.area * gamma
|
||
soil_x = soil.centroid.x if soil_w > 0 else heel
|
||
soil_z = soil.centroid.y if soil_w > 0 else zb
|
||
|
||
ka = (
|
||
ka_rankine(phi, beta_deg)
|
||
if method == "rankine"
|
||
else ka_coulomb(phi, delta, theta, beta_deg)
|
||
)
|
||
q_eff = surcharge_kpa * (
|
||
1.0
|
||
if method == "rankine"
|
||
else math.cos(math.radians(theta)) / math.cos(math.radians(theta - beta_deg))
|
||
)
|
||
Pa, ya = _pressure(ka, H, gamma, gamma_sat, q_eff, hw)
|
||
loads = [("earth", Pa, ya)]
|
||
kae = None
|
||
if kh > 0:
|
||
psi = math.degrees(math.atan(kh))
|
||
kae = ka_coulomb(phi, delta, theta, beta_deg, psi)
|
||
Pae, _ = _pressure(kae, H, gamma, gamma_sat, q_eff, hw)
|
||
loads.append(("seismic_earth", max(Pae - Pa, 0.0), 0.6 * H))
|
||
incline = math.radians(delta + theta)
|
||
|
||
def plane_x(y: float) -> float: # 가상면 위 높이 y 자리 x
|
||
return heel + (x_top - heel) * (y / H if H > 0 else 0.0)
|
||
|
||
def arm(x: float) -> float: # 앞굽에서 배면 쪽 수평 팔 길이
|
||
return (x - toe) * back
|
||
|
||
W = wall.area * wall_gamma
|
||
xw, zw = wall.centroid.x, wall.centroid.y
|
||
Ph = sum(P for _, P, _ in loads) * math.cos(incline)
|
||
Pv = sum(P for _, P, _ in loads) * math.sin(incline)
|
||
m_res = W * arm(xw) + soil_w * arm(soil_x)
|
||
m_over = 0.0
|
||
for _, P, y in loads:
|
||
m_res += P * math.sin(incline) * arm(plane_x(y))
|
||
m_over += P * math.cos(incline) * y
|
||
Pw = 0.5 * GAMMA_W * hw**2
|
||
U = 0.5 * GAMMA_W * hw * B
|
||
m_over += Pw * hw / 3.0 + U * (2.0 * B / 3.0)
|
||
inertia = kh * (W + soil_w)
|
||
m_over += kh * W * (zw - zb) + kh * soil_w * (soil_z - zb)
|
||
H_total = Ph + Pw + inertia
|
||
V = W + soil_w + Pv - U
|
||
|
||
phi_f = float(foundation["phi"])
|
||
mu = base_friction if base_friction is not None else math.tan(math.radians(2.0 / 3.0 * phi_f))
|
||
sliding = (V * mu + base_adhesion * B) / H_total if H_total > 0 else math.inf
|
||
overturning = m_res / m_over if m_over > 0 else math.inf
|
||
xbar = (m_res - m_over) / V if V > 0 else -math.inf
|
||
e = B / 2.0 - xbar
|
||
if xbar <= 0:
|
||
q_max, b_eff = math.inf, 0.0
|
||
elif abs(e) <= B / 6.0:
|
||
q_max, b_eff = V / B * (1.0 + 6.0 * abs(e) / B), B - 2.0 * abs(e)
|
||
else:
|
||
q_max, b_eff = 2.0 * V / (3.0 * min(xbar, B - xbar)), B - 2.0 * abs(e)
|
||
nc, nq, ng = bearing_factors(phi_f)
|
||
alpha = math.degrees(math.atan2(H_total, V)) if V > 0 else 90.0
|
||
i_cq = (1.0 - alpha / 90.0) ** 2
|
||
i_g = (1.0 - alpha / phi_f) ** 2 if phi_f > 0 and alpha < phi_f else 0.0
|
||
gf = float(foundation["gamma_t"])
|
||
q_ult = (
|
||
float(foundation.get("c") or 0.0) * nc * i_cq
|
||
+ gf * max(embed_m, 0.0) * nq * i_cq
|
||
+ 0.5 * gf * max(b_eff, 0.0) * ng * i_g
|
||
)
|
||
bearing = q_ult / q_max if q_max > 0 and math.isfinite(q_max) else 0.0
|
||
case = "seismic" if kh > 0 else "normal"
|
||
fs = {"sliding": sliding, "overturning": overturning, "bearing": bearing}
|
||
need = REQUIRED[case]
|
||
return {
|
||
"case": case,
|
||
"method": method,
|
||
"geometry": {"H": H, "B": B, "toe_x": toe, "heel_x": heel, "theta_deg": theta},
|
||
"coefficients": {"ka": ka, "kae": kae, "delta_deg": delta, "beta_deg": beta_deg},
|
||
"loads": {
|
||
"W": W,
|
||
"W_soil": soil_w,
|
||
"Pa": Pa,
|
||
"Pa_arm": ya,
|
||
"Ph": Ph,
|
||
"Pv": Pv,
|
||
"Pw": Pw,
|
||
"U": U,
|
||
"inertia": inertia,
|
||
"V": V,
|
||
"H": H_total,
|
||
"M_resist": m_res,
|
||
"M_overturn": m_over,
|
||
},
|
||
"base": {"e": e, "q_max": q_max, "q_ult": q_ult, "B_eff": b_eff, "mu": mu, "alpha": alpha},
|
||
"fs": fs,
|
||
"required": dict(need),
|
||
"ok": {k: bool(fs[k] >= need[k]) for k in fs},
|
||
}
|