# -*- 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"]))