Files
Aislo/resources/tester/test_532_2_stab_section_model.py
T
eomsangdonandClaude Opus 5.5 208d755123 test(768-4): 검증 프로젝트 다시 만든 뒤 시험 상수 새 id 로 · 기슭막이 없는 검증_sub2 벽 시험 건너뜀
- 시험 20 파일의 옛 프로젝트 id → 같은 이름 새 id(B 검증_sub1~8 · D 가이드_예정노선 · 검증_sub1_D · E 가이드_안정성평가)
- test_536_3 벽 시험 둘 — 새 검증_sub2 = 구조물 0(옛 사용자 기슭막이 351 m 없음) · 벽 측점 없으면 건너뜀
- 고정 자료 golden_472 · helper_472_1_dump 는 옛 id 그대로(살아 있는 프로젝트 안 읽음)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 18:54:09 +09:00

156 lines
5.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""532-2 E03 2D 지층 모델 빌더 — 폴리곤 닫힘 · 지층 · 성토체 · 절성토 면적 = B06 · 비탈 목록 · 측점 해석 꼴.
- 순수 함수(`build_section_model`)는 만든 단면으로 · B06 대조는 검증_sub4 초기값 스냅샷(읽기만 · 없으면 건너뜀).
./venv/Scripts/python.exe -m pytest resources/tester/test_532_2_stab_section_model.py -q
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
import pytest
from shapely.geometry import Polygon
ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
from E03_StabSection.E03_StabSection_Engine_Model import ( # noqa: E402
build_section_model,
polygon_closed,
section_model_from_b06,
side_cases,
)
from E03_StabSection.E03_StabSection_Engine_Station import analyze_station # noqa: E402
SUB4 = ROOT / "storage/1/3/b5cf922b-2eaf-44b1-9e28-da78b4c92d00/initial_snapshot"
# 오른쪽(−) 산 · 왼쪽(+) 골 · 편절편성: 노면 −2~2 · 우 절토 1:0.5 · 좌 성토 1:1.5
GROUND = [(x / 2, 10.0 - 0.6 * (x / 2)) for x in range(-40, 41)] # 10 − 0.6x (좌로 내려감)
DESIGN = [
(-20.0, 22.0),
(-6.0, 13.6),
(-2.6, 10.0),
(-2.0, 10.0),
(2.0, 10.0),
(14.0, 2.0),
(20.0, -2.0),
]
def _trapezoid(ground, line, sign):
"""설계선 꼭짓점 · 지반 표본 합집합 위 사다리꼴(B06 `_trapezoid_areas` 와 같은 정의) — 절토(+) / 성토(−)."""
import numpy as np
gx, gz = zip(*ground)
lx, lz = zip(*line)
xs = sorted({*gx, *lx})
xs = [x for x in xs if lx[0] <= x <= lx[-1]]
d = np.interp(xs, gx, gz) - np.interp(xs, lx, lz)
area = 0.0
for x0, x1, d0, d1 in zip(xs, xs[1:], d, d[1:]):
if d0 * d1 < 0:
xm = x0 + (x1 - x0) * d0 / (d0 - d1)
area += 0.5 * (xm - x0) * max(sign * d0, 0) + 0.5 * (x1 - xm) * max(sign * d1, 0)
else:
area += 0.5 * (x1 - x0) * (max(sign * d0, 0) + max(sign * d1, 0))
return area
def test_build_model_polygons_layers_fill_and_areas():
m = build_section_model(
GROUND,
DESIGN,
layers=[("weathered_soil", 0.5), ("weathered_rock", None)],
area_range=(-20.0, 20.0),
)
assert m["regions"] and all(polygon_closed(r) for r in m["regions"])
ids = {r["material_id"] for r in m["regions"]}
assert ids == {"weathered_soil", "weathered_rock", "fill"}
# 지층 사이 겹침 없음 · 합 = 공사 뒤 지표 아래 전체
polys = [Polygon(r["polygon"], r.get("holes")) for r in m["regions"]]
total = sum(p.area for p in polys)
union = polys[0]
for p in polys[1:]:
union = union.union(p)
assert abs(union.area - total) < 1e-6
a = m["areas"]
assert abs(a["cut_m2"] - _trapezoid(GROUND, DESIGN, 1)) < 1e-6
assert abs(a["fill_m2"] - _trapezoid(GROUND, DESIGN, -1)) < 1e-6
assert (
0 < a["cut_soil_m2"] < a["cut_m2"]
and abs(a["cut_soil_m2"] + a["cut_rock_m2"] - a["cut_m2"]) < 1e-9
)
def test_side_cases_roles_directions_limits():
design = {
"section_mode": "right_cut",
"road_edges": {"left": {"offset_m": 2.0}, "right": {"offset_m": -2.0}},
}
left, right = side_cases(design, GROUND, DESIGN)
assert (left["role"], left["direction"]) == ("fill", 1) # 좌 성토 → 바깥(+x)으로
assert (right["role"], right["direction"]) == ("cut", 1) # 우 절토 → 노면(+x)으로
assert left["limits"][0] == -2.0 and right["limits"][1] == 2.0
assert left["height_m"] > 0 and right["height_m"] > 0
def test_analyze_station_shape():
cross = {
"chainage_m": 0.0,
"label": "STA.0+000",
"samples": [{"offset_m": x, "elevation_m": z} for x, z in GROUND],
}
design = {
"section_mode": "right_cut",
"ground_type": "ripping_rock",
"rock_boundary_offset_m": -0.5,
"design_line": [{"offset_m": x, "elevation_m": z} for x, z in DESIGN],
"road_edges": {
"left": {"offset_m": 2.0, "elevation_m": 10.0},
"right": {"offset_m": -2.0, "elevation_m": 10.0},
},
}
res = analyze_station(
cross, design, conditions=("dry",), methods=("bishop",), noncircular=False
)
assert len(res["cases"]) == 2
for case in res["cases"]:
F = case["min_F"]["bishop"]
assert F is not None and 0.3 < F < 20
best = case["result"]["methods"]["bishop"]["circular"]
assert best["slices"] and best["center"] and best["radius"] > 0
lo, hi = case["limits"]
assert (
lo - 1e-6 <= min(best["entry"][0], best["exit"][0])
and max(best["entry"][0], best["exit"][0]) <= hi + 1e-6
)
assert res["cases"][0]["result"]["contour"]["F"]
@pytest.mark.skipif(not (SUB4 / "db.json").exists(), reason="검증_sub4 스냅샷 없음")
def test_b06_areas_match_all_stations():
"""검증_sub4 전 측점 — 모델 절토 · 성토 · 토사 · 암 면적 = B06 저장값(반올림 4 자리 안)."""
rows = json.loads((SUB4 / "db.json").read_text(encoding="utf-8"))["cross_sections"]
assert rows
for row in rows:
design = json.loads(row["data"])["design"]
name = Path(row["cross_section_file_path"]).name
cross = json.loads(
(SUB4 / "B06_Section__cross_sections" / name).read_text(encoding="utf-8")
)
m = section_model_from_b06(cross, design)
assert all(polygon_closed(r) for r in m["regions"]), name
a = m["areas"]
assert abs(a["cut_m2"] - design["cut_area_m2"]) < 2e-3, name
assert abs(a["fill_m2"] - design["fill_area_m2"]) < 2e-3, name
assert abs(a["cut_soil_m2"] - design["cut_soil_area_m2"]) < 2e-3, name
assert abs(a["cut_rock_m2"] - design["cut_rock_area_m2"]) < 2e-3, name
if __name__ == "__main__":
sys.exit(pytest.main([__file__, "-q"]))