Merge remote-tracking branches 'origin/sub_laptop_2', 'origin/sub_laptop_3' and 'origin/sub_laptop_4' into main_laptop_1
This commit is contained in:
@@ -4133,6 +4133,452 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "s12",
|
||||
"이름": "확률해석",
|
||||
"칸": {
|
||||
"A1": {
|
||||
"서식": 1,
|
||||
"값": "확률 해석 · 민감도 (대표 단면)"
|
||||
},
|
||||
"B1": {
|
||||
"서식": 1
|
||||
},
|
||||
"C1": {
|
||||
"서식": 1
|
||||
},
|
||||
"D1": {
|
||||
"서식": 1
|
||||
},
|
||||
"E1": {
|
||||
"서식": 1
|
||||
},
|
||||
"F1": {
|
||||
"서식": 1
|
||||
},
|
||||
"G1": {
|
||||
"서식": 1
|
||||
},
|
||||
"H1": {
|
||||
"서식": 1
|
||||
},
|
||||
"I1": {
|
||||
"서식": 1
|
||||
},
|
||||
"J1": {
|
||||
"서식": 1
|
||||
},
|
||||
"K1": {
|
||||
"서식": 1
|
||||
},
|
||||
"A2": {
|
||||
"서식": 2,
|
||||
"값": "물성 = 확률 변수(문헌 추정 변동계수) · Pf = P(Fs < 1) · β 정규 = (μ − 1)/σ · β 대수정규 = ln(μ/√(1+V²))/√ln(1+V²) — Slide2 · SLOPE/W Probabilistic 과 같은 정의"
|
||||
},
|
||||
"B2": {
|
||||
"서식": 2
|
||||
},
|
||||
"C2": {
|
||||
"서식": 2
|
||||
},
|
||||
"D2": {
|
||||
"서식": 2
|
||||
},
|
||||
"E2": {
|
||||
"서식": 2
|
||||
},
|
||||
"F2": {
|
||||
"서식": 2
|
||||
},
|
||||
"G2": {
|
||||
"서식": 2
|
||||
},
|
||||
"H2": {
|
||||
"서식": 2
|
||||
},
|
||||
"I2": {
|
||||
"서식": 2
|
||||
},
|
||||
"J2": {
|
||||
"서식": 2
|
||||
},
|
||||
"K2": {
|
||||
"서식": 2
|
||||
},
|
||||
"A3": {
|
||||
"서식": 12
|
||||
},
|
||||
"B3": {
|
||||
"서식": 12
|
||||
},
|
||||
"C3": {
|
||||
"서식": 12
|
||||
},
|
||||
"D3": {
|
||||
"서식": 12
|
||||
},
|
||||
"E3": {
|
||||
"서식": 12
|
||||
},
|
||||
"F3": {
|
||||
"서식": 12
|
||||
},
|
||||
"G3": {
|
||||
"서식": 12
|
||||
},
|
||||
"H3": {
|
||||
"서식": 12
|
||||
},
|
||||
"I3": {
|
||||
"서식": 12
|
||||
},
|
||||
"J3": {
|
||||
"서식": 12
|
||||
},
|
||||
"K3": {
|
||||
"서식": 12
|
||||
},
|
||||
"A4": {
|
||||
"서식": 3,
|
||||
"값": "1. 측점별 결과 — 안전율 분포 · 파괴확률 · 신뢰도 지수"
|
||||
},
|
||||
"B4": {
|
||||
"서식": 3
|
||||
},
|
||||
"C4": {
|
||||
"서식": 3
|
||||
},
|
||||
"D4": {
|
||||
"서식": 3
|
||||
},
|
||||
"E4": {
|
||||
"서식": 3
|
||||
},
|
||||
"F4": {
|
||||
"서식": 3
|
||||
},
|
||||
"G4": {
|
||||
"서식": 3
|
||||
},
|
||||
"H4": {
|
||||
"서식": 3
|
||||
},
|
||||
"I4": {
|
||||
"서식": 3
|
||||
},
|
||||
"J4": {
|
||||
"서식": 3
|
||||
},
|
||||
"K4": {
|
||||
"서식": 3
|
||||
},
|
||||
"A5": {
|
||||
"서식": 6,
|
||||
"값": "측점"
|
||||
},
|
||||
"B5": {
|
||||
"서식": 6,
|
||||
"값": "비탈 · 조건"
|
||||
},
|
||||
"C5": {
|
||||
"서식": 6,
|
||||
"값": "방법 · 임계면"
|
||||
},
|
||||
"D5": {
|
||||
"서식": 6,
|
||||
"값": "표본"
|
||||
},
|
||||
"E5": {
|
||||
"서식": 6,
|
||||
"값": "결정론 Fs"
|
||||
},
|
||||
"F5": {
|
||||
"서식": 6,
|
||||
"값": "Fs 평균"
|
||||
},
|
||||
"G5": {
|
||||
"서식": 6,
|
||||
"값": "표준편차"
|
||||
},
|
||||
"H5": {
|
||||
"서식": 6,
|
||||
"값": "Pf %"
|
||||
},
|
||||
"I5": {
|
||||
"서식": 6,
|
||||
"값": "β 정규"
|
||||
},
|
||||
"J5": {
|
||||
"서식": 6,
|
||||
"값": "β 대수정규"
|
||||
},
|
||||
"K5": {
|
||||
"서식": 6,
|
||||
"값": "P(F<기준) %"
|
||||
},
|
||||
"A6": {
|
||||
"서식": 7
|
||||
},
|
||||
"B6": {
|
||||
"서식": 7
|
||||
},
|
||||
"C6": {
|
||||
"서식": 5
|
||||
},
|
||||
"D6": {
|
||||
"서식": 9
|
||||
},
|
||||
"E6": {
|
||||
"서식": 8
|
||||
},
|
||||
"F6": {
|
||||
"서식": 8
|
||||
},
|
||||
"G6": {
|
||||
"서식": 8
|
||||
},
|
||||
"H6": {
|
||||
"서식": 8
|
||||
},
|
||||
"I6": {
|
||||
"서식": 8
|
||||
},
|
||||
"J6": {
|
||||
"서식": 8
|
||||
},
|
||||
"K6": {
|
||||
"서식": 8
|
||||
},
|
||||
"A8": {
|
||||
"서식": 3,
|
||||
"값": "2. 확률 변수 · 민감도 — 토네이도 순(나머지 평균 · 임계면 고정 · 범위 끝 Fs)"
|
||||
},
|
||||
"B8": {
|
||||
"서식": 3
|
||||
},
|
||||
"C8": {
|
||||
"서식": 3
|
||||
},
|
||||
"D8": {
|
||||
"서식": 3
|
||||
},
|
||||
"E8": {
|
||||
"서식": 3
|
||||
},
|
||||
"F8": {
|
||||
"서식": 3
|
||||
},
|
||||
"G8": {
|
||||
"서식": 3
|
||||
},
|
||||
"H8": {
|
||||
"서식": 3
|
||||
},
|
||||
"I8": {
|
||||
"서식": 3
|
||||
},
|
||||
"J8": {
|
||||
"서식": 3
|
||||
},
|
||||
"K8": {
|
||||
"서식": 3
|
||||
},
|
||||
"A9": {
|
||||
"서식": 6,
|
||||
"값": "측점 · 비탈 · 조건"
|
||||
},
|
||||
"B9": {
|
||||
"서식": 6,
|
||||
"값": "변수"
|
||||
},
|
||||
"C9": {
|
||||
"서식": 6,
|
||||
"값": "분포"
|
||||
},
|
||||
"D9": {
|
||||
"서식": 6,
|
||||
"값": "평균"
|
||||
},
|
||||
"E9": {
|
||||
"서식": 6,
|
||||
"값": "표준편차"
|
||||
},
|
||||
"F9": {
|
||||
"서식": 6,
|
||||
"값": "절단 ±σ"
|
||||
},
|
||||
"G9": {
|
||||
"서식": 6,
|
||||
"값": "아래 끝"
|
||||
},
|
||||
"H9": {
|
||||
"서식": 6,
|
||||
"값": "Fs(아래)"
|
||||
},
|
||||
"I9": {
|
||||
"서식": 6,
|
||||
"값": "위 끝"
|
||||
},
|
||||
"J9": {
|
||||
"서식": 6,
|
||||
"값": "Fs(위)"
|
||||
},
|
||||
"K9": {
|
||||
"서식": 6,
|
||||
"값": "순위"
|
||||
},
|
||||
"A10": {
|
||||
"서식": 7
|
||||
},
|
||||
"B10": {
|
||||
"서식": 5
|
||||
},
|
||||
"C10": {
|
||||
"서식": 7
|
||||
},
|
||||
"D10": {
|
||||
"서식": 8
|
||||
},
|
||||
"E10": {
|
||||
"서식": 8
|
||||
},
|
||||
"F10": {
|
||||
"서식": 16
|
||||
},
|
||||
"G10": {
|
||||
"서식": 8
|
||||
},
|
||||
"H10": {
|
||||
"서식": 8
|
||||
},
|
||||
"I10": {
|
||||
"서식": 8
|
||||
},
|
||||
"J10": {
|
||||
"서식": 8
|
||||
},
|
||||
"K10": {
|
||||
"서식": 9
|
||||
},
|
||||
"A12": {
|
||||
"서식": 5,
|
||||
"값": "확률 해석 없음 — E03 [확률 · 민감도] 뒤 [보고서 갱신]"
|
||||
},
|
||||
"B12": {
|
||||
"서식": 5
|
||||
},
|
||||
"C12": {
|
||||
"서식": 5
|
||||
},
|
||||
"D12": {
|
||||
"서식": 5
|
||||
},
|
||||
"E12": {
|
||||
"서식": 5
|
||||
},
|
||||
"F12": {
|
||||
"서식": 5
|
||||
},
|
||||
"G12": {
|
||||
"서식": 5
|
||||
},
|
||||
"H12": {
|
||||
"서식": 5
|
||||
},
|
||||
"I12": {
|
||||
"서식": 5
|
||||
},
|
||||
"J12": {
|
||||
"서식": 5
|
||||
},
|
||||
"K12": {
|
||||
"서식": 5
|
||||
}
|
||||
},
|
||||
"병합": [
|
||||
"A1:K1",
|
||||
"A2:K2",
|
||||
"A3:K3",
|
||||
"A4:K4",
|
||||
"A8:K8",
|
||||
"A12:K12"
|
||||
],
|
||||
"열": {
|
||||
"A": {
|
||||
"폭": 13
|
||||
},
|
||||
"B": {
|
||||
"폭": 16
|
||||
},
|
||||
"C": {
|
||||
"폭": 18
|
||||
},
|
||||
"D": {
|
||||
"폭": 7
|
||||
},
|
||||
"E": {
|
||||
"폭": 8
|
||||
},
|
||||
"F": {
|
||||
"폭": 8
|
||||
},
|
||||
"G": {
|
||||
"폭": 8
|
||||
},
|
||||
"H": {
|
||||
"폭": 8
|
||||
},
|
||||
"I": {
|
||||
"폭": 8
|
||||
},
|
||||
"J": {
|
||||
"폭": 8
|
||||
},
|
||||
"K": {
|
||||
"폭": 8
|
||||
}
|
||||
},
|
||||
"행": {
|
||||
"1": {
|
||||
"높이": 36
|
||||
},
|
||||
"2": {
|
||||
"높이": 30
|
||||
},
|
||||
"3": {
|
||||
"높이": 22
|
||||
},
|
||||
"4": {
|
||||
"높이": 22
|
||||
},
|
||||
"5": {
|
||||
"높이": 30
|
||||
},
|
||||
"8": {
|
||||
"높이": 22
|
||||
},
|
||||
"9": {
|
||||
"높이": 30
|
||||
}
|
||||
},
|
||||
"보기": {
|
||||
"눈금선": false
|
||||
},
|
||||
"인쇄": {
|
||||
"용지": "A4",
|
||||
"방향": "landscape",
|
||||
"맞춤": {
|
||||
"폭": 1,
|
||||
"높이": 0
|
||||
},
|
||||
"여백": {
|
||||
"위": 15,
|
||||
"아래": 15,
|
||||
"왼": 10,
|
||||
"오른": 10
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "s10",
|
||||
"이름": "부록_절편",
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
|
||||
## 공통
|
||||
|
||||
- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(목차 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토 · 암반검토 · 부록_절편) = 갱신 때 통째 다시 씀 · 작업본에 없는 프로그램 몫 시트는 양식 차례 자리에 끼움 · 새 서식은 작업본 서식 표 끝에 붙여 번호를 바꿈.
|
||||
- 시트 차례: 표지 · 목차 · 개요 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · (보강검토) · (암반검토) · 부록_절편.
|
||||
- 몫: 사용자 몫(표지 · 개요) = 처음 만들 때만 채움 · 다시 만들 때(보고서 갱신) 안 건드림. 프로그램 몫(목차 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · 보강검토 · 암반검토 · 확률해석 · 부록_절편) = 갱신 때 통째 다시 씀 · 작업본에 없는 프로그램 몫 시트는 양식 차례 자리에 끼움 · 새 서식은 작업본 서식 표 끝에 붙여 번호를 바꿈.
|
||||
- 시트 차례: 표지 · 목차 · 개요 · 입력조건 · 측점표 · 대표단면 · 3D요약 · 광역요약 · (보강검토) · (암반검토) · (확률해석) · 부록_절편.
|
||||
- PDF: 표지 = 쪽 번호 · 머리말 없음 · 본문 = 머리말(왼 사업명 = 표지 `C8` · 오른 보고서 이름) · 꼬리말 「- n / N -」(목차 = 1 쪽).
|
||||
- 사용자 확정 전 표기 한 규칙 — 기본값 그대로 = 「(기본값 · 사용자 확정 전)」 · 바꿈 = 「(사용자 입력)」 · 물성 = 「문헌 추정치 · 사용자 확정 전」.
|
||||
- 서식 번호(`서식` 표): 1 title · 2 sub · 3 section · 4 label · 5 text · 6 head · 7 body(가운데 글) · 8 fs(0.00) · 9 int(0) · 10 ng(N.G 빨강 채움) · 11 ok(O.K 초록) · 12 notice · 13 num1(#,##0.0) · 14 num0(#,##0) · 15 para(위 맞춤 긴 글) · 16 prop(0.0) · 17 chain(#,##0.00) · 18 head8 · 19 num8(0.00) · 20 num8(0.0) · 21 int8 · 22 목차 내용 · 23 목차 쪽 · 24 목차 장 · 25 sum8(0.00 · 회색) · 26 표지 제목.
|
||||
@@ -126,6 +126,16 @@
|
||||
| 11 · 12 행 꼴(제목 3 · 머리 6) 다음 절리군 하나 = 한 줄(13 행 서식) | A 이름(7) · B 경사방향 · C 경사 · D φj · E cj(16) · F 지속성(13) · G · H 구간 시작 · 끝(7) · 모르면 「-」 · 없으면 「절리군 없음」 |
|
||||
| 절리 줄 다음 한 줄 띄고 15 · 16 행 꼴 · 그 아래 측점 비탈 × 위험 모드 × 조건 = 한 줄(17 행 서식) | A 측점(7) · B 비탈 글(쪽 · 경사 / 방향 · 높이)(5) · C 파괴 형태(평면 · 쐐기 · 전도 + 절리)(7) · D 조건(7) · E Fs · F 기준(8) · G 판정(7 → 10 · 11 · 전도 「검토」) · H 비고(블록 안 생김 · 미끄러짐 면 · 지속성)(5) · 위험 모드 없는 비탈 = 「없음」 한 줄 |
|
||||
|
||||
## s12 확률해석 (프로그램 몫 · E03 확률 해석 저장본 · 없으면 시트를 뺌 · 가로 쪽)
|
||||
|
||||
| 칸 | 내용 |
|
||||
| --- | --- |
|
||||
| `A3` (A:K 병합 · 서식 12) | 「변동계수 · 분포 · 표본 = 문헌 기본값 후보(근거 E03 탭)」 + 사용자 확정 전 표기(모든 기록이 `prob_defaults.json` 그대로면 기본값 · 아니면 사용자 입력) |
|
||||
| 4 · 5 행 꼴(제목 3 · 머리 6) 다음 (측점 · 비탈 · 조건)마다 한 줄(6 행 서식) | A 측점(7) · B 비탈 · 조건(7) · C 방법 · 임계면 고정 / 매회 탐색(5) · D 유효 표본(9) · E 결정론 Fs · F 평균 · G 표준편차 · H Pf % · I β 정규 · J β 대수정규 · K P(F < 기준) %(8) |
|
||||
| 한 줄 띄고 8 · 9 행 꼴 · 그 아래 기록마다 변수 = 한 줄(10 행 서식 · 토네이도 순) | A 측점 · 비탈 · 조건(7) · B 변수(5) · C 분포(7) · D 평균 · E 표준편차(8) · F 절단 ±σ(16 · 없으면 「-」) · G 아래 끝 · H 그때 Fs · I 위 끝 · J 그때 Fs(8) · K 순위(9) |
|
||||
|
||||
- 저장본 = (측점 · 비탈 · 조건)마다 가장 최근 하나 · 지금 2D 결과 측점 지문에 붙은 것만.
|
||||
|
||||
## s10 부록_절편 (프로그램 몫 · 대표 단면마다 한 쪽)
|
||||
|
||||
양식 1~5 행 = 덩이 꼴(1 제목 · 2 단위 · 3 머리 · 4 절편 줄 서식 · 5 합계 서식). 대표 단면마다 지배 조건(Fs / 기준 가장 낮음) 임계면의 절편표.
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"version": 1,
|
||||
"note": "확률 해석 기본값 후보 — 문헌 · 상용 SW 예제 값 · 현장 시험 없음 · 사용자 확정 전(E03 확률 · 민감도 탭에서 바꿈)",
|
||||
"run": {
|
||||
"n": 1000,
|
||||
"sampling": "lhs",
|
||||
"seed": 0,
|
||||
"mode": "fixed",
|
||||
"method": "bishop"
|
||||
},
|
||||
"properties": {
|
||||
"c": {
|
||||
"dist": "lognormal",
|
||||
"cov": 0.3,
|
||||
"trunc_sigma": 3.0
|
||||
},
|
||||
"phi": {
|
||||
"dist": "normal",
|
||||
"cov": 0.1,
|
||||
"trunc_sigma": 3.0
|
||||
},
|
||||
"gamma": {
|
||||
"dist": "normal",
|
||||
"cov": 0.05,
|
||||
"trunc_sigma": 3.0
|
||||
},
|
||||
"water": {
|
||||
"dist": "normal",
|
||||
"std": 0.0,
|
||||
"trunc_sigma": 3.0
|
||||
},
|
||||
"ru": {
|
||||
"dist": "normal",
|
||||
"std": 0.0,
|
||||
"trunc_sigma": 3.0
|
||||
}
|
||||
},
|
||||
"rho_c_phi": 0.0,
|
||||
"sources": {
|
||||
"c": "COV 0.30(범위 0.2~0.5) · 대수정규(음수 막음) — 국내 화강풍화토 실측 정남수 외(2011) 0.34~0.49 · 장정윤 · 박혁진(2017) 0.21 · 해석 가정 Cho(2018 · 2019) 0.20 · Lee 외(2020) 0.25 · ISSMGE TC304(2021) 홍콩 완전풍화화강암 0.51 · Harr(1987) 최대 0.40 · 붕적토 0.40 = 국내 자료 없어 국외 범위 위쪽(협의 필요)",
|
||||
"phi": "COV 0.10(범위 0.05~0.15) · 정규 — 국내 화강풍화토 실측 정남수 외(2011) 0.09~0.12 · 장정윤 · 박혁진(2017) 0.03 · Cho(2018 · 2019) · Lee 외(2020) 0.10 · TC304(2021) 모래 평균 0.079 · 홍콩 CDG 0.096 · Duncan(2000) 2~13 %",
|
||||
"gamma": "COV 0.05(범위 0.03~0.07) · 정규 — Duncan(2000) 3~7 % · 홍콩 CDG 2.6 % · Li & Lumb(1987) 5 % · 국내 논문은 고정값으로 둠",
|
||||
"water": "지하수위 이동 표준편차 기본 0(확률 변수 아님) · 수위선 갈래(E02)일 때만",
|
||||
"ru": "r_u 표준편차 기본 0 · r_u 갈래(E02)일 때만 · Li & Lumb(1987) COV 10 %",
|
||||
"rho_c_phi": "기본 0(상관 무시 = 보수적) · Slide2 · Cho(2019) 기본 −0.5 · 문헌 −0.25~−0.75(Wolff 1985 −0.47 · Lumb 1970 −0.7~−0.37)",
|
||||
"trunc_sigma": "평균 ±3σ 절단 — Slide2 「3x 표준편차」 · SLOPE/W James Bay 예제",
|
||||
"run": "LHS 1000 · 임계면 고정(Slide2 Global Minimum) · Bishop — Slide2 Tutorial 08"
|
||||
},
|
||||
"materials": {
|
||||
"colluvium": {
|
||||
"c": {
|
||||
"cov": 0.4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,7 @@ SEGMENTS = [
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def hill() -> Terrain:
|
||||
def _hill() -> Terrain:
|
||||
x = np.arange(0.0, 1102.0, 2.0)
|
||||
y = np.arange(0.0, 402.0, 2.0)
|
||||
xx, yy = np.meshgrid(x, y)
|
||||
@@ -26,6 +25,16 @@ def hill() -> Terrain:
|
||||
return Terrain(x, y, z, np.ones(xx.shape, dtype=bool))
|
||||
|
||||
|
||||
def hill_loader(root, selection, models_dir, cache_dir):
|
||||
"""나란히 셈 프로세스가 이름으로 부르는 지형(모듈 맨 위 함수)."""
|
||||
return _hill(), []
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def hill() -> Terrain:
|
||||
return _hill()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def run(tmp_path, hill):
|
||||
_POOLS.clear()
|
||||
@@ -37,7 +46,7 @@ def run(tmp_path, hill):
|
||||
|
||||
def go(criteria=CRITERIA, corridor_m=150.0):
|
||||
return analyze.analyze_plan(
|
||||
tmp_path, SEGMENTS, criteria, loader=loader, corridor_m=corridor_m
|
||||
tmp_path, SEGMENTS, criteria, loader=loader, corridor_m=corridor_m, workers=1
|
||||
)
|
||||
|
||||
go.calls = calls
|
||||
@@ -91,6 +100,54 @@ def test_same_input_reads_store(run, monkeypatch) -> None:
|
||||
run(criteria={**CRITERIA, "max_grade": 0.12})
|
||||
|
||||
|
||||
def test_parallel_years_match_sequential(tmp_path) -> None:
|
||||
"""연도 구간 나란히(프로세스 둘) = 차례로 — 갈래 넷 노선 좌표 · 잰 값 차 0(531-6)."""
|
||||
_POOLS.clear()
|
||||
seq = analyze.analyze_plan(
|
||||
tmp_path / "a", SEGMENTS, CRITERIA, loader=hill_loader, corridor_m=150.0, workers=1
|
||||
)
|
||||
_POOLS.clear()
|
||||
steps = []
|
||||
par = analyze.analyze_plan(
|
||||
tmp_path / "b",
|
||||
SEGMENTS,
|
||||
CRITERIA,
|
||||
loader=hill_loader,
|
||||
corridor_m=150.0,
|
||||
workers=2,
|
||||
on_progress=lambda p, m: steps.append(m),
|
||||
)
|
||||
assert [r["routes"] for r in par] == [r["routes"] for r in seq]
|
||||
assert [r["fingerprint"] for r in par] == [r["fingerprint"] for r in seq]
|
||||
assert "나란히 2" in steps[0] and steps[-1] == "분석 끝"
|
||||
# 저장본은 제 프로세스에서 지문만 보고 그대로
|
||||
again = analyze.analyze_plan(
|
||||
tmp_path / "b", SEGMENTS, CRITERIA, loader=hill_loader, corridor_m=150.0, workers=2
|
||||
)
|
||||
assert [r["stored"] for r in again] == [True, True]
|
||||
_POOLS.clear()
|
||||
|
||||
|
||||
def test_near_streams_keeps_every_nearest_line(hill) -> None:
|
||||
"""계류 거르기 — 지형 상자 안 점의 계류 거리 · 건너기가 통째와 같고 먼 선은 빠짐(531-6)."""
|
||||
import shapely
|
||||
from shapely.geometry import LineString, MultiLineString
|
||||
|
||||
rng = np.random.default_rng(7)
|
||||
streams = []
|
||||
for _ in range(80): # 지형(1.1 × 0.4 km) 둘레 5 km 에 흩은 계류
|
||||
start = rng.uniform(-2000.0, 3000.0, 2)
|
||||
streams.append([tuple(start + step) for step in np.cumsum(rng.normal(0, 60, (6, 2)), 0)])
|
||||
kept = analyze.near_streams(hill, streams)
|
||||
assert 0 < len(kept) < len(streams)
|
||||
points = shapely.points(rng.uniform((0.0, 0.0), (1100.0, 400.0), (4000, 2)))
|
||||
whole, part = MultiLineString(streams), MultiLineString(kept)
|
||||
assert np.array_equal(shapely.distance(points, whole), shapely.distance(points, part))
|
||||
route = LineString([(0.0, 0.0), (1100.0, 400.0)])
|
||||
assert route.intersection(whole).equals(route.intersection(part))
|
||||
assert analyze.near_streams(hill, []) == []
|
||||
|
||||
|
||||
def test_corridor_argument_keeps_b_default(hill) -> None:
|
||||
line = [SEGMENTS[0]["start"], SEGMENTS[0]["end"]]
|
||||
base = SearchGrid(line, hill, [], stream_offset_m=10.0)
|
||||
|
||||
@@ -38,7 +38,7 @@ PRINT_KEYS = {"용지", "방향", "배율", "맞춤", "여백", "머리행", "
|
||||
A1 = re.compile(r"^([A-Z]{1,3})(\d+)$")
|
||||
NAMES = [
|
||||
"표지", "목차", "개요", "입력조건", "측점표", "대표단면", "3D요약", "광역요약", "보강검토", "암반검토",
|
||||
"부록_절편",
|
||||
"확률해석", "부록_절편",
|
||||
] # fmt: skip
|
||||
|
||||
needs_node = pytest.mark.skipif(shutil.which("node") is None, reason="node 가 없음")
|
||||
@@ -151,7 +151,7 @@ def test_양식_계약():
|
||||
book = _form()
|
||||
_contract(book)
|
||||
assert [s["이름"] for s in book["시트"]] == NAMES
|
||||
ids = ["s1", "s9", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s11", "s10"]
|
||||
ids = ["s1", "s9", "s2", "s3", "s4", "s5", "s6", "s7", "s8", "s11", "s12", "s10"]
|
||||
assert [s["id"] for s in book["시트"]] == ids
|
||||
|
||||
|
||||
@@ -175,6 +175,7 @@ def test_양식_인쇄_쪽나눔_머리행_그림():
|
||||
"광역요약": 0,
|
||||
"보강검토": 0,
|
||||
"암반검토": 0,
|
||||
"확률해석": 0,
|
||||
}
|
||||
# N.G 빨강 조건부 서식 — 측점표 판정 열 · 서식 표에 N.G 칸 서식(빌더가 인쇄 · xlsx 용으로 직접 씀)
|
||||
assert by["측점표"]["조건부서식"][0]["범위"][0]["c0"] == 9
|
||||
@@ -239,9 +240,9 @@ def test_견본_pdf_쪽수_머리행(png):
|
||||
sections = [i for i, t in enumerate(texts) if "대표 단면" in t and "안전율 Fs" in t]
|
||||
assert len(sections) == 2 # 단면마다 한 쪽
|
||||
# 표지 1 + 목차 1 + 개요 1 + 입력조건 1 + 측점표 n + 대표단면 2 + 3D 1 + 광역 1 + 보강 1 + 암반 1
|
||||
# + 부록 1
|
||||
# + 확률 1 + 부록 1
|
||||
# (양식 빈 판)
|
||||
assert len(pages) == 1 + 1 + 1 + 1 + len(stab) + 2 + 1 + 1 + 1 + 1 + 1
|
||||
assert len(pages) == 1 + 1 + 1 + 1 + len(stab) + 2 + 1 + 1 + 1 + 1 + 1 + 1
|
||||
assert all(float(pages[i].mediabox.width) > float(pages[i].mediabox.height) for i in stab)
|
||||
html = spreadsheet.print_htmls([{"book": book, "figures": {k: png for k in figs}}])[0]
|
||||
assert html.count("<img ") == 3 # 단면 2 + 3D 띠 1
|
||||
|
||||
@@ -0,0 +1,227 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""537-1 E03 2D 확률 해석 · 민감도 — 엔진(`E03_StabSection_Engine_Prob`).
|
||||
|
||||
- 무한사면 해석해: F = c/(γ z sinβ cosβ) + (1 − m γw/γ) tanφ/tanβ · c · φ 정규(MIT OCW 1.151 App. 17 ·
|
||||
z 5 · m 0.5 · β 35° · c N(25, 5) · φ N(30°, 7.5°)) — Pf · μ · σ 정확값 = φ 위 수치 적분 · LHS 1만 ±2 %
|
||||
· c · tanφ 정규면 F 도 정규 → Pf = Φ(−β) · 절편 경로(임계면 고정 · 건조 무한사면) = 닫힌 꼴.
|
||||
- Slide2 Tutorial 08(Probabilistic Analysis · LHS 1000 · Global Minimum · Bishop): FS 평균 1.145 · σ 0.1206
|
||||
· PF 10.2 % · RI 정규 1.204 · 대수정규 1.238.
|
||||
- Slide2 Verification #36(Li & Lumb 1987 · Hassan & Wolff 1999 · r_u): 결정론 1.340(H&W 1.334) · 평균 1.355 ·
|
||||
PF 1.5 % · RI 대수정규 2.482(H&W 2.336).
|
||||
- 고정 시드 재현 · LHS 층 · 절단 · 대수정규 평균 · 민감도(평균 = 결정론 F · 토네이도 순) · 매회 탐색 갈래.
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_537_1_e03_prob.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import math
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
from scipy import integrate
|
||||
from scipy.stats import norm
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
from E03_StabSection import E03_StabSection_Engine_Prob as prob # noqa: E402
|
||||
from E03_StabSection.E03_StabSection_Engine_Search import analyze # noqa: E402
|
||||
|
||||
|
||||
def var(name, prop, mean, std, rel=None, mat="s", dist="normal"):
|
||||
return {
|
||||
"name": name, "material": mat, "prop": prop, "dist": dist,
|
||||
"mean": mean, "std": std, "lo": rel, "hi": rel,
|
||||
} # fmt: skip
|
||||
|
||||
|
||||
def near(v, ref, tol):
|
||||
assert v is not None and abs(v - ref) <= tol * abs(ref), (v, ref)
|
||||
|
||||
|
||||
# ---- 무한사면 해석해 ---------------------------------------------------------------
|
||||
Z, BETA, G, GW = 5.0, math.radians(35.0), 20.0, 9.81
|
||||
C_PHI = [var("c", "c", 25.0, 5.0), var("phi", "phi", 30.0, 7.5)]
|
||||
|
||||
|
||||
def _ab(m):
|
||||
return 1 / (G * Z * math.sin(BETA) * math.cos(BETA)), (1 - m * GW / G) / math.tan(BETA)
|
||||
|
||||
|
||||
def _exact(m):
|
||||
"""φ 위 수치 적분 — Pf = ∫ Φ((1 − b tanφ − a μc)/(a σc)) f(φ) dφ · 평균 · 표준편차."""
|
||||
a, b = _ab(m)
|
||||
f = lambda p: norm.pdf(p, 30.0, 7.5) # noqa: E731
|
||||
F = lambda p: a * 25.0 + b * math.tan(math.radians(p)) # noqa: E731
|
||||
pf = integrate.quad(lambda p: norm.cdf((1 - F(p)) / (a * 5.0)) * f(p), 0, 89, limit=200)[0]
|
||||
mu = integrate.quad(lambda p: F(p) * f(p), 0, 89)[0]
|
||||
m2 = integrate.quad(lambda p: (F(p) ** 2 + (a * 5.0) ** 2) * f(p), 0, 89)[0]
|
||||
return pf, mu, math.sqrt(m2 - mu * mu)
|
||||
|
||||
|
||||
def test_infinite_slope_closed_form_pf_beta():
|
||||
"""MIT App. 17 무한사면 — LHS 1만 Pf · β ±2 % · 평균 · 표준편차 ±1 %(φ 정규 · 각도)."""
|
||||
a, b = _ab(0.5)
|
||||
X = prob.sample(C_PHI, 10000, seed=0)
|
||||
s = prob.statistics(a * X[:, 0] + b * np.tan(np.radians(X[:, 1])))
|
||||
pf, mu, sd = _exact(0.5)
|
||||
near(pf, 0.2252, 0.01) # 같은 문헌 MC 2×10⁶
|
||||
near(s["pf"], pf, 0.02)
|
||||
near(s["beta_normal"], (mu - 1) / sd, 0.02)
|
||||
near(s["mean"], mu, 0.01)
|
||||
near(s["std"], sd, 0.01)
|
||||
V = sd / mu
|
||||
near(
|
||||
s["beta_lognormal"],
|
||||
math.log(mu / math.sqrt(1 + V * V)) / math.sqrt(math.log1p(V * V)),
|
||||
0.02,
|
||||
)
|
||||
|
||||
|
||||
def test_infinite_slope_normal_tanphi_exact():
|
||||
"""c · tanφ 정규 · 독립 → F 정규 → Pf = Φ(−β) 정확(LHS 2만 ±2 %)."""
|
||||
a, b = _ab(0.5)
|
||||
vs = [var("c", "c", 25.0, 5.0), {**var("t", "c", 0.57735, 0.11547), "prop": "x"}]
|
||||
X = prob.sample(vs, 20000, seed=0)
|
||||
s = prob.statistics(a * X[:, 0] + b * X[:, 1])
|
||||
mu = a * 25.0 + b * 0.57735
|
||||
sd = math.hypot(a * 5.0, b * 0.11547)
|
||||
near(mu, 1.154412, 1e-5)
|
||||
near(sd, 0.163756, 1e-4)
|
||||
near(s["beta_normal"], (mu - 1) / sd, 0.02)
|
||||
near(s["pf"], norm.cdf(-(mu - 1) / sd), 0.02)
|
||||
|
||||
|
||||
def _infinite_model():
|
||||
"""건조 무한사면 β 35° · 지표 아래 5 m 나란한 면 · 모형 양 끝까지(끝 효과 없음)."""
|
||||
t, L = math.tan(BETA), 100.0
|
||||
model = {
|
||||
"materials": [{"id": "s", "name": "s", "gamma_t": G, "c": 25.0, "phi": 30.0}],
|
||||
"regions": [{"material_id": "s", "polygon": [[0, 0], [L, -t * L], [L, -t * L - 20], [0, -20]]}],
|
||||
} # fmt: skip
|
||||
return model, {"kind": "polyline", "points": [[0, -Z], [L, -t * L - Z]]}
|
||||
|
||||
|
||||
@pytest.mark.parametrize("method", ["ordinary", "bishop", "janbu"])
|
||||
def test_fixed_surface_slices_equal_closed_form(method):
|
||||
"""임계면 고정 절편 경로 = 무한사면 닫힌 꼴(물성 바꾼 줄마다) · LHS 1만 Pf ±2 %."""
|
||||
model, surface = _infinite_model()
|
||||
fixed = prob.Fixed(model, surface, 1)
|
||||
a, b = _ab(0.0)
|
||||
X = np.array([[25.0, 30.0], [10.0, 20.0], [40.0, 35.0]])
|
||||
got = fixed.F(C_PHI, X, method)
|
||||
assert got == pytest.approx(a * X[:, 0] + b * np.tan(np.radians(X[:, 1])), rel=1e-9)
|
||||
out = prob.run(model, surface=surface, direction=1, variables=C_PHI, n=10000, method=method)
|
||||
near(out["stats"]["pf"], _exact(0.0)[0], 0.02)
|
||||
|
||||
|
||||
# ---- Slide2 대조 -------------------------------------------------------------------
|
||||
T08 = {
|
||||
"materials": [{"id": "s", "name": "s", "gamma_t": 19.0, "c": 5.0, "phi": 30.0}],
|
||||
"regions": [{"material_id": "s", "polygon": [
|
||||
[0, 0], [130, 0], [130, 50], [80, 50], [50, 30], [0, 30]]}],
|
||||
} # fmt: skip
|
||||
T08_VARS = [var("c", "c", 5, 1, 3), var("phi", "phi", 30, 3, 9), var("g", "gamma", 19, 0.5, 1.5)]
|
||||
|
||||
|
||||
def _surface(model, method="bishop"):
|
||||
return analyze(model, direction=-1, noncircular=False, methods=(method,))["methods"][method][
|
||||
"min"
|
||||
]
|
||||
|
||||
|
||||
def test_slide2_tutorial08_global_minimum():
|
||||
"""Tutorial 08 — LHS 1000 · 고정 시드 셋 평균: FS 평균 ±1 % · σ ±3 % · PF ±2 %p · RI ±3 %."""
|
||||
surface = _surface(T08)
|
||||
near(surface["F"], 1.142, 0.01)
|
||||
runs = [
|
||||
prob.run(T08, surface=surface, direction=-1, variables=T08_VARS, n=1000, seed=k)["stats"]
|
||||
for k in range(3)
|
||||
]
|
||||
avg = {
|
||||
k: float(np.mean([r[k] for r in runs]))
|
||||
for k in ("mean", "std", "pf", "beta_normal", "beta_lognormal")
|
||||
}
|
||||
near(avg["mean"], 1.145, 0.01)
|
||||
near(avg["std"], 0.1206, 0.03)
|
||||
assert abs(avg["pf"] - 0.102) <= 0.02
|
||||
near(avg["beta_normal"], 1.204, 0.03)
|
||||
near(avg["beta_lognormal"], 1.238, 0.03)
|
||||
|
||||
|
||||
def test_slide2_36_li_lumb_ru():
|
||||
"""#36 r_u 확률 변수 · MC 1만 — 결정론 ±1 % · 평균 ±1.5 % · PF ±1 %p · RI 대수정규 = Hassan & Wolff ±3 %."""
|
||||
model = {
|
||||
"materials": [{"id": "s", "name": "s", "gamma_t": 18.0, "c": 18.0, "phi": 30.0}],
|
||||
"regions": [{"material_id": "s", "polygon": [[0, 0], [20, 0], [20, 15], [15, 15], [5, 5], [0, 5]]}],
|
||||
"groundwater": {"mode": "ru", "ru": 0.2},
|
||||
} # fmt: skip
|
||||
surface = _surface(model)
|
||||
near(surface["F"], 1.340, 0.01)
|
||||
vs = [
|
||||
var("c", "c", 18, 3.6), var("phi", "phi", 30, 3), var("g", "gamma", 18, 0.9),
|
||||
var("ru", "ru", 0.2, 0.02, mat=None),
|
||||
] # fmt: skip
|
||||
s = prob.run(model, surface=surface, direction=-1, variables=vs, n=10000, sampling="mc")[
|
||||
"stats"
|
||||
]
|
||||
near(s["mean"], 1.355, 0.015)
|
||||
assert abs(s["pf"] - 0.015) <= 0.01
|
||||
near(s["beta_lognormal"], 2.336, 0.03)
|
||||
|
||||
|
||||
# ---- 표본 · 재현 · 민감도 · 갈래 ---------------------------------------------------------
|
||||
def test_seed_reproducible_and_lhs_strata():
|
||||
"""같은 시드 = 같은 표본 · 다른 시드 = 다름 · LHS = 변수마다 n 칸에 하나씩 · 절단 안 · 상관."""
|
||||
a = prob.sample(T08_VARS, 500, seed=7)
|
||||
assert np.array_equal(a, prob.sample(T08_VARS, 500, seed=7))
|
||||
assert not np.array_equal(a, prob.sample(T08_VARS, 500, seed=8))
|
||||
for j, v in enumerate(T08_VARS):
|
||||
lo, hi = v["mean"] - v["lo"], v["mean"] + v["hi"]
|
||||
assert a[:, j].min() >= lo and a[:, j].max() <= hi
|
||||
pa, pb = norm.cdf(-3), norm.cdf(3)
|
||||
u = (norm.cdf((a[:, j] - v["mean"]) / v["std"]) - pa) / (pb - pa)
|
||||
assert sorted(np.floor(u * 500).astype(int)) == list(range(500))
|
||||
rho = prob.sample(C_PHI, 5000, seed=1, correlations=[(0, 1, -0.5)])
|
||||
assert np.corrcoef(rho.T)[0, 1] == pytest.approx(-0.5, abs=0.03)
|
||||
|
||||
|
||||
def test_truncation_and_lognormal_moments():
|
||||
"""대수정규 = 평균 · σ 그대로 · 음수 없음 · 정규 c 는 물리 한계 0 에서 절단."""
|
||||
ln = prob.sample([var("c", "c", 10.0, 5.0, dist="lognormal")], 20000, seed=2)[:, 0]
|
||||
near(ln.mean(), 10.0, 0.01)
|
||||
near(ln.std(), 5.0, 0.03)
|
||||
assert ln.min() > 0
|
||||
nc = prob.sample([var("c", "c", 5.0, 5.0)], 5000, seed=2)[:, 0]
|
||||
assert nc.min() >= 0
|
||||
|
||||
|
||||
def test_sensitivity_and_tornado():
|
||||
"""민감도 — 평균 줄 F = 결정론 F · 곡선 51 점 · c ↑ = F ↑ · 토네이도 = 폭 큰 순."""
|
||||
surface = _surface(T08)
|
||||
out = prob.run(T08, surface=surface, direction=-1, variables=T08_VARS, n=200)
|
||||
sens = out["sensitivity"]
|
||||
assert sens["F_mean"] == pytest.approx(surface["F"], rel=1e-9)
|
||||
curves = {c["name"]: c for c in sens["curves"]}
|
||||
assert len(curves["c"]["F"]) == 51 and curves["c"]["x"][0] == pytest.approx(2.0)
|
||||
assert np.all(np.diff(curves["c"]["F"]) > 0) and np.all(np.diff(curves["phi"]["F"]) > 0)
|
||||
swings = [t["swing"] for t in sens["tornado"]]
|
||||
assert swings == sorted(swings, reverse=True) and sens["tornado"][0]["name"] in ("c", "phi")
|
||||
assert out["stats"]["n"] == 200 and len(out["F"]) == 200
|
||||
|
||||
|
||||
def test_search_mode_matches_fixed_at_low_variance():
|
||||
"""매회 탐색 — 변동이 아주 작으면 고정면과 같은 F(±1 %) · 표본 수 · 시간 칸."""
|
||||
surface = _surface(T08)
|
||||
tiny = [var("c", "c", 5, 0.01), var("phi", "phi", 30, 0.01)]
|
||||
out = prob.run(T08, surface=surface, direction=-1, variables=tiny, n=4, mode="search")
|
||||
for f in out["F"]:
|
||||
near(f, surface["F"], 0.01)
|
||||
assert out["mode"] == "search" and out["elapsed_s"]["samples"] > 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-q"]))
|
||||
@@ -0,0 +1,295 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""537-1 E03 확률 해석 길(`E03_StabSection_Router_Prob`) — 검증_sub4 동결 복사 · 측점 둘 · Bishop 원호만.
|
||||
|
||||
- 준비(setup): 단면 물성 = E02 값 · 기본값 후보 · 기준안전율 · 결정론 F
|
||||
- 임계면 고정: 평균 줄 F = 저장된 결정론 F · Pf · β · 표본 수 · 같은 요청 = 저장본(다시 안 셈) · 기록 목록
|
||||
- 대표 단면 아님 · 단면에 없는 물성 · 수위선 아닌데 지하수위 변수 · 변수 없음 = 422
|
||||
- 매회 탐색 일꾼(프로세스 풀 · 진행 줄 · 결과 파일)
|
||||
- E05 확률해석 시트(있을 때만 · 결과 = 저장본 · 기본값 그대로 = 사용자 확정 전 · 바꾸면 사용자 입력 표기)
|
||||
- 화면 계산(logic/E03_StabSection_Prob.ts): 기본값 후보 · 물성 덮어쓰기 · 요청 꼴 = 서버 시험 요청
|
||||
|
||||
./venv/Scripts/python.exe -m pytest resources/tester/test_537_1_e03_prob_route.py -q
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import json
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
from uuid import UUID
|
||||
|
||||
import pytest
|
||||
from fastapi import HTTPException
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
|
||||
from common_util import common_util_project_copy as cp # noqa: E402
|
||||
from E01_StabRegister import E01_StabRegister_Source as src # noqa: E402
|
||||
from E02_StabGround import E02_StabGround_Router as e02 # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router as router # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router_Prob as rp # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Router_WhatIf as wf # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_View as view # noqa: E402
|
||||
from E03_StabSection import E03_StabSection_Worker as worker # noqa: E402
|
||||
from E03_StabSection.E03_StabSection_Engine_Station import analyze_station # noqa: E402
|
||||
|
||||
SOURCE_ID = "c725d747-50ae-46b0-87ce-4d4e56a53cf3" # 검증_sub4(읽기만)
|
||||
PID = "d" * 8 + "-" + "d" * 4 + "-" + "d" * 4 + "-" + "d" * 4 + "-" + "d" * 12
|
||||
|
||||
|
||||
async def _copy(target: Path) -> None:
|
||||
import aiomysql
|
||||
|
||||
from config.config_db import DB_HOST, DB_NAME, DB_PASSWORD, DB_PORT, DB_USER
|
||||
|
||||
connection = await aiomysql.connect(
|
||||
host=DB_HOST, port=DB_PORT, user=DB_USER, password=DB_PASSWORD, db=DB_NAME, autocommit=True
|
||||
)
|
||||
try:
|
||||
await cp.copy_project(
|
||||
connection, SOURCE_ID, scope="stability", new_id=PID, project_root=target
|
||||
)
|
||||
finally:
|
||||
connection.close()
|
||||
|
||||
|
||||
def _fast(cross, design, **kw):
|
||||
return analyze_station(cross, design, **{**kw, "methods": ("bishop",), "noncircular": False})
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def project():
|
||||
folder = Path(tempfile.mkdtemp(prefix="aislo_e03_prob_"))
|
||||
try:
|
||||
asyncio.run(_copy(folder / "copy"))
|
||||
except Exception as error: # noqa: BLE001
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
pytest.skip(f"검증 원본 복사 실패: {error}")
|
||||
root = folder / "copy"
|
||||
mp = pytest.MonkeyPatch()
|
||||
original = src.stations
|
||||
mp.setattr(router.src, "stations", lambda r: original(r)[20:22])
|
||||
|
||||
async def fake_project(_pid):
|
||||
return root, {"company_id": 1, "project_kind": "STABILITY"}
|
||||
|
||||
mp.setattr(e02, "_project", fake_project)
|
||||
mp.setattr(worker, "analyze_station", _fast)
|
||||
mp.setattr(wf, "analyze_station", _fast)
|
||||
mp.setattr(router, "WORKERS", 1)
|
||||
mp.setattr(rp, "WORKERS", 1)
|
||||
router._JOBS.clear()
|
||||
|
||||
async def go():
|
||||
await router.analyze2d(UUID(PID))
|
||||
while router._TASKS:
|
||||
await asyncio.gather(*list(router._TASKS))
|
||||
|
||||
asyncio.run(go())
|
||||
yield root
|
||||
mp.undo()
|
||||
shutil.rmtree(folder, ignore_errors=True)
|
||||
|
||||
|
||||
def _slot(root: Path) -> tuple[str, str]:
|
||||
"""대표 단면 중 깎기 · 쌓기 비탈 하나."""
|
||||
rows = router._latest_summary(root, "")[0]["stations"]
|
||||
view.mark_representative(rows, router._overrides(root))
|
||||
row = next(r for r in rows if (r.get("representative") or {}).get("on"))
|
||||
return row["key"], row["sides"][0]["side"]
|
||||
|
||||
|
||||
def _req(side, mats=("weathered_soil",), **kw):
|
||||
"""단면 물성마다 c(대수정규 0.3) · φ(0.1) · γ(0.05)."""
|
||||
variables = [
|
||||
v
|
||||
for m in mats
|
||||
for v in (
|
||||
{"material": m, "prop": "c", "dist": "lognormal", "cov": 0.3},
|
||||
{"material": m, "prop": "phi", "cov": 0.1},
|
||||
{"material": m, "prop": "gamma", "cov": 0.05},
|
||||
)
|
||||
]
|
||||
return rp.ProbRequest(
|
||||
**{"side": side, "condition": "dry", "n": 400, "variables": variables, **kw}
|
||||
)
|
||||
|
||||
|
||||
def test_setup_and_fixed_run(project, monkeypatch):
|
||||
key, side = _slot(project)
|
||||
s = rp.setup(project, key, side, "dry")
|
||||
ids = [m["id"] for m in s["materials"]]
|
||||
assert "weathered_soil" in ids and "wall" not in ids
|
||||
assert s["defaults"]["properties"]["c"]["cov"] == 0.3 and s["required"] in (1.5, 1.3)
|
||||
fs = s["fs_deterministic"]["bishop"]
|
||||
seen = []
|
||||
req = _req(side, ids)
|
||||
rec = rp.run_prob(project, key, req, lambda p, m: seen.append(p))
|
||||
res = rec["result"]
|
||||
assert res["sensitivity"]["F_mean"] == pytest.approx(fs, rel=1e-9)
|
||||
assert rec["fs_deterministic"] == fs and res["stats"]["n"] == 400
|
||||
st = res["stats"]
|
||||
assert 0 <= st["pf"] <= 1 and st["std"] > 0 and abs(st["mean"] - fs) < 0.1 * fs
|
||||
assert st["beta_normal"] == pytest.approx((st["mean"] - 1) / st["std"])
|
||||
assert seen and max(seen) <= 95
|
||||
# 같은 요청 = 저장본 · 기록 목록 · setup 의 마지막 결과
|
||||
calls = []
|
||||
monkeypatch.setattr(rp.prob, "run", lambda **kw: calls.append(1))
|
||||
again = rp.run_prob(project, key, req, lambda p, m: None)
|
||||
assert not calls and again["computed_at"] == rec["computed_at"]
|
||||
listed = rp.records(project)
|
||||
assert [(r["station_key"], r["side"], r["condition"]) for r in listed] == [(key, side, "dry")]
|
||||
assert rp.setup(project, key, side, "dry")["last"]["computed_at"] == rec["computed_at"]
|
||||
|
||||
|
||||
def test_rejects(project):
|
||||
key, side = _slot(project)
|
||||
with pytest.raises(HTTPException) as e:
|
||||
rp.run_prob(
|
||||
project, key, _req(side, variables=[{"material": "xx", "prop": "c", "cov": 0.2}]), print
|
||||
)
|
||||
assert e.value.status_code == 422
|
||||
with pytest.raises(HTTPException) as e:
|
||||
rp.run_prob(project, key, _req(side, variables=[{"prop": "water", "std": 0.5}]), print)
|
||||
assert e.value.status_code == 422 and "수위선" in e.value.detail
|
||||
with pytest.raises(HTTPException) as e:
|
||||
rp.run_prob(
|
||||
project,
|
||||
key,
|
||||
_req(side, variables=[{"material": "weathered_soil", "prop": "c", "cov": 0}]),
|
||||
print,
|
||||
)
|
||||
assert "변수가 없습니다" in e.value.detail
|
||||
# 대표 단면 끔 → 422
|
||||
path = project / router.REPRESENTATIVE
|
||||
old = path.read_text(encoding="utf-8") if path.is_file() else None
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(json.dumps({"overrides": {key: False}}), encoding="utf-8")
|
||||
try:
|
||||
with pytest.raises(HTTPException) as e:
|
||||
rp.run_prob(project, key, _req(side), print)
|
||||
assert "대표 단면" in e.value.detail
|
||||
finally:
|
||||
path.unlink() if old is None else path.write_text(old, encoding="utf-8")
|
||||
|
||||
|
||||
def test_search_worker_process():
|
||||
"""매회 탐색 일꾼 — 프로세스 풀 둘 · 진행 줄 · 결과 파일(Slide2 Tutorial 08 모형 · 작은 변동)."""
|
||||
from test_537_1_e03_prob import T08, _surface, var
|
||||
|
||||
job = {
|
||||
"model": T08,
|
||||
"surface": _surface(T08),
|
||||
"direction": -1,
|
||||
"limits": None,
|
||||
"variables": [var("c", "c", 5, 0.01), var("phi", "phi", 30, 0.01)],
|
||||
"n": 4,
|
||||
"mode": "search",
|
||||
"workers": 2,
|
||||
}
|
||||
done = []
|
||||
out = rp._run_search(job, done.append)
|
||||
assert sorted(done) == [1, 2, 3, 4] and out["n"] == 4
|
||||
assert all(abs(f - 1.139) < 0.02 for f in out["F"])
|
||||
|
||||
|
||||
def test_e05_prob_sheet(project, monkeypatch):
|
||||
"""E05 s12 — 측점 줄 = 저장본 값 · 변수 줄 = 토네이도 순 · 기본값 그대로 표기 · 없으면 시트 뺌."""
|
||||
from E05_StabReport import E05_StabReport_Build as build
|
||||
|
||||
recs = rp.records(project)
|
||||
assert len(recs) == 1
|
||||
rec = recs[0]
|
||||
book = build.form()
|
||||
build.build_program(book, build.gather(project))
|
||||
s12 = next(s for s in book["시트"] if s["id"] == "s12")
|
||||
cells = s12["칸"]
|
||||
st = rec["result"]["stats"]
|
||||
assert cells["E6"]["값"] == pytest.approx(rec["fs_deterministic"], abs=1e-6)
|
||||
assert cells["F6"]["값"] == pytest.approx(st["mean"], abs=1e-6)
|
||||
assert cells["H6"]["값"] == pytest.approx(100 * st["pf"], abs=1e-6)
|
||||
assert cells["J6"]["값"] == pytest.approx(st["beta_lognormal"], abs=1e-6)
|
||||
assert "사용자 입력" in cells["A3"]["값"] # 표본 400 ≠ 기본 1000
|
||||
d = rp.load_defaults()
|
||||
plain = {
|
||||
**rec["request"],
|
||||
**{k: d["run"][k] for k in ("n", "sampling", "seed", "mode", "method")},
|
||||
}
|
||||
assert build.prob._is_default({"request": plain}, d)
|
||||
plain["variables"] = [{**plain["variables"][0], "cov": 0.25}]
|
||||
assert not build.prob._is_default({"request": plain}, d)
|
||||
first = rec["result"]["sensitivity"]["tornado"][0]["name"]
|
||||
assert cells["B10"]["값"] == first and cells["K10"]["값"] == 1
|
||||
monkeypatch.setattr(build.prob, "read", lambda root: [])
|
||||
empty = build.form()
|
||||
build.build_program(empty, build.gather(project))
|
||||
assert "s12" not in {s["id"] for s in empty["시트"]}
|
||||
|
||||
|
||||
TYPESCRIPT = ROOT / "config" / "node_modules" / "typescript"
|
||||
LOGIC_TS = ROOT / "E03_StabSection" / "logic" / "E03_StabSection_Prob.ts"
|
||||
LOGIC_SCRIPT = r"""
|
||||
const ts = require(process.argv[1]);
|
||||
const fs = require("fs");
|
||||
const src = fs.readFileSync(process.argv[2], "utf8");
|
||||
const opts = { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2020 };
|
||||
const js = ts.transpileModule(src, { compilerOptions: opts }).outputText;
|
||||
const box = { exports: {} };
|
||||
new Function("exports", "module", js)(box.exports, box);
|
||||
const m = box.exports;
|
||||
const setup = JSON.parse(fs.readFileSync(0, "utf8"));
|
||||
const cache = m.fillCache(setup, null);
|
||||
const req = m.probRequest(setup, cache);
|
||||
cache.vars[m.varKey("colluvium", "c")].value = 0.5;
|
||||
process.stdout.write(JSON.stringify({ req, changed: m.changedCount(setup, cache),
|
||||
ticks: m.axisTicks(0.83, 1.47, 5) }));
|
||||
"""
|
||||
|
||||
|
||||
@pytest.mark.skipif(not TYPESCRIPT.is_dir(), reason="config/node_modules 없음")
|
||||
def test_logic_ts_request_matches_server():
|
||||
"""기본값 후보(붕적토 c 0.4 덮어씀) → 요청 꼴 = 서버 ProbRequest 로 읽힘 · 바꾼 줄 수 · 눈금."""
|
||||
setup = {
|
||||
"side": "left",
|
||||
"condition": "wet",
|
||||
"materials": [
|
||||
{"id": "weathered_soil", "name": "풍화토", "c": 15, "phi": 30, "gamma_t": 18},
|
||||
{"id": "colluvium", "name": "붕적토", "c": 5, "phi": 27, "gamma_t": 17.5},
|
||||
],
|
||||
"groundwater": {"mode": "line", "ru": None},
|
||||
"defaults": rp.load_defaults(),
|
||||
}
|
||||
out = subprocess.run(
|
||||
["node", "-e", LOGIC_SCRIPT, str(TYPESCRIPT), str(LOGIC_TS)],
|
||||
input=json.dumps(setup),
|
||||
capture_output=True,
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
check=True,
|
||||
)
|
||||
got = json.loads(out.stdout)
|
||||
req = rp.ProbRequest(**got["req"])
|
||||
assert (req.side, req.condition, req.n, req.sampling, req.mode) == (
|
||||
"left",
|
||||
"wet",
|
||||
1000,
|
||||
"lhs",
|
||||
"fixed",
|
||||
)
|
||||
by = {(v.material, v.prop): v for v in req.variables}
|
||||
assert len(by) == 6 # 물 σ 0 = 뺌
|
||||
assert by[("colluvium", "c")].cov == 0.4 and by[("weathered_soil", "c")].cov == 0.3
|
||||
assert by[("weathered_soil", "c")].dist == "lognormal" and by[("colluvium", "phi")].cov == 0.1
|
||||
assert all(v.trunc_sigma == 3.0 for v in req.variables)
|
||||
assert got["changed"] == 1
|
||||
assert got["ticks"][0] <= 0.83 and got["ticks"][-1] >= 1.47 and 0 not in got["ticks"]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(pytest.main([__file__, "-q"]))
|
||||
Reference in New Issue
Block a user