353 lines
13 KiB
Python
353 lines
13 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""532-2 E03 2D 한계평형 솔버 — 공개 검증 예제 대조(±3 %).
|
||
|
||
출처(기대값 = 원문 그대로):
|
||
- S2VM = Rocscience Slide2 Slope Stability Verification Manual (2022) — 지식DB
|
||
`original/안정성평가/참고문헌/Rocscience_Slide2_SlopeStabilityVerification.pdf` · 문제 번호 = 그 책 번호.
|
||
- ACADS = Giam & Donald (1989) ACADS 사면 시험문제(S2VM #1~#9 에 실림).
|
||
- FK = Fredlund & Krahn (1977) Can. Geotech. J. 14(3) (S2VM #21).
|
||
- AT = Arai & Tagyo (1985) · YU = Yamagami & Ueta (1988) · LOUK = Loukidis et al. (2003) (S2VM #14~#17 · #62).
|
||
- 지정 활동면(원 · 꺾은선)은 방법 대조 · 탐색 문제는 상용 SW 탐색 결과 대조.
|
||
- Ordinary(Fellenius) 간극수압 = F&K 식 N = W·cosα − u·l (지식DB 02 §2.1) — Slide 는 (W − u·b)·cosα 라 r_u 문제 값이 다름.
|
||
|
||
./venv/Scripts/python.exe -m pytest resources/tester/test_532_2_stab_lem_benchmarks.py -q
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import sys
|
||
from pathlib import Path
|
||
|
||
import pytest
|
||
from shapely.geometry import Polygon, box
|
||
|
||
ROOT = Path(__file__).resolve().parents[2]
|
||
sys.path.insert(0, str(ROOT))
|
||
|
||
from E03_StabSection.E03_StabSection_Engine_Search import analyze, evaluate_specified # noqa: E402
|
||
|
||
TOL = 0.03
|
||
|
||
|
||
def mats(*rows):
|
||
return [{"id": i, "name": i, "gamma_t": g, "c": c, "phi": p} for i, g, c, p in rows]
|
||
|
||
|
||
def near(got, expected, tol=TOL):
|
||
assert got is not None, "안전율 없음"
|
||
assert abs(got / expected - 1) <= tol, (
|
||
f"{got:.4f} vs {expected} ({(got / expected - 1) * 100:+.2f} %)"
|
||
)
|
||
|
||
|
||
def circ(res, method):
|
||
return (res["methods"][method].get("circular") or {}).get("F")
|
||
|
||
|
||
def nonc(res, method):
|
||
return (res["methods"][method].get("noncircular") or {}).get("F")
|
||
|
||
|
||
ACADS_EXT = [[20, 20], [70, 20], [70, 35], [50, 35], [30, 25], [20, 25]]
|
||
ACADS1C_REGIONS = [
|
||
{
|
||
"material_id": "soil1",
|
||
"polygon": [[30, 25], [50, 35], [70, 35], [70, 31], [54, 31], [50, 29], [40, 27]],
|
||
},
|
||
{
|
||
"material_id": "soil2",
|
||
"polygon": [[40, 27], [50, 29], [54, 31], [70, 31], [70, 24], [52, 24]],
|
||
},
|
||
{
|
||
"material_id": "soil3",
|
||
"polygon": [[20, 20], [70, 20], [70, 24], [52, 24], [40, 27], [30, 25], [20, 25]],
|
||
},
|
||
]
|
||
ACADS1C_MATS = mats(("soil1", 19.5, 0, 38), ("soil2", 19.5, 5.3, 23), ("soil3", 19.5, 7.2, 20))
|
||
|
||
|
||
def test_acads_1a_homogeneous_circular_search():
|
||
# S2VM #1 (ACADS 1a) — Bishop 0.987 · Spencer 0.986 · Janbu 보정 0.990 · 심판값 1.00
|
||
m = {
|
||
"materials": mats(("s", 20, 3, 19.6)),
|
||
"regions": [{"material_id": "s", "polygon": ACADS_EXT}],
|
||
}
|
||
r = analyze(m, direction=-1, noncircular=False)
|
||
near(circ(r, "bishop"), 0.987)
|
||
near(circ(r, "spencer"), 0.986)
|
||
near(circ(r, "janbu"), 0.990)
|
||
|
||
|
||
def test_acads_1b_tension_crack_full_of_water():
|
||
# S2VM #2 (ACADS 1b) — 균열 깊이 4 m · 물 가득 — Bishop 1.596 · Spencer 1.592 · Janbu 보정 1.489
|
||
m = {
|
||
"materials": mats(("s", 20, 32, 10)),
|
||
"regions": [{"material_id": "s", "polygon": ACADS_EXT}],
|
||
"tension_crack": {"depth": 4.0, "water": 1.0},
|
||
}
|
||
r = analyze(m, direction=-1, noncircular=False)
|
||
near(circ(r, "bishop"), 1.596)
|
||
near(circ(r, "spencer"), 1.592)
|
||
near(circ(r, "janbu"), 1.489)
|
||
|
||
|
||
def test_acads_1c_three_layers():
|
||
# S2VM #3 (ACADS 1c) — Bishop 1.405 · Spencer 1.375 · Janbu 보정 1.357
|
||
r = analyze(
|
||
{"materials": ACADS1C_MATS, "regions": ACADS1C_REGIONS}, direction=-1, noncircular=False
|
||
)
|
||
near(circ(r, "bishop"), 1.405)
|
||
near(circ(r, "spencer"), 1.375)
|
||
near(circ(r, "janbu"), 1.357)
|
||
|
||
|
||
def test_acads_1d_three_layers_seismic():
|
||
# S2VM #4 (ACADS 1d) — kh 0.15 — Bishop 1.016 · Spencer 0.991 · Janbu 보정 0.965
|
||
m = {"materials": ACADS1C_MATS, "regions": ACADS1C_REGIONS, "seismic_kh": 0.15}
|
||
r = analyze(m, direction=-1, noncircular=False)
|
||
near(circ(r, "bishop"), 1.016)
|
||
near(circ(r, "spencer"), 0.991)
|
||
near(circ(r, "janbu"), 0.965)
|
||
|
||
|
||
def test_acads_2b_embankment_dam_specified_circle():
|
||
# S2VM #6 (ACADS 2b Talbingo 댐 · 성토) — 지정 원 (100.3, 291.0, R 278.8)
|
||
# Bishop 2.208 · Spencer 2.292 · Janbu 보정 2.073 · 심판값 2.29
|
||
ext = [
|
||
[0, 0], [315.5, 162], [319.5, 162], [321.6, 162], [327.6, 162], [386.9, 130.6], [394.1, 130.6],
|
||
[453.4, 97.9], [460.6, 97.9], [515, 65.3], [521.1, 65.3], [577.9, 31.4], [585.1, 31.4], [648, 0],
|
||
] # fmt: skip
|
||
core = [
|
||
[200.7, 0],
|
||
[311.9, 130.6],
|
||
[319.5, 162],
|
||
[321.6, 162],
|
||
[328.8, 146.1],
|
||
[331.3, 130.6],
|
||
[307.1, 0],
|
||
]
|
||
rest = Polygon(ext).difference(Polygon(core))
|
||
regions = [{"material_id": "core", "polygon": core}] + [
|
||
{"material_id": "rockfill", "polygon": list(p.exterior.coords)}
|
||
for p in getattr(rest, "geoms", [rest])
|
||
]
|
||
m = {"materials": mats(("rockfill", 20.4, 0, 45), ("core", 18.1, 85, 23)), "regions": regions}
|
||
r = evaluate_specified(m, circle=[100.3, 291.0, 278.8], direction=-1)
|
||
near(r["bishop"]["F"], 2.208)
|
||
near(r["spencer"]["F"], 2.292)
|
||
near(r["janbu"]["F"], 2.073)
|
||
|
||
|
||
def _acads3_model():
|
||
ext = Polygon([[20, 20], [84, 20], [84, 40], [67.5, 40], [43, 27.75], [20, 27.75]])
|
||
regions = [
|
||
{
|
||
"material_id": "soil",
|
||
"polygon": list(ext.intersection(box(0, 27, 100, 100)).exterior.coords),
|
||
},
|
||
{
|
||
"material_id": "seam",
|
||
"polygon": list(ext.intersection(box(0, 26.5, 100, 27)).exterior.coords),
|
||
},
|
||
{
|
||
"material_id": "soil",
|
||
"polygon": list(ext.intersection(box(0, 0, 100, 26.5)).exterior.coords),
|
||
},
|
||
]
|
||
return {
|
||
"materials": mats(("soil", 18.84, 28.5, 20), ("seam", 18.84, 0, 10)),
|
||
"regions": regions,
|
||
"groundwater": {"mode": "line", "line": [[20, 26.5], [84, 26.5]]},
|
||
}
|
||
|
||
|
||
def test_acads_3b_weak_seam_specified_polyline():
|
||
# S2VM #8 (ACADS 3b) — 약층 + 지하수위 · 지정 비원호 — Spencer 1.277 · Janbu 보정 1.294
|
||
r = evaluate_specified(
|
||
_acads3_model(),
|
||
polyline=[[41.85, 27.75], [44.0, 26.5], [63.5, 27.0], [73.31, 40.0]],
|
||
direction=-1,
|
||
methods=("janbu", "spencer"),
|
||
)
|
||
near(r["spencer"]["F"], 1.277)
|
||
near(r["janbu"]["F"], 1.294)
|
||
|
||
|
||
def test_acads_3a_weak_seam_noncircular_search():
|
||
# S2VM #7 (ACADS 3a) — 블록 탐색 Janbu 보정 1.275 · Spencer 1.258(심판값 1.24~1.27)
|
||
r = analyze(_acads3_model(), direction=-1, methods=("janbu", "spencer"))
|
||
near(nonc(r, "janbu"), 1.275)
|
||
near(nonc(r, "spencer"), 1.258)
|
||
|
||
|
||
def _acads4_model():
|
||
# S2VM #9 (ACADS 4) — 기운 약층 · 지하수위선 · 등분포 20 kPa + 선형 20→40 kPa
|
||
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)}] + [
|
||
{"material_id": "soil", "polygon": list(p.exterior.coords)}
|
||
for p in ext.difference(seam).geoms
|
||
]
|
||
linear = [{"x0": 70 + i, "x1": 71 + i, "q": 20 + 20 * (i + 0.5) / 10} for i in range(10)]
|
||
m = {
|
||
"materials": mats(("soil", 18.84, 28.5, 20), ("seam", 18.84, 0, 10)),
|
||
"regions": regions,
|
||
"surcharge": [{"x0": 23, "x1": 43, "q": 20}] + linear,
|
||
"groundwater": {
|
||
"mode": "line",
|
||
"line": [
|
||
[20, 27.75],
|
||
[43, 27.75],
|
||
[49, 29.8],
|
||
[60, 34],
|
||
[66, 35.8],
|
||
[74, 37.6],
|
||
[80, 38.4],
|
||
[84, 38.4],
|
||
],
|
||
},
|
||
}
|
||
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)
|
||
|
||
|
||
FK_EXT = [[0, 0], [180, 0], [180, 20], [140, 20], [60, 60], [0, 60]]
|
||
|
||
|
||
def test_fredlund_krahn_specified_circle_dry_and_ru():
|
||
# S2VM #21 (F&K 1977, ft · lbf) — 지정 원 (120, 90, R 80)
|
||
# 건기 Ordinary 1.928 · Bishop 2.080 · Spencer 2.073 / r_u 0.25 Ordinary 1.607 · Bishop 1.766 · Spencer 1.761
|
||
m = {
|
||
"materials": mats(("s", 120, 600, 20)),
|
||
"regions": [{"material_id": "s", "polygon": FK_EXT}],
|
||
}
|
||
r = evaluate_specified(
|
||
m, circle=[120, 90, 80], direction=1, methods=("ordinary", "bishop", "spencer")
|
||
)
|
||
near(r["ordinary"]["F"], 1.928)
|
||
near(r["bishop"]["F"], 2.080)
|
||
near(r["spencer"]["F"], 2.073)
|
||
m["groundwater"] = {"mode": "ru", "ru": 0.25}
|
||
r = evaluate_specified(
|
||
m, circle=[120, 90, 80], direction=1, methods=("ordinary", "bishop", "spencer")
|
||
)
|
||
near(r["ordinary"]["F"], 1.607)
|
||
near(r["bishop"]["F"], 1.766)
|
||
near(r["spencer"]["F"], 1.761)
|
||
|
||
|
||
def test_loukidis_seismic_with_ru():
|
||
# S2VM #62 — 건기 kh 0.432: Bishop 0.991 · Spencer 1.001 / r_u 0.5 + kh 0.132: Bishop 0.987 · Spencer 1.001
|
||
ext = [[-50, -25], [150, -25], [150, 25], [75, 25], [0, 0], [-50, 0]]
|
||
m = {
|
||
"materials": mats(("c", 20, 25, 30)),
|
||
"regions": [{"material_id": "c", "polygon": ext}],
|
||
"seismic_kh": 0.432,
|
||
}
|
||
r = analyze(m, direction=-1, noncircular=False, methods=("bishop", "spencer"))
|
||
near(circ(r, "bishop"), 0.991)
|
||
near(circ(r, "spencer"), 1.001)
|
||
m.update(seismic_kh=0.132, groundwater={"mode": "ru", "ru": 0.5})
|
||
r = analyze(m, direction=-1, noncircular=False, methods=("bishop", "spencer"))
|
||
near(circ(r, "bishop"), 0.987)
|
||
near(circ(r, "spencer"), 1.001)
|
||
|
||
|
||
AT_EXT = [[0, 0], [66, 0], [66, 35], [48, 35], [18, 15], [0, 15]]
|
||
|
||
|
||
def test_arai_tagyo_3_water_table():
|
||
# S2VM #16 — 지하수위선 · 원호 Bishop 1.118 · Spencer 1.118 · Janbu 보정 1.131 /
|
||
# 비원호 Janbu 보정 1.050 · Spencer 1.094
|
||
m = {
|
||
"materials": mats(("s", 18.82, 41.65, 15)),
|
||
"regions": [{"material_id": "s", "polygon": AT_EXT}],
|
||
"groundwater": {"mode": "line", "line": [[0, 15], [18, 15], [30, 23], [48, 29], [66, 32]]},
|
||
}
|
||
r = analyze(m, direction=-1)
|
||
near(circ(r, "bishop"), 1.118)
|
||
near(circ(r, "spencer"), 1.118)
|
||
near(circ(r, "janbu"), 1.131)
|
||
near(nonc(r, "janbu"), 1.050)
|
||
near(nonc(r, "spencer"), 1.094)
|
||
|
||
|
||
def test_arai_tagyo_2_weak_middle_layer():
|
||
# S2VM #15 — 약한 가운데 층 · Bishop 0.420 · Spencer 0.409 · Janbu 보정 0.423
|
||
regions = [
|
||
{"material_id": "upper", "polygon": [[24, 19], [48, 35], [72, 35]]},
|
||
{
|
||
"material_id": "middle",
|
||
"polygon": [[0, 3], [96, 35], [72, 35], [24, 19], [18, 15], [0, 15]],
|
||
},
|
||
{"material_id": "lower", "polygon": [[0, 3], [96, 3], [96, 35]]},
|
||
]
|
||
m = {
|
||
"materials": mats(
|
||
("upper", 18.82, 29.4, 12), ("middle", 18.82, 9.8, 5), ("lower", 18.82, 294, 40)
|
||
),
|
||
"regions": regions,
|
||
}
|
||
r = analyze(m, direction=-1, noncircular=False)
|
||
near(circ(r, "bishop"), 0.420)
|
||
near(circ(r, "spencer"), 0.409)
|
||
near(circ(r, "janbu"), 0.423)
|
||
|
||
|
||
def test_yamagami_ueta_ordinary_vs_bishop():
|
||
# S2VM #17 — Bishop 1.344 · Ordinary 1.278 / 비원호 Spencer 1.325
|
||
ext = [[0, 0], [25, 0], [25, 10], [15, 10], [5, 5], [0, 5]]
|
||
m = {
|
||
"materials": mats(("s", 17.64, 9.8, 10)),
|
||
"regions": [{"material_id": "s", "polygon": ext}],
|
||
}
|
||
r = analyze(m, direction=-1, methods=("ordinary", "bishop", "spencer"))
|
||
near(circ(r, "bishop"), 1.344)
|
||
near(circ(r, "ordinary"), 1.278)
|
||
near(nonc(r, "spencer"), 1.325)
|
||
|
||
|
||
def test_mirror_direction_same_factor():
|
||
# 좌우 뒤집은 같은 비탈 = 같은 안전율 · 임계면 좌표도 거울
|
||
m = {
|
||
"materials": mats(("s", 20, 3, 19.6)),
|
||
"regions": [{"material_id": "s", "polygon": ACADS_EXT}],
|
||
}
|
||
mirrored = {**m, "regions": [{"material_id": "s", "polygon": [[-x, z] for x, z in ACADS_EXT]}]}
|
||
a = evaluate_specified(m, circle=[29.104, 55.527, 30.527], direction=-1)
|
||
b = evaluate_specified(mirrored, circle=[-29.104, 55.527, 30.527], direction=1)
|
||
for k in ("ordinary", "bishop", "janbu", "spencer"):
|
||
assert abs(a[k]["F"] - b[k]["F"]) < 1e-9
|
||
assert abs(a["bishop"]["entry"][0] + b["bishop"]["entry"][0]) < 1e-9
|
||
near(a["bishop"]["F"], 0.987) # S2VM #1 Bishop 임계 원 그대로
|
||
|
||
|
||
def test_slice_table_and_convergence_info():
|
||
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",))[
|
||
"spencer"
|
||
]
|
||
assert r["converged"] and r["iterations"] > 0 and -1.25 <= r["lambda"] <= 0.25
|
||
rows = r["slices"]
|
||
assert len(rows) == 50 and all(row["width"] > 0 for row in rows)
|
||
# 절편간 힘 E — 머리(0) 에서 시작해 발끝에서 다시 0 근처(힘 평형이 닫힘)
|
||
weights = sum(row["weight"] for row in rows)
|
||
assert abs(rows[0]["E_left"]) < 1e-9
|
||
assert {"alpha_deg", "pore_pressure", "normal_eff", "shear_mobilized", "material"} <= rows[
|
||
0
|
||
].keys()
|
||
assert weights > 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
sys.exit(pytest.main([__file__, "-q"]))
|