Files
Aislo/resources/tester/test_532_2_stab_lem_benchmarks.py
T

353 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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"]))