Files
Aislo/E06_StabRock/E06_StabRock_Stereonet.py
T
eomsangdonandClaude Opus 5.5 f2e8bd6d5f feat(536-1): E06 암반 평사투영 — Markland 판정 · Hoek-Bray 평면 · 쐐기 안전율 · 스테레오넷 · 절리 입력 · 화면
- 엔진(자체 · NumPy · SciPy): 평면 · 쐐기(교선) · 전도 Markland(측방 한계 20° 기본) · 평면 Hoek-Bray(인장균열 없음 · 임계 · 깊이 · 수압 · kh · 수동 볼트) · 쐐기 SWedge 꼴(미끄럼 방향 8 경우 · 두 면 · 한 면 · 수압 중력식 · kh · 볼트)
- 스테레오넷 좌표(등각 · 등면적 하반구 · 극점 · 대원 · 교선 · 마찰원 · 일광 엔벨로프 · 위험 영역 = 판정 부등식)
- 길: 절리 작업본 E06_StabRock/joints.json(초기값 · 저장 · 초기화) · 해석 = 암 측점 깎기 쪽만 · 파생 저장본 stab_rock · 결과 · 측점 스테레오넷
- 화면: 좌 절리군 표 · 설정 / 가운데 위험 띠 · 스테레오넷 / 우 측점 결과 · 안전율 표 · E 단계 막대에 E06(끝에 덧붙임)
- 시험 test_536_1(Hoek Sau Mau Ping 0.8184 · 1.0655 · Sharma 윗면 무게 · Hoek-Bray 쐐기 계수 · Kumsar · Priest 두 예제 · Markland 경계 · 투영 왕복 · 위험 영역 = 판정) · test_528 E 단계 수 4 → 5

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 21:26:37 +09:00

138 lines
5.0 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.
"""E06 스테레오넷 투영 좌표 — 하반구 · 등각(Wulff) · 등면적(Schmidt) (PLAN 536-1).
화면(SVG)은 이 좌표를 그리기만 함 — 반지름 1 원 · x = 동 · y = 북(화면은 y 를 뒤집음).
극점 · 대원 · 교선 · 마찰원 · 일광 엔벨로프 · 위험 영역(Markland 부등식과 같은 경계 —
`E06_StabRock_Engine.kinematic`)을 냄. 투영식 = 표준 구면 투영(mplstereonet 등은 대조만).
"""
from __future__ import annotations
import math
from typing import Any
from E06_StabRock.E06_StabRock_Engine import (
LATERAL_PLANAR_DEG,
LATERAL_TOPPLING_DEG,
apparent_dip,
intersection,
pole,
)
PROJECTIONS = ("equal_angle", "equal_area")
STEP_DEG = 2.0 # 곡선 표본 간격
def radius(plunge: float, kind: str) -> float:
"""선주각 → 중심에서 거리(0 = 연직 · 1 = 수평)."""
half = math.radians(90.0 - plunge) / 2.0
return math.tan(half) if kind == "equal_angle" else math.sqrt(2.0) * math.sin(half)
def project(trend: float, plunge: float, kind: str) -> list[float]:
r = radius(plunge, kind)
t = math.radians(trend)
return [round(r * math.sin(t), 5), round(r * math.cos(t), 5)]
def inverse(x: float, y: float, kind: str) -> tuple[float, float]:
"""투영 좌표 → (추향 · 선주각) — 왕복 시험 · 화면 마우스 자리 읽기."""
r = math.hypot(x, y)
half = math.atan(r) if kind == "equal_angle" else math.asin(min(1.0, r / math.sqrt(2.0)))
return math.degrees(math.atan2(x, y)) % 360.0, 90.0 - math.degrees(2.0 * half)
def _samples(lo: float, hi: float) -> list[float]:
n = max(2, int(math.ceil((hi - lo) / STEP_DEG)) + 1)
return [lo + (hi - lo) * k / (n - 1) for k in range(n)]
def great_circle(dip_dir: float, dip: float, kind: str) -> list[list[float]]:
"""면의 대원 — 주향 한끝 → 경사 방향 → 다른 끝."""
return [
project(dip_dir + b, max(0.0, apparent_dip(dip, dip_dir, dip_dir + b)), kind)
for b in _samples(-90.0, 90.0)
]
def _zone(trend_lo, trend_hi, plunge_lo, plunge_hi, kind) -> list[list[float]] | None:
"""(추향 · 선주각) 띠 → 닫힌 다각형 — plunge_lo/hi 는 추향의 함수 · 비면 None."""
ts = _samples(trend_lo, trend_hi)
lows = [plunge_lo(t) for t in ts]
highs = [plunge_hi(t) for t in ts]
keep = [k for k in range(len(ts)) if highs[k] > lows[k]]
if len(keep) < 2:
return None
outer = [project(ts[k], lows[k], kind) for k in keep]
inner = [project(ts[k], highs[k], kind) for k in reversed(keep)]
return outer + inner
def stereonet(
slope: dict,
joints: list[dict],
phi: float,
kind: str = "equal_angle",
lateral_planar: float = LATERAL_PLANAR_DEG,
lateral_toppling: float = LATERAL_TOPPLING_DEG,
) -> dict[str, Any]:
"""slope = {dip_direction, dip} · joints = [{id, dip_direction, dip}] · phi = 영역 마찰각."""
af, pf = float(slope["dip_direction"]), float(slope["dip"])
def face_app(t: float) -> float:
return apparent_dip(pf, af, t)
planes = []
for j in joints:
tr, pl = pole(j["dip_direction"], j["dip"])
planes.append(
{
"id": j["id"],
"pole": project(tr, pl, kind),
"great_circle": great_circle(j["dip_direction"], j["dip"], kind),
}
)
lines = []
for i, a in enumerate(joints):
for b in joints[i + 1 :]:
line = intersection(a, b)
if line is not None:
lines.append({"ids": [a["id"], b["id"]], "point": project(*line, kind)})
# 일광 엔벨로프(극점 꼴) = 사면 겉보기 경사로 기운 면들의 극점
envelope = [
project(af + b + 180.0, 90.0 - face_app(af + b), kind) for b in _samples(-90.0, 90.0)
]
zones = {
# 평면(극점) — 극점 추향 αf + 180 ± 측방 · 선주각 90 − ψapp ~ 90 − φ
"planar": _zone(
af + 180.0 - lateral_planar,
af + 180.0 + lateral_planar,
lambda t: 90.0 - face_app(t - 180.0),
lambda t: 90.0 - phi,
kind,
),
# 쐐기(교선) — 추향 αf ± 90 · 선주각 φ ~ ψapp
"wedge": _zone(af - 90.0, af + 90.0, lambda t: phi, face_app, kind),
# 전도(극점) — 극점 추향 αf ± 측방 · 선주각 0 ~ ψf − φ
"toppling": _zone(
af - lateral_toppling,
af + lateral_toppling,
lambda t: 0.0,
lambda t: pf - phi,
kind,
),
}
return {
"projection": kind,
"slope": {
"pole": project(*pole(af, pf), kind),
"great_circle": great_circle(af, pf, kind),
},
"planes": planes,
"intersections": lines,
# 마찰원 — 극점 꼴 = 중심에서 φ(선주각 90 − φ) · 교선 꼴 = 선주각 φ
"friction_pole_r": round(radius(90.0 - phi, kind), 5),
"friction_line_r": round(radius(phi, kind), 5),
"daylight_envelope": envelope,
"zones": zones,
}