Skip to content

Commit 37f6471

Browse files
authored
Add files via upload
1 parent 61a849c commit 37f6471

File tree

3 files changed

+721
-0
lines changed

3 files changed

+721
-0
lines changed

functions.py

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import numpy as np
2+
import matplotlib.pyplot as plt
3+
4+
# ======================
5+
# 双线性插值采样函数
6+
# ======================
7+
def bilinear_sample(arr, yf, xf):
8+
"""在浮点索引 (yf=row, xf=col) 上采样 2D 数组 arr 的双线性插值值。"""
9+
rows, cols = arr.shape
10+
if yf < 0 or xf < 0 or yf > rows - 1 or xf > cols - 1:
11+
return np.nan
12+
i0 = int(np.floor(yf)); j0 = int(np.floor(xf))
13+
i1 = min(i0 + 1, rows - 1); j1 = min(j0 + 1, cols - 1)
14+
dy = yf - i0; dx = xf - j0
15+
v00 = arr[i0, j0]; v10 = arr[i1, j0]; v01 = arr[i0, j1]; v11 = arr[i1, j1]
16+
return (v00 * (1 - dy) * (1 - dx) +
17+
v10 * dy * (1 - dx) +
18+
v01 * (1 - dy) * dx +
19+
v11 * dy * dx)
20+
21+
22+
# ======================
23+
# 稳定版落石物理模拟
24+
# ======================
25+
def rockfall_physics_stable(dem, start_idx, cellsize=1.0,
26+
g=9.81, dt=0.1, friction=0.3,
27+
max_steps=20000, speed_tol=1e-2, slope_tol=1e-3):
28+
"""
29+
稳定版落石运动模拟,包含半隐式积分与低坡度停止判断。
30+
返回路径 (row, col)、速度数组、动能数组。
31+
"""
32+
rows, cols = dem.shape
33+
grad_y_full, grad_x_full = np.gradient(dem, cellsize, cellsize)
34+
35+
start_row, start_col = start_idx
36+
pos_y = start_row * cellsize
37+
pos_x = start_col * cellsize
38+
vx, vy = 0.0, 0.0
39+
40+
path_indices = [(start_row, start_col)]
41+
path_speed = [0.0]
42+
path_energy = [0.0] # 动能 (0.5 * v^2),假设单位质量=1
43+
44+
for step in range(max_steps):
45+
frac_row = pos_y / cellsize
46+
frac_col = pos_x / cellsize
47+
48+
if frac_row < 1 or frac_row > rows - 2 or frac_col < 1 or frac_col > cols - 2:
49+
break
50+
51+
dg_dx = bilinear_sample(grad_x_full, frac_row, frac_col)
52+
dg_dy = bilinear_sample(grad_y_full, frac_row, frac_col)
53+
if np.isnan(dg_dx) or np.isnan(dg_dy):
54+
break
55+
56+
grad_norm = np.hypot(dg_dx, dg_dy)
57+
denom = np.sqrt(1.0 + grad_norm**2)
58+
59+
# 半隐式积分
60+
a_x = -g * dg_dx / denom - friction * vx
61+
a_y = -g * dg_dy / denom - friction * vy
62+
63+
vx += a_x * dt
64+
vy += a_y * dt
65+
pos_x += vx * dt
66+
pos_y += vy * dt
67+
68+
speed = np.hypot(vx, vy)
69+
kinetic_E = 0.5 * speed**2
70+
71+
path_indices.append((pos_y / cellsize, pos_x / cellsize))
72+
path_speed.append(speed)
73+
path_energy.append(kinetic_E)
74+
75+
if speed < speed_tol or grad_norm < slope_tol:
76+
break
77+
78+
return np.array(path_indices), np.array(path_speed), np.array(path_energy)
79+
80+
81+
# ======================
82+
# 绘图函数(在独立弹窗中显示)
83+
# ======================
84+
def plot_with_speed_and_energy(dem, path_idx, speed, energy, start_idx, cmap='terrain'):
85+
plt.figure(figsize=(14, 6))
86+
gs = plt.GridSpec(1, 2, width_ratios=[2, 1])
87+
88+
# ---- 左图:DEM + 路径 ----
89+
ax1 = plt.subplot(gs[0])
90+
im = ax1.imshow(dem, cmap=cmap, origin='upper')
91+
plt.colorbar(im, ax=ax1, label='Elevation (m)')
92+
93+
if len(path_idx) > 1:
94+
for k in range(len(path_idx)-1):
95+
y0, x0 = path_idx[k]
96+
y1, x1 = path_idx[k+1]
97+
s = speed[k]
98+
ax1.plot([x0, x1], [y0, y1], color=plt.cm.autumn_r(min(1.0, s/10.0)), linewidth=2)
99+
100+
ax1.scatter([start_idx[1]], [start_idx[0]], c='k', s=60, label='Start', zorder=3)
101+
ax1.set_title('Rockfall Path (color ≈ speed)')
102+
ax1.legend()
103+
ax1.invert_yaxis()
104+
105+
# ---- 右图:动能曲线 ----
106+
ax2 = plt.subplot(gs[1])
107+
ax2.plot(energy, color='orange', linewidth=2)
108+
ax2.set_title("Kinetic Energy vs Step")
109+
ax2.set_xlabel("Step")
110+
ax2.set_ylabel("Energy (J/kg)")
111+
ax2.grid(alpha=0.3)
112+
113+
plt.tight_layout()
114+
plt.show() # 原生 Matplotlib 弹窗(含放大/缩放/回退功能)

main.py

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import tkinter as tk
2+
from tkinter import ttk, filedialog, messagebox
3+
import numpy as np
4+
from functions import rockfall_physics_stable, plot_with_speed_and_energy
5+
6+
class RockfallGUI:
7+
def __init__(self, root):
8+
self.root = root
9+
self.root.title("Rockfall Simulation")
10+
self.root.geometry("350x400")
11+
12+
# 左侧控制区
13+
control_frame = ttk.Frame(root, width=300)
14+
control_frame.pack(fill='both', expand=True, padx=10, pady=10)
15+
16+
ttk.Label(
17+
control_frame,
18+
text="Rockfall Simulation Parameters",
19+
font=('Arial', 12, 'bold')
20+
).pack(pady=5)
21+
22+
# 参数变量
23+
self.cellsize = tk.DoubleVar(value=30.0)
24+
self.dt = tk.DoubleVar(value=0.1)
25+
self.friction = tk.DoubleVar(value=0.15)
26+
self.max_steps = tk.IntVar(value=5000)
27+
self.start_row = tk.IntVar(value=357)
28+
self.start_col = tk.IntVar(value=100)
29+
self.g = tk.DoubleVar(value=9.81)
30+
31+
# 创建输入框
32+
self.entries = {}
33+
self._add_param(control_frame, "Cellsize (m):", self.cellsize)
34+
self._add_param(control_frame, "Time Step (s):", self.dt)
35+
self._add_param(control_frame, "Friction:", self.friction)
36+
self._add_param(control_frame, "Max Steps:", self.max_steps)
37+
self._add_param(control_frame, "Start Row:", self.start_row)
38+
self._add_param(control_frame, "Start Col:", self.start_col)
39+
self._add_param(control_frame, "Gravity:", self.g)
40+
41+
# 操作按钮
42+
ttk.Button(control_frame, text="Load DEM", command=self.load_dem).pack(pady=10, fill='x')
43+
ttk.Button(control_frame, text="Run Simulation", command=self.run_simulation).pack(pady=5, fill='x')
44+
45+
self.dem = None
46+
47+
def _add_param(self, parent, label, variable):
48+
frame = ttk.Frame(parent)
49+
frame.pack(fill='x', pady=3)
50+
ttk.Label(frame, text=label, width=15).pack(side='left')
51+
entry = ttk.Entry(frame, textvariable=variable)
52+
entry.pack(side='right', fill='x', expand=True)
53+
self.entries[label] = entry # 存起来备用
54+
55+
def load_dem(self):
56+
filepath = filedialog.askopenfilename(
57+
title="Select DEM File",
58+
filetypes=[("ASCII DEM (*.asc)", "*.asc"), ("All Files", "*.*")]
59+
)
60+
if not filepath:
61+
return
62+
63+
try:
64+
self.dem = np.loadtxt(filepath, skiprows=6)
65+
messagebox.showinfo("Success", f"DEM loaded: shape = {self.dem.shape}")
66+
except Exception as e:
67+
messagebox.showerror("Error", f"Failed to load DEM:\n{e}")
68+
69+
def run_simulation(self):
70+
if self.dem is None:
71+
messagebox.showwarning("No DEM", "Please load a DEM file first.")
72+
return
73+
74+
try:
75+
cellsize = self.cellsize.get()
76+
dt = self.dt.get()
77+
friction = self.friction.get()
78+
max_steps = self.max_steps.get()
79+
start_row = self.start_row.get()
80+
start_col = self.start_col.get()
81+
g = self.g.get()
82+
except tk.TclError:
83+
messagebox.showerror("Invalid Input", "Please check your parameter values.")
84+
return
85+
86+
start = (start_row, start_col)
87+
88+
try:
89+
path_idx, speed, energy = rockfall_physics_stable(
90+
self.dem, start,
91+
cellsize=cellsize, g=g, dt=dt,
92+
friction=friction, max_steps=max_steps
93+
)
94+
plot_with_speed_and_energy(self.dem, path_idx, speed, energy, start)
95+
except Exception as e:
96+
messagebox.showerror("Error", f"Simulation failed:\n{e}")
97+
98+
if __name__ == "__main__":
99+
root = tk.Tk()
100+
app = RockfallGUI(root)
101+
root.mainloop()

0 commit comments

Comments
 (0)