Geo-Lab / engine /climate.py
HANSOL
Geo-Lab v4.1 - Clean deployment
2afa69c
"""
Climate Kernel (๊ธฐํ›„ ์ปค๋„)
๊ธฐํ›„ ์กฐ๊ฑด์— ๋”ฐ๋ฅธ ๊ฐ•์ˆ˜/๊ธฐ์˜จ ๋ถ„ํฌ ์ƒ์„ฑ
- ์œ„๋„ ๊ธฐ๋ฐ˜ ๊ฐ•์ˆ˜ ํŒจํ„ด
- ๊ณ ๋„ ๊ธฐ๋ฐ˜ ์ง€ํ˜•์„ฑ ๊ฐ•์ˆ˜
- ๊ธฐ์˜จ์— ๋”ฐ๋ฅธ ํ’ํ™”์œจ ์กฐ์ ˆ
"""
import numpy as np
from .grid import WorldGrid
class ClimateKernel:
"""
๊ธฐํ›„ ์ปค๋„
๊ฐ•์ˆ˜์™€ ๊ธฐ์˜จ ๋ถ„ํฌ๋ฅผ ์ƒ์„ฑํ•˜์—ฌ ๋‹ค๋ฅธ ํ”„๋กœ์„ธ์Šค์— ์˜ํ–ฅ.
"""
def __init__(self, grid: WorldGrid,
base_precipitation: float = 1000.0, # mm/year
base_temperature: float = 15.0, # ยฐC
lapse_rate: float = 6.5): # ยฐC/km (๊ณ ๋„ ๊ฐ๋ฅ )
self.grid = grid
self.base_precipitation = base_precipitation
self.base_temperature = base_temperature
self.lapse_rate = lapse_rate
def generate_precipitation(self,
orographic_factor: float = 0.5,
latitude_effect: bool = True) -> np.ndarray:
"""
๊ฐ•์ˆ˜ ๋ถ„ํฌ ์ƒ์„ฑ
Args:
orographic_factor: ์ง€ํ˜•์„ฑ ๊ฐ•์ˆ˜ ๊ฐ•๋„ (0~1)
latitude_effect: ์œ„๋„ ํšจ๊ณผ ์ ์šฉ ์—ฌ๋ถ€
Returns:
precipitation: ๊ฐ•์ˆ˜๋Ÿ‰ ๋ฐฐ์—ด (mm/year โ†’ m/timestep์œผ๋กœ ๋ณ€ํ™˜ ํ•„์š”)
"""
h, w = self.grid.height, self.grid.width
elev = self.grid.elevation
# ๊ธฐ๋ณธ ๊ฐ•์ˆ˜ (๊ท ์ผ)
precip = np.ones((h, w)) * self.base_precipitation
# 1. ์œ„๋„ ํšจ๊ณผ (์ ๋„ > ๊ทน์ง€๋ฐฉ)
if latitude_effect:
# ๊ทธ๋ฆฌ๋“œ Y์ถ•์„ ์œ„๋„๋กœ ๊ทผ์‚ฌ (0=์ ๋„, h=๊ทน)
# ์—ด๋Œ€ = ๊ฐ€์žฅ ๋งŽ์Œ, ์•„์—ด๋Œ€ ๊ฑด์กฐ, ์˜จ๋Œ€ ์ฆ๊ฐ€, ๊ทน ๊ฐ์†Œ
lat_factor = np.zeros(h)
for r in range(h):
# ์ •๊ทœํ™”๋œ ์œ„๋„ (0~1)
normalized_lat = r / h
# ๊ฐ„๋‹จํ•œ ์œ„๋„-๊ฐ•์ˆ˜ ๊ด€๊ณ„ (์‚ผ๋ด‰ ํŒจํ„ด ๊ทผ์‚ฌ)
lat_factor[r] = 1.0 - 0.3 * abs(normalized_lat - 0.5) * 2
precip *= lat_factor[:, np.newaxis]
# 2. ์ง€ํ˜•์„ฑ ๊ฐ•์ˆ˜ (๋ฐ”๋žŒ๋ฐ›์ด vs ๊ทธ๋Š˜)
if orographic_factor > 0:
# ๋™์ชฝ์—์„œ ๋ฐ”๋žŒ์ด ๋ถ„๋‹ค๊ณ  ๊ฐ€์ •
# ๊ณ ๋„ ์ฆ๊ฐ€ ๊ตฌ๊ฐ„ = ๊ฐ•์ˆ˜ ์ฆ๊ฐ€ (์ƒ์Šน ๊ธฐ๋ฅ˜)
# ๊ณ ๋„ ๊ฐ์†Œ ๊ตฌ๊ฐ„ = ๊ฐ•์ˆ˜ ๊ฐ์†Œ (ํ•˜๊ฐ• ๊ธฐ๋ฅ˜)
# X ๋ฐฉํ–ฅ ๊ฒฝ์‚ฌ
_, dx = self.grid.get_gradient()
# ์Œ์˜ ๊ฒฝ์‚ฌ = ๋™์ชฝ์œผ๋กœ ์ƒ์Šน = ๊ฐ•์ˆ˜ ์ฆ๊ฐ€
orographic = 1.0 + orographic_factor * (-dx) * 0.1
orographic = np.clip(orographic, 0.2, 2.0)
precip *= orographic
# 3. ๊ณ ๋„ ํšจ๊ณผ (์ผ์ • ๊ณ ๋„๊นŒ์ง€๋Š” ์ฆ๊ฐ€, ์ดํ›„ ๊ฐ์†Œ)
# ์ตœ๋Œ€ ๊ฐ•์ˆ˜ ๊ณ ๋„ (์˜ˆ: 2000m)
optimal_elev = 2000.0
elev_effect = 1.0 - 0.2 * np.abs(elev - optimal_elev) / optimal_elev
elev_effect = np.clip(elev_effect, 0.3, 1.2)
precip *= elev_effect
return precip
def get_temperature(self) -> np.ndarray:
"""
๊ธฐ์˜จ ๋ถ„ํฌ ์ƒ์„ฑ
Returns:
temperature: ๊ธฐ์˜จ ๋ฐฐ์—ด (ยฐC)
"""
h, w = self.grid.height, self.grid.width
elev = self.grid.elevation
# ๊ธฐ๋ณธ ๊ธฐ์˜จ
temp = np.ones((h, w)) * self.base_temperature
# 1. ์œ„๋„ ํšจ๊ณผ (์ ๋„ > ๊ทน)
for r in range(h):
normalized_lat = r / h
# ์ ๋„(0.5) = ๊ธฐ๋ณธ, ๊ทน(0, 1) = -30ยฐC
lat_temp_diff = 30.0 * abs(normalized_lat - 0.5) * 2
temp[r, :] -= lat_temp_diff
# 2. ๊ณ ๋„ ํšจ๊ณผ (์ฒด๊ฐ ์˜จ๋„ ๊ฐ๋ฅ )
# ํ•ด์ˆ˜๋ฉด ๊ธฐ์ค€์—์„œ km๋‹น lapse_rate๋งŒํผ ๊ฐ์†Œ
temp -= (elev / 1000.0) * self.lapse_rate
return temp
def get_weathering_rate(self, temperature: np.ndarray = None) -> np.ndarray:
"""
๊ธฐ์˜จ์— ๋”ฐ๋ฅธ ํ’ํ™”์œจ ๊ณ„์‚ฐ
ํ™”ํ•™์  ํ’ํ™”: ์˜จ๋‚œ ๋‹ค์Šต โ†’ ๋น ๋ฆ„
๋ฌผ๋ฆฌ์  ํ’ํ™”: ๋™๊ฒฐ-์œตํ•ด (-10~10ยฐC) โ†’ ๋น ๋ฆ„
Args:
temperature: ๊ธฐ์˜จ ๋ฐฐ์—ด (์—†์œผ๋ฉด ์ƒ์„ฑ)
Returns:
weathering_rate: ์ƒ๋Œ€ ํ’ํ™”์œจ (0~1)
"""
if temperature is None:
temperature = self.get_temperature()
h, w = self.grid.height, self.grid.width
# ํ™”ํ•™์  ํ’ํ™” (์˜จ๋„ ๋†’์„์ˆ˜๋ก)
chemical = np.clip((temperature + 10) / 40.0, 0, 1)
# ๋ฌผ๋ฆฌ์  ํ’ํ™” (๋™๊ฒฐ-์œตํ•ด ๋ฒ”์œ„์—์„œ ์ตœ๋Œ€)
freeze_thaw = np.exp(-((temperature - 0) ** 2) / (2 * 10 ** 2))
# ํ†ตํ•ฉ ํ’ํ™”์œจ
weathering = chemical * 0.5 + freeze_thaw * 0.5
return weathering