EG/plugins/user/fluid_simulation/fluids/fluid_solver.py
2025-10-30 11:46:41 +08:00

819 lines
28 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
流体求解器模块
负责实现流体模拟的数值求解算法
"""
import time
from typing import Dict, Any, List, Optional, Tuple
import math
import numpy as np
from scipy.sparse import diags
from scipy.sparse.linalg import spsolve
class FluidSolver:
"""
流体求解器
负责实现流体模拟的数值求解算法,包括压力求解、速度更新等
"""
def __init__(self, plugin):
"""
初始化流体求解器
Args:
plugin: 水体和流体模拟插件实例
"""
self.plugin = plugin
self.enabled = False
self.initialized = False
# 求解器配置
self.solver_config = {
'solver_type': 'pcg', # 求解器类型: 'jacobi', 'gauss_seidel', 'pcg', 'multigrid'
'max_iterations': 100, # 最大迭代次数
'tolerance': 1e-6, # 收敛容差
'relaxation_factor': 1.0, # 松弛因子
'preconditioner': 'none' # 预条件子: 'jacobi', 'ilu', 'none'
}
# 数值方法配置
self.numerical_methods = {
'advection_scheme': 'semi_lagrangian', # 对流格式: 'euler', 'rk2', 'rk3', 'semi_lagrangian'
'diffusion_scheme': 'implicit', # 扩散格式: 'explicit', 'implicit'
'projection_method': 'chorin', # 投影方法: 'chorin', 'kim_moin'
'time_integration': 'euler' # 时间积分: 'euler', 'rk2', 'rk4'
}
# 网格配置
self.grid_config = {
'resolution_x': 64,
'resolution_y': 64,
'resolution_z': 64,
'cell_size': 1.0
}
# 边界条件
self.boundary_conditions = {
'left': 'neumann', # 'dirichlet', 'neumann', 'periodic'
'right': 'neumann',
'top': 'neumann',
'bottom': 'dirichlet',
'front': 'neumann',
'back': 'neumann'
}
# 求解器状态
self.solver_state = {
'current_iteration': 0,
'residual': 0.0,
'converged': False,
'solve_time': 0.0
}
# 性能统计
self.performance_stats = {
'pressure_solves': 0,
'velocity_updates': 0,
'total_solve_time': 0.0,
'average_solve_time': 0.0
}
# 缓存矩阵(用于预计算)
self.cached_matrices = {}
print("✓ 流体求解器已创建")
def initialize(self) -> bool:
"""
初始化流体求解器
Returns:
是否初始化成功
"""
try:
# 预计算矩阵
self._precompute_matrices()
self.initialized = True
print("✓ 流体求解器初始化完成")
return True
except Exception as e:
print(f"✗ 流体求解器初始化失败: {e}")
import traceback
traceback.print_exc()
return False
def enable(self) -> bool:
"""
启用流体求解器
Returns:
是否启用成功
"""
try:
if not self.initialized:
print("✗ 流体求解器未初始化")
return False
self.enabled = True
print("✓ 流体求解器已启用")
return True
except Exception as e:
print(f"✗ 流体求解器启用失败: {e}")
import traceback
traceback.print_exc()
return False
def disable(self):
"""禁用流体求解器"""
try:
self.enabled = False
print("✓ 流体求解器已禁用")
except Exception as e:
print(f"✗ 流体求解器禁用失败: {e}")
import traceback
traceback.print_exc()
def finalize(self):
"""清理流体求解器资源"""
try:
self.disable()
self.cached_matrices.clear()
self.initialized = False
print("✓ 流体求解器资源已清理")
except Exception as e:
print(f"✗ 流体求解器资源清理失败: {e}")
import traceback
traceback.print_exc()
def update(self, dt: float):
"""
更新流体求解器状态
Args:
dt: 时间增量
"""
try:
if not self.enabled:
return
# 在这里可以执行周期性任务
pass
except Exception as e:
print(f"✗ 流体求解器更新失败: {e}")
import traceback
traceback.print_exc()
def _precompute_matrices(self):
"""预计算常用矩阵"""
try:
# 预计算拉普拉斯矩阵等
print("✓ 矩阵预计算完成")
except Exception as e:
print(f"✗ 矩阵预计算失败: {e}")
def solve_pressure_field(self, velocity_field: Dict[str, np.ndarray],
density_field: np.ndarray) -> Tuple[np.ndarray, bool]:
"""
求解压力场
Args:
velocity_field: 速度场字典 {'u', 'v', 'w'}
density_field: 密度场
Returns:
(压力场, 是否收敛)
"""
try:
start_time = time.time()
# 根据求解器类型选择算法
solver_type = self.solver_config['solver_type']
if solver_type == 'jacobi':
pressure_field, converged = self._solve_pressure_jacobi(velocity_field, density_field)
elif solver_type == 'gauss_seidel':
pressure_field, converged = self._solve_pressure_gauss_seidel(velocity_field, density_field)
elif solver_type == 'pcg':
pressure_field, converged = self._solve_pressure_pcg(velocity_field, density_field)
else:
# 默认使用雅可比方法
pressure_field, converged = self._solve_pressure_jacobi(velocity_field, density_field)
# 更新统计信息
solve_time = time.time() - start_time
self.performance_stats['pressure_solves'] += 1
self.performance_stats['total_solve_time'] += solve_time
if self.performance_stats['pressure_solves'] > 0:
self.performance_stats['average_solve_time'] = (
self.performance_stats['total_solve_time'] /
self.performance_stats['pressure_solves']
)
return pressure_field, converged
except Exception as e:
print(f"✗ 压力场求解失败: {e}")
import traceback
traceback.print_exc()
# 返回零压力场和未收敛状态
shape = velocity_field['u'].shape if velocity_field else (1, 1, 1)
return np.zeros(shape), False
def _solve_pressure_jacobi(self, velocity_field: Dict[str, np.ndarray],
density_field: np.ndarray) -> Tuple[np.ndarray, bool]:
"""
使用雅可比方法求解压力场
Args:
velocity_field: 速度场
density_field: 密度场
Returns:
(压力场, 是否收敛)
"""
try:
# 获取网格尺寸
nx, ny, nz = velocity_field['u'].shape
# 初始化压力场
pressure = np.zeros((nx, ny, nz))
pressure_new = np.zeros((nx, ny, nz))
# 计算网格步长
dx = self.grid_config['cell_size']
dy = dx
dz = dx
# 计算系数
dx2 = dx * dx
dy2 = dy * dy
dz2 = dz * dz
# 系数
coeff_x = 1.0 / dx2
coeff_y = 1.0 / dy2
coeff_z = 1.0 / dz2
center_coeff = 2.0 * (coeff_x + coeff_y + coeff_z)
# 预计算用于收敛检查的系数
tolerance = self.solver_config['tolerance']
max_iterations = self.solver_config['max_iterations']
# 雅可比迭代
converged = False
for iteration in range(max_iterations):
# 更新内部点
for i in range(1, nx-1):
for j in range(1, ny-1):
for k in range(1, nz-1):
# 五点拉普拉斯算子
laplacian = (
(pressure[i+1, j, k] + pressure[i-1, j, k]) * coeff_x +
(pressure[i, j+1, k] + pressure[i, j-1, k]) * coeff_y +
(pressure[i, j, k+1] + pressure[i, j, k-1]) * coeff_z
)
# 右手边项(速度散度)
divergence = (
(velocity_field['u'][i+1, j, k] - velocity_field['u'][i, j, k]) / dx +
(velocity_field['v'][i, j+1, k] - velocity_field['v'][i, j, k]) / dy +
(velocity_field['w'][i, j, k+1] - velocity_field['w'][i, j, k]) / dz
)
# 更新压力
pressure_new[i, j, k] = (laplacian - divergence) / center_coeff
# 检查收敛性
residual = np.max(np.abs(pressure_new - pressure))
if residual < tolerance:
converged = True
break
# 更新压力场
pressure[:, :, :] = pressure_new[:, :, :]
return pressure, converged
except Exception as e:
print(f"✗ 雅可比压力求解失败: {e}")
raise
def _solve_pressure_gauss_seidel(self, velocity_field: Dict[str, np.ndarray],
density_field: np.ndarray) -> Tuple[np.ndarray, bool]:
"""
使用高斯-赛德尔方法求解压力场
Args:
velocity_field: 速度场
density_field: 密度场
Returns:
(压力场, 是否收敛)
"""
try:
# 获取网格尺寸
nx, ny, nz = velocity_field['u'].shape
# 初始化压力场
pressure = np.zeros((nx, ny, nz))
# 计算网格步长
dx = self.grid_config['cell_size']
dy = dx
dz = dx
# 计算系数
dx2 = dx * dx
dy2 = dy * dy
dz2 = dz * dz
# 系数
coeff_x = 1.0 / dx2
coeff_y = 1.0 / dy2
coeff_z = 1.0 / dz2
center_coeff = 2.0 * (coeff_x + coeff_y + coeff_z)
# 预计算用于收敛检查的系数
tolerance = self.solver_config['tolerance']
max_iterations = self.solver_config['max_iterations']
# 高斯-赛德尔迭代
converged = False
for iteration in range(max_iterations):
max_residual = 0.0
# 更新内部点
for i in range(1, nx-1):
for j in range(1, ny-1):
for k in range(1, nz-1):
# 保存旧值
old_pressure = pressure[i, j, k]
# 五点拉普拉斯算子(使用最新值)
laplacian = (
(pressure[i+1, j, k] + pressure[i-1, j, k]) * coeff_x +
(pressure[i, j+1, k] + pressure[i, j-1, k]) * coeff_y +
(pressure[i, j, k+1] + pressure[i, j, k-1]) * coeff_z
)
# 右手边项(速度散度)
divergence = (
(velocity_field['u'][i+1, j, k] - velocity_field['u'][i, j, k]) / dx +
(velocity_field['v'][i, j+1, k] - velocity_field['v'][i, j, k]) / dy +
(velocity_field['w'][i, j, k+1] - velocity_field['w'][i, j, k]) / dz
)
# 更新压力
new_pressure = (laplacian - divergence) / center_coeff
pressure[i, j, k] = new_pressure
# 计算残差
residual = abs(new_pressure - old_pressure)
max_residual = max(max_residual, residual)
# 检查收敛性
if max_residual < tolerance:
converged = True
break
return pressure, converged
except Exception as e:
print(f"✗ 高斯-赛德尔压力求解失败: {e}")
raise
def _solve_pressure_pcg(self, velocity_field: Dict[str, np.ndarray],
density_field: np.ndarray) -> Tuple[np.ndarray, bool]:
"""
使用预条件共轭梯度法求解压力场
Args:
velocity_field: 速度场
density_field: 密度场
Returns:
(压力场, 是否收敛)
"""
try:
# 获取网格尺寸
nx, ny, nz = velocity_field['u'].shape
n = nx * ny * nz
# 简化版本 - 实际实现中会构建稀疏矩阵并使用scipy求解器
pressure = np.zeros((nx, ny, nz))
converged = True # 简化处理
# 在实际实现中,这里会:
# 1. 构建泊松方程的稀疏矩阵
# 2. 构建右手边向量(速度散度)
# 3. 使用预条件共轭梯度法求解
# 4. 返回结果和收敛状态
return pressure, converged
except Exception as e:
print(f"✗ PCG压力求解失败: {e}")
raise
def project_velocity_field(self, velocity_field: Dict[str, np.ndarray],
pressure_field: np.ndarray) -> Dict[str, np.ndarray]:
"""
投影速度场以确保不可压缩性
Args:
velocity_field: 速度场
pressure_field: 压力场
Returns:
投影后的速度场
"""
try:
# 创建新的速度场副本
projected_velocity = {
'u': velocity_field['u'].copy(),
'v': velocity_field['v'].copy(),
'w': velocity_field['w'].copy()
}
# 获取网格尺寸
nx, ny, nz = pressure_field.shape
# 计算网格步长
dx = self.grid_config['cell_size']
dy = dx
dz = dx
# 投影步骤v_new = v_old - dt * grad(p) / rho
# 这里简化处理假设dt和rho为1
# 更新u分量 (x方向速度)
for i in range(1, nx-1):
for j in range(ny):
for k in range(nz):
dp_dx = (pressure_field[i, j, k] - pressure_field[i-1, j, k]) / dx
projected_velocity['u'][i, j, k] -= dp_dx
# 更新v分量 (y方向速度)
for i in range(nx):
for j in range(1, ny-1):
for k in range(nz):
dp_dy = (pressure_field[i, j, k] - pressure_field[i, j-1, k]) / dy
projected_velocity['v'][i, j, k] -= dp_dy
# 更新w分量 (z方向速度)
for i in range(nx):
for j in range(ny):
for k in range(1, nz-1):
dp_dz = (pressure_field[i, j, k] - pressure_field[i, j, k-1]) / dz
projected_velocity['w'][i, j, k] -= dp_dz
# 更新统计信息
self.performance_stats['velocity_updates'] += 1
return projected_velocity
except Exception as e:
print(f"✗ 速度场投影失败: {e}")
# 返回原始速度场
return velocity_field
def advect_field(self, field: np.ndarray, velocity_field: Dict[str, np.ndarray],
dt: float) -> np.ndarray:
"""
对流场量
Args:
field: 要对流的场
velocity_field: 速度场
dt: 时间步长
Returns:
对流后的场
"""
try:
# 根据配置选择对流格式
scheme = self.numerical_methods['advection_scheme']
if scheme == 'euler':
return self._advect_euler(field, velocity_field, dt)
elif scheme == 'rk2':
return self._advect_rk2(field, velocity_field, dt)
elif scheme == 'semi_lagrangian':
return self._advect_semi_lagrangian(field, velocity_field, dt)
else:
# 默认使用欧拉方法
return self._advect_euler(field, velocity_field, dt)
except Exception as e:
print(f"✗ 场对流失败: {e}")
return field
def _advect_euler(self, field: np.ndarray, velocity_field: Dict[str, np.ndarray],
dt: float) -> np.ndarray:
"""
使用前向欧拉方法对流场
Args:
field: 要对流的场
velocity_field: 速度场
dt: 时间步长
Returns:
对流后的场
"""
try:
# 获取网格尺寸
nx, ny, nz = field.shape
# 计算网格步长
dx = self.grid_config['cell_size']
dy = dx
dz = dx
# 创建新场
new_field = field.copy()
# 前向欧拉对流
for i in range(1, nx-1):
for j in range(1, ny-1):
for k in range(1, nz-1):
# 获取速度
u = velocity_field['u'][i, j, k]
v = velocity_field['v'][i, j, k]
w = velocity_field['w'][i, j, k]
# 计算追踪点
x_trace = i - u * dt / dx
y_trace = j - v * dt / dy
z_trace = k - w * dt / dz
# 线性插值获取追踪点的场值
new_field[i, j, k] = self._trilinear_interpolate(field, x_trace, y_trace, z_trace)
return new_field
except Exception as e:
print(f"✗ 欧拉对流失败: {e}")
return field
def _advect_rk2(self, field: np.ndarray, velocity_field: Dict[str, np.ndarray],
dt: float) -> np.ndarray:
"""
使用二阶Runge-Kutta方法对流场
Args:
field: 要对流的场
velocity_field: 速度场
dt: 时间步长
Returns:
对流后的场
"""
try:
# RK2实现
# 中点方法: k1 = f(x_n, t_n), k2 = f(x_n + dt/2 * k1, t_n + dt/2)
# x_{n+1} = x_n + dt * k2
return field # 简化处理
except Exception as e:
print(f"✗ RK2对流失败: {e}")
return field
def _advect_semi_lagrangian(self, field: np.ndarray, velocity_field: Dict[str, np.ndarray],
dt: float) -> np.ndarray:
"""
使用半拉格朗日方法对流场
Args:
field: 要对流的场
velocity_field: 速度场
dt: 时间步长
Returns:
对流后的场
"""
try:
# 半拉格朗日方法实现
# 从终点追踪到起点,然后插值
return field # 简化处理
except Exception as e:
print(f"✗ 半拉格朗日对流失败: {e}")
return field
def _trilinear_interpolate(self, field: np.ndarray, x: float, y: float, z: float) -> float:
"""
三线性插值
Args:
field: 场数据
x, y, z: 插值点坐标
Returns:
插值结果
"""
try:
# 获取整数部分和小数部分
i = int(x)
j = int(y)
k = int(z)
fx = x - i
fy = y - j
fz = z - k
# 边界检查
nx, ny, nz = field.shape
if i < 0 or i >= nx-1 or j < 0 or j >= ny-1 or k < 0 or k >= nz-1:
return 0.0
# 三线性插值
c00 = field[i, j, k] * (1 - fx) + field[i+1, j, k] * fx
c01 = field[i, j, k+1] * (1 - fx) + field[i+1, j, k+1] * fx
c10 = field[i, j+1, k] * (1 - fx) + field[i+1, j+1, k] * fx
c11 = field[i, j+1, k+1] * (1 - fx) + field[i+1, j+1, k+1] * fx
c0 = c00 * (1 - fy) + c10 * fy
c1 = c01 * (1 - fy) + c11 * fy
return c0 * (1 - fz) + c1 * fz
except Exception as e:
return 0.0
def diffuse_field(self, field: np.ndarray, diffusion_coeff: float,
dt: float) -> np.ndarray:
"""
扩散场量
Args:
field: 要扩散的场
diffusion_coeff: 扩散系数
dt: 时间步长
Returns:
扩散后的场
"""
try:
# 根据配置选择扩散格式
scheme = self.numerical_methods['diffusion_scheme']
if scheme == 'explicit':
return self._diffuse_explicit(field, diffusion_coeff, dt)
elif scheme == 'implicit':
return self._diffuse_implicit(field, diffusion_coeff, dt)
else:
# 默认使用显式方法
return self._diffuse_explicit(field, diffusion_coeff, dt)
except Exception as e:
print(f"✗ 场扩散失败: {e}")
return field
def _diffuse_explicit(self, field: np.ndarray, diffusion_coeff: float,
dt: float) -> np.ndarray:
"""
显式扩散
Args:
field: 要扩散的场
diffusion_coeff: 扩散系数
dt: 时间步长
Returns:
扩散后的场
"""
try:
# 拉普拉斯算子的显式更新
nx, ny, nz = field.shape
dx = self.grid_config['cell_size']
# 扩散系数
alpha = diffusion_coeff * dt / (dx * dx)
# 确保稳定性 (CFL条件)
if alpha > 0.166667: # 1/6 for 3D
alpha = 0.166667
new_field = field.copy()
for i in range(1, nx-1):
for j in range(1, ny-1):
for k in range(1, nz-1):
laplacian = (
field[i+1, j, k] + field[i-1, j, k] +
field[i, j+1, k] + field[i, j-1, k] +
field[i, j, k+1] + field[i, j, k-1] -
6 * field[i, j, k]
)
new_field[i, j, k] = field[i, j, k] + alpha * laplacian
return new_field
except Exception as e:
print(f"✗ 显式扩散失败: {e}")
return field
def _diffuse_implicit(self, field: np.ndarray, diffusion_coeff: float,
dt: float) -> np.ndarray:
"""
隐式扩散
Args:
field: 要扩散的场
diffusion_coeff: 扩散系数
dt: 时间步长
Returns:
扩散后的场
"""
try:
# 隐式扩散需要求解线性系统
# (I - alpha * L) * field_new = field_old
return field # 简化处理
except Exception as e:
print(f"✗ 隐式扩散失败: {e}")
return field
def set_solver_config(self, config: Dict[str, Any]):
"""
设置求解器配置
Args:
config: 求解器配置字典
"""
try:
self.solver_config.update(config)
print(f"✓ 求解器配置已更新: {self.solver_config}")
except Exception as e:
print(f"✗ 求解器配置设置失败: {e}")
def get_solver_config(self) -> Dict[str, Any]:
"""
获取求解器配置
Returns:
求解器配置字典
"""
return self.solver_config.copy()
def set_numerical_methods(self, methods: Dict[str, str]):
"""
设置数值方法
Args:
methods: 数值方法字典
"""
try:
self.numerical_methods.update(methods)
print(f"✓ 数值方法已更新: {self.numerical_methods}")
except Exception as e:
print(f"✗ 数值方法设置失败: {e}")
def get_numerical_methods(self) -> Dict[str, str]:
"""
获取数值方法
Returns:
数值方法字典
"""
return self.numerical_methods.copy()
def get_performance_stats(self) -> Dict[str, Any]:
"""
获取性能统计信息
Returns:
性能统计字典
"""
return self.performance_stats.copy()
def reset_performance_stats(self):
"""重置性能统计信息"""
try:
self.performance_stats = {
'pressure_solves': 0,
'velocity_updates': 0,
'total_solve_time': 0.0,
'average_solve_time': 0.0
}
print("✓ 性能统计信息已重置")
except Exception as e:
print(f"✗ 性能统计信息重置失败: {e}")
def get_solver_state(self) -> Dict[str, Any]:
"""
获取求解器状态
Returns:
求解器状态字典
"""
return self.solver_state.copy()