"""533-2 E04 3D 기둥 솔버 — 문헌 기준 문제 ±3 %. - Gens 외(1988) · Hungr 외(1989) · Scoops3D 6.1: 2:1 평면 사면 · 구 R=1 · 중심-지표 0.5 · c=0.1 γ=1 φ=0 → 해석해 1.402 · 부피 0.654. - Zhang(1988) 경우 1 · 2(PLoS ONE 18(7) e0287998 그림 5 · 표 2): 1:2 사면 H 12.2 m · 타원체 중심 (6.1, 0, 21.3) Rx=Rz=24.4 · Ry=78.2 · c 29 kPa φ 20° γ 18.8 → Hungr 3D Bishop 2.167 · Zhang 2.122 / 약층(c 0 · φ 10°, 발끝 아래 0.94 m — 그림 화소로 잼) → Hungr 1.620 · Zhang 1.533. - 경우 3 = 경우 2 + 수위선(Fredlund & Krahn 1977 그림 8 · Slide3 2D Extruded #16 그림 16: 뒤 경계 42.7 m 에서 발끝 위 6.1 m → 발끝 → 발끝 앞 지표) → Lam & Fredlund 1993 1.481~1.54 · Gharti 2011 1.49 · Zhang 1.441(경우 2 처럼 Zhang 은 다른 풀이보다 4~6 % 낮음). """ import numpy as np import pytest from E04_Stab3D.E04_Stab3D_Engine_Column import Strata from E04_Stab3D.E04_Stab3D_Engine_Search import evaluate_surface, governing def _mat(gamma, c, phi, mid="m"): return {"id": mid, "name": mid, "gamma_t": gamma, "gamma_sat": gamma, "c": c, "phi": phi} def _gens(dx=0.02, angle=0.0): """2:1 평면 사면 — 아래로 향하는 방향을 angle(rad) 만큼 돌림.""" beta = np.arctan(0.5) x = np.arange(-2, 2 + 1e-9, dx) xx, yy = np.meshgrid(x, x) down = np.array([np.cos(angle), np.sin(angle)]) z = -(xx * down[0] + yy * down[1]) * np.tan(beta) st = Strata(x, x, z, np.zeros((0,) + z.shape), [_mat(1.0, 0.1, 0.0)]) c = np.r_[0.5 * np.sin(beta) * down, 0.5 * np.cos(beta)] return st, c def test_gens_sphere_analytic(): st, c = _gens() r = evaluate_surface(st, c, 1.0) assert r["bishop"] == pytest.approx(1.402, rel=0.01) assert r["hovland"] == pytest.approx(1.402, rel=0.01) assert r["volume"] == pytest.approx(np.pi * 0.25 * 2.5 / 3, rel=0.01) # 구 모자 부피 0.6545 def test_rotated_slope_same_factor(): """Scoops3D 6.7 대칭 — 낙하 방향이 격자 축과 어긋나도 같은 F.""" f0 = evaluate_surface(*_gens(0.02), 1.0)["bishop"] f1 = evaluate_surface(*_gens(0.02, np.radians(33.0)), 1.0)["bishop"] assert f1 == pytest.approx(f0, rel=0.01) def _zhang(weak: bool, dx=0.5): x = np.arange(-15, 45 + 1e-9, dx) y = np.arange(-90, 90 + 1e-9, dx) xx, _ = np.meshgrid(x, y) z = np.clip(xx * 0.5, 0, 12.2) soil = _mat(18.8, 29.0, 20.0, "soil") if not weak: return Strata(x, y, z, np.zeros((0,) + z.shape), [soil]) zw = -0.94 bottoms = np.stack([np.full(z.shape, zw + 0.3), np.full(z.shape, zw)]) mats = [soil, _mat(18.8, 0.0, 10.0, "weak"), _mat(18.8, 1000.0, 45.0, "hard")] return Strata(x, y, z, bottoms, mats, hard=[False, False, True]) def test_zhang_case1_ellipsoid(): r = evaluate_surface(_zhang(False), [6.1, 0.0, 21.3], 24.4, 78.2 / 24.4, [-1.0, 0.0]) assert r["bishop"] == pytest.approx(2.167, rel=0.03) # Hungr 외 1989 assert r["bishop"] == pytest.approx(2.122, rel=0.03) # Zhang 1988 assert r["hovland"] < r["bishop"] # Hovland 은 보수적 def test_zhang_case2_weak_layer_truncated(): r = evaluate_surface(_zhang(True), [6.1, 0.0, 21.3], 24.4, 78.2 / 24.4, [-1.0, 0.0]) assert r["bishop"] == pytest.approx(1.620, rel=0.03) # Hungr 외 1989 assert r["truncated_ratio"] > 0.1 # 잘린 면 → Janbu 보정으로 전환하되 Bishop 이 낮으면 Bishop(작은 쪽) assert r["governing"] == pytest.approx(min(r["bishop"], r["janbu_corrected"])) assert 1.45 < r["janbu_corrected"] < 1.80 # 문헌 폭 1.53~1.67 둘레 def test_zhang_case3_weak_layer_piezometric_line(): st = _zhang(True) xx, _ = np.meshgrid(st.x, st.y) st.z_water = np.clip(xx * 6.1 / 42.67, 0.0, None) args = ([6.1, 0.0, 21.3], 24.4, 78.2 / 24.4, [-1.0, 0.0]) r = evaluate_surface(st, *args, groundwater={"mode": "line"}) assert 1.481 * 0.99 < r["bishop"] < 1.54 * 1.01 # Lam & Fredlund 1993 네 값 폭 assert r["bishop"] == pytest.approx(1.49, rel=0.03) # Gharti 2011 # 수위선이 낮추는 비 = Lam & Fredlund 경우 3/경우 2(0.940~0.951) dry = evaluate_surface(_zhang(True), *args)["bishop"] assert 0.93 < r["bishop"] / dry < 0.96 def test_pore_pressure_and_seismic(): st, c = _gens(0.04) dry = evaluate_surface(st, c, 1.0) # φ=0 이면 간극수압은 저항에 안 들어감 assert evaluate_surface(st, c, 1.0, groundwater={"mode": "ru", "ru": 0.3})[ "bishop" ] == pytest.approx(dry["bishop"]) # c−φ 흙: r_u 가 F 를 낮춤 · 깊은 지하수위는 영향 없음 · kh 는 낮춤 st2 = Strata(st.x, st.y, st.z_top, st.bottoms, [_mat(1.0, 0.05, 25.0)], z_water=st.z_top - 5.0) base = evaluate_surface(st2, c, 1.0)["bishop"] assert evaluate_surface(st2, c, 1.0, groundwater={"mode": "ru", "ru": 0.3})["bishop"] < base assert evaluate_surface(st2, c, 1.0, groundwater={"mode": "line"})["bishop"] == pytest.approx( base ) st2.z_water = st2.z_top + 0.0 assert evaluate_surface(st2, c, 1.0, groundwater={"mode": "line"})["bishop"] < base assert evaluate_surface(st2, c, 1.0, seismic_kh=0.1)["bishop"] < base def test_governing_picks_bishop_when_not_truncated(): res = { "bishop": np.array([1.5, 1.5]), "janbu_corrected": np.array([1.4, 1.4]), "truncated_ratio": np.array([0.0, 0.5]), } f, meth = governing(res) assert f.tolist() == [1.5, 1.4] and meth.tolist() == [0, 1]