| import numpy as np |
| from .grid import WorldGrid |
|
|
| class ErosionProcess: |
| """ |
| ์งํ ๋ณ๊ฒฝ ์ปค๋ (Erosion/Deposition Kernel) |
| |
| ๋ฌผ๋ฆฌ ๊ณต์์ ์ ์ฉํ์ฌ ์งํ(๊ณ ๋)์ ๋ณ๊ฒฝํฉ๋๋ค. |
| 1. Stream Power Law: ํ์ฒ ์นจ์ (E = K * A^m * S^n) |
| 2. Hillslope Diffusion: ์ฌ๋ฉด ๋ถ๊ดด/ํ์ฐ (dz/dt = D * del^2 z) |
| """ |
| |
| def __init__(self, grid: WorldGrid, K: float = 1e-4, m: float = 0.5, n: float = 1.0, D: float = 0.01): |
| self.grid = grid |
| self.K = K |
| self.m = m |
| self.n = n |
| self.D = D |
| |
| def stream_power_erosion(self, discharge: np.ndarray, dt: float = 1.0) -> np.ndarray: |
| """Stream Power Law ๊ธฐ๋ฐ ํ์ฒ ์นจ์""" |
| slope, _ = self.grid.get_gradient() |
| |
| |
| |
| erosion_rate = self.K * np.power(discharge, self.m) * np.power(slope, self.n) |
| |
| |
| erosion_amount = erosion_rate * dt |
| |
| |
| |
| |
| underwater = self.grid.is_underwater() |
| erosion_amount[underwater] *= 0.1 |
| |
| |
| self.grid.elevation -= erosion_amount |
| |
| |
| |
| |
| |
| return erosion_amount |
|
|
| def hillslope_diffusion(self, dt: float = 1.0) -> np.ndarray: |
| """์ฌ๋ฉด ํ์ฐ ํ๋ก์ธ์ค (Linear Diffusion)""" |
| elev = self.grid.elevation |
| |
| |
| |
| |
| |
| up = np.roll(elev, -1, axis=0) |
| down = np.roll(elev, 1, axis=0) |
| left = np.roll(elev, -1, axis=1) |
| right = np.roll(elev, 1, axis=1) |
| |
| dx2 = self.grid.cell_size ** 2 |
| laplacian = (up + down + left + right - 4 * elev) / dx2 |
| |
| |
| laplacian[0, :] = 0 |
| laplacian[-1, :] = 0 |
| laplacian[:, 0] = 0 |
| laplacian[:, -1] = 0 |
| |
| |
| change = self.D * laplacian * dt |
| |
| self.grid.elevation += change |
| return change |
|
|
| def overbank_deposition(self, discharge: np.ndarray, |
| bankfull_capacity: float = 100.0, |
| decay_rate: float = 0.1, |
| dt: float = 1.0) -> np.ndarray: |
| """ |
| ๋ฒ๋์ ํด์ (Overbank Deposition) |
| |
| ํ์ฒ ์ฉ๋ ์ด๊ณผ ์ ๋ฒ๋ํ์ฌ ์ฃผ๋ณ์ ์ธ๋ฆฝ์ง ํด์ . |
| ํ๋์์ ๋ฉ์ด์ง์๋ก ํด์ ๋ ๊ฐ์ (์์ฐ์ ๋ฐฉ ํ์ฑ). |
| |
| Args: |
| discharge: ์ ๋ ๋ฐฐ์ด |
| bankfull_capacity: ํ๋ ์ฉ๋ (์ด๊ณผ ์ ๋ฒ๋) |
| decay_rate: ๊ฑฐ๋ฆฌ์ ๋ฐ๋ฅธ ํด์ ๊ฐ์ ์จ |
| dt: ์๊ฐ ๊ฐ๊ฒฉ |
| |
| Returns: |
| deposition: ํด์ ๋ ๋ฐฐ์ด |
| """ |
| from scipy.ndimage import distance_transform_edt |
| |
| h, w = self.grid.height, self.grid.width |
| |
| |
| overflow = np.maximum(0, discharge - bankfull_capacity) |
| flood_mask = overflow > 0 |
| |
| if not np.any(flood_mask): |
| return np.zeros((h, w)) |
| |
| |
| |
| channel_mask = discharge > bankfull_capacity * 0.5 |
| |
| if not np.any(channel_mask): |
| return np.zeros((h, w)) |
| |
| |
| distance = distance_transform_edt(~channel_mask) * self.grid.cell_size |
| |
| |
| |
| |
| max_overflow = overflow.max() |
| if max_overflow <= 0: |
| return np.zeros((h, w)) |
| |
| normalized_overflow = overflow / max_overflow |
| |
| |
| max_distance = 50 * self.grid.cell_size |
| influence = np.exp(-decay_rate * distance / self.grid.cell_size) |
| influence[distance > max_distance] = 0 |
| |
| |
| deposition = normalized_overflow.max() * influence * 0.1 * dt |
| |
| |
| underwater = self.grid.is_underwater() |
| deposition[underwater] = 0 |
| |
| |
| deposition[channel_mask] = 0 |
| |
| |
| self.grid.add_sediment(deposition) |
| |
| return deposition |
|
|
| def transport_and_deposit(self, discharge: np.ndarray, dt: float = 1.0, Kf: float = 0.01) -> np.ndarray: |
| """ |
| ํด์ ๋ฌผ ์ด๋ฐ ๋ฐ ํด์ (Sediment Transport & Deposition) |
| |
| Transport Capacity Law: |
| Q_cap = Kf * Q^m * S^n |
| |
| - Q_cap > Q_sed: ์นจ์ (Erosion) -> ํด์ ๋ฌผ ์ฆ๊ฐ |
| - Q_cap < Q_sed: ํด์ (Deposition) -> ํด์ ๋ฌผ ๊ฐ์, ์งํ ์์น |
| |
| Args: |
| discharge: ์ ๋ |
| dt: ์๊ฐ ๊ฐ๊ฒฉ |
| Kf: ์ด๋ฐ ํจ์จ ๊ณ์ (Transport Efficiency) |
| """ |
| slope, _ = self.grid.get_gradient() |
| |
| slope = np.maximum(slope, 0.001) |
| |
| |
| |
| capacity = Kf * np.power(discharge, self.m) * np.power(slope, self.n) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| h, w = self.grid.height, self.grid.width |
| sediment_flux = np.zeros((h, w)) |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pass |
| |
| |
| |
| return capacity |
|
|
| def simulate_transport(self, discharge: np.ndarray, dt: float = 1.0, |
| sediment_influx_map: np.ndarray = None) -> np.ndarray: |
| """ |
| ํตํฉ ํด์ ๋ฌผ ์ด์ก ์๋ฎฌ๋ ์ด์
(Flux-based) |
| 1. ์๋ฅ์์ ํด์ ๋ฌผ ์ ์
(Flux In) |
| 2. ๋ก์ปฌ ์นจ์/ํด์ (Erosion/Deposition) |
| 3. ํ๋ฅ๋ก ๋ฐฐ์ถ (Flux Out) |
| """ |
| h, w = self.grid.height, self.grid.width |
| elev = self.grid.elevation |
| |
| |
| indices = np.argsort(elev.ravel())[::-1] |
| |
| |
| flux = np.zeros((h, w)) |
| if sediment_influx_map is not None: |
| flux += sediment_influx_map |
| |
| slope, _ = self.grid.get_gradient() |
| slope = np.maximum(slope, 0.001) |
| |
| change = np.zeros((h, w)) |
| |
| |
| d8_dr = [-1, -1, -1, 0, 0, 1, 1, 1] |
| d8_dc = [-1, 0, 1, -1, 1, -1, 0, 1] |
| |
| |
| use_flow_dir = (self.grid.flow_dir is not None) |
| |
| for idx in indices: |
| r, c = idx // w, idx % w |
| |
|
|
|
|
| |
| underwater = self.grid.is_underwater()[r, c] |
| |
| |
| |
| |
| eff_slope = slope[r, c] if not underwater else slope[r, c] * 0.01 |
| |
| |
| |
| qs_cap = self.K * 500 * np.power(discharge[r, c], self.m) * np.power(eff_slope, self.n) |
| |
| |
| qs_in = flux[r, c] |
| |
| |
| |
| potential_erosion = self.K * np.power(discharge[r, c], self.m) * np.power(slope[r, c], self.n) * dt |
| |
| |
| if qs_in > qs_cap: |
| |
| deposition_amount = (qs_in - qs_cap) * 1.0 |
| change[r, c] += deposition_amount |
| qs_out = qs_cap |
| else: |
| |
| |
| erosion_amount = potential_erosion |
| |
| change[r, c] -= erosion_amount |
| qs_out = qs_in + erosion_amount |
|
|
| |
| |
| target_r, target_c = -1, -1 |
| |
| if use_flow_dir: |
| k = self.grid.flow_dir[r, c] |
| |
| |
| |
| |
| |
| if discharge[r, c] > 0: |
| nr = r + d8_dr[k] |
| nc = c + d8_dc[k] |
| if 0 <= nr < h and 0 <= nc < w: |
| target_r, target_c = nr, nc |
| else: |
| |
| min_z = elev[r, c] |
| for k in range(8): |
| nr = r + d8_dr[k] |
| nc = c + d8_dc[k] |
| if 0 <= nr < h and 0 <= nc < w: |
| if elev[nr, nc] < min_z: |
| min_z = elev[nr, nc] |
| target_r, target_c = nr, nc |
| |
| if target_r != -1: |
| flux[target_r, target_c] += qs_out |
| else: |
| |
| |
| change[r, c] += qs_out |
| |
| |
| |
| |
| |
| |
| self.grid.add_sediment(np.maximum(change, 0)) |
| |
| |
| |
| |
| erosion_mask = change < 0 |
| loss = -change[erosion_mask] |
| |
| |
| sed_thickness = self.grid.sediment[erosion_mask] |
| sed_loss = np.minimum(loss, sed_thickness) |
| rock_loss = loss - sed_loss |
| |
| self.grid.sediment[erosion_mask] -= sed_loss |
| self.grid.bedrock[erosion_mask] -= rock_loss |
| self.grid.update_elevation() |
| |
| return change |
|
|