From 3b73e819c026ebe26cb287243e243dc3cead1868 Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 04:22:36 +0900 Subject: [PATCH 1/2] =?UTF-8?q?fix(E04):=20=EC=9E=84=EA=B3=84=20=ED=99=9C?= =?UTF-8?q?=EB=8F=99=EC=B2=B4=20=3D=20=EC=97=94=EC=A7=84=20=EB=8D=A9?= =?UTF-8?q?=EC=9D=B4=20=EC=85=80(best.cells)=20=E2=80=94=20=EC=A7=80?= =?UTF-8?q?=EB=8F=84=20=ED=85=8C=EB=91=90=EB=A6=AC=20=C2=B7=20=EC=B5=9C?= =?UTF-8?q?=EC=86=8C=20=ED=91=9C=EC=8B=9C=20=C2=B7=203D=20=ED=99=9C?= =?UTF-8?q?=EB=8F=99=EB=A9=B4=20=C2=B7=20=EB=AA=BB=20=EB=9A=AB=EB=8A=94=20?= =?UTF-8?q?=EC=B8=B5=20=EC=B9=B8=20=C2=B7=20=EA=B0=80=EA=B9=8C=EC=9A=B4=20?= =?UTF-8?q?=EC=B8=A1=EC=A0=90=EC=9D=B4=20=ED=95=9C=20=EA=B5=AC=EA=B0=80=20?= =?UTF-8?q?=EC=9E=90=EB=A5=B8=20=EB=8B=A4=EB=A5=B8=20=EB=8D=A9=EC=9D=B4?= =?UTF-8?q?=EA=B9=8C=EC=A7=80=20=EA=B7=B8=EB=A6=AC=EB=8D=98=20=ED=9D=A0=20?= =?UTF-8?q?=C2=B7=20=EC=97=94=EC=A7=84=20=ED=8C=90=20e04-4=20(533-3)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- E04_Stab3D/E04_Stab3D_Engine_Search.py | 6 +++++- E04_Stab3D/E04_Stab3D_Run.py | 20 +++++++++++++++++--- E04_Stab3D/logic/E04_Stab3D_Mesh.ts | 5 +++-- E04_Stab3D/logic/E04_Stab3D_Raster.ts | 2 ++ resources/tester/test_533_3_e04_engine.py | 3 +++ resources/tester/test_533_3_e04_logic.py | 2 ++ 6 files changed, 32 insertions(+), 6 deletions(-) diff --git a/E04_Stab3D/E04_Stab3D_Engine_Search.py b/E04_Stab3D/E04_Stab3D_Engine_Search.py index 2dda11d70..490138b94 100644 --- a/E04_Stab3D/E04_Stab3D_Engine_Search.py +++ b/E04_Stab3D/E04_Stab3D_Engine_Search.py @@ -80,7 +80,7 @@ class SearchResult: f_min: np.ndarray # 셀별 최소 안전율(다스리는 값) · 닿지 않은 셀 = nan volume: np.ndarray # 그 임계 활동체 부피 method: np.ndarray # 0 = Bishop · 1 = Janbu 보정 - best: dict # 전체 최소 활동체(중심 · 반지름 · 비 · 방향 · 방법별 F · 부피) + best: dict # 전체 최소 활동체(중심 · 반지름 · 비 · 방향 · 방법별 F · 부피 · 셀) trials: int = 0 evaluated: int = 0 seconds: float = 0.0 @@ -422,6 +422,10 @@ def search(strata: Strata, params: SearchParams | None = None) -> SearchResult: center=xyz[part][trial[k]].tolist(), radius=float(r), ratio=float(ratio), + # 활동체 셀(iy·nx+ix) — 한 구가 자른 여러 덩이 중 이 덩이만 + cells=np.unique( + cols.iy[cols.group == k] * nx + cols.ix[cols.group == k] + ).tolist(), ) return improved diff --git a/E04_Stab3D/E04_Stab3D_Run.py b/E04_Stab3D/E04_Stab3D_Run.py index 9b646b528..fcd600263 100644 --- a/E04_Stab3D/E04_Stab3D_Run.py +++ b/E04_Stab3D/E04_Stab3D_Run.py @@ -9,7 +9,8 @@ z = 합성 지표(원지형 + 노선면) · f = 셀별 최소 안전율 · v = 그 임계 체적 m³ · m = 방법(0 Bishop · 1 Janbu 보정) · 닿지 않은 셀 = null best = 전체 최소 활동면 {x, y, z(중심), r, ratio, azimuth_deg(활동 방향), f, volume, method, ix, iy, - hovland · bishop · janbu_corrected(같은 면 방법별 F) · truncated_ratio · floor([셀, 못 뚫는 층 윗면])} + hovland · bishop · janbu_corrected(같은 면 방법별 F) · truncated_ratio · floor([셀, 못 뚫는 층 윗면]) + · cells(활동체 덩이 셀 번호 — 한 구가 자른 여러 덩이 중 이 덩이)} 타원체 = 회전축(활동 방향에 수직) 반축만 ratio·r — 533-2 `trial_columns` 와 같은 꼴. """ @@ -33,7 +34,7 @@ from E03_StabSection.E03_StabSection_Types import DEFAULT_ROAD_SURCHARGE_KPA from E04_Stab3D import E04_Stab3D_Engine_Grid as grid_engine from E04_Stab3D import E04_Stab3D_Engine_Search as search_engine -ENGINE_VERSION = "e04-3" # 엔진 판 — 바뀌면 지문이 달라져 다시 셈 +ENGINE_VERSION = "e04-4" # 엔진 판 — 바뀌면 지문이 달라져 다시 셈 STAGE_DIR = "E04_Stab3D" SECTION_LENGTH_M = 100.0 # 자동 구간 길이 MAX_SECTION_M = 500.0 # 한 번에 셀 구간 상한(시간 · 메모리) @@ -167,6 +168,8 @@ def _floor(strata: Any, best: dict[str, Any]) -> list[list[float]]: slip = np.where(rest >= 0, cz - np.sqrt(np.maximum(rest, 0)), np.nan) hard = strata.hard_top hit = np.isfinite(slip) & (slip < strata.z_top) & (hard > slip) & (hard < strata.z_top) + if "cells" in best: # 활동체 덩이 셀만 + hit &= np.isin(np.arange(hit.size), best["cells"]).reshape(hit.shape) return [[int(i), round(float(hard.ravel()[i]), 2)] for i in np.flatnonzero(hit.ravel())] @@ -258,6 +261,7 @@ def run_engine( "janbu_corrected": float(best["janbu_corrected"]), "truncated_ratio": float(best["truncated_ratio"]), "floor": _floor(strata, best), + "cells": [int(i) for i in best["cells"]], }, "trials": int(res.trials), "reference_fs": reference_fs(ground, settings["condition"]), @@ -292,6 +296,16 @@ def pack( best["ix"] = int(round((best["x"] - grid["x0"]) / grid["dx"])) best["iy"] = int(round((best["y"] - grid["y0"]) / grid["dx"])) f = np.asarray(raw["f"], dtype=float) + cells = np.asarray(best.get("cells") or [], int) + # 가까운 측점 = 활동체 평면 중심(없으면 구 중심) — 화면 최소 표시와 같은 자리 + where = ( + ( + grid["x0"] + (cells % grid["nx"]).mean() * grid["dx"], + grid["y0"] + (cells // grid["nx"]).mean() * grid["dx"], + ) + if len(cells) + else (best["x"], best["y"]) + ) return { "fingerprint": finger, "engine": ENGINE_VERSION, @@ -316,7 +330,7 @@ def pack( "by_method": { k: round(float(best[k]), 3) for k in ("hovland", "bishop", "janbu_corrected") }, - "chainage_m": float(_nearest_chainage(stations, best["x"], best["y"])), + "chainage_m": float(_nearest_chainage(stations, *where)), "cells": int(np.isfinite(f).sum()), "trials": int(raw.get("trials") or 0), }, diff --git a/E04_Stab3D/logic/E04_Stab3D_Mesh.ts b/E04_Stab3D/logic/E04_Stab3D_Mesh.ts index 66361007a..84ac09770 100644 --- a/E04_Stab3D/logic/E04_Stab3D_Mesh.ts +++ b/E04_Stab3D/logic/E04_Stab3D_Mesh.ts @@ -70,16 +70,17 @@ export function slipHeight(best: SlipBest, x: number, y: number): number | null return rest < 0 ? null : best.z - Math.sqrt(rest); } -/** 잘린 셀 → 활동면 높이(활동면이 지표보다 낮은 셀만 · 못 뚫는 층 윗면 아래로는 안 내려감) */ +/** 잘린 셀 → 활동면 높이(활동면이 지표보다 낮은 셀만 · 못 뚫는 층 윗면 아래로는 안 내려감 · 엔진 덩이 셀만) */ export function cutCells( grid: Grid, z: readonly (number | null)[], best: SlipBest, ): Map { const floor = new Map(best.floor ?? []); + const mass = best.cells ? new Set(best.cells) : null; const out = new Map(); z.forEach((top, index) => { - if (top === null) return; + if (top === null || (mass && !mass.has(index))) return; const x = grid.x0 + (index % grid.nx) * grid.dx; const y = grid.y0 + Math.floor(index / grid.nx) * grid.dx; const slip = slipHeight(best, x, y); diff --git a/E04_Stab3D/logic/E04_Stab3D_Raster.ts b/E04_Stab3D/logic/E04_Stab3D_Raster.ts index 9a50303c5..7cbb809bf 100644 --- a/E04_Stab3D/logic/E04_Stab3D_Raster.ts +++ b/E04_Stab3D/logic/E04_Stab3D_Raster.ts @@ -28,6 +28,8 @@ export interface SlipBest { iy: number; /** 못 뚫는 층에 잘린 셀 — [셀 번호, 층 윗면] · 활동면을 그 높이에서 자름 */ floor?: [number, number][]; + /** 활동체 덩이 셀 번호(엔진 · 한 구가 자른 여러 덩이 중 이 덩이) — 없으면(옛 저장본) 잘린 셀 전부 */ + cells?: number[]; } export interface Summary3d { diff --git a/resources/tester/test_533_3_e04_engine.py b/resources/tester/test_533_3_e04_engine.py index 102294d2a..25d77abdc 100644 --- a/resources/tester/test_533_3_e04_engine.py +++ b/resources/tester/test_533_3_e04_engine.py @@ -109,6 +109,9 @@ def test_engine_raster_and_best(root): assert best["r"] > 0 and best["ratio"] in (1.0, 2.0) and 0 <= best["azimuth_deg"] < 360 assert best["f"] == best["bishop"] or best["method"] == 1 # 다스림 = Bishop(잘림 없음) assert raw["reference_fs"] == 1.5 # 마스터 기본 건기 + # 활동체 셀 = 엔진 덩이 — 모두 그 셀 최소(= 전체 최소) + cells = np.asarray(best["cells"]) + assert len(cells) and np.allclose(f.ravel()[cells], best["f"]) # 합성 지표 — 노면 위(측점 40 · o=0) = 설계 100 · 깎기 끝 밖(o=+20 · 원지형) = 지반 z = raw["z"] jy0 = round((0.0 - grid["y0"]) / grid["dx"]) diff --git a/resources/tester/test_533_3_e04_logic.py b/resources/tester/test_533_3_e04_logic.py index e9f3d6350..b085d08f0 100644 --- a/resources/tester/test_533_3_e04_logic.py +++ b/resources/tester/test_533_3_e04_logic.py @@ -63,6 +63,7 @@ writeFileSync(process.argv[3], JSON.stringify({ sphere: mesh(M.slipMesh(c.grid, c.z, c.sphere)), ellipsoid: mesh(M.slipMesh(c.grid, c.z, c.ellipsoid)), cut: Array.from(M.cutCells(c.grid, c.z, c.sphere).keys()), + massCut: Array.from(M.cutCells(c.grid, c.z, { ...c.sphere, cells: [7 * 21 + 10, 7 * 21 + 11, 0] }).keys()), center: M.footprintCenter(c.grid, M.cutCells(c.grid, c.z, c.sphere), c.sphere), centerEmpty: M.footprintCenter(c.grid, new Map(), c.sphere), floored: Object.fromEntries(M.cutCells(c.grid, c.z, @@ -207,6 +208,7 @@ def test_terrain_hole_matches_cut(ts): """활동체를 걷어낸 지표 = 지표 꼭짓점 − 잘린 셀 · 구멍 테두리는 활동면 메쉬와 같은 꼭짓점.""" cut = [i for i, _ in _py_cut(SPHERE)] assert ts["cut"] == cut + assert ts["massCut"] == [7 * 21 + 10, 7 * 21 + 11] # 엔진 덩이 셀만 · 안 잘린 셀(0)은 빠짐 assert ts["holed"]["n"] == ts["terrain"]["n"] - len(cut) assert not set(ts["holed"]["cells"]) & set(cut) assert _py_rim(set(cut)) <= set(ts["holed"]["cells"]) From 503155f53bcf780604e795aade76d77d51051e8f Mon Sep 17 00:00:00 2001 From: umsangdon Date: Mon, 26 Oct 2026 04:27:32 +0900 Subject: [PATCH 2/2] auto: 2026-10-26 04:27 (ESD_LAPTOP) --- E04_Stab3D/logic/E04_Stab3D_Mesh.ts | 7 ++++--- resources/tester/test_533_3_e04_logic.py | 4 +++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/E04_Stab3D/logic/E04_Stab3D_Mesh.ts b/E04_Stab3D/logic/E04_Stab3D_Mesh.ts index 84ac09770..b334d60f9 100644 --- a/E04_Stab3D/logic/E04_Stab3D_Mesh.ts +++ b/E04_Stab3D/logic/E04_Stab3D_Mesh.ts @@ -84,9 +84,10 @@ export function cutCells( const x = grid.x0 + (index % grid.nx) * grid.dx; const y = grid.y0 + Math.floor(index / grid.nx) * grid.dx; const slip = slipHeight(best, x, y); - if (slip === null) return; - const base = Math.max(slip, floor.get(index) ?? -Infinity); - if (base < top) out.set(index, base); + const base = Math.max(slip ?? top, floor.get(index) ?? -Infinity); + // 엔진 덩이 셀 = 그대로(셀 안 일부만 잘린 가장자리 = 두께 0) · 옛 저장본 = 셀 중심이 잘린 칸만 + if (mass) out.set(index, Math.min(base, top)); + else if (base < top) out.set(index, base); }); return out; } diff --git a/resources/tester/test_533_3_e04_logic.py b/resources/tester/test_533_3_e04_logic.py index b085d08f0..d323e7f1d 100644 --- a/resources/tester/test_533_3_e04_logic.py +++ b/resources/tester/test_533_3_e04_logic.py @@ -64,6 +64,7 @@ writeFileSync(process.argv[3], JSON.stringify({ ellipsoid: mesh(M.slipMesh(c.grid, c.z, c.ellipsoid)), cut: Array.from(M.cutCells(c.grid, c.z, c.sphere).keys()), massCut: Array.from(M.cutCells(c.grid, c.z, { ...c.sphere, cells: [7 * 21 + 10, 7 * 21 + 11, 0] }).keys()), + massBase0: M.cutCells(c.grid, c.z, { ...c.sphere, cells: [0] }).get(0), center: M.footprintCenter(c.grid, M.cutCells(c.grid, c.z, c.sphere), c.sphere), centerEmpty: M.footprintCenter(c.grid, new Map(), c.sphere), floored: Object.fromEntries(M.cutCells(c.grid, c.z, @@ -208,7 +209,8 @@ def test_terrain_hole_matches_cut(ts): """활동체를 걷어낸 지표 = 지표 꼭짓점 − 잘린 셀 · 구멍 테두리는 활동면 메쉬와 같은 꼭짓점.""" cut = [i for i, _ in _py_cut(SPHERE)] assert ts["cut"] == cut - assert ts["massCut"] == [7 * 21 + 10, 7 * 21 + 11] # 엔진 덩이 셀만 · 안 잘린 셀(0)은 빠짐 + assert ts["massCut"] == [0, 7 * 21 + 10, 7 * 21 + 11] # 엔진 덩이 셀 그대로(0 = 안 잘림 · 두께 0) + assert ts["massBase0"] == Z[0] assert ts["holed"]["n"] == ts["terrain"]["n"] - len(cut) assert not set(ts["holed"]["cells"]) & set(cut) assert _py_rim(set(cut)) <= set(ts["holed"]["cells"])