Merge remote-tracking branches 'origin/sub_laptop_7' and 'origin/sub_laptop_8' into main_laptop_1
This commit is contained in:
@@ -36,6 +36,8 @@ WALL_MATERIAL = {
|
||||
"phi": 45.0,
|
||||
}
|
||||
DEFAULT_HALF_WIDTH_M = 40.0
|
||||
#: 탐색 한계 바깥 끝 — "model_end"(모형 끝 · Slide2 기본) · "2h"(비탈 끝 + max(5 m, 2H)) · 수(비탈 끝 + m)
|
||||
SEARCH_REACH = "model_end"
|
||||
|
||||
|
||||
def _polys(geom) -> list[Polygon]:
|
||||
@@ -180,11 +182,13 @@ def widen_ground(cross: dict, sampler, half_width: float = DEFAULT_HALF_WIDTH_M,
|
||||
return sorted(ground + added)
|
||||
|
||||
|
||||
def side_cases(design: dict, ground: list, line: list) -> list[dict]:
|
||||
def side_cases(
|
||||
design: dict, ground: list, line: list, reach: str | float = SEARCH_REACH
|
||||
) -> list[dict]:
|
||||
"""좌 · 우 비탈마다 역할(절토 · 성토) · 활동 방향 · 탐색 한계. 좌 = +offset.
|
||||
|
||||
탐색 한계 = 노면 반대쪽 끝 ~ 비탈 끝(설계선이 원지반과 만나는 점) 바깥 max(5 m, 2H) — 설계 비탈을
|
||||
지나는 면만 봄(바깥 자연 비탈 전체 붕괴는 E 광역 · 2차 몫). H = 노면 끝 ~ 비탈 끝 높이차.
|
||||
탐색 한계 = 노면 반대쪽 끝 ~ reach(`SEARCH_REACH` · 기본 모형 끝 = 깎기 위 · 쌓기 아래 자연 비탈 큰 면까지).
|
||||
H = 노면 끝 ~ 비탈 끝(설계선이 원지반과 만나는 점) 높이차.
|
||||
"""
|
||||
mode = design.get("section_mode") or "right_cut"
|
||||
roles = {
|
||||
@@ -215,10 +219,13 @@ def side_cases(design: dict, ground: list, line: list) -> list[dict]:
|
||||
else:
|
||||
day = edge
|
||||
H = abs(float(np.interp(day, lx, lz)) - float(np.interp(edge, lx, lz)))
|
||||
reach = day + sign * max(5.0, 2.0 * H)
|
||||
reach = min(max(reach, gx[0]), gx[-1])
|
||||
if reach == "model_end":
|
||||
far = float(gx[-1] if sign > 0 else gx[0])
|
||||
else:
|
||||
far = day + sign * (max(5.0, 2.0 * H) if reach == "2h" else float(reach))
|
||||
far = min(max(far, gx[0]), gx[-1])
|
||||
inner = right_edge if side == "left" else left_edge
|
||||
limits = tuple(sorted((inner, reach)))
|
||||
limits = tuple(sorted((inner, far)))
|
||||
direction = int(-sign) if inward else int(sign)
|
||||
out.append(
|
||||
{"side": side, "role": role, "direction": direction, "limits": limits, "height_m": H}
|
||||
@@ -237,6 +244,7 @@ def section_model_from_b06(
|
||||
seismic_kh: float = 0.0,
|
||||
road_surcharge_kpa: float = DEFAULT_ROAD_SURCHARGE_KPA,
|
||||
walls: list[dict] | None = None,
|
||||
search_reach: str | float = SEARCH_REACH,
|
||||
) -> dict:
|
||||
"""B06 횡단 파일(samples · frame) + 설계(design) → 해석 모형 + 비탈 목록(`sides`).
|
||||
|
||||
@@ -244,6 +252,7 @@ def section_model_from_b06(
|
||||
layers 기본 = 토사 측점 풍화토 한 층 · 암 측점 풍화토(암 경계까지) + 풍화암 / 연암.
|
||||
wet = 우기 — 지하수위 = 공사 뒤 지표(KDS 11 70 05 표 4.3-3 · 토사 · 풍화암 지표 포화).
|
||||
노면 하중 = 노면 끝 사이 등분포. 벽 도형(walls)은 B06 화면 산식(TS)이 그리므로 부르는 쪽이 넘김.
|
||||
search_reach = 탐색 한계 바깥 끝(`side_cases` reach).
|
||||
"""
|
||||
ground = ground or ground_from_cross(cross)
|
||||
line = [
|
||||
@@ -281,7 +290,7 @@ def section_model_from_b06(
|
||||
surcharge=loads,
|
||||
area_range=(line[0][0], line[-1][0]) if line else None,
|
||||
)
|
||||
model["sides"] = side_cases(design, ground, line or ground)
|
||||
model["sides"] = side_cases(design, ground, line or ground, search_reach)
|
||||
model["station"] = {"chainage_m": cross.get("chainage_m"), "label": cross.get("label")}
|
||||
return model
|
||||
|
||||
|
||||
@@ -40,6 +40,8 @@ SPENCER_SEED_POOL = 300
|
||||
CROSS_TOP = 10
|
||||
#: Spencer 원호 씨앗 — Bishop 임계 둘레 구름 크기
|
||||
CLOUD_SIZE = 200
|
||||
#: 비원호 출발 횟수 — 난수 씨앗 · 출발점을 바꿔 여러 번 찾아 최소(한 번은 국부 최소에 갇혀 ±2~4 % 흔들림)
|
||||
NONCIRC_STARTS = 3
|
||||
#: 엄밀해 「해 없음」 허용 — Bishop 임계 원에서 해 없어도 찾은 최소가 Bishop × (1 + 이 값) 안이면 값을 살림
|
||||
FAR_TOL = 0.02
|
||||
|
||||
@@ -526,14 +528,17 @@ def analyze(
|
||||
# Bishop)은 비원호 중심이 임의라 따로 최적화하지 않고 찾은 면에서 값만 셈(참고 칸 · 상용 SW 도
|
||||
# 비원호 최적화 = Janbu · Spencer · GLE) · M-P 는 찾은 면에서 값만
|
||||
found = []
|
||||
for m in [k for k in ("janbu", "spencer") if k in methods]:
|
||||
iters = LS_ITER_SPENCER if m == "spencer" else LS_ITER
|
||||
fmin, _x, geom, top = optimize_polyline(
|
||||
prep, lim, m, seeds, n_slices, min_depth, mv, seed, iters
|
||||
)
|
||||
seeds = [*top, *seeds]
|
||||
if fmin < _BIG:
|
||||
found.append(geom)
|
||||
for k in range(NONCIRC_STARTS):
|
||||
# 출발마다 난수 씨앗 + 출발점 묶음(가장 좋은 씨앗 + 다음 차례 7 개)을 바꿈
|
||||
run = list(seeds)
|
||||
for m in [x for x in ("janbu", "spencer") if x in methods]:
|
||||
iters = LS_ITER_SPENCER if m == "spencer" else LS_ITER
|
||||
fmin, _x, geom, top = optimize_polyline(
|
||||
prep, lim, m, run, n_slices, min_depth, mv, seed + k, iters
|
||||
)
|
||||
run = [*top, *run]
|
||||
if fmin < _BIG:
|
||||
found.append(geom)
|
||||
for m in methods:
|
||||
g = _cross(prep, "polyline", found, m, n_slices, min_depth)
|
||||
if g is not None:
|
||||
@@ -551,6 +556,7 @@ def analyze(
|
||||
out = {
|
||||
"direction": s,
|
||||
"limits": sorted([s * lim[0], s * lim[1]]),
|
||||
"model_x": sorted([s * float(prep.surface_x[0]), s * float(prep.surface_x[-1])]),
|
||||
"methods": results,
|
||||
"contour": contour,
|
||||
"safety_map": smap,
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
"""E03 2D 해석 결과 → 화면 꼴 (요구 345 · 347 · PLAN 532-3) — storage 를 모르는 순수 함수.
|
||||
|
||||
엔진 출력 = `E03_StabSection_Engine_Station.analyze_station` 그대로(cases × methods).
|
||||
판정 = 대표 방법(Bishop) 최소 안전율 ≥ 기준안전율(E02 `safety_factors[조건][깎기 · 쌓기]`).
|
||||
판정 = 원호 Bishop · 비원호 Spencer · M-P 중 작은 값 ≥ 기준안전율(E02 `safety_factors[조건][깎기 · 쌓기]` ·
|
||||
지식DB 06 해석법 조합) · 비원호가 원호 Bishop 보다 15 % 넘게 낮으면 「면 확인」(SLOPE/W 최적화 기준) ·
|
||||
판정 면 진입 · 진출이 모형 끝 1 m 안이면 「모형 끝 면」(edge · 값이 모형 폭에 따라 바뀜).
|
||||
대표 단면(보고서 몫) = 자동(쌓기 높이 ≥ 10 m · 깎기 ≥ 15 m · 최소 안전율 하위 5) + 사용자 켬/끔.
|
||||
좌표 = 엔진 그대로(x = B06 offset · 좌 + · z = 표고) — 화면이 같은 좌표로 그림.
|
||||
"""
|
||||
@@ -12,6 +14,16 @@ import math
|
||||
from typing import Any
|
||||
|
||||
PRIMARY = "bishop"
|
||||
#: 판정 후보(방법 · 면 꼴)
|
||||
GOVERNING = (
|
||||
("bishop", "circular"),
|
||||
("spencer", "noncircular"),
|
||||
("morgenstern_price", "noncircular"),
|
||||
)
|
||||
#: 비원호 F < 원호 Bishop F × 이 값 = 「면 확인」
|
||||
SURFACE_CHECK_RATIO = 0.85
|
||||
#: 판정 면 진입 · 진출이 모형 끝에서 이 거리(m) 안 = 「모형 끝 면」
|
||||
EDGE_M = 1.0
|
||||
CONDITIONS = ("dry", "wet", "seismic")
|
||||
REPRESENTATIVE_FILL_M = 10.0
|
||||
REPRESENTATIVE_CUT_M = 15.0
|
||||
@@ -34,9 +46,31 @@ def required_factor(factors: dict[str, Any], condition: str, role: str) -> float
|
||||
return float(value) if isinstance(value, int | float) else None
|
||||
|
||||
|
||||
def _governing(case: dict[str, Any]) -> dict[str, Any] | None:
|
||||
"""한 비탈 · 조건의 대표 방법 최소 면(원호 · 비원호 중 작은 쪽)."""
|
||||
return ((case.get("result") or {}).get("methods") or {}).get(PRIMARY, {}).get("min")
|
||||
def _governing(case: dict[str, Any]) -> tuple[str, dict[str, Any] | None, float | None, bool]:
|
||||
"""한 비탈 · 조건의 판정 면 — (방법, 면, 원호 Bishop F, 면 확인).
|
||||
|
||||
후보가 없으면(옛 결과 · 원호만 셈) Bishop 최소."""
|
||||
methods = (case.get("result") or {}).get("methods") or {}
|
||||
circ = ((methods.get(PRIMARY) or {}).get("circular") or {}).get("F")
|
||||
cands = [
|
||||
(m, found)
|
||||
for m, kind in GOVERNING
|
||||
if (found := (methods.get(m) or {}).get(kind)) and found.get("F") is not None
|
||||
]
|
||||
if not cands:
|
||||
return PRIMARY, (methods.get(PRIMARY) or {}).get("min"), circ, False
|
||||
method, best = min(cands, key=lambda c: c[1]["F"])
|
||||
check = method != PRIMARY and circ is not None and best["F"] < SURFACE_CHECK_RATIO * circ
|
||||
return method, best, circ, check
|
||||
|
||||
|
||||
def _edge(case: dict[str, Any], best: dict[str, Any] | None) -> bool | None:
|
||||
"""판정 면이 모형 끝에 닿음 — 옛 결과(모형 끝 모름) · 면 없음 = None."""
|
||||
ends = (case.get("result") or {}).get("model_x")
|
||||
if not ends or not best or not best.get("entry") or not best.get("exit"):
|
||||
return None
|
||||
xs = (best["entry"][0], best["exit"][0])
|
||||
return bool(min(xs) <= ends[0] + EDGE_M or max(xs) >= ends[1] - EDGE_M)
|
||||
|
||||
|
||||
def station_row(
|
||||
@@ -51,7 +85,7 @@ def station_row(
|
||||
for case in (out or {}).get("cases", []):
|
||||
cond, side, role = case["condition"], case["side"], case["role"]
|
||||
sides.setdefault(side, {"side": side, "role": role, "height_m": _r(case.get("height_m"))})
|
||||
best = _governing(case)
|
||||
method, best, circ, check = _governing(case)
|
||||
fs = best.get("F") if best else None
|
||||
need = required_factor(factors, cond, role)
|
||||
cases.setdefault(cond, {})[side] = {
|
||||
@@ -60,6 +94,10 @@ def station_row(
|
||||
"ok": None if fs is None or need is None else bool(fs >= need),
|
||||
"kind": best.get("kind") if best else None,
|
||||
"converged": best.get("converged") if best else None,
|
||||
"method": method if best else None,
|
||||
"fs_circular": _r(circ),
|
||||
"surface_check": check,
|
||||
"edge": _edge(case, best),
|
||||
}
|
||||
minimum: dict[str, float | None] = {}
|
||||
verdict: dict[str, str] = {}
|
||||
@@ -81,6 +119,9 @@ def station_row(
|
||||
"cases": cases,
|
||||
"min": minimum,
|
||||
"verdict": verdict,
|
||||
"surface_check": any(
|
||||
v["surface_check"] for sides in cases.values() for v in sides.values()
|
||||
),
|
||||
"error": error,
|
||||
"elapsed_s": (out or {}).get("elapsed_s"),
|
||||
}
|
||||
@@ -137,7 +178,7 @@ def governing_row(row: dict[str, Any]) -> dict[str, Any]:
|
||||
cases = row.get("cases") or {}
|
||||
worst = min(
|
||||
(
|
||||
(c["fs"] / c["required"], side, c["fs"])
|
||||
(c["fs"] / c["required"], side, c["fs"], c.get("method") or PRIMARY)
|
||||
for sides in cases.values()
|
||||
for side, c in sides.items()
|
||||
if c.get("fs") is not None and c.get("required")
|
||||
@@ -155,7 +196,7 @@ def governing_row(row: dict[str, Any]) -> dict[str, Any]:
|
||||
"fs_by_condition": {
|
||||
cond: sides[side]["fs"] for cond, sides in cases.items() if side in sides
|
||||
},
|
||||
"method": PRIMARY,
|
||||
"method": worst[3] if worst else PRIMARY,
|
||||
"verdict_overall": "ng" if "ng" in verdicts else "ok" if verdicts == {"ok"} else None,
|
||||
}
|
||||
|
||||
@@ -168,6 +209,14 @@ def summarize(rows: list[dict[str, Any]], conditions: list[str]) -> dict[str, An
|
||||
out[cond] = {
|
||||
"min_fs": min(values) if values else None,
|
||||
"ng": sum(1 for r in rows if r["verdict"].get(cond) == "ng"),
|
||||
"surface_check": sum(
|
||||
1
|
||||
for r in rows
|
||||
if any(v.get("surface_check") for v in (r["cases"].get(cond) or {}).values())
|
||||
),
|
||||
"edge": sum(
|
||||
1 for r in rows if any(v.get("edge") for v in (r["cases"].get(cond) or {}).values())
|
||||
),
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -231,7 +280,7 @@ def section_view(
|
||||
for case in out.get("cases", []):
|
||||
result = case.get("result") or {}
|
||||
methods = result.get("methods") or {}
|
||||
best = (methods.get(PRIMARY) or {}).get("min")
|
||||
method, best, _circ, _check = _governing(case)
|
||||
candidates = []
|
||||
for method, found in methods.items():
|
||||
for kind in ("circular", "noncircular"):
|
||||
@@ -250,6 +299,8 @@ def section_view(
|
||||
"direction": case.get("direction"),
|
||||
"required": side_row.get("required"),
|
||||
"ok": side_row.get("ok"),
|
||||
"surface_check": side_row.get("surface_check"),
|
||||
"edge": side_row.get("edge"),
|
||||
"min_by_method": {m: _r(v) for m, v in (case.get("min_F") or {}).items()},
|
||||
# 엄밀해(Spencer · M-P) 「해 없음」 면이 있는 방법 — 화면이 빈 값을 「수렴 안 됨」으로
|
||||
"no_solution": sorted(
|
||||
@@ -260,7 +311,7 @@ def section_view(
|
||||
for k in ("circular", "noncircular")
|
||||
)
|
||||
),
|
||||
"critical": _surface(best, PRIMARY),
|
||||
"critical": _surface(best, method),
|
||||
"candidates": candidates,
|
||||
"slices": _slices(best.get("slices") or []) if best else [],
|
||||
"grid": (
|
||||
|
||||
@@ -197,7 +197,8 @@ export function labelStride(intervalPx: number, minPx: number): number {
|
||||
return PLAN_LABEL_STRIDES.find((s) => s * intervalPx >= minPx) ?? 100;
|
||||
}
|
||||
|
||||
/** 측점 글 붙일 열쇠 — 본 측점(간격 배수) 중 걸음 배수 · 걸음 1 이면 보조 측점도 · 고른 측점은 늘 */
|
||||
/** 측점 글 붙일 열쇠 — 본 측점(간격 배수) 중 걸음 배수 · 걸음 1 이면 보조 측점도 · 고른 측점은 늘
|
||||
* (고른 측점 곁 반 걸음 안 글은 뺌 — 겹침) */
|
||||
export function planLabelKeys(
|
||||
rows: StationRow[],
|
||||
interval: number,
|
||||
@@ -205,10 +206,13 @@ export function planLabelKeys(
|
||||
active: string | null,
|
||||
): string[] {
|
||||
const step = interval > 0 ? interval : 1;
|
||||
const at = rows.find((r) => r.key === active)?.chainage_m;
|
||||
return rows
|
||||
.filter((r) => r.center && r.center[0] !== null && r.center[1] !== null)
|
||||
.filter((r) => {
|
||||
if (r.key === active || stride === 1) return true;
|
||||
if (r.key === active) return true;
|
||||
if (at !== undefined && Math.abs(r.chainage_m - at) < (stride * step) / 2) return false;
|
||||
if (stride === 1) return true;
|
||||
const n = Math.round(r.chainage_m / step);
|
||||
return Math.abs(r.chainage_m - n * step) < 0.05 && n % stride === 0;
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user