Files
Aislo/resources/tester/test_532_2_stab_nosolution.py
T

138 lines
6.0 KiB
Python

# -*- coding: utf-8 -*-
"""532-2 덧 3 엄밀해(Spencer · M-P) 해 없음 — 넓은 λ 다시 찾기 · 「수렴 안 됨」 · 수렴 정보 칸.
- 해 없는 견본 = 검증_sub4 STA.0+340 우기 깎기 비탈을 줄인 꼴(풍화토 위 · 풍화암 · 지표 수위 · 노면 하중):
Bishop 임계 원에서 F_m · F_f 가 λ −2 ~ 2(M-P ±π)에서 안 만남 → 옛 엔진은 먼 원(F 1.82 · Bishop 1.34 의
136 %)을 Spencer 값으로 냈음 → 이제 Spencer · M-P 원호 = F None · status no_solution · 까닭 · 넓은 곡선.
- 허용 2 % — 찾은 최소가 Bishop + 2 % 안이면 값을 살림(status converged_far).
- 해 있는 면 = 상태 converged · 잔차 < 1e-4 · λ 좁은 범위 안 · 곡선 21 점(옛 꼴 그대로).
./venv/Scripts/python.exe -m pytest resources/tester/test_532_2_stab_nosolution.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 test_532_2_stab_lem_benchmarks import ACADS_EXT, mats # noqa: E402
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 import E03_StabSection_Engine_Search as search # noqa: E402
from E03_StabSection.E03_StabSection_Engine_Search import ( # noqa: E402
analyze,
evaluate_specified,
)
MP = "morgenstern_price"
SURFACE = [
[-25.0, 876.6], [-17.0, 871.7], [-13.0, 867.7], [-11.5, 867.1],
[-3.9, 859.7], [-2.5, 856.6], [2.0, 857.0], [10.0, 850.3],
] # fmt: skip
NO_SOLUTION_MODEL = {
"materials": [
{"id": "ws", "gamma_t": 18.0, "gamma_sat": 19.0, "c": 15.0, "phi": 30.0},
{"id": "wr", "gamma_t": 20.0, "gamma_sat": 21.0, "c": 30.0, "phi": 33.0},
],
"regions": [
{"material_id": "ws", "polygon": [
[-24.5, 876.5], [-16.5, 871.4], [-13.0, 867.7], [-11.5, 867.1], [-8.0, 863.0],
[-5.5, 861.4], [-3.5, 858.9], [-8.0, 862.5], [-11.5, 866.6], [-14.5, 868.5],
[-16.5, 870.9], [-25.0, 876.1],
]},
{"material_id": "ws", "polygon": [
[-0.2, 856.9], [3.5, 855.3], [10.0, 849.2], [3.5, 854.8], [1.0, 855.4], [-0.8, 856.9],
]},
{"material_id": "wr", "polygon": [
[-3.0, 857.6], [-2.5, 856.6], [-0.8, 856.9], [1.0, 855.4], [3.5, 854.8],
[10.0, 849.2], [10.0, 801.5], [-25.0, 801.5], [-25.0, 876.1], [-16.5, 870.9],
[-5.0, 860.5],
]},
],
"surface": SURFACE,
"groundwater": {"mode": "line", "line": SURFACE},
"surcharge": [{"x0": -2.0162, "x1": 2.0, "q": 10.0}],
"bottom_z": 801.5,
} # fmt: skip
@pytest.fixture(scope="module")
def unsolved():
return analyze(
NO_SOLUTION_MODEL, direction=1, limits=[-9.561, 2.0], min_depth=0.3, noncircular=False
)
def test_no_solution_reported_not_a_far_circle(unsolved):
ms = unsolved["methods"]
bishop = ms["bishop"]["circular"]
assert 1.30 < bishop["F"] < 1.40
for key, rng in (("spencer", lem.LAMBDA_WIDE), (MP, gle.LAMBDA_WIDE)):
c = ms[key]["circular"]
assert c["F"] is None and c["status"] == "no_solution" and c["converged"] is False
assert c["lambda"] is None and "해 없음" in c["reason"]
assert c["lambda_range"] == pytest.approx(list(rng))
# 「해 없음」 면 = Bishop 임계 원 · 최소 F 후보에서 빠짐
assert c["radius"] == pytest.approx(bishop["radius"]) and c["center"] == bishop["center"]
assert ms[key]["min"] is None
assert isinstance(c["iterations"], int)
assert c["residual"] is None or c["residual"] > 1e-4
# F_m · F_f – λ 곡선은 넓은 범위 그대로(화면 그래프로 해 없음이 보이게)
curve = c["lambda_curve"]
assert len(curve["lambda"]) == lem.WIDE_POINTS
assert curve["lambda"][0] == pytest.approx(rng[0]) and curve["lambda"][-1] == pytest.approx(
rng[1]
)
assert sum(v is not None for v in curve["Fm"]) > 10
# 판정 쪽(Bishop · Fellenius · Janbu)은 그대로
assert all(ms[k]["min"]["F"] is not None for k in ("ordinary", "bishop", "janbu"))
def test_spencer_curve_has_no_crossing_in_wide_range(unsolved):
curve = unsolved["methods"]["spencer"]["circular"]["lambda_curve"]
lam = np.array(curve["lambda"], float)
d = np.array(curve["Fm"], float) - np.array(curve["Ff"], float)
near0 = np.isfinite(d) & (lam <= 0.25)
assert near0.sum() > 20 and np.all(d[near0] < 0)
def test_solved_surface_status_and_shape_unchanged():
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, rng in (("spencer", lem.LAMBDA_RANGE), (MP, gle.LAMBDA_RANGE)):
out = r[key]
assert out["status"] == "converged" and out["reason"] is None and out["converged"] is True
assert out["residual"] < 1e-4 and rng[0] <= out["lambda"] <= rng[1]
assert out["lambda_range"] == pytest.approx(list(rng))
assert len(out["lambda_curve"]["lambda"]) == gle.CURVE_POINTS
def test_far_tolerance_keeps_value_within_limit(monkeypatch):
"""허용(FAR_TOL) 안이면 찾은 최소를 살림(converged_far) — 견본은 136 % 라 2 % 기본에선 해 없음 그대로."""
monkeypatch.setattr(search, "FAR_TOL", 0.5)
r = analyze(
NO_SOLUTION_MODEL, direction=1, limits=[-9.561, 2.0], min_depth=0.3, noncircular=False,
methods=("bishop", "spencer"),
) # fmt: skip
fb = r["methods"]["bishop"]["circular"]["F"]
c = r["methods"]["spencer"]["circular"]
assert c["status"] == "converged_far" and fb < c["F"] <= 1.5 * fb
assert "살림" in c["reason"] and r["methods"]["spencer"]["min"]["F"] == c["F"]
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-q"]))