- UI_Prob(새 파일): 대표 단면 · 고른 비탈 · 조건 · 단면 물성 × c · φ · γ(+ 수위선 = 지하수위 이동 · r_u 갈래 = r_u) · 쓰기 · 분포 · 변동계수/σ · 절단 ±kσ · 임계면 고정 / 매회 탐색 · 방법 · LHS / MC · N · 시드 · c–φ 상관 · [기본값] · 입력 = sessionStorage 캐시(자동저장 없음) · 계산 = 서버(잠금판 없이 카드 안 진행률) - UI_ProbChart(새 파일): F < 1 빨강 히스토그램 · F = 1 · 기준 · 결정론 선 · 누적분포(Pf) · 민감도(범위 %) · 토네이도 - logic/E03_StabSection_Prob.ts: 변수 줄 · 기본값 후보(물성 덮어쓰기) · 요청 꼴 · 바꾼 줄 수 · 눈금 - Page: 보기 고름에 「확률 · 민감도」 · 문구 locale_e03 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
267 lines
7.4 KiB
TypeScript
267 lines
7.4 KiB
TypeScript
/* =============================================================================
|
|
* E03_StabSection_Prob.ts
|
|
* E03 「확률 · 민감도」 탭 계산(PLAN 537-1) — 변수 줄(단면 물성 × c · φ · γ + 지하수위 · r_u) ·
|
|
* 기본값 후보(서버 `prob_defaults.json`) · 입력 캐시 → 요청 꼴 · 그래프 눈금.
|
|
* 화면 · fetch · sessionStorage 없음(페이지가 부름).
|
|
* ========================================================================== */
|
|
|
|
export type ProbProp = "c" | "phi" | "gamma" | "water" | "ru";
|
|
export type ProbDist = "normal" | "lognormal";
|
|
export type ProbMethod = "ordinary" | "bishop" | "janbu" | "spencer" | "morgenstern_price";
|
|
|
|
export interface ProbPropDefault {
|
|
dist: ProbDist;
|
|
cov?: number;
|
|
std?: number;
|
|
trunc_sigma: number | null;
|
|
}
|
|
|
|
export interface ProbDefaults {
|
|
run: {
|
|
n: number;
|
|
sampling: "lhs" | "mc";
|
|
seed: number;
|
|
mode: "fixed" | "search";
|
|
method: ProbMethod;
|
|
};
|
|
properties: Record<ProbProp, ProbPropDefault>;
|
|
rho_c_phi: number;
|
|
sources: Record<string, string>;
|
|
materials?: Record<string, Partial<Record<ProbProp, Partial<ProbPropDefault>>>>;
|
|
}
|
|
|
|
export interface ProbMaterial {
|
|
id: string;
|
|
name: string | null;
|
|
c: number;
|
|
phi: number;
|
|
gamma_t: number;
|
|
}
|
|
|
|
export interface ProbStats {
|
|
n: number;
|
|
n_valid: number;
|
|
mean: number | null;
|
|
std: number | null;
|
|
cov?: number | null;
|
|
min?: number;
|
|
max?: number;
|
|
pf: number | null;
|
|
n_fail?: number;
|
|
beta_normal: number | null;
|
|
beta_lognormal?: number | null;
|
|
required?: number | null;
|
|
p_below_required?: number | null;
|
|
histogram?: { edges: number[]; counts: number[] };
|
|
cdf?: { p: number[]; F: number[] };
|
|
}
|
|
|
|
export interface ProbCurve {
|
|
name: string;
|
|
x: (number | null)[];
|
|
F: (number | null)[];
|
|
F_lo: number | null;
|
|
F_hi: number | null;
|
|
swing: number | null;
|
|
}
|
|
|
|
export interface ProbVariable {
|
|
name: string;
|
|
material: string | null;
|
|
prop: ProbProp;
|
|
dist: ProbDist;
|
|
mean: number;
|
|
std: number;
|
|
lo: number | null;
|
|
hi: number | null;
|
|
}
|
|
|
|
export interface ProbResult {
|
|
mode: "fixed" | "search";
|
|
method: ProbMethod;
|
|
n: number;
|
|
sampling: string;
|
|
seed: number;
|
|
variables: ProbVariable[];
|
|
stats: ProbStats;
|
|
sensitivity: {
|
|
F_mean: number | null;
|
|
curves: ProbCurve[];
|
|
tornado: { name: string; F_lo: number | null; F_hi: number | null; swing: number }[];
|
|
};
|
|
elapsed_s: { total: number; samples: number };
|
|
}
|
|
|
|
export interface ProbRecord {
|
|
station_key: string;
|
|
side: string;
|
|
condition: string;
|
|
required: number | null;
|
|
fs_deterministic: number | null;
|
|
request: Record<string, unknown>;
|
|
result: ProbResult;
|
|
computed_at: string;
|
|
}
|
|
|
|
export interface ProbSetup {
|
|
station_key: string;
|
|
side: string;
|
|
condition: string;
|
|
role: "cut" | "fill";
|
|
required: number | null;
|
|
fs_deterministic: Record<string, number | null>;
|
|
materials: ProbMaterial[];
|
|
groundwater: { mode: "none" | "line" | "ru"; ru: number | null };
|
|
defaults: ProbDefaults;
|
|
search_max_n: number;
|
|
last: ProbRecord | null;
|
|
}
|
|
|
|
/** 변수 줄 하나 — kind cov = 변동계수(평균 곱) · std = 표준편차(m · r_u) */
|
|
export interface ProbRow {
|
|
key: string;
|
|
material: string | null;
|
|
prop: ProbProp;
|
|
name: string;
|
|
mean: number;
|
|
kind: "cov" | "std";
|
|
}
|
|
|
|
export interface ProbInput {
|
|
use: boolean;
|
|
dist: ProbDist;
|
|
value: number;
|
|
trunc: number | null;
|
|
}
|
|
|
|
export interface ProbRunInput {
|
|
n: number;
|
|
sampling: "lhs" | "mc";
|
|
seed: number;
|
|
mode: "fixed" | "search";
|
|
method: ProbMethod;
|
|
rho: number;
|
|
}
|
|
|
|
export interface ProbCache {
|
|
vars: Record<string, ProbInput>;
|
|
run: ProbRunInput;
|
|
}
|
|
|
|
export const varKey = (material: string | null, prop: ProbProp): string =>
|
|
`${material ?? "*"}|${prop}`;
|
|
|
|
/** 단면 물성마다 c · φ · γ + 지하수 갈래가 수위선이면 지하수위 · r_u 면 r_u */
|
|
export function probRows(setup: ProbSetup): ProbRow[] {
|
|
const rows: ProbRow[] = [];
|
|
for (const m of setup.materials) {
|
|
const name = m.name ?? m.id;
|
|
rows.push(
|
|
{ key: varKey(m.id, "c"), material: m.id, prop: "c", name, mean: m.c, kind: "cov" },
|
|
{ key: varKey(m.id, "phi"), material: m.id, prop: "phi", name, mean: m.phi, kind: "cov" },
|
|
{
|
|
key: varKey(m.id, "gamma"),
|
|
material: m.id,
|
|
prop: "gamma",
|
|
name,
|
|
mean: m.gamma_t,
|
|
kind: "cov",
|
|
},
|
|
);
|
|
}
|
|
const gw = setup.groundwater;
|
|
if (gw.mode === "line")
|
|
rows.push({
|
|
key: varKey(null, "water"),
|
|
material: null,
|
|
prop: "water",
|
|
name: "",
|
|
mean: 0,
|
|
kind: "std",
|
|
});
|
|
if (gw.mode === "ru")
|
|
rows.push({
|
|
key: varKey(null, "ru"),
|
|
material: null,
|
|
prop: "ru",
|
|
name: "",
|
|
mean: gw.ru ?? 0,
|
|
kind: "std",
|
|
});
|
|
return rows;
|
|
}
|
|
|
|
/** 기본값 후보 — 성질 기본 위에 물성별 덮어쓰기 · 0 이면 끔 */
|
|
export function defaultInput(defaults: ProbDefaults, row: ProbRow): ProbInput {
|
|
const base = defaults.properties[row.prop];
|
|
const own = (row.material && defaults.materials?.[row.material]?.[row.prop]) || {};
|
|
const merged = { ...base, ...own };
|
|
const value = Number((row.kind === "cov" ? merged.cov : merged.std) ?? 0);
|
|
return {
|
|
use: value > 0,
|
|
dist: row.prop === "water" ? "normal" : merged.dist,
|
|
value,
|
|
trunc: merged.trunc_sigma ?? null,
|
|
};
|
|
}
|
|
|
|
/** 캐시가 없는 줄만 기본값 후보로 채움 */
|
|
export function fillCache(setup: ProbSetup, cache: ProbCache | null): ProbCache {
|
|
const d = setup.defaults;
|
|
const vars: Record<string, ProbInput> = { ...(cache?.vars ?? {}) };
|
|
for (const row of probRows(setup)) vars[row.key] ??= defaultInput(d, row);
|
|
const run = cache?.run ?? { ...d.run, rho: d.rho_c_phi };
|
|
return { vars, run };
|
|
}
|
|
|
|
/** 기본값과 다른 줄 수(「사용자 입력」 표기) */
|
|
export function changedCount(setup: ProbSetup, cache: ProbCache): number {
|
|
return probRows(setup).filter((row) => {
|
|
const a = defaultInput(setup.defaults, row);
|
|
const b = cache.vars[row.key];
|
|
return (
|
|
b && (a.use !== b.use || a.dist !== b.dist || a.value !== b.value || a.trunc !== b.trunc)
|
|
);
|
|
}).length;
|
|
}
|
|
|
|
/** POST …/stab/prob/{측점} 몸 — 켠 줄만 · 값 0 은 뺌 */
|
|
export function probRequest(setup: ProbSetup, cache: ProbCache): Record<string, unknown> {
|
|
const variables = probRows(setup)
|
|
.map((row) => ({ row, v: cache.vars[row.key] }))
|
|
.filter(({ v }) => v?.use && v.value > 0)
|
|
.map(({ row, v }) => ({
|
|
material: row.material,
|
|
prop: row.prop,
|
|
dist: v.dist,
|
|
...(row.kind === "cov" ? { cov: v.value } : { std: v.value }),
|
|
trunc_sigma: v.trunc,
|
|
}));
|
|
const r = cache.run;
|
|
return {
|
|
side: setup.side,
|
|
condition: setup.condition,
|
|
method: r.method,
|
|
mode: r.mode,
|
|
n: Math.round(r.n),
|
|
sampling: r.sampling,
|
|
seed: Math.round(r.seed),
|
|
rho_c_phi: r.rho,
|
|
variables,
|
|
};
|
|
}
|
|
|
|
/** 그래프 눈금 — 0 을 억지로 넣지 않음(안전율 축) */
|
|
export function axisTicks(min: number, max: number, count = 5): number[] {
|
|
const lo = Math.min(min, max);
|
|
const hi = Math.max(lo + 1e-9, max);
|
|
const raw = (hi - lo) / Math.max(count, 1);
|
|
const mag = 10 ** Math.floor(Math.log10(raw));
|
|
const step = [1, 2, 5, 10].map((m) => m * mag).find((v) => v >= raw) ?? raw;
|
|
const out: number[] = [];
|
|
for (let v = Math.floor(lo / step) * step; v <= hi + step * 1e-6; v += step)
|
|
out.push(Math.round(v * 1e6) / 1e6);
|
|
if (out[out.length - 1] < hi) out.push(out[out.length - 1] + step);
|
|
return out;
|
|
}
|