Geo-Lab / engine /erosion.py
HANSOL
Geo-Lab v4.1 - Clean deployment
2afa69c
"""
Geo-Lab AI Engine: ์นจ์‹ ๋กœ์ง
Stream Power Law ๊ธฐ๋ฐ˜ ํ•˜๋ฐฉ/์ธก๋ฐฉ ์นจ์‹ ๊ตฌํ˜„
"""
import numpy as np
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from .base import Terrain, Water
def vertical_erosion(terrain: 'Terrain', water: 'Water',
k_erosion: float = 0.0001,
m_exponent: float = 0.5,
n_exponent: float = 1.0,
dt: float = 1.0) -> np.ndarray:
"""
ํ•˜๋ฐฉ ์นจ์‹ (Vertical/Downcutting Erosion)
Stream Power Law: E = K * A^m * S^n
Parameters:
-----------
terrain : Terrain
์ง€ํ˜• ๊ฐ์ฒด
water : Water
์ˆ˜๋ฌธ ๊ฐ์ฒด
k_erosion : float
์นจ์‹ ๊ณ„์ˆ˜ (์•”์„ ๊ฒฝ๋„์˜ ์—ญ์ˆ˜)
m_exponent : float
์œ ๋Ÿ‰ ์ง€์ˆ˜ (๋ณดํ†ต 0.4-0.6)
n_exponent : float
๊ฒฝ์‚ฌ ์ง€์ˆ˜ (๋ณดํ†ต 1.0)
dt : float
์‹œ๊ฐ„ ๋‹จ์œ„ (๋…„)
Returns:
--------
erosion_amount : np.ndarray
๊ฐ ์…€์˜ ์นจ์‹๋Ÿ‰ (m)
"""
# ๊ฒฝ์‚ฌ ๊ณ„์‚ฐ
slope = terrain.get_slope()
# Stream Power Law
# E = K * Q^m * S^n
# ์•”์„ ๊ฒฝ๋„๋กœ K ์กฐ์ ˆ (๊ฒฝ๋„๊ฐ€ ๋†’์œผ๋ฉด ์นจ์‹์ด ์ ์Œ)
effective_k = k_erosion * (1 - terrain.rock_hardness * 0.9)
erosion_rate = effective_k * np.power(water.discharge + 0.1, m_exponent) * np.power(slope + 0.001, n_exponent)
erosion_amount = erosion_rate * dt
# ์นจ์‹๋Ÿ‰ ์ œํ•œ (๋„ˆ๋ฌด ๊ธ‰๊ฒฉํ•œ ๋ณ€ํ™” ๋ฐฉ์ง€)
max_erosion = 10.0 # ์ตœ๋Œ€ 10m/yr
erosion_amount = np.clip(erosion_amount, 0, max_erosion)
return erosion_amount
def lateral_erosion(terrain: 'Terrain', water: 'Water',
k_lateral: float = 0.00005,
curvature_factor: float = 1.0,
dt: float = 1.0) -> np.ndarray:
"""
์ธก๋ฐฉ ์นจ์‹ (Lateral Erosion)
๊ณก๋ฅ˜ ํ•˜์ฒœ์—์„œ ๋ฐ”๊นฅ์ชฝ(๊ณต๊ฒฉ์‚ฌ๋ฉด)์„ ๊นŽ์Œ
Parameters:
-----------
terrain : Terrain
์ง€ํ˜• ๊ฐ์ฒด
water : Water
์ˆ˜๋ฌธ ๊ฐ์ฒด
k_lateral : float
์ธก๋ฐฉ ์นจ์‹ ๊ณ„์ˆ˜
curvature_factor : float
๊ณก๋ฅ  ๊ฐ•์กฐ ๊ณ„์ˆ˜
dt : float
์‹œ๊ฐ„ ๋‹จ์œ„ (๋…„)
Returns:
--------
erosion_amount : np.ndarray
๊ฐ ์…€์˜ ์นจ์‹๋Ÿ‰ (m)
"""
h, w = terrain.height, terrain.width
erosion = np.zeros((h, w))
# ์œ ๋กœ ๊ณก๋ฅ  ๊ณ„์‚ฐ (ํ๋ฆ„ ๋ฐฉํ–ฅ์˜ 2์ฐจ ๋ฏธ๋ถ„)
flow_x, flow_y = water.flow_x, water.flow_y
# ๊ณก๋ฅ  ๊ทผ์‚ฌ: ํ๋ฆ„ ๋ฐฉํ–ฅ์˜ ๋ณ€ํ™”์œจ
curvature_x = np.gradient(flow_x, axis=1)
curvature_y = np.gradient(flow_y, axis=0)
curvature = np.sqrt(curvature_x**2 + curvature_y**2)
# ์ธก๋ฐฉ ์นจ์‹ = ์œ ๋Ÿ‰ * ์œ ์† * ๊ณก๋ฅ 
# ๊ณก๋ฅ ์ด ํฐ ๊ณณ(๊ธ‰์ปค๋ธŒ) = ๋ฐ”๊นฅ์ชฝ ์นจ์‹ ๊ฐ•ํ•จ
erosion = k_lateral * water.discharge * water.velocity * curvature * curvature_factor * dt
# ํ•˜์ฒœ์ด ์žˆ๋Š” ๊ณณ์—์„œ๋งŒ ์นจ์‹ (์œ ๋Ÿ‰ ์ž„๊ณ„๊ฐ’)
channel_mask = water.discharge > 0.1
erosion = erosion * channel_mask
return np.clip(erosion, 0, 5.0)
def headward_erosion(terrain: 'Terrain', water: 'Water',
k_headward: float = 0.0002,
dt: float = 1.0) -> np.ndarray:
"""
๋‘๋ถ€ ์นจ์‹ (Headward Erosion)
ํ•˜์ฒœ์˜ ์ƒ๋ฅ˜ ๋์ด ์ ์  ๋’ค๋กœ ๋ฌผ๋Ÿฌ๋‚จ
ํญํฌ, ํ˜‘๊ณก ํ˜•์„ฑ์˜ ํ•ต์‹ฌ ๋ฉ”์ปค๋‹ˆ์ฆ˜
Returns:
--------
erosion_amount : np.ndarray
๊ฐ ์…€์˜ ์นจ์‹๋Ÿ‰ (m)
"""
h, w = terrain.height, terrain.width
erosion = np.zeros((h, w))
# ๊ธ‰๊ฒฝ์‚ฌ ์ง€์ (Knickpoint) ์ฐพ๊ธฐ
slope = terrain.get_slope()
steep_mask = slope > np.percentile(slope[slope > 0], 90) # ์ƒ์œ„ 10% ๊ธ‰๊ฒฝ์‚ฌ
# ๊ธ‰๊ฒฝ์‚ฌ + ์œ ๋Ÿ‰์ด ์žˆ๋Š” ๊ณณ์—์„œ ๋‘๋ถ€ ์นจ์‹ ๋ฐœ์ƒ
channel_mask = water.discharge > 0.5
knickpoint_mask = steep_mask & channel_mask
# ์ƒ๋ฅ˜ ๋ฐฉํ–ฅ์œผ๋กœ ์นจ์‹ ํ™•์žฅ
erosion[knickpoint_mask] = k_headward * water.discharge[knickpoint_mask] * dt
return np.clip(erosion, 0, 2.0)
def apply_erosion(terrain: 'Terrain', erosion_amount: np.ndarray,
min_elevation: float = 0.0):
"""
์ง€ํ˜•์— ์นจ์‹ ์ ์šฉ
Parameters:
-----------
terrain : Terrain
์ˆ˜์ •ํ•  ์ง€ํ˜• ๊ฐ์ฒด
erosion_amount : np.ndarray
์นจ์‹๋Ÿ‰ ๋ฐฐ์—ด
min_elevation : float
์ตœ์†Œ ๊ณ ๋„ (ํ•ด์ˆ˜๋ฉด)
"""
terrain.elevation -= erosion_amount
terrain.elevation = np.maximum(terrain.elevation, min_elevation)
def mass_wasting(terrain: 'Terrain',
critical_slope: float = 0.7, # ~35๋„
transfer_rate: float = 0.3) -> np.ndarray:
"""
์‚ฌ๋ฉด ๋ถ•๊ดด (Mass Wasting)
V์ž๊ณก ํ˜•์„ฑ ์‹œ ์–‘์˜† ์‚ฌ๋ฉด์ด ๋ฌด๋„ˆ์ง€๋Š” ๊ณผ์ •
Returns:
--------
elevation_change : np.ndarray
๊ณ ๋„ ๋ณ€ํ™”๋Ÿ‰ (๋†’์€ ๊ณณ -, ๋‚ฎ์€ ๊ณณ +)
"""
h, w = terrain.height, terrain.width
change = np.zeros((h, w))
slope = terrain.get_slope()
# ์ž„๊ณ„ ๊ฒฝ์‚ฌ ์ดˆ๊ณผ ์ง€์ ์—์„œ ๋ฌผ์งˆ ์ด๋™
unstable = slope > critical_slope
for y in range(1, h-1):
for x in range(1, w-1):
if unstable[y, x]:
# ์ฃผ๋ณ€์œผ๋กœ ๋ฌผ์งˆ ๋ถ„๋ฐฐ
elev = terrain.elevation[y, x]
neighbors = [
(y-1, x), (y+1, x), (y, x-1), (y, x+1)
]
for ny, nx in neighbors:
if terrain.elevation[ny, nx] < elev:
transfer = (elev - terrain.elevation[ny, nx]) * transfer_rate * 0.25
change[y, x] -= transfer
change[ny, nx] += transfer
return change