Geo-Lab / engine /wave.py
HANSOL
Geo-Lab v4.1 - Clean deployment
2afa69c
"""
Wave Kernel (ํŒŒ๋ž‘ ์ปค๋„)
ํ•ด์•ˆ ์ง€ํ˜• ํ˜•์„ฑ ํ”„๋กœ์„ธ์Šค
- ํŒŒ๋ž‘ ์นจ์‹ (Wave Erosion): ํ•ด์‹์ ˆ๋ฒฝ ํ›„ํ‡ด
- ์—ฐ์•ˆ ํ‡ด์  (Coastal Deposition): ์‚ฌ์ฃผ, ์‚ฌ์ทจ ํ˜•์„ฑ
- ์—ฐ์•ˆ๋ฅ˜ (Longshore Drift): ์ธก๋ฐฉ ํ‡ด์ ๋ฌผ ์ด๋™
ํ•ต์‹ฌ ๊ณต์‹:
์นจ์‹๋ฅ  E = K * H^2 * (1/R)
- H: ํŒŒ๊ณ  (Wave Height)
- R: ์•”์„ ์ €ํ•ญ๋ ฅ (Rock Resistance)
"""
import numpy as np
from .grid import WorldGrid
class WaveKernel:
"""
ํŒŒ๋ž‘ ์ปค๋„
ํ•ด์•ˆ์„ ์—์„œ์˜ ํŒŒ๋ž‘ ์ž‘์šฉ์„ ์‹œ๋ฎฌ๋ ˆ์ด์…˜.
์นจ์‹๊ณผ ํ‡ด์ ์„ ๋™์‹œ์— ์ฒ˜๋ฆฌ.
"""
def __init__(self, grid: WorldGrid,
wave_height: float = 2.0, # m
wave_period: float = 8.0, # s
wave_direction: float = 0.0, # degrees from N
K_erosion: float = 0.001):
self.grid = grid
self.wave_height = wave_height
self.wave_period = wave_period
self.wave_direction = np.radians(wave_direction)
self.K = K_erosion
def identify_coastline(self) -> np.ndarray:
"""
ํ•ด์•ˆ์„  ์‹๋ณ„
์œก์ง€-๋ฐ”๋‹ค ๊ฒฝ๊ณ„์„ ์„ ์ฐพ์Œ
Returns:
coastline_mask: ํ•ด์•ˆ์„  ์…€ ๋งˆ์Šคํฌ
"""
underwater = self.grid.is_underwater()
# ํ•ด์•ˆ์„  = ์œก์ง€์ธ๋ฐ ์ธ์ ‘ ์…€์— ๋ฐ”๋‹ค๊ฐ€ ์žˆ๋Š” ๊ณณ
h, w = self.grid.height, self.grid.width
coastline = np.zeros((h, w), dtype=bool)
for r in range(h):
for c in range(w):
if underwater[r, c]:
continue # ๋ฐ”๋‹ค๋Š” ํ•ด์•ˆ์„  ์•„๋‹˜
# ์ธ์ ‘ ์…€ ํ™•์ธ
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = r + dr, c + dc
if 0 <= nr < h and 0 <= nc < w:
if underwater[nr, nc]:
coastline[r, c] = True
break
if coastline[r, c]:
break
return coastline
def calculate_wave_energy(self) -> np.ndarray:
"""
ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ๋ถ„ํฌ ๊ณ„์‚ฐ
Returns:
energy: ๊ฐ ์…€์˜ ํŒŒ๋ž‘ ์—๋„ˆ์ง€
"""
h, w = self.grid.height, self.grid.width
# ๊ธฐ๋ณธ ์—๋„ˆ์ง€ = H^2 (ํŒŒ๊ณ  ์ œ๊ณฑ์— ๋น„๋ก€)
base_energy = self.wave_height ** 2
# ์ˆ˜์‹ฌ์— ๋”ฐ๋ฅธ ๊ฐ์‡ 
# ๊นŠ์€ ๋ฐ”๋‹ค: ์—๋„ˆ์ง€ ์œ ์ง€, ์–•์€ ๊ณณ: ์—๋„ˆ์ง€ ์ง‘์ค‘ ํ›„ ์‡„ํŒŒ
sea_depth = np.maximum(0, self.grid.sea_level - self.grid.elevation)
# ์ฒœํ•ด ํšจ๊ณผ: ์ˆ˜์‹ฌ < ํŒŒ์žฅ/2 ์ผ ๋•Œ ์—๋„ˆ์ง€ ์ฆ๊ฐ€
wavelength = 1.56 * (self.wave_period ** 2) # ์‹ฌํ•ด ํŒŒ์žฅ ๊ทผ์‚ฌ
depth_factor = np.ones((h, w))
shallow = sea_depth < wavelength / 2
depth_factor[shallow] = 1.0 + 0.5 * (1 - sea_depth[shallow] / (wavelength / 2))
energy = base_energy * depth_factor
# ์œก์ง€๋Š” ์—๋„ˆ์ง€ 0
energy[~self.grid.is_underwater()] = 0
return energy
def erode_coast(self, coastline: np.ndarray,
wave_energy: np.ndarray,
rock_resistance: np.ndarray = None,
dt: float = 1.0) -> np.ndarray:
"""
ํ•ด์•ˆ ์นจ์‹
Args:
coastline: ํ•ด์•ˆ์„  ๋งˆ์Šคํฌ
wave_energy: ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ๋ฐฐ์—ด
rock_resistance: ์•”์„ ์ €ํ•ญ๋ ฅ (0~1, ๋†’์„์ˆ˜๋ก ์ €ํ•ญ)
dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
Returns:
erosion: ์นจ์‹๋Ÿ‰ ๋ฐฐ์—ด
"""
h, w = self.grid.height, self.grid.width
if rock_resistance is None:
rock_resistance = np.ones((h, w)) * 0.5
erosion = np.zeros((h, w), dtype=np.float64)
# ํ•ด์•ˆ์„  ์…€์— ๋Œ€ํ•ด ์นจ์‹ ๊ณ„์‚ฐ
coast_coords = np.argwhere(coastline)
for r, c in coast_coords:
# ์ธ์ ‘ ๋ฐ”๋‹ค ์…€์˜ ํ‰๊ท  ์—๋„ˆ์ง€
adjacent_energy = 0
count = 0
for dr in [-1, 0, 1]:
for dc in [-1, 0, 1]:
if dr == 0 and dc == 0:
continue
nr, nc = r + dr, c + dc
if 0 <= nr < h and 0 <= nc < w:
if self.grid.is_underwater()[nr, nc]:
adjacent_energy += wave_energy[nr, nc]
count += 1
if count > 0:
avg_energy = adjacent_energy / count
# ์นจ์‹๋ฅ  = K * Energy / Resistance
resistance = rock_resistance[r, c]
erosion[r, c] = self.K * avg_energy * (1 - resistance) * dt
return erosion
def longshore_drift(self, coastline: np.ndarray,
sediment_available: np.ndarray,
dt: float = 1.0) -> np.ndarray:
"""
์—ฐ์•ˆ๋ฅ˜์— ์˜ํ•œ ํ‡ด์ ๋ฌผ ์ด๋™
Args:
coastline: ํ•ด์•ˆ์„  ๋งˆ์Šคํฌ
sediment_available: ์ด๋™ ๊ฐ€๋Šฅํ•œ ํ‡ด์ ๋ฌผ
dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
Returns:
change: ํ‡ด์ ๋ฌผ ๋ณ€ํ™”๋Ÿ‰
"""
h, w = self.grid.height, self.grid.width
change = np.zeros((h, w), dtype=np.float64)
# ํŒŒ๋ž‘ ๋ฐฉํ–ฅ์— ๋”ฐ๋ฅธ ์—ฐ์•ˆ๋ฅ˜ ๋ฐฉํ–ฅ
# 0 = N, 90 = E, etc.
drift_dx = np.sin(self.wave_direction)
drift_dy = -np.cos(self.wave_direction) # Y์ถ• ๋ฐ˜์ „
coast_coords = np.argwhere(coastline)
for r, c in coast_coords:
available = sediment_available[r, c]
if available <= 0:
continue
# ์ด๋™๋Ÿ‰
move_amount = min(available * 0.1 * dt, available)
# ๋ชฉํ‘œ ์…€ (์—ฐ์•ˆ๋ฅ˜ ๋ฐฉํ–ฅ)
tr = int(r + drift_dy)
tc = int(c + drift_dx)
if 0 <= tr < h and 0 <= tc < w:
if coastline[tr, tc] or self.grid.is_underwater()[tr, tc]:
change[r, c] -= move_amount
change[tr, tc] += move_amount
return change
def step(self, dt: float = 1.0,
rock_resistance: np.ndarray = None) -> dict:
"""
1๋‹จ๊ณ„ ํŒŒ๋ž‘ ์ž‘์šฉ ์‹คํ–‰
Args:
dt: ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ
rock_resistance: ์•”์„ ์ €ํ•ญ๋ ฅ ๋ฐฐ์—ด
Returns:
result: ์นจ์‹/ํ‡ด์  ๊ฒฐ๊ณผ
"""
# 1. ํ•ด์•ˆ์„  ์‹๋ณ„
coastline = self.identify_coastline()
if not np.any(coastline):
return {'erosion': np.zeros_like(self.grid.elevation),
'drift': np.zeros_like(self.grid.elevation)}
# 2. ํŒŒ๋ž‘ ์—๋„ˆ์ง€ ๊ณ„์‚ฐ
wave_energy = self.calculate_wave_energy()
# 3. ํ•ด์•ˆ ์นจ์‹
erosion = self.erode_coast(coastline, wave_energy, rock_resistance, dt)
# ์นจ์‹ ์ ์šฉ (bedrock ๊ฐ์†Œ)
self.grid.bedrock -= erosion
# ์นจ์‹๋œ ์–‘์€ ํ‡ด์ ๋ฌผ๋กœ ๋ณ€ํ™˜
self.grid.sediment += erosion * 0.8 # ์ผ๋ถ€ ์œ ์‹ค
# 4. ์—ฐ์•ˆ๋ฅ˜ (Longshore Drift)
drift = self.longshore_drift(coastline, self.grid.sediment, dt)
self.grid.sediment += drift
# ๊ณ ๋„ ๋™๊ธฐํ™”
self.grid.update_elevation()
return {
'erosion': erosion,
'drift': drift
}