121 lines
5.0 KiB
Python
121 lines
5.0 KiB
Python
"""G4 — 교각별 지층 기둥 (1D).
|
|
|
|
- 교각에서 가까운 두 공(bore_max 이내)의 지층 경계 EL을 거리 역수 가중으로 평균한다.
|
|
- 두 공의 지층 순서가 다르거나 한 공만 있으면 가까운 공 하나만 쓴다 (순서 불일치는 W12).
|
|
- 지층 경계는 교각 지반고보다 높을 수 없게 자른다.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import math
|
|
from dataclasses import dataclass, field
|
|
|
|
from .boreholes import Borehole
|
|
from .config import GroundConfig
|
|
|
|
|
|
@dataclass
|
|
class Layer:
|
|
name: str
|
|
top_el: float
|
|
bottom_el: float
|
|
|
|
|
|
@dataclass
|
|
class SoilColumn:
|
|
method: str # idw2 / nearest / none
|
|
layers: list[Layer] = field(default_factory=list)
|
|
used: list[dict] = field(default_factory=list) # {"id", "dist", "weight"}
|
|
nearest_dist: float | None = None
|
|
order_mismatch: bool = False
|
|
support_top_el: float | None = None # 지지층 상단 EL (없으면 None)
|
|
support_name: str | None = None
|
|
gwl_el: float | None = None
|
|
bottom_el: float | None = None # 자료가 끝나는 EL (시추 종료 깊이)
|
|
top_shift: float | None = None # 지반고 − 시추 공구EL (꼭대기를 지반고로 바꾼 양, m)
|
|
|
|
|
|
def _support_index(names: list[str], cfg: GroundConfig) -> int | None:
|
|
if cfg.support_layer not in cfg.strata_order:
|
|
return names.index(cfg.support_layer) if cfg.support_layer in names else None
|
|
rank = cfg.strata_order.index(cfg.support_layer)
|
|
for i, n in enumerate(names):
|
|
if n in cfg.strata_order and cfg.strata_order.index(n) >= rank:
|
|
return i
|
|
return None
|
|
|
|
|
|
def _order_conflict(a: list[str], b: list[str]) -> bool:
|
|
"""두 공에 공통으로 있는 지층끼리의 순서가 다른지 본다. 한쪽에만 있는 층은 불일치로 보지 않는다."""
|
|
common = set(a) & set(b)
|
|
return [n for n in a if n in common] != [n for n in b if n in common]
|
|
|
|
|
|
def _layer_bottoms(ref: Borehole, sel: list[tuple[float, Borehole]]) -> list[float]:
|
|
"""기준 공(가장 가까운 공)의 층마다 하단 EL을 가중 평균한다. 다른 공에 같은 층이 없으면 그 공은 빼고 평균한다."""
|
|
if all(b.names == ref.names for _, (_, b) in sel):
|
|
return [sum(w * b.strata[i].bottom_el for w, (_, b) in sel) for i in range(len(ref.strata))]
|
|
out = []
|
|
for name in ref.names:
|
|
vals = [(w, next(s.bottom_el for s in b.strata if s.name == name))
|
|
for w, (_, b) in sel if name in b.names]
|
|
total = sum(w for w, _ in vals)
|
|
out.append(sum(w * v for w, v in vals) / total)
|
|
return out
|
|
|
|
|
|
def pier_soil_column(X: float, Y: float, ground_el: float | None, bores: list[Borehole],
|
|
cfg: GroundConfig | None = None) -> SoilColumn:
|
|
cfg = cfg or GroundConfig()
|
|
cand = [(math.hypot(b.X - X, b.Y - Y), b) for b in bores
|
|
if b.X is not None and b.Y is not None and b.top_el is not None and b.strata]
|
|
cand.sort(key=lambda t: t[0])
|
|
nearest = cand[0][0] if cand else None
|
|
cand = [c for c in cand if c[0] <= cfg.bore_max][:2]
|
|
if not cand:
|
|
return SoilColumn(method="none", nearest_dist=nearest)
|
|
|
|
mismatch = len(cand) == 2 and _order_conflict(cand[0][1].names, cand[1][1].names)
|
|
if len(cand) == 1 or mismatch:
|
|
sel = cand[:1]
|
|
weights = [1.0]
|
|
else:
|
|
if cand[0][0] < 1e-6:
|
|
sel, weights = cand[:1], [1.0]
|
|
else:
|
|
inv = [1 / d for d, _ in cand]
|
|
sel, weights = cand, [w / sum(inv) for w in inv]
|
|
|
|
ref = sel[0][1]
|
|
top = sum(w * b.top_el for w, (_, b) in zip(weights, sel))
|
|
top_shift = None
|
|
if ground_el is not None:
|
|
top_shift = ground_el - top # 경계 EL은 시추 기준이라 차이만큼 맨 위 층 두께가 달라짐
|
|
top = ground_el # 기둥 꼭대기는 교각 위치의 지반고
|
|
bottoms = _layer_bottoms(ref, list(zip(weights, sel)))
|
|
|
|
layers: list[Layer] = []
|
|
cur = top
|
|
for name, bot in zip(ref.names, bottoms):
|
|
bot = min(bot, top)
|
|
if bot < cur - 1e-9:
|
|
layers.append(Layer(name, cur, bot))
|
|
cur = bot
|
|
si = _support_index([ly.name for ly in layers], cfg)
|
|
|
|
gw = [(w, b.gwl_el) for w, (_, b) in zip(weights, sel) if b.gwl_el is not None]
|
|
gwl = sum(w * g for w, g in gw) / sum(w for w, _ in gw) if gw else None
|
|
|
|
return SoilColumn(
|
|
method="idw2" if len(sel) == 2 else "nearest",
|
|
layers=layers,
|
|
used=[{"id": b.id, "dist": round(d, 2), "weight": round(w, 3)} for w, (d, b) in zip(weights, sel)]
|
|
+ ([{"id": cand[1][1].id, "dist": round(cand[1][0], 2), "weight": 0.0}] if mismatch else []),
|
|
nearest_dist=nearest,
|
|
order_mismatch=mismatch,
|
|
support_top_el=layers[si].top_el if si is not None else None,
|
|
support_name=layers[si].name if si is not None else None,
|
|
gwl_el=gwl,
|
|
bottom_el=layers[-1].bottom_el if layers else None,
|
|
top_shift=top_shift,
|
|
)
|