fix(E03): Spencer · M-P 해 없음 = 「수렴 안 됨」 — 넓은 λ 다시 찾기 · 수렴 정보 칸 (532-2 덧 3 · 요구 351)
- 좁은 λ 에 해 없으면 넓은 λ(Spencer −2 ~ 2 · M-P ±π · 41 점 거친 훑기 → 뉴턴) 다시 찾음 - Bishop 임계 원에서도 해 없음 + 찾은 최소 > Bishop = 그 방법 원호 「해 없음」(F None · status · reason) · 최소 F 후보에서 빠짐 - 수렴 정보 칸 residual · lambda_range · status(converged · no_solution · rejected) 덧붙임 · 해 없음 때도 F_m · F_f–λ 곡선(넓은 범위) - E03 방법 표 빈 값 = 「수렴 안 됨」(View no_solution 칸 · 옛 저장본은 없음) - 시험 test_532_2_stab_nosolution 새로 · 검증_sub4 59 측점 Bishop · Fellenius · Janbu 값 차 0 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,15 +19,17 @@ SAFETY_MAP_TOP = 50
|
||||
_MAP_POINTS = 25
|
||||
|
||||
|
||||
def _curve(sl, center, method, f=None) -> dict:
|
||||
"""F_m · F_f 대 λ (화면 그래프) — Spencer = 상수 f · M-P = 반정현."""
|
||||
def _curve(sl, center, method, f=None, wide=False) -> dict:
|
||||
"""F_m · F_f 대 λ (화면 그래프) — Spencer = 상수 f · M-P = 반정현 · wide = 넓은 범위(해 없음이 보이게)."""
|
||||
if method == "spencer":
|
||||
grid, fm, ff = lem.lambda_curve(
|
||||
sl, center, np.linspace(*lem.LAMBDA_RANGE, gle.CURVE_POINTS)
|
||||
rng, n = (
|
||||
(lem.LAMBDA_WIDE, lem.WIDE_POINTS) if wide else (lem.LAMBDA_RANGE, gle.CURVE_POINTS)
|
||||
)
|
||||
grid, fm, ff = lem.lambda_curve(sl, center, np.linspace(*rng, n))
|
||||
fm, ff = fm[:, 0], ff[:, 0]
|
||||
else:
|
||||
grid, fm, ff = gle.lambda_curves(sl, center, f)
|
||||
grid = np.linspace(*gle.LAMBDA_WIDE, lem.WIDE_POINTS) if wide else None
|
||||
grid, fm, ff = gle.lambda_curves(sl, center, f, grid)
|
||||
fm, ff = fm[0], ff[0]
|
||||
num = lambda v: [float(x) if np.isfinite(x) else None for x in v] # noqa: E731
|
||||
return {"lambda": num(grid), "Fm": num(fm), "Ff": num(ff)}
|
||||
@@ -75,10 +77,29 @@ def describe(prep, kind, geom, method, n, min_depth, seed_F=None) -> dict:
|
||||
else:
|
||||
out["moment_center"] = [float(s * center[0, 0]), float(center[0, 1])]
|
||||
if method in ("spencer", "morgenstern_price"):
|
||||
out["lambda"] = lam
|
||||
out["theta_deg"] = float(np.degrees(np.arctan(lam)))
|
||||
if np.isfinite(F):
|
||||
out["lambda_curve"] = _curve(sl, center, method, f)
|
||||
# 수렴 정보 — λ · 반복 수 · 잔차(max |F_m − F|, |F_f − F|) · 상태 · 해 없음 까닭
|
||||
solved = np.isfinite(F)
|
||||
wide = bool(res["wide"][0])
|
||||
rng = (
|
||||
(lem.LAMBDA_WIDE if wide else lem.LAMBDA_RANGE)
|
||||
if method == "spencer"
|
||||
else (gle.LAMBDA_WIDE if wide else gle.LAMBDA_RANGE)
|
||||
)
|
||||
out["lambda"] = lam if solved else None
|
||||
out["theta_deg"] = float(np.degrees(np.arctan(lam))) if solved else None
|
||||
r = float(res["residual"][0])
|
||||
out["residual"] = r if np.isfinite(r) else None
|
||||
out["lambda_range"] = [float(rng[0]), float(rng[1])]
|
||||
crossed = bool(res["converged"][0])
|
||||
out["status"] = "converged" if solved else ("rejected" if crossed else "no_solution")
|
||||
out["reason"] = (
|
||||
None
|
||||
if solved
|
||||
else "해는 있으나 버림(m_α < 0.2 · 저항 없음 · F 하한)"
|
||||
if crossed
|
||||
else f"F_m · F_f 가 λ {rng[0]:.2f} ~ {rng[1]:.2f} 안에서 안 만남(해 없음)"
|
||||
)
|
||||
out["lambda_curve"] = _curve(sl, center, method, f, wide or not solved)
|
||||
if method == "morgenstern_price":
|
||||
out["interslice_function"] = "half_sine"
|
||||
if method == "janbu":
|
||||
|
||||
@@ -17,6 +17,8 @@ _TINY = 1e-9
|
||||
CURVE_POINTS = 21
|
||||
#: M-P λ 범위 — 반정현 평균 2/π 라 평균 X/E 가 Spencer 범위(LEM.LAMBDA_RANGE)와 같게 π/2 배
|
||||
LAMBDA_RANGE = tuple(v * np.pi / 2 for v in lem.LAMBDA_RANGE)
|
||||
#: 위 범위에 해가 없을 때만 다시 찾는 넓은 범위(같은 π/2 배 · 거친 훑기 → 뉴턴) — 그래도 없으면 「해 없음」
|
||||
LAMBDA_WIDE = tuple(v * np.pi / 2 for v in lem.LAMBDA_WIDE)
|
||||
|
||||
|
||||
def interslice_function(xe: np.ndarray, kind: str = "half_sine") -> np.ndarray:
|
||||
@@ -167,21 +169,39 @@ def _newton2(sl, center, f, F, lam, iters=40):
|
||||
return F, lam, it
|
||||
|
||||
|
||||
def _solve(sl, center, f, grid, fm, ff, F0, rng):
|
||||
"""곡선 교점 → 뉴턴 → (F, λ, 반복, 수렴, 잔차) — 수렴 = 두 잔차 < 1e-4 · λ 범위 안 · 참고값 0.5~2 배."""
|
||||
F, lam = _crossing(grid, fm, ff)
|
||||
F, lam, it = _newton2(sl, center, f, F, lam)
|
||||
ok = np.isfinite(F)
|
||||
safe = np.where(ok & (F > 0), F, 1.0)
|
||||
rm, rff, _ = _fm_ff(sl, center, safe, np.where(ok, lam, 0.0), f)
|
||||
res = np.where(ok, np.maximum(np.abs(rm - F), np.abs(rff - F)), np.nan)
|
||||
conv = ok & (res < 1e-4) & (lam >= rng[0]) & (lam <= rng[1])
|
||||
if F0 is not None:
|
||||
conv &= (F > 0.5 * F0) & (F < 2.0 * F0)
|
||||
return F, lam, it, conv, res
|
||||
|
||||
|
||||
def morgenstern_price(sl: dict, center: np.ndarray, kind="half_sine", F0=None, curve=True):
|
||||
"""M-P (F, λ) · 수렴 · (curve) F_m · F_f–λ 곡선. F0 = 출발 참고값(Spencer · Bishop) — 엉뚱한 근 거름."""
|
||||
f = interslice_function(sl["xe"], kind)
|
||||
grid, fm, ff = lambda_curves(sl, center, f, F0=F0)
|
||||
F, lam = _crossing(grid, fm, ff)
|
||||
F, lam, it = _newton2(sl, center, f, F, lam)
|
||||
F, lam, it, conv, res = _solve(sl, center, f, grid, fm, ff, F0, LAMBDA_RANGE)
|
||||
wide = ~conv & sl["valid"]
|
||||
if wide.any():
|
||||
# 좁은 범위에 해 없음 → 넓은 범위 거친 훑기 → 뉴턴(그 면만)
|
||||
sub, cw, fw = lem._subset(sl, wide), center[wide], f[wide]
|
||||
g2 = np.linspace(*LAMBDA_WIDE, lem.WIDE_POINTS)
|
||||
F0w = None if F0 is None else np.asarray(F0, float)[wide]
|
||||
_, fm2, ff2 = lambda_curves(sub, cw, fw, g2, F0w)
|
||||
F2, l2, it2, c2, r2 = _solve(sub, cw, fw, g2, fm2, ff2, F0w, LAMBDA_WIDE)
|
||||
F[wide], lam[wide], it[wide], conv[wide], res[wide] = F2, l2, it[wide] + it2, c2, r2
|
||||
ok = np.isfinite(F)
|
||||
safe = np.where(ok & (F > 0), F, 1.0)
|
||||
rm, rff, den = _fm_ff(sl, center, safe, np.where(ok, lam, 0.0), f)
|
||||
p = lem._pre(sl)
|
||||
N = forces(sl, safe, np.where(ok, lam, 0.0), f)[0]
|
||||
conv = ok & (np.abs(rm - F) < 1e-4) & (np.abs(rff - F) < 1e-4)
|
||||
conv &= (lam >= LAMBDA_RANGE[0]) & (lam <= LAMBDA_RANGE[1])
|
||||
if F0 is not None:
|
||||
conv &= (F > 0.5 * F0) & (F < 2.0 * F0)
|
||||
malpha_ok = np.all(den >= lem.MALPHA_MIN, axis=1)
|
||||
R = p["cu"] + N * sl["tanphi"]
|
||||
good = sl["valid"] & conv & (F > lem.F_MIN) & malpha_ok & (R.sum(1) > 0)
|
||||
@@ -191,6 +211,8 @@ def morgenstern_price(sl: dict, center: np.ndarray, kind="half_sine", F0=None, c
|
||||
"iterations": it,
|
||||
"converged": conv,
|
||||
"malpha_ok": malpha_ok,
|
||||
"residual": res,
|
||||
"wide": wide,
|
||||
"function": kind,
|
||||
}
|
||||
if curve:
|
||||
|
||||
@@ -22,6 +22,9 @@ MALPHA_MIN = 0.2
|
||||
#: 윗 덩이가 아랫 덩이를 내리누르면 λ < 0. 아래 한도 −1.25 = SLOPE/W GLE λ 범위 · 위는 작은 + 만 허용
|
||||
#: (아랫 덩이를 들어 올리는 해 = 비현실 근 — Yamagami & Ueta 대조에서 λ ≈ +1 근이 F 를 9 % 낮췄음).
|
||||
LAMBDA_RANGE = (-1.25, 0.25)
|
||||
#: 위 범위에 해가 없을 때만 다시 찾는 넓은 범위(거친 훑기 → 뉴턴) — 그래도 없으면 「해 없음」
|
||||
LAMBDA_WIDE = (-2.0, 2.0)
|
||||
WIDE_POINTS = 41
|
||||
#: 이보다 작은 F = 1/F 항이 터진 엉뚱한 근(실제 비탈 F 는 이 위) — 버림.
|
||||
F_MIN = 0.05
|
||||
TOL = 1e-6
|
||||
@@ -467,20 +470,30 @@ def spencer(sl: dict, center: np.ndarray, F0=None, scan: bool = True) -> dict:
|
||||
lam = np.zeros(S)
|
||||
# 엉뚱한 근(F → 0 쪽) 거르기 — 엄밀해는 출발값(Bishop)의 절반~두 배 안에 있음
|
||||
near = lambda f, ref: (f > 0.5 * ref) & (f < 2.0 * ref) # noqa: E731
|
||||
F, lam, it, conv = _spencer_newton(sl, center, F, lam)
|
||||
F, lam, it, conv, res = _spencer_newton(sl, center, F, lam)
|
||||
conv &= near(F, start)
|
||||
bad = ~conv & sl["valid"]
|
||||
if scan and bad.any():
|
||||
wide = np.zeros(S, bool)
|
||||
for grid, rng in (
|
||||
(None, LAMBDA_RANGE),
|
||||
(np.linspace(*LAMBDA_WIDE, WIDE_POINTS), LAMBDA_WIDE),
|
||||
):
|
||||
bad = ~conv & sl["valid"]
|
||||
if not (scan and bad.any()):
|
||||
break
|
||||
sub = _subset(sl, bad)
|
||||
Fs, ls = _spencer_scan(sub, center[bad])
|
||||
F2, l2, it2, c2 = _spencer_newton(sub, center[bad], Fs, ls)
|
||||
F[bad], lam[bad], it[bad], conv[bad] = F2, l2, it[bad] + it2, c2 & near(F2, start[bad])
|
||||
Fs, ls = _spencer_scan(sub, center[bad], grid)
|
||||
F2, l2, it2, c2, r2 = _spencer_newton(sub, center[bad], Fs, ls, rng=rng)
|
||||
F[bad], lam[bad], it[bad], res[bad] = F2, l2, it[bad] + it2, r2
|
||||
conv[bad] = c2 & near(F2, start[bad])
|
||||
wide[bad] = rng is LAMBDA_WIDE
|
||||
out = _finish(sl, F, it, conv, lam)
|
||||
out["lambda"] = np.where(np.isfinite(out["F"]), lam, np.nan)
|
||||
out["residual"] = res
|
||||
out["wide"] = wide
|
||||
return out
|
||||
|
||||
|
||||
def _spencer_newton(sl, center, F, lam, iters=25):
|
||||
def _spencer_newton(sl, center, F, lam, iters=25, rng=LAMBDA_RANGE):
|
||||
"""(F_m − F, F_f − F) = 0 뉴턴 — 걸음 크기 제한(F 절반 · λ 0.3) · 수렴 = 두 잔차 < 1e-4 · λ 범위 안.
|
||||
|
||||
끝난 면은 빼고(남은 면이 절반 아래면 묶음을 줄임) · F < F_MIN 으로 빠진 면은 버림(어차피 `_finish` 가 버림).
|
||||
@@ -520,8 +533,8 @@ def _spencer_newton(sl, center, F, lam, iters=25):
|
||||
rm = _fm(sl, center, F, lam, nd=nd)[0] - F
|
||||
rf = _ff(sl, F, lam, nd=nd)[0] - F
|
||||
conv = done & np.isfinite(F) & (F > 0) & (np.abs(rm) < 1e-4) & (np.abs(rf) < 1e-4)
|
||||
conv &= (lam >= LAMBDA_RANGE[0]) & (lam <= LAMBDA_RANGE[1])
|
||||
return F, lam, it, conv
|
||||
conv &= (lam >= rng[0]) & (lam <= rng[1])
|
||||
return F, lam, it, conv, np.maximum(np.abs(rm), np.abs(rf))
|
||||
|
||||
|
||||
def lambda_curve(sl, center, grid=None):
|
||||
@@ -538,9 +551,9 @@ def lambda_curve(sl, center, grid=None):
|
||||
return grid, fm_all, ff_all
|
||||
|
||||
|
||||
def _spencer_scan(sl, center):
|
||||
def _spencer_scan(sl, center, grid=None):
|
||||
"""λ 를 훑어 F_m(λ) − F_f(λ) 부호가 바뀌는 곳(λ=0 가까운 쪽)을 출발점으로."""
|
||||
grid, fm_all, ff_all = lambda_curve(sl, center)
|
||||
grid, fm_all, ff_all = lambda_curve(sl, center, grid)
|
||||
S = len(sl["W"])
|
||||
diff = fm_all - ff_all
|
||||
best_F, best_l = np.full(S, 1.5), np.zeros(S)
|
||||
|
||||
@@ -487,6 +487,17 @@ def analyze(
|
||||
if g is not None:
|
||||
best_circles[m] = g
|
||||
results[m]["circular"] = _describe(prep, "circle", g, m, n_slices, min_depth)
|
||||
# 엄밀해(Spencer · M-P)가 Bishop 임계 원에서 해 없음 · 찾은 최소가 Bishop 보다 높음 = 임계 둘레에서
|
||||
# 풀리는 원이 없어 먼 원을 고른 것 → 그 방법 원호 = 「해 없음」(Bishop 임계 원 · F None · 까닭 · F–λ 곡선)
|
||||
ref = best_circles.get("bishop") if "bishop" in results else None
|
||||
fb = (results.get("bishop", {}).get("circular") or {}).get("F")
|
||||
for m in [k for k in ("spencer", "morgenstern_price") if k in methods and ref and fb]:
|
||||
cur = results[m].get("circular")
|
||||
if cur and cur["F"] is not None and cur["F"] <= fb:
|
||||
continue
|
||||
at = _describe(prep, "circle", ref, m, n_slices, min_depth)
|
||||
if at["F"] is None:
|
||||
results[m]["circular"] = at
|
||||
if noncircular:
|
||||
mv = polyline_vertices
|
||||
seeds = []
|
||||
|
||||
@@ -69,7 +69,11 @@ function methodTable(analysis: Analysis): HTMLElement {
|
||||
const body = el("tr");
|
||||
for (const m of METHODS) {
|
||||
head.append(el("th", { className: "is-num", text: t(`E03_Method_${m}` as LocaleKey) }));
|
||||
body.append(el("td", { className: "is-num", text: fmt(analysis.min_by_method[m], 3) }));
|
||||
const v = analysis.min_by_method[m];
|
||||
const unsolved = (v === null || v === undefined) && analysis.no_solution?.includes(m);
|
||||
body.append(
|
||||
el("td", { className: "is-num", text: unsolved ? t("E03_NoSolution") : fmt(v, 3) }),
|
||||
);
|
||||
}
|
||||
table.append(el("thead", { children: [head] }), el("tbody", { children: [body] }));
|
||||
return table;
|
||||
|
||||
@@ -251,6 +251,15 @@ def section_view(
|
||||
"required": side_row.get("required"),
|
||||
"ok": side_row.get("ok"),
|
||||
"min_by_method": {m: _r(v) for m, v in (case.get("min_F") or {}).items()},
|
||||
# 엄밀해(Spencer · M-P) 「해 없음」 면이 있는 방법 — 화면이 빈 값을 「수렴 안 됨」으로
|
||||
"no_solution": sorted(
|
||||
m
|
||||
for m, found in methods.items()
|
||||
if any(
|
||||
(found.get(k) or {}).get("status") == "no_solution"
|
||||
for k in ("circular", "noncircular")
|
||||
)
|
||||
),
|
||||
"critical": _surface(best, PRIMARY),
|
||||
"candidates": candidates,
|
||||
"slices": _slices(best.get("slices") or []) if best else [],
|
||||
|
||||
@@ -83,6 +83,8 @@ export interface Analysis {
|
||||
required: number | null;
|
||||
ok: boolean | null;
|
||||
min_by_method: Record<string, number | null>;
|
||||
/** 「해 없음」 면이 있는 방법(Spencer · M-P) — 옛 저장본은 없음 */
|
||||
no_solution?: string[];
|
||||
critical: Surface | null;
|
||||
candidates: Surface[];
|
||||
slices: SliceRow[];
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
# -*- 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 · 까닭 · 넓은 곡선.
|
||||
- 해 있는 면 = 상태 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.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
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-q"]))
|
||||
@@ -98,6 +98,7 @@ export const ui_locales_e03 = {
|
||||
E03_Converged: ["수렴", "Converged"],
|
||||
E03_Yes: ["예", "Yes"],
|
||||
E03_No: ["아니오", "No"],
|
||||
E03_NoSolution: ["수렴 안 됨", "Not converged"],
|
||||
E03_Iter: ["회", " iter."],
|
||||
E03_Height: ["비탈 높이", "Slope height"],
|
||||
E03_Center: ["원호 중심", "Center"],
|
||||
|
||||
Reference in New Issue
Block a user