/
DerTyu
/
forest_fire_simulation
Обзор
Документация
Войти
/
DerTyu
/
forest_fire_simulation
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
master
forest.py
291 строка
11 KB
DerTyu
Initial Commit
30 ноя 2025, 21:23
30 ноя 2025, 21:23
4e34cad
Код
Авторство
О чём код?
import numpy as np import matplotlib try: matplotlib.use("TkAgg") except Exception: print("Ошибка подключения библиотеки") import matplotlib.pyplot as plt from matplotlib.widgets import Button, TextBox, Slider # Параметры окна GRID_W, GRID_H = 96, 56 T_BURN = 3 BASE_RATE = 0.9 DRYNESS = 1.0 MOISTURE = 0.2 TIMER_MS = 120 NEIGHBORS = [(-1,-1), (-1,0), (-1,1), (0,-1), (0,1), (1,-1), (1,0), (1,1)] NEIGHBOR_WEIGHT = {(dx,dy): (1.0 if dx==0 or dy==0 else 0.7) for dx,dy in NEIGHBORS} class FireModel: def __init__(self, w=GRID_W, h=GRID_H, t_burn=T_BURN, base_rate=BASE_RATE, dryness=DRYNESS, moisture=MOISTURE, wind_x=0.0, wind_y=0.0): self.W = int(w); self.H = int(h) self.t_burn = int(t_burn) self.base_rate = float(base_rate) self.dryness = float(dryness) self.moisture = float(np.clip(moisture, 0.0, 0.999)) self.wind_x = float(wind_x); self.wind_y = float(wind_y) self.state = np.zeros((self.H, self.W), dtype=np.int8) # 0,1,2 self.timer = np.zeros((self.H, self.W), dtype=np.int16) self.time = 0 def ignite(self, x, y): if 0 <= x < self.W and 0 <= y < self.H: self.state[y, x] = 1 self.timer[y, x] = self.t_burn def reset(self, seed_x=None, seed_y=None): self.state.fill(0) self.timer.fill(0) self.time = 0 if seed_x is not None and seed_y is not None: self.ignite(seed_x, seed_y) def step(self): burning = (self.state == 1) self.timer[burning] -= 1 done = burning & (self.timer <= 0) self.state[done] = 2 influence = np.zeros_like(self.state, dtype=float) burning_f = burning.astype(float) wv = np.array([self.wind_x, self.wind_y], dtype=float) wv_norm = np.linalg.norm(wv) for (dx,dy), wbase in NEIGHBOR_WEIGHT.items(): sx0 = max(0, -dx); sx1 = self.W - max(0, dx) sy0 = max(0, -dy); sy1 = self.H - max(0, dy) dx0 = max(0, dx); dx1 = self.W - max(0, -dx) dy0 = max(0, dy); dy1 = self.H - max(0, -dy) neigh = burning_f[sy0:sy1, sx0:sx1] nv = np.array([dx, dy], dtype=float) nv_norm = np.linalg.norm(nv) if nv_norm > 0 and wv_norm > 0: cosang = float(np.dot(nv / nv_norm, wv / wv_norm)) wind_factor = 1.0 + (cosang * 0.5 * wv_norm) else: wind_factor = 1.0 influence[dy0:dy1, dx0:dx1] += wbase * wind_factor * neigh susceptible = (self.state == 0) lam = self.base_rate * influence * self.dryness * (1.0 - self.moisture) lam = np.minimum(lam, 50.0) p = 1.0 - np.exp(-lam) r = np.random.random((self.H, self.W)) ignite = susceptible & (r < p) self.state[ignite] = 1 self.timer[ignite] = self.t_burn self.time += 1 def is_active(self): return np.any(self.state == 1) def stats(self): b = int(np.count_nonzero(self.state == 1)) burned = int(np.count_nonzero(self.state == 2)) return {"t": int(self.time), "burning": b, "burned": burned} def burn_time_estimate(d_m, q_w_m2, rho, L): if q_w_m2 <= 0: return float('inf') v = q_w_m2 / (rho * L) if v <= 0: return float('inf') return d_m / v class App: def __init__(self): self.model = FireModel() self.seed_x = max(0, self.model.W // 12) self.seed_y = self.model.H // 2 self.model.reset(self.seed_x, self.seed_y) self.timer = None self.running = False self._build_ui() def _cmap(self): from matplotlib.colors import ListedColormap return ListedColormap(['#2ca02c', '#ff7f0e', '#2f2f2f']) def _build_ui(self): self.fig = plt.figure(figsize=(13,7)) # карта ax_map = self.fig.add_axes([0.03, 0.06, 0.62, 0.88]) ax_map.set_title("Распространение пожара - кликните по карте") self.img = ax_map.imshow(self.model.state, cmap=self._cmap(), vmin=0, vmax=2, interpolation='nearest') ax_map.axis('off') self.ax_map = ax_map # контролы справа left = 0.70; width = 0.27 y = 0.88; h = 0.055; gap = 0.04 ax = self.fig.add_axes([left, y, width, h]) self.s_wx = Slider(ax, 'Ветер X', -3, 3, valinit=0.0) y -= (h + gap) ax = self.fig.add_axes([left, y, width, h]) self.s_wy = Slider(ax, 'Ветер Y', -3, 3, valinit=0.0) y -= (h + gap) ax = self.fig.add_axes([left, y, width, h]) self.s_base = Slider(ax, 'Баз. скорость', 0.01, 2.0, valinit=BASE_RATE) y -= (h + gap) ax = self.fig.add_axes([left, y, width, h]) self.s_dry = Slider(ax, 'Сухость', 0.1, 2.0, valinit=DRYNESS) y -= (h + gap) ax = self.fig.add_axes([left, y, width, h]) self.s_moist = Slider(ax, 'Влажность', 0.0, 0.9, valinit=MOISTURE) y -= (h + gap) ax = self.fig.add_axes([left, y, width, h]) self.s_tburn = Slider(ax, 'Время горения', 1, 10, valinit=T_BURN, valfmt='%0.0f') y -= (h + gap + 0.01) btn_w = (width - 0.03) / 2; btn_h = 0.07 ax = self.fig.add_axes([left, y, btn_w, btn_h]) self.btn_start = Button(ax, 'Старт', color='lightgreen') ax = self.fig.add_axes([left + btn_w + 0.03, y, btn_w, btn_h]) self.btn_pause = Button(ax, 'Пауза', color='salmon') y -= (btn_h + 0.03) ax = self.fig.add_axes([left, y, btn_w*2 + 0.03, btn_h]) self.btn_rebuild = Button(ax, 'Пересобрать', color='lightblue') y -= (btn_h + 0.04) th = 0.048 ax = self.fig.add_axes([left, 0.16, width, th]) self.tb_d = TextBox(ax, 'd (м)', initial='0.02') ax = self.fig.add_axes([left, 0.16 - (th + 0.012), width, th]) self.tb_q = TextBox(ax, "q'' (Вт/м2)", initial='50000') ax = self.fig.add_axes([left, 0.16 - 2*(th + 0.012), width, th]) self.tb_rho = TextBox(ax, 'rho (кг/м3)', initial='500') ax = self.fig.add_axes([left, 0.16 - 3*(th + 0.012), width*0.66, th]) self.tb_L = TextBox(ax, 'L (Дж/кг)', initial='2000000') ax = self.fig.add_axes([left + width*0.68, 0.16 - 3*(th + 0.012), width*0.32, th]) self.btn_calc = Button(ax, 'Рассчитать', color='khaki') ax = self.fig.add_axes([left, 0.96, width, 0.035]) ax.axis('off') self.status = ax.text(0.01, 0.6, self._status_str(), va='center', fontsize=10) self.btn_start.on_clicked(self.on_start) self.btn_pause.on_clicked(self.on_pause) self.btn_rebuild.on_clicked(self.on_rebuild) self.btn_calc.on_clicked(self.on_calc) self.fig.canvas.mpl_connect('button_press_event', self.on_map_click) self.timer = self.fig.canvas.new_timer(interval=TIMER_MS) self.timer.add_callback(self._timer_tick) self.img.set_data(self.model.state) self.fig.canvas.draw_idle() def _status_str(self): s = self.model.stats() return f"t={s['t']} горит={s['burning']} выгорело={s['burned']}" def _read_ui(self): self.model.wind_x = float(self.s_wx.val) self.model.wind_y = float(self.s_wy.val) self.model.base_rate = float(self.s_base.val) self.model.dryness = float(self.s_dry.val) self.model.moisture = float(self.s_moist.val) self.model.t_burn = int(round(float(self.s_tburn.val))) def on_start(self, event): if not self.running: self._read_ui() try: self.timer.start() except Exception: print("Ошибка таймера") self.running = True self.status.set_text("Запущено...") self.fig.canvas.draw_idle() def on_pause(self, event): if self.running: try: self.timer.stop() except Exception: print("Ошибка таймера") self.running = False self.status.set_text("Пауза. " + self._status_str()) self.fig.canvas.draw_idle() def on_rebuild(self, event): if self.running: try: self.timer.stop() except Exception: pass self.running = False self._read_ui() self.model = FireModel(W=self.model.W, H=self.model.H, t_burn=int(round(self.s_tburn.val)), base_rate=float(self.s_base.val), dryness=float(self.s_dry.val), moisture=float(self.s_moist.val), wind_x=float(self.s_wx.val), wind_y=float(self.s_wy.val)) self.model.reset(self.seed_x, self.seed_y) self.img.set_data(self.model.state) self.status.set_text("Пересобрано. " + self._status_str()) self.fig.canvas.draw_idle() def on_calc(self, event): try: d = float(self.tb_d.text.strip()) q = float(self.tb_q.text.strip()) rho = float(self.tb_rho.text.strip()) L = float(self.tb_L.text.strip()) t_sec = burn_time_estimate(d, q, rho, L) if t_sec == float('inf'): txt = "Невозможно оценить (q'' <= 0)" else: txt = f"Оценка: {t_sec:.1f} с (~{t_sec/60:.2f} мин)" except Exception: txt = "Ошибка входных данных" self.status.set_text(txt) self.fig.canvas.draw_idle() def on_map_click(self, event): if event.inaxes == self.ax_map and event.xdata is not None and event.ydata is not None: x = int(round(event.xdata)); y = int(round(event.ydata)) x = max(0, min(self.model.W-1, x)); y = max(0, min(self.model.H-1, y)) self.seed_x, self.seed_y = x, y self.model.reset(x, y) self.img.set_data(self.model.state) self.status.set_text(f"Очаг: ({x},{y}). Нажмите 'Старт'.") self.fig.canvas.draw_idle() def _timer_tick(self): if not self.running: return self._read_ui() self.model.step() self.img.set_data(self.model.state) self.status.set_text(self._status_str()) if not self.model.is_active(): try: self.timer.stop() except Exception: print("Ошибка таймера") self.running = False self.status.set_text("Завершено. " + self._status_str()) try: self.fig.canvas.draw_idle() except Exception: pass def run(self): plt.show() if __name__ == "__main__": App().run()