Merge remote-tracking branch 'origin/sub_laptop_4' into sub_laptop_3

This commit is contained in:
2026-10-26 03:19:27 +09:00
8 changed files with 1101 additions and 375 deletions
@@ -180,9 +180,8 @@ def test_acads_3a_weak_seam_noncircular_search():
near(nonc(r, "spencer"), 1.258)
def test_acads_4_inclined_seam_water_surcharge():
def _acads4_model():
# S2VM #9 (ACADS 4) — 기운 약층 · 지하수위선 · 등분포 20 kPa + 선형 20→40 kPa
# 최적화 비원호 Spencer 0.707 · Janbu 보정 0.699 (약층 좌표 = 저해상 그림 판독 — 출처 ±5 % 권함)
ext = Polygon([[20, 15], [84, 15], [84, 40], [67.5, 40], [43, 27.75], [20, 27.75]])
seam = Polygon([[20, 18.26], [84, 36.2], [84, 36.8], [20, 18.88]])
regions = [{"material_id": "seam", "polygon": list(ext.intersection(seam).exterior.coords)}] + [
@@ -208,7 +207,12 @@ def test_acads_4_inclined_seam_water_surcharge():
],
},
}
r = analyze(m, direction=-1, methods=("janbu", "spencer"))
return m
def test_acads_4_inclined_seam_water_surcharge():
# S2VM #9 — 최적화 비원호 Spencer 0.707 · Janbu 보정 0.699 (약층 좌표 = 저해상 그림 판독 — 출처 ±5 % 권함)
r = analyze(_acads4_model(), direction=-1, methods=("janbu", "spencer"))
near(nonc(r, "spencer"), 0.707)
near(nonc(r, "janbu"), 0.699)
@@ -0,0 +1,141 @@
# -*- coding: utf-8 -*-
"""532-2 2차 E03 2D — Morgenstern-Price(반정현) 대조 · 결과 자료(자유물체도 · F–λ 곡선 · Safety Map).
기대값 = Rocscience Slide2 Slope Stability Verification Manual(지식DB 참고문헌) 원문 GLE · M-P 값.
- #1 ACADS 1a GLE 0.986 · #3 ACADS 1c GLE 1.374 · #8 ACADS 3b 지정 비원호 GLE 1.262 ·
#9 ACADS 4 블록 + 최적화 GLE 0.683 · #21 F&K 지정 원 M-P 건기 2.075 · r_u 1.760(Slide 열).
- 뺀 것: #7 · #15 — 원문 GLE(1.275 · 0.437)가 같은 표 Spencer(1.246 · 0.409)보다 높음(Slide 탐색이 GLE
임계면을 놓친 값 · 같은 문제 #8 지정면 GLE 는 Spencer 보다 낮음) → 대조 기준으로 못 씀.
./venv/Scripts/python.exe -m pytest resources/tester/test_532_2_stab_mp_outputs.py -q
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pytest
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(Path(__file__).resolve().parent))
from E03_StabSection import E03_StabSection_Engine_GLE as gle # noqa: E402
from E03_StabSection import E03_StabSection_Engine_LEM as lem # noqa: E402
from E03_StabSection.E03_StabSection_Engine_Search import ( # noqa: E402
analyze,
evaluate_specified,
)
from E03_StabSection.E03_StabSection_Engine_Surface import ( # noqa: E402
poly_slices,
three_point_center,
)
from test_532_2_stab_lem_benchmarks import ( # noqa: E402
ACADS1C_MATS,
ACADS1C_REGIONS,
ACADS_EXT,
FK_EXT,
_acads3_model,
_acads4_model,
circ,
mats,
near,
nonc,
)
MP = "morgenstern_price"
SEAM_3B = [[41.85, 27.75], [44.0, 26.5], [63.5, 27.0], [73.31, 40.0]]
def test_mp_constant_function_equals_spencer():
# f(x) ≡ 1 이면 M-P 식 = Spencer 식 — 같은 F · λ
prep = lem.Prepared(_acads3_model(), -1)
pts = sorted((-x, z) for x, z in SEAM_3B)
vx, vz = np.array([[p[0] for p in pts]]), np.array([[p[1] for p in pts]])
sl = poly_slices(prep, vx, vz, 50)
center = three_point_center(vx, vz)
sp = lem.spencer(sl, center, np.array([1.3]))
mp = gle.morgenstern_price(sl, center, "constant", F0=np.array([1.3]))
assert abs(mp["F"][0] - sp["F"][0]) < 1e-6
assert abs(mp["lambda"][0] - sp["lambda"][0]) < 1e-5
def test_mp_specified_surfaces_slide2():
# #8 지정 비원호 GLE 1.262 · #21 지정 원 M-P 건기 2.075 · r_u 0.25 1.760
r = evaluate_specified(_acads3_model(), polyline=SEAM_3B, direction=-1, methods=(MP,))
near(r[MP]["F"], 1.262, 0.01)
m = {"materials": mats(("s", 120, 600, 20)), "regions": [{"material_id": "s", "polygon": FK_EXT}]}
near(evaluate_specified(m, circle=[120, 90, 80], direction=1, methods=(MP,))[MP]["F"], 2.075, 0.01)
m["groundwater"] = {"mode": "ru", "ru": 0.25}
near(evaluate_specified(m, circle=[120, 90, 80], direction=1, methods=(MP,))[MP]["F"], 1.760, 0.01)
def test_mp_search_slide2():
# #1 원호 GLE 0.986 · #3 원호 GLE 1.374
m = {"materials": mats(("s", 20, 3, 19.6)), "regions": [{"material_id": "s", "polygon": ACADS_EXT}]}
near(circ(analyze(m, direction=-1, noncircular=False), MP), 0.986, 0.01)
r = analyze({"materials": ACADS1C_MATS, "regions": ACADS1C_REGIONS}, direction=-1, noncircular=False)
near(circ(r, MP), 1.374, 0.01)
def test_mp_acads4_noncircular():
# #9 블록 + 최적화 GLE 0.683 (약층 좌표 = 그림 판독 → ±2 %)
r = analyze(_acads4_model(), direction=-1, methods=("janbu", "spencer", MP))
near(nonc(r, MP), 0.683, 0.02)
def _closure(rows, lam, f_last=1.0):
weight = sum(r["weight"] for r in rows)
for a, b in zip(rows, rows[1:]):
assert abs(a["E_right"] - b["E_left"]) < 1e-9 and abs(a["X_right"] - b["X_left"]) < 1e-9
assert abs(rows[-1]["E_right"]) < 1e-3 * weight # 끝 절편에서 힘 평형이 닫힘
assert abs(rows[0]["X_left"]) < 1e-12
for r in rows:
# 연직 평형 — N·cosα + S·sinα = W + (X_right − X_left) (지진 · 균열 없음)
a = np.radians(r["alpha_deg"])
lhs = r["normal_total"] * np.cos(a) + r["shear_mobilized"] * np.sin(a)
assert abs(lhs - (r["weight"] + r["X_right"] - r["X_left"])) < 1e-6 * weight
def test_free_body_and_lambda_curve_outputs():
m = {"materials": mats(("s", 20, 3, 19.6)), "regions": [{"material_id": "s", "polygon": ACADS_EXT}]}
r = evaluate_specified(m, circle=[29.104, 55.527, 30.527], direction=-1, methods=("spencer", MP))
for key in ("spencer", MP):
out = r[key]
rows = out["slices"]
assert {"normal_total", "pore_force", "shear_strength", "E_right", "X_right", "x_mid"} <= rows[0].keys()
_closure(rows, out["lambda"])
curve = out["lambda_curve"]
lam, fm, ff = (np.array(curve[k], float) for k in ("lambda", "Fm", "Ff"))
assert len(lam) == gle.CURVE_POINTS and np.isfinite(fm).sum() > 10
# 곡선 교점 = 해 (F · λ)
d = fm - ff
k = np.where(np.isfinite(d[:-1]) & np.isfinite(d[1:]) & (np.sign(d[:-1]) != np.sign(d[1:])))[0]
assert len(k)
j = k[np.argmin(np.abs(lam[k] - out["lambda"]))]
t = d[j] / (d[j] - d[j + 1])
assert abs(lam[j] + t * (lam[j + 1] - lam[j]) - out["lambda"]) < 0.02
assert abs(fm[j] + t * (fm[j + 1] - fm[j]) - out["F"]) < 0.01 * out["F"]
assert abs(r[MP]["slices"][-1]["X_right"]) < 1e-9 # 반정현 f = 0 (끝)
def test_safety_map_and_reference_only():
m = {"materials": mats(("s", 17.64, 9.8, 10)), "regions": [{"material_id": "s", "polygon": [[0, 0], [25, 0], [25, 10], [15, 10], [5, 5], [0, 5]]}]}
r = analyze(m, direction=-1)
smap = r["safety_map"]["surfaces"]
assert 0 < len(smap) <= 50
F = [s["F"] for s in smap]
assert F == sorted(F) and F[0] >= 0.97 * circ(r, "bishop")
assert all(len(s["points"]) == 25 and s["points"][0][0] < s["points"][-1][0] for s in smap)
# 비원호 Fellenius · Bishop = 참고 칸 · 최소 F 는 원호에서만
for k in ("ordinary", "bishop"):
res = r["methods"][k]
assert res["noncircular"]["reference_only"] is True
assert res["min"]["kind"] == "circle"
assert r["methods"]["spencer"]["min"]["F"] <= circ(r, "spencer") + 1e-12
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-q"]))