Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import numpy as np | |
| from dataclasses import dataclass | |
| from typing import Optional, Dict | |
| GAMMA_W = 9.81 # kN/m^3 (unit weight of water) | |
| class Priors: | |
| N: int = 2000 | |
| c_mu: float = 20.0 | |
| c_sd: float = 5.0 | |
| phi_mu: float = 30.0 | |
| phi_sd: float = 5.0 | |
| gamma_mu: float = 19.0 | |
| gamma_sd: float = 1.0 | |
| z_mu: float = 5.0 | |
| z_sd: float = 1.0 | |
| ru_mu: float = 0.2 | |
| ru_sd: float = 0.05 | |
| class Targets: | |
| pf_target: float = 0.05 | |
| def compute_slope_deg(dem: np.ndarray, pixel_size: Optional[float] = None) -> np.ndarray: | |
| gy, gx = np.gradient(dem.astype(np.float32)) if pixel_size is None else np.gradient(dem.astype(np.float32), pixel_size) | |
| slope_rad = np.arctan(np.hypot(gx, gy)) | |
| return np.degrees(slope_rad).astype(np.float32) | |
| def pf_and_prescriptions( | |
| slope_deg: np.ndarray, | |
| priors: Priors = Priors(), | |
| targets: Targets = Targets(), | |
| mask: Optional[np.ndarray] = None, | |
| seed: Optional[int] = 42, | |
| ): | |
| assert slope_deg.ndim == 2, "slope_deg must be 2D" | |
| rng = np.random.default_rng(seed) | |
| th = np.radians(np.clip(slope_deg, 0, 89.9)).astype(np.float32) | |
| cos_th = np.cos(th)[..., None] | |
| sin_th = np.sin(th)[..., None] | |
| N = priors.N | |
| c = rng.normal(priors.c_mu, priors.c_sd, N) # kPa | |
| phi = np.radians(rng.normal(priors.phi_mu, priors.phi_sd, N)) # rad | |
| gamma = rng.normal(priors.gamma_mu, priors.gamma_sd, N) # kN/m^3 | |
| z = rng.normal(priors.z_mu, priors.z_sd, N) # m | |
| ru = np.clip(rng.normal(priors.ru_mu, priors.ru_sd, N), 0, 1) | |
| A = (gamma * z)[None, None, :] | |
| Acos2 = A * (cos_th ** 2) | |
| u = (ru * GAMMA_W * z)[None, None, :] * (cos_th ** 2) | |
| numerator = c[None, None, :] + (Acos2 - u) * np.tan(phi)[None, None, :] | |
| denominator = A * (sin_th * cos_th) | |
| denominator = np.maximum(denominator, 1e-6) | |
| FoS = numerator / denominator | |
| Pf = (FoS < 1.0).mean(axis=-1).astype(np.float32) | |
| # cohesion prescription | |
| delta_c_per_sample = np.maximum((1.0 - FoS) * denominator, 0.0) | |
| q_index = np.clip(int(np.ceil((1.0 - targets.pf_target) * N)) - 1, 0, N - 1) | |
| delta_c_sorted = np.partition(delta_c_per_sample, q_index, axis=-1) | |
| delta_c_needed = delta_c_sorted[..., q_index].astype(np.float32) | |
| # friction prescription | |
| Aeff = Acos2 - u | |
| deltaN = np.maximum((1.0 - FoS) * denominator, 0.0) | |
| tan_phi = np.tan(phi)[None, None, :] | |
| with np.errstate(divide='ignore', invalid='ignore'): | |
| needed_delta_tan = np.where(Aeff > 1e-6, deltaN / Aeff, np.inf) | |
| target_tan = tan_phi + needed_delta_tan | |
| target_phi = np.arctan(target_tan) | |
| delta_phi_per_sample = np.degrees(np.maximum(target_phi - phi[None, None, :], 0.0)).astype(np.float32) | |
| delta_phi_sorted = np.partition(delta_phi_per_sample, q_index, axis=-1) | |
| delta_phi_needed = delta_phi_sorted[..., q_index].astype(np.float32) | |
| if mask is not None: | |
| Pf = np.where(mask, Pf, np.nan) | |
| delta_c_needed = np.where(mask, delta_c_needed, np.nan) | |
| delta_phi_needed = np.where(mask, delta_phi_needed, np.nan) | |
| return { | |
| "pf": Pf, | |
| "delta_c_kpa": delta_c_needed, | |
| "delta_phi_deg": delta_phi_needed, | |
| } | |