|
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?立即注册
x
引言:NumPy与科学计算
NumPy(Numerical Python)是Python语言中用于科学计算的核心库,它提供了高性能的多维数组对象以及用于处理这些数组的工具。在物理模拟和数值解法领域,NumPy的重要性不言而喻。它不仅提供了高效的数组操作,还包含了大量的数学函数,使得科学家和工程师能够快速实现复杂的物理模型和数值算法。
物理模拟和数值解法是现代科学研究和工程应用的基础。从天体运动到量子力学,从流体动力学到电磁场分析,这些领域都需要大量的数值计算。NumPy凭借其强大的数组操作能力和丰富的数学函数库,成为了这些领域不可或缺的工具。
本文将从NumPy的基础知识开始,逐步深入到其在物理模拟和数值解法中的高级应用,帮助读者掌握使用NumPy解决复杂物理问题的能力。
NumPy基础:数组与操作
NumPy数组的创建与基本属性
NumPy的核心是ndarray对象,它是一个多维数组,可以存储相同类型的数据。让我们从最基本的数组创建开始:
- import numpy as np
- # 创建一维数组
- a = np.array([1, 2, 3, 4, 5])
- print("一维数组:", a)
- # 创建二维数组
- b = np.array([[1, 2, 3], [4, 5, 6]])
- print("二维数组:\n", b)
- # 创建特定数组
- zeros_array = np.zeros((3, 4)) # 创建3x4的全零数组
- ones_array = np.ones((2, 3)) # 创建2x3的全一数组
- random_array = np.random.rand(3, 3) # 创建3x3的随机数组
- print("全零数组:\n", zeros_array)
- print("全一数组:\n", ones_array)
- print("随机数组:\n", random_array)
- # 数组的基本属性
- print("数组a的形状:", a.shape)
- print("数组b的维度:", b.ndim)
- print("数组a的数据类型:", a.dtype)
- print("数组b的大小:", b.size)
复制代码
数组操作与数学运算
NumPy提供了丰富的数组操作和数学运算功能:
- import numpy as np
- # 创建两个数组
- a = np.array([[1, 2], [3, 4]])
- b = np.array([[5, 6], [7, 8]])
- # 基本数学运算
- print("数组加法:\n", a + b)
- print("数组减法:\n", a - b)
- print("数组乘法(元素级):\n", a * b)
- print("数组除法(元素级):\n", a / b)
- # 矩阵乘法
- print("矩阵乘法:\n", np.dot(a, b))
- # 统计运算
- print("数组a的所有元素和:", np.sum(a))
- print("数组a的每列和:", np.sum(a, axis=0))
- print("数组a的每行和:", np.sum(a, axis=1))
- print("数组a的平均值:", np.mean(a))
- print("数组a的标准差:", np.std(a))
- # 形状操作
- c = np.array([1, 2, 3, 4, 5, 6])
- print("重塑数组:", c.reshape(2, 3))
- print("数组转置:\n", a.T)
复制代码
广播机制
NumPy的广播机制是其在科学计算中强大功能之一,它允许不同形状的数组进行运算:
- import numpy as np
- # 广播示例
- a = np.array([[1, 2, 3], [4, 5, 6]]) # 形状(2, 3)
- b = np.array([10, 20, 30]) # 形状(3,)
- # b会被"广播"以匹配a的形状
- print("广播加法:\n", a + b)
- # 另一个广播示例
- c = np.array([[1], [2]]) # 形状(2, 1)
- d = np.array([10, 20, 30]) # 形状(3,)
- # c和d都会被广播以匹配形状(2, 3)
- print("多维广播:\n", c + d)
复制代码
物理模拟中的NumPy应用
运动学模拟:抛体运动
抛体运动是物理学中的经典问题,我们可以使用NumPy来模拟和可视化抛体运动轨迹:
- import numpy as np
- import matplotlib.pyplot as plt
- # 抛体运动参数
- g = 9.8 # 重力加速度 (m/s^2)
- v0 = 20 # 初始速度 (m/s)
- angle = 45 # 发射角度 (度)
- angle_rad = np.radians(angle) # 转换为弧度
- # 时间参数
- t_max = 2 * v0 * np.sin(angle_rad) / g # 总飞行时间
- dt = 0.01 # 时间步长
- t = np.arange(0, t_max, dt) # 时间数组
- # 计算位置
- x = v0 * np.cos(angle_rad) * t
- y = v0 * np.sin(angle_rad) * t - 0.5 * g * t**2
- # 计算速度分量
- vx = v0 * np.cos(angle_rad) * np.ones_like(t)
- vy = v0 * np.sin(angle_rad) - g * t
- v = np.sqrt(vx**2 + vy**2) # 速度大小
- # 绘制轨迹
- plt.figure(figsize=(12, 6))
- # 位置轨迹
- plt.subplot(1, 2, 1)
- plt.plot(x, y)
- plt.xlabel('水平距离 (m)')
- plt.ylabel('垂直距离 (m)')
- plt.title('抛体运动轨迹')
- plt.grid(True)
- # 速度变化
- plt.subplot(1, 2, 2)
- plt.plot(t, v)
- plt.xlabel('时间 (s)')
- plt.ylabel('速度 (m/s)')
- plt.title('速度随时间变化')
- plt.grid(True)
- plt.tight_layout()
- plt.show()
- # 计算并显示关键数据
- max_height = np.max(y)
- range_x = x[-1]
- print(f"最大高度: {max_height:.2f} m")
- print(f"水平射程: {range_x:.2f} m")
- print(f"飞行时间: {t_max:.2f} s")
复制代码
振动系统:简谐运动与阻尼振动
简谐运动和阻尼振动是物理学中的重要概念,我们可以使用NumPy来模拟这些振动系统:
- import numpy as np
- import matplotlib.pyplot as plt
- # 简谐运动参数
- k = 10.0 # 弹簧常数 (N/m)
- m = 1.0 # 质量 (kg)
- omega0 = np.sqrt(k/m) # 固有角频率
- # 阻尼振动参数
- b = 0.5 # 阻尼系数
- omega_d = np.sqrt(omega0**2 - (b/(2*m))**2) # 阻尼角频率
- # 时间参数
- t_max = 10.0 # 总时间
- dt = 0.01 # 时间步长
- t = np.arange(0, t_max, dt) # 时间数组
- # 初始条件
- A = 1.0 # 初始振幅
- phi = 0 # 初始相位
- # 简谐运动
- x_shm = A * np.cos(omega0 * t + phi)
- v_shm = -A * omega0 * np.sin(omega0 * t + phi)
- # 欠阻尼振动 (b < 2*sqrt(k*m))
- x_damped = A * np.exp(-b*t/(2*m)) * np.cos(omega_d * t + phi)
- v_damped = A * np.exp(-b*t/(2*m)) * (-b/(2*m) * np.cos(omega_d * t + phi) - omega_d * np.sin(omega_d * t + phi))
- # 绘制结果
- plt.figure(figsize=(12, 8))
- # 位移图
- plt.subplot(2, 1, 1)
- plt.plot(t, x_shm, label='简谐运动')
- plt.plot(t, x_damped, label='阻尼振动')
- plt.xlabel('时间 (s)')
- plt.ylabel('位移 (m)')
- plt.title('振动系统的位移')
- plt.legend()
- plt.grid(True)
- # 速度图
- plt.subplot(2, 1, 2)
- plt.plot(t, v_shm, label='简谐运动')
- plt.plot(t, v_damped, label='阻尼振动')
- plt.xlabel('时间 (s)')
- plt.ylabel('速度 (m/s)')
- plt.title('振动系统的速度')
- plt.legend()
- plt.grid(True)
- plt.tight_layout()
- plt.show()
- # 计算并显示系统特性
- T0 = 2 * np.pi / omega0 # 简谐运动周期
- T_d = 2 * np.pi / omega_d # 阻尼振动周期
- print(f"简谐运动周期: {T0:.2f} s")
- print(f"阻尼振动周期: {T_d:.2f} s")
- print(f"阻尼比: {b/(2*np.sqrt(k*m)):.2f}")
复制代码
电磁场模拟:点电荷的电场
电磁场模拟是物理学中的重要应用,我们可以使用NumPy来计算和可视化点电荷产生的电场:
- import numpy as np
- import matplotlib.pyplot as plt
- # 点电荷参数
- k = 8.99e9 # 库仑常数 (N·m²/C²)
- q1 = 1e-9 # 第一个电荷 (C)
- q2 = -1e-9 # 第二个电荷 (C)
- # 电荷位置
- r1 = np.array([-1.0, 0.0]) # 第一个电荷的位置
- r2 = np.array([1.0, 0.0]) # 第二个电荷的位置
- # 创建网格
- x = np.linspace(-3, 3, 20)
- y = np.linspace(-3, 3, 20)
- X, Y = np.meshgrid(x, y)
- # 计算电场
- Ex = np.zeros_like(X)
- Ey = np.zeros_like(Y)
- for i in range(len(x)):
- for j in range(len(y)):
- r = np.array([X[j, i], Y[j, i]]) # 网格点位置
-
- # 计算从电荷1到网格点的向量
- r_vec1 = r - r1
- r_mag1 = np.linalg.norm(r_vec1)
-
- # 计算从电荷2到网格点的向量
- r_vec2 = r - r2
- r_mag2 = np.linalg.norm(r_vec2)
-
- # 避免除以零
- if r_mag1 > 0.1 and r_mag2 > 0.1:
- # 计算电场分量
- E1 = k * q1 / r_mag1**3 * r_vec1
- E2 = k * q2 / r_mag2**3 * r_vec2
-
- # 总电场
- E_total = E1 + E2
- Ex[j, i] = E_total[0]
- Ey[j, i] = E_total[1]
- # 计算电场大小
- E_mag = np.sqrt(Ex**2 + Ey**2)
- # 绘制电场
- plt.figure(figsize=(12, 10))
- # 电场矢量图
- plt.subplot(2, 1, 1)
- plt.quiver(X, Y, Ex, Ey, E_mag, cmap='viridis')
- plt.plot(r1[0], r1[1], 'ro', markersize=10, label='正电荷')
- plt.plot(r2[0], r2[1], 'bo', markersize=10, label='负电荷')
- plt.xlabel('x (m)')
- plt.ylabel('y (m)')
- plt.title('电场矢量图')
- plt.colorbar(label='电场强度 (N/C)')
- plt.legend()
- plt.grid(True)
- # 电势图
- plt.subplot(2, 1, 2)
- V = np.zeros_like(X)
- for i in range(len(x)):
- for j in range(len(y)):
- r = np.array([X[j, i], Y[j, i]])
- r_mag1 = np.linalg.norm(r - r1)
- r_mag2 = np.linalg.norm(r - r2)
-
- if r_mag1 > 0.1 and r_mag2 > 0.1:
- V[j, i] = k * q1 / r_mag1 + k * q2 / r_mag2
- contour = plt.contourf(X, Y, V, 50, cmap='RdBu_r')
- plt.plot(r1[0], r1[1], 'ro', markersize=10, label='正电荷')
- plt.plot(r2[0], r2[1], 'bo', markersize=10, label='负电荷')
- plt.xlabel('x (m)')
- plt.ylabel('y (m)')
- plt.title('电势分布')
- plt.colorbar(contour, label='电势 (V)')
- plt.legend()
- plt.grid(True)
- plt.tight_layout()
- plt.show()
复制代码
数值解法中的NumPy应用
常微分方程的数值解:欧拉法与龙格-库塔法
常微分方程在物理模拟中非常常见,我们可以使用NumPy实现欧拉法和龙格-库塔法来求解这些方程:
- import numpy as np
- import matplotlib.pyplot as plt
- # 定义微分方程 dy/dt = f(t, y)
- def f(t, y):
- return -2 * y # 简单的衰减方程
- # 解析解
- def analytical_solution(t):
- return np.exp(-2 * t)
- # 欧拉法
- def euler_method(f, y0, t):
- y = np.zeros_like(t)
- y[0] = y0
-
- for i in range(1, len(t)):
- h = t[i] - t[i-1]
- y[i] = y[i-1] + h * f(t[i-1], y[i-1])
-
- return y
- # 四阶龙格-库塔法
- def runge_kutta_4(f, y0, t):
- y = np.zeros_like(t)
- y[0] = y0
-
- for i in range(1, len(t)):
- h = t[i] - t[i-1]
- k1 = f(t[i-1], y[i-1])
- k2 = f(t[i-1] + h/2, y[i-1] + h*k1/2)
- k3 = f(t[i-1] + h/2, y[i-1] + h*k2/2)
- k4 = f(t[i], y[i-1] + h*k3)
-
- y[i] = y[i-1] + h * (k1 + 2*k2 + 2*k3 + k4) / 6
-
- return y
- # 时间参数
- t0 = 0
- tf = 5
- dt = 0.1
- t = np.arange(t0, tf, dt)
- # 初始条件
- y0 = 1.0
- # 求解
- y_euler = euler_method(f, y0, t)
- y_rk4 = runge_kutta_4(f, y0, t)
- y_analytical = analytical_solution(t)
- # 计算误差
- error_euler = np.abs(y_euler - y_analytical)
- error_rk4 = np.abs(y_rk4 - y_analytical)
- # 绘制结果
- plt.figure(figsize=(12, 8))
- # 解的比较
- plt.subplot(2, 1, 1)
- plt.plot(t, y_analytical, 'k-', label='解析解')
- plt.plot(t, y_euler, 'b--', label='欧拉法')
- plt.plot(t, y_rk4, 'r-.', label='龙格-库塔法')
- plt.xlabel('时间 (s)')
- plt.ylabel('y')
- plt.title('数值解与解析解的比较')
- plt.legend()
- plt.grid(True)
- # 误差比较
- plt.subplot(2, 1, 2)
- plt.plot(t, error_euler, 'b-', label='欧拉法误差')
- plt.plot(t, error_rk4, 'r-', label='龙格-库塔法误差')
- plt.xlabel('时间 (s)')
- plt.ylabel('绝对误差')
- plt.title('数值方法的误差比较')
- plt.legend()
- plt.grid(True)
- plt.tight_layout()
- plt.show()
- # 输出最大误差
- print(f"欧拉法的最大误差: {np.max(error_euler):.6f}")
- print(f"龙格-库塔法的最大误差: {np.max(error_rk4):.6f}")
复制代码
偏微分方程的数值解:热传导方程
偏微分方程在物理模拟中也非常重要,我们可以使用NumPy来实现热传导方程的数值解:
- import numpy as np
- import matplotlib.pyplot as plt
- # 热传导方程参数
- L = 1.0 # 杆的长度 (m)
- T = 0.5 # 总时间 (s)
- alpha = 0.01 # 热扩散系数 (m²/s)
- # 空间和时间离散化
- nx = 50 # 空间网格点数
- nt = 1000 # 时间步数
- dx = L / (nx - 1) # 空间步长
- dt = T / nt # 时间步长
- # 稳定性条件检查
- r = alpha * dt / dx**2
- if r > 0.5:
- print(f"警告:稳定性条件不满足!r = {r} > 0.5")
- print("减小时间步长或增加空间步长")
- else:
- print(f"稳定性条件满足:r = {r}")
- # 初始化温度数组
- T_old = np.zeros(nx)
- T_new = np.zeros(nx)
- # 设置初始条件:中间加热
- T_old[nx//4:3*nx//4] = 1.0
- # 边界条件:两端温度为零
- T_old[0] = 0.0
- T_old[-1] = 0.0
- # 存储特定时间步的温度分布
- T_history = []
- save_steps = [0, nt//4, nt//2, 3*nt//4, nt-1]
- # 时间步进
- for n in range(nt):
- # 更新内部点
- for i in range(1, nx-1):
- T_new[i] = T_old[i] + r * (T_old[i+1] - 2*T_old[i] + T_old[i-1])
-
- # 应用边界条件
- T_new[0] = 0.0
- T_new[-1] = 0.0
-
- # 保存特定时间步的结果
- if n in save_steps:
- T_history.append((n*dt, T_new.copy()))
-
- # 更新温度数组
- T_old, T_new = T_new, T_old
- # 绘制结果
- plt.figure(figsize=(12, 8))
- # 温度分布随时间的变化
- plt.subplot(2, 1, 1)
- x = np.linspace(0, L, nx)
- for t_val, T_val in T_history:
- plt.plot(x, T_val, label=f't = {t_val:.3f} s')
- plt.xlabel('位置 (m)')
- plt.ylabel('温度')
- plt.title('热传导方程的数值解:温度分布随时间的变化')
- plt.legend()
- plt.grid(True)
- # 热图显示温度分布
- plt.subplot(2, 1, 2)
- # 创建一个完整的温度历史矩阵
- T_matrix = np.zeros((len(save_steps), nx))
- for i, (t_val, T_val) in enumerate(T_history):
- T_matrix[i, :] = T_val
- plt.imshow(T_matrix, aspect='auto', origin='lower', extent=[0, L, 0, T])
- plt.colorbar(label='温度')
- plt.xlabel('位置 (m)')
- plt.ylabel('时间 (s)')
- plt.title('热传导方程的数值解:温度分布热图')
- plt.tight_layout()
- plt.show()
复制代码
线性方程组的数值解:高斯消元法与迭代法
线性方程组在科学计算中非常常见,我们可以使用NumPy来实现高斯消元法和迭代法:
- import numpy as np
- # 高斯消元法
- def gaussian_elimination(A, b):
- """
- 使用高斯消元法求解线性方程组 Ax = b
- """
- n = len(b)
-
- # 增广矩阵
- Ab = np.hstack([A, b.reshape(-1, 1)])
-
- # 前向消元
- for i in range(n):
- # 寻找主元
- max_row = np.argmax(np.abs(Ab[i:, i])) + i
- Ab[[i, max_row]] = Ab[[max_row, i]]
-
- # 消元
- for j in range(i+1, n):
- factor = Ab[j, i] / Ab[i, i]
- Ab[j, i:] -= factor * Ab[i, i:]
-
- # 回代
- x = np.zeros(n)
- for i in range(n-1, -1, -1):
- x[i] = (Ab[i, -1] - np.dot(Ab[i, i+1:n], x[i+1:n])) / Ab[i, i]
-
- return x
- # 雅可比迭代法
- def jacobi_iteration(A, b, max_iterations=1000, tolerance=1e-10):
- """
- 使用雅可比迭代法求解线性方程组 Ax = b
- """
- n = len(b)
- x = np.zeros(n)
-
- # 分解矩阵
- D = np.diag(A)
- R = A - np.diagflat(D)
-
- # 迭代
- for iteration in range(max_iterations):
- x_new = (b - np.dot(R, x)) / D
-
- # 检查收敛性
- if np.linalg.norm(x_new - x, ord=np.inf) < tolerance:
- print(f"雅可比迭代在 {iteration+1} 次迭代后收敛")
- return x_new
-
- x = x_new
-
- print("雅可比迭代未在最大迭代次数内收敛")
- return x
- # 高斯-赛德尔迭代法
- def gauss_seidel_iteration(A, b, max_iterations=1000, tolerance=1e-10):
- """
- 使用高斯-赛德尔迭代法求解线性方程组 Ax = b
- """
- n = len(b)
- x = np.zeros(n)
-
- # 迭代
- for iteration in range(max_iterations):
- x_old = x.copy()
-
- for i in range(n):
- sum_ax = np.dot(A[i, :i], x[:i]) + np.dot(A[i, i+1:], x_old[i+1:])
- x[i] = (b[i] - sum_ax) / A[i, i]
-
- # 检查收敛性
- if np.linalg.norm(x - x_old, ord=np.inf) < tolerance:
- print(f"高斯-赛德尔迭代在 {iteration+1} 次迭代后收敛")
- return x
-
- print("高斯-赛德尔迭代未在最大迭代次数内收敛")
- return x
- # 测试线性方程组
- A = np.array([[10, -1, 2, 0],
- [-1, 11, -1, 3],
- [2, -1, 10, -1],
- [0, 3, -1, 8]], dtype=float)
- b = np.array([6, 25, -11, 15], dtype=float)
- # 使用不同方法求解
- x_gauss = gaussian_elimination(A, b)
- x_jacobi = jacobi_iteration(A, b)
- x_gs = gauss_seidel_iteration(A, b)
- x_numpy = np.linalg.solve(A, b) # NumPy内置解法
- # 输出结果
- print("系数矩阵 A:")
- print(A)
- print("\n右侧向量 b:")
- print(b)
- print("\n高斯消元法解:", x_gauss)
- print("雅可比迭代法解:", x_jacobi)
- print("高斯-赛德尔迭代法解:", x_gs)
- print("NumPy内置解法解:", x_numpy)
- # 计算误差
- error_gauss = np.linalg.norm(A @ x_gauss - b)
- error_jacobi = np.linalg.norm(A @ x_jacobi - b)
- error_gs = np.linalg.norm(A @ x_gs - b)
- error_numpy = np.linalg.norm(A @ x_numpy - b)
- print("\n残差范数:")
- print(f"高斯消元法残差: {error_gauss:.2e}")
- print(f"雅可比迭代法残差: {error_jacobi:.2e}")
- print(f"高斯-赛德尔迭代法残差: {error_gs:.2e}")
- print(f"NumPy内置解法残差: {error_numpy:.2e}")
复制代码
高级实践案例
分子动力学模拟
分子动力学模拟是计算物理学中的重要应用,我们可以使用NumPy来实现一个简单的分子动力学模拟:
- import numpy as np
- import matplotlib.pyplot as plt
- from matplotlib.animation import FuncAnimation
- from mpl_toolkits.mplot3d import Axes3D
- # Lennard-Jones势参数
- epsilon = 1.0 # 能量参数
- sigma = 1.0 # 长度参数
- mass = 1.0 # 粒子质量
- # 模拟参数
- n_particles = 20 # 粒子数
- box_size = 10.0 # 盒子大小
- dt = 0.005 # 时间步长
- n_steps = 1000 # 总步数
- temperature = 1.0 # 初始温度
- # 初始化粒子位置和速度
- np.random.seed(42)
- positions = box_size * np.random.rand(n_particles, 3)
- velocities = np.random.randn(n_particles, 3)
- # 调整速度以匹配所需温度
- current_temp = np.mean(mass * np.sum(velocities**2, axis=1)) / (3 * n_particles)
- velocities *= np.sqrt(temperature / current_temp)
- # 计算Lennard-Jones力
- def lennard_jones_force(positions, box_size):
- n = len(positions)
- forces = np.zeros((n, 3))
- potential_energy = 0.0
-
- for i in range(n):
- for j in range(i+1, n):
- # 计算距离向量(考虑周期性边界条件)
- r_vec = positions[j] - positions[i]
- r_vec = r_vec - box_size * np.round(r_vec / box_size)
- r = np.linalg.norm(r_vec)
-
- if r < 2.5 * sigma: # 截断距离
- # Lennard-Jones势
- sr6 = (sigma / r)**6
- force_magnitude = 24 * epsilon * sr6 * (2 * sr6 - 1) / r**2
- force_vec = force_magnitude * r_vec
-
- forces[i] += force_vec
- forces[j] -= force_vec
-
- # 势能
- potential_energy += 4 * epsilon * sr6 * (sr6 - 1)
-
- return forces, potential_energy
- # 速度Verlet算法
- def velocity_verlet(positions, velocities, forces, dt, box_size):
- # 更新位置
- positions += velocities * dt + 0.5 * forces / mass * dt**2
-
- # 应用周期性边界条件
- positions = positions % box_size
-
- # 计算新力
- new_forces, potential_energy = lennard_jones_force(positions, box_size)
-
- # 更新速度
- velocities += 0.5 * (forces + new_forces) / mass * dt
-
- return positions, velocities, new_forces, potential_energy
- # 初始化力
- forces, _ = lennard_jones_force(positions, box_size)
- # 存储能量数据
- kinetic_energies = []
- potential_energies = []
- total_energies = []
- temperature_history = []
- # 运行模拟
- for step in range(n_steps):
- # 速度Verlet步骤
- positions, velocities, forces, potential_energy = velocity_verlet(
- positions, velocities, forces, dt, box_size
- )
-
- # 计算动能和温度
- kinetic_energy = 0.5 * mass * np.sum(velocities**2)
- current_temp = kinetic_energy / (1.5 * n_particles)
-
- # 存储数据
- kinetic_energies.append(kinetic_energy)
- potential_energies.append(potential_energy)
- total_energies.append(kinetic_energy + potential_energy)
- temperature_history.append(current_temp)
- # 绘制能量和温度随时间的变化
- plt.figure(figsize=(12, 8))
- # 能量图
- plt.subplot(2, 1, 1)
- plt.plot(kinetic_energies, label='动能')
- plt.plot(potential_energies, label='势能')
- plt.plot(total_energies, label='总能量')
- plt.xlabel('时间步')
- plt.ylabel('能量')
- plt.title('分子动力学模拟中的能量变化')
- plt.legend()
- plt.grid(True)
- # 温度图
- plt.subplot(2, 1, 2)
- plt.plot(temperature_history)
- plt.xlabel('时间步')
- plt.ylabel('温度')
- plt.title('分子动力学模拟中的温度变化')
- plt.grid(True)
- plt.tight_layout()
- plt.show()
- # 3D可视化最终粒子位置
- fig = plt.figure(figsize=(10, 8))
- ax = fig.add_subplot(111, projection='3d')
- ax.scatter(positions[:, 0], positions[:, 1], positions[:, 2], s=100)
- ax.set_xlim(0, box_size)
- ax.set_ylim(0, box_size)
- ax.set_zlim(0, box_size)
- ax.set_xlabel('X')
- ax.set_ylabel('Y')
- ax.set_zlabel('Z')
- ax.set_title('分子动力学模拟的最终粒子位置')
- plt.show()
- # 输出平均能量和温度
- print(f"平均动能: {np.mean(kinetic_energies):.4f}")
- print(f"平均势能: {np.mean(potential_energies):.4f}")
- print(f"平均总能量: {np.mean(total_energies):.4f}")
- print(f"平均温度: {np.mean(temperature_history):.4f}")
- print(f"温度标准差: {np.std(temperature_history):.4f}")
复制代码
量子力学模拟:一维谐振子
量子力学是物理学的重要分支,我们可以使用NumPy来模拟一维谐振子:
- import numpy as np
- import matplotlib.pyplot as plt
- from scipy.linalg import eigh
- # 一维谐振子参数
- hbar = 1.0 # 约化普朗克常数
- m = 1.0 # 质量
- omega = 1.0 # 角频率
- # 空间网格
- x_min = -10.0
- x_max = 10.0
- n_points = 1000
- x = np.linspace(x_min, x_max, n_points)
- dx = x[1] - x[0]
- # 谐振子势能
- def harmonic_potential(x):
- return 0.5 * m * omega**2 * x**2
- # 解析解
- def analytical_wavefunction(n, x):
- """
- 计算第n个谐振子本征态的解析波函数
- """
- # 归一化常数
- prefactor = 1.0 / np.sqrt(2**n * np.math.factorial(n)) * (m * omega / (np.pi * hbar))**(0.25)
-
- # Hermite多项式
- if n == 0:
- hermite = np.ones_like(x)
- elif n == 1:
- hermite = 2 * np.sqrt(m * omega / hbar) * x
- elif n == 2:
- hermite = 4 * (m * omega / hbar) * x**2 - 2
- elif n == 3:
- hermite = 8 * (m * omega / hbar)**(1.5) * x**3 - 12 * np.sqrt(m * omega / hbar) * x
- else:
- # 对于更高的n,可以使用递归关系或特殊函数库
- raise ValueError("目前仅支持n=0,1,2,3")
-
- # 完整波函数
- xi = np.sqrt(m * omega / hbar) * x
- psi = prefactor * np.exp(-xi**2 / 2) * hermite
-
- return psi
- # 构建哈密顿矩阵
- def build_hamiltonian(x, V):
- n = len(x)
- H = np.zeros((n, n))
-
- # 动能项(使用有限差分)
- for i in range(1, n-1):
- H[i, i-1] = -hbar**2 / (2 * m * dx**2)
- H[i, i] = hbar**2 / (m * dx**2) + V[i]
- H[i, i+1] = -hbar**2 / (2 * m * dx**2)
-
- # 边界条件
- H[0, 0] = hbar**2 / (m * dx**2) + V[0]
- H[0, 1] = -hbar**2 / (2 * m * dx**2)
- H[n-1, n-1] = hbar**2 / (m * dx**2) + V[n-1]
- H[n-1, n-2] = -hbar**2 / (2 * m * dx**2)
-
- return H
- # 计算势能
- V = harmonic_potential(x)
- # 构建哈密顿矩阵
- H = build_hamiltonian(x, V)
- # 对角化哈密顿矩阵
- eigenvalues, eigenvectors = eigh(H)
- # 计算解析本征值
- analytical_eigenvalues = hbar * omega * (np.arange(4) + 0.5)
- # 绘制结果
- plt.figure(figsize=(12, 10))
- # 势能和前几个能级
- plt.subplot(2, 1, 1)
- plt.plot(x, V, 'k-', label='势能')
- for i in range(4):
- plt.axhline(y=eigenvalues[i], color=f'C{i}', linestyle='--',
- label=f'E_{i} = {eigenvalues[i]:.3f} (解析: {analytical_eigenvalues[i]:.3f})')
- plt.xlabel('位置')
- plt.ylabel('能量')
- plt.title('一维谐振子的势能和能级')
- plt.ylim(0, 5)
- plt.legend()
- plt.grid(True)
- # 前几个波函数
- plt.subplot(2, 1, 2)
- for i in range(4):
- # 数值波函数(归一化)
- psi_numerical = eigenvectors[:, i]
- # 确保波函数是实数并归一化
- if np.abs(np.mean(psi_numerical.imag)) > 1e-10:
- psi_numerical = np.abs(psi_numerical)
- else:
- psi_numerical = psi_numerical.real
- norm = np.sqrt(np.sum(psi_numerical**2) * dx)
- psi_numerical = psi_numerical / norm
-
- # 解析波函数
- psi_analytical = analytical_wavefunction(i, x)
-
- # 绘制波函数(垂直偏移以便于可视化)
- plt.plot(x, psi_numerical + eigenvalues[i], '-',
- label=f'ψ_{i} (数值)', color=f'C{i}')
- plt.plot(x, psi_analytical + eigenvalues[i], '--',
- label=f'ψ_{i} (解析)', color=f'C{i}')
- plt.plot(x, V, 'k-', alpha=0.3, label='势能')
- plt.xlabel('位置')
- plt.ylabel('能量')
- plt.title('一维谐振子的波函数')
- plt.ylim(0, 5)
- plt.legend()
- plt.grid(True)
- plt.tight_layout()
- plt.show()
- # 输出能级比较
- print("能级比较:")
- print("态 | 数值解 | 解析解 | 相对误差")
- print("--------------------------------")
- for i in range(4):
- error = abs(eigenvalues[i] - analytical_eigenvalues[i]) / analytical_eigenvalues[i]
- print(f"{i} | {eigenvalues[i]:.6f} | {analytical_eigenvalues[i]:.6f} | {error:.2e}")
复制代码
有限元方法:梁的弯曲分析
有限元方法是工程中的重要数值方法,我们可以使用NumPy来实现一个简单的梁弯曲分析:
- import numpy as np
- import matplotlib.pyplot as plt
- # 梁的参数
- L = 1.0 # 梁的长度 (m)
- E = 200e9 # 弹性模量 (Pa)
- I = 1e-6 # 截面惯性矩 (m^4)
- q = -1000 # 均布载荷 (N/m)
- # 有限元参数
- n_elements = 10 # 单元数
- n_nodes = n_elements + 1 # 节点数
- element_length = L / n_elements
- # 创建节点坐标
- x = np.linspace(0, L, n_nodes)
- # 构建全局刚度矩阵
- K = np.zeros((2*n_nodes, 2*n_nodes))
- # 单元刚度矩阵
- def element_stiffness_matrix(E, I, L):
- """
- 计算梁单元的刚度矩阵
- """
- k = np.array([
- [12, 6*L, -12, 6*L],
- [6*L, 4*L**2, -6*L, 2*L**2],
- [-12, -6*L, 12, -6*L],
- [6*L, 2*L**2, -6*L, 4*L**2]
- ])
- return E * I / L**3 * k
- # 组装全局刚度矩阵
- for e in range(n_elements):
- # 单元节点
- node1 = e
- node2 = e + 1
-
- # 单元自由度
- dofs = [2*node1, 2*node1+1, 2*node2, 2*node2+1]
-
- # 单元刚度矩阵
- k_e = element_stiffness_matrix(E, I, element_length)
-
- # 组装到全局刚度矩阵
- for i in range(4):
- for j in range(4):
- K[dofs[i], dofs[j]] += k_e[i, j]
- # 构建载荷向量
- F = np.zeros(2*n_nodes)
- # 均布载荷转换为节点载荷
- for e in range(n_elements):
- node1 = e
- node2 = e + 1
-
- # 单元自由度
- dofs = [2*node1, 2*node1+1, 2*node2, 2*node2+1]
-
- # 均布载荷的等效节点力
- f_e = np.array([
- q * element_length / 2,
- q * element_length**2 / 12,
- q * element_length / 2,
- -q * element_length**2 / 12
- ])
-
- # 组装到全局载荷向量
- for i in range(4):
- F[dofs[i]] += f_e[i]
- # 应用边界条件(左端固定)
- fixed_dofs = [0, 1] # 固定左端的位移和转角
- # 修改刚度矩阵和载荷向量
- for dof in fixed_dofs:
- F = F - K[:, dof] * 0 # 修改载荷向量
- K[dof, :] = 0
- K[:, dof] = 0
- K[dof, dof] = 1
- F[dof] = 0
- # 求解线性方程组
- U = np.linalg.solve(K, F)
- # 提取位移和转角
- displacements = U[0::2]
- rotations = U[1::2]
- # 计算弯矩
- M = np.zeros(n_elements)
- for e in range(n_elements):
- node1 = e
- node2 = e + 1
-
- # 单元自由度
- dofs = [2*node1, 2*node1+1, 2*node2, 2*node2+1]
-
- # 单元位移
- u_e = U[dofs]
-
- # 单元弯矩(在单元中点)
- M_e = E * I / element_length**3 * np.array([
- -6/element_length, -4, 6/element_length, -2
- ]) @ u_e
-
- M[e] = M_e
- # 解析解(用于比较)
- def analytical_deflection(x, L, E, I, q):
- """
- 悬臂梁在均布载荷下的解析挠度解
- """
- return q * x**2 / (24 * E * I) * (6 * L**2 - 4 * L * x + x**2)
- def analytical_moment(x, L, q):
- """
- 悬臂梁在均布载荷下的解析弯矩解
- """
- return q * (x - L)**2 / 2
- # 绘制结果
- plt.figure(figsize=(12, 8))
- # 挠度图
- plt.subplot(2, 1, 1)
- plt.plot(x, displacements, 'bo-', label='有限元解')
- x_fine = np.linspace(0, L, 100)
- plt.plot(x_fine, analytical_deflection(x_fine, L, E, I, q), 'r-', label='解析解')
- plt.xlabel('位置 (m)')
- plt.ylabel('挠度 (m)')
- plt.title('梁的挠度分布')
- plt.legend()
- plt.grid(True)
- # 弯矩图
- plt.subplot(2, 1, 2)
- x_elements = np.linspace(element_length/2, L-element_length/2, n_elements)
- plt.plot(x_elements, M, 'bo-', label='有限元解')
- plt.plot(x_fine, analytical_moment(x_fine, L, q), 'r-', label='解析解')
- plt.xlabel('位置 (m)')
- plt.ylabel('弯矩 (N·m)')
- plt.title('梁的弯矩分布')
- plt.legend()
- plt.grid(True)
- plt.tight_layout()
- plt.show()
- # 输出最大挠度和最大弯矩
- max_deflection_num = np.min(displacements)
- max_deflection_ana = analytical_deflection(L, L, E, I, q)
- max_moment_num = np.max(np.abs(M))
- max_moment_ana = np.max(np.abs(analytical_moment(x_fine, L, q)))
- print(f"最大挠度:")
- print(f" 有限元解: {max_deflection_num:.6f} m")
- print(f" 解析解: {max_deflection_ana:.6f} m")
- print(f" 相对误差: {abs(max_deflection_num - max_deflection_ana) / abs(max_deflection_ana):.2e}")
- print(f"\n最大弯矩:")
- print(f" 有限元解: {max_moment_num:.2f} N·m")
- print(f" 解析解: {max_moment_ana:.2f} N·m")
- print(f" 相对误差: {abs(max_moment_num - max_moment_ana) / abs(max_moment_ana):.2e}")
复制代码
性能优化技巧
向量化操作
NumPy的向量化操作可以显著提高代码性能,避免使用Python循环:
- import numpy as np
- import time
- # 创建大型数组
- size = 1000000
- a = np.random.rand(size)
- b = np.random.rand(size)
- # 使用Python循环
- start_time = time.time()
- c_loop = np.zeros(size)
- for i in range(size):
- c_loop[i] = a[i] + b[i]
- loop_time = time.time() - start_time
- # 使用NumPy向量化操作
- start_time = time.time()
- c_vectorized = a + b
- vectorized_time = time.time() - start_time
- # 比较结果
- print(f"循环时间: {loop_time:.6f} 秒")
- print(f"向量化时间: {vectorized_time:.6f} 秒")
- print(f"加速比: {loop_time / vectorized_time:.2f}x")
- # 验证结果是否相同
- print(f"结果是否相同: {np.allclose(c_loop, c_vectorized)}")
复制代码
内存布局优化
NumPy数组的内存布局对性能有重要影响:
- import numpy as np
- import time
- # 创建大型数组
- size = 5000
- A = np.random.rand(size, size)
- B = np.random.rand(size, size)
- # 行优先访问(C风格)
- start_time = time.time()
- result_c = np.zeros((size, size))
- for i in range(size):
- for j in range(size):
- result_c[i, j] = A[i, j] + B[i, j]
- c_time = time.time() - start_time
- # 列优先访问(Fortran风格)
- start_time = time.time()
- result_f = np.zeros((size, size))
- for j in range(size):
- for i in range(size):
- result_f[i, j] = A[i, j] + B[i, j]
- f_time = time.time() - start_time
- # 使用NumPy内置操作
- start_time = time.time()
- result_numpy = A + B
- numpy_time = time.time() - start_time
- # 比较结果
- print(f"行优先访问时间: {c_time:.6f} 秒")
- print(f"列优先访问时间: {f_time:.6f} 秒")
- print(f"NumPy内置操作时间: {numpy_time:.6f} 秒")
- print(f"行优先/列优先时间比: {c_time / f_time:.2f}")
- print(f"循环/NumPy时间比: {c_time / numpy_time:.2f}")
- # 验证结果是否相同
- print(f"结果是否相同: {np.allclose(result_c, result_f) and np.allclose(result_c, result_numpy)}")
复制代码
使用NumPy的内置函数
NumPy提供了许多优化的内置函数,可以替代Python循环:
- import numpy as np
- import time
- # 创建大型数组
- size = 1000000
- a = np.random.rand(size)
- # 使用Python循环计算平方根
- start_time = time.time()
- sqrt_loop = np.zeros(size)
- for i in range(size):
- sqrt_loop[i] = np.sqrt(a[i])
- loop_time = time.time() - start_time
- # 使用NumPy的sqrt函数
- start_time = time.time()
- sqrt_numpy = np.sqrt(a)
- numpy_time = time.time() - start_time
- # 使用数学函数的通用函数版本
- start_time = time.time()
- sqrt_ufunc = np.sqrt(a) # 这与上面的相同,但展示了通用函数的概念
- ufunc_time = time.time() - start_time
- # 比较结果
- print(f"循环时间: {loop_time:.6f} 秒")
- print(f"NumPy sqrt时间: {numpy_time:.6f} 秒")
- print(f"通用函数时间: {ufunc_time:.6f} 秒")
- print(f"加速比: {loop_time / numpy_time:.2f}x")
- # 验证结果是否相同
- print(f"结果是否相同: {np.allclose(sqrt_loop, sqrt_numpy) and np.allclose(sqrt_loop, sqrt_ufunc)}")
复制代码
使用Numba进行即时编译
Numba是一个可以将Python函数编译为机器码的库,可以显著提高NumPy代码的性能:
- import numpy as np
- import time
- from numba import jit
- # 创建大型数组
- size = 1000
- A = np.random.rand(size, size)
- B = np.random.rand(size, size)
- # 纯Python矩阵乘法
- def matrix_multiply_python(A, B):
- size = A.shape[0]
- C = np.zeros((size, size))
- for i in range(size):
- for j in range(size):
- for k in range(size):
- C[i, j] += A[i, k] * B[k, j]
- return C
- # 使用Numba加速的矩阵乘法
- @jit(nopython=True)
- def matrix_multiply_numba(A, B):
- size = A.shape[0]
- C = np.zeros((size, size))
- for i in range(size):
- for j in range(size):
- for k in range(size):
- C[i, j] += A[i, k] * B[k, j]
- return C
- # 使用NumPy的矩阵乘法
- def matrix_multiply_numpy(A, B):
- return np.dot(A, B)
- # 测试纯Python版本
- start_time = time.time()
- C_python = matrix_multiply_python(A, B)
- python_time = time.time() - start_time
- # 测试Numba版本(第一次运行包括编译时间)
- start_time = time.time()
- C_numba = matrix_multiply_numba(A, B)
- numba_time = time.time() - start_time
- # 再次测试Numba版本(不包括编译时间)
- start_time = time.time()
- C_numba = matrix_multiply_numba(A, B)
- numba_time_compiled = time.time() - start_time
- # 测试NumPy版本
- start_time = time.time()
- C_numpy = matrix_multiply_numpy(A, B)
- numpy_time = time.time() - start_time
- # 比较结果
- print(f"纯Python时间: {python_time:.6f} 秒")
- print(f"Numba时间(含编译): {numba_time:.6f} 秒")
- print(f"Numba时间(不含编译): {numba_time_compiled:.6f} 秒")
- print(f"NumPy时间: {numpy_time:.6f} 秒")
- print(f"Python/Numba加速比: {python_time / numba_time_compiled:.2f}x")
- print(f"Python/NumPy加速比: {python_time / numpy_time:.2f}x")
- # 验证结果是否相同
- print(f"结果是否相同: {np.allclose(C_python, C_numba) and np.allclose(C_python, C_numpy)}")
复制代码
总结与展望
本文详细介绍了NumPy在物理模拟与数值解法中的应用,从基础入门到高级实践。我们首先介绍了NumPy的基础知识,包括数组创建、操作和广播机制。然后,我们探讨了NumPy在物理模拟中的应用,包括抛体运动、振动系统和电磁场模拟。接着,我们深入研究了NumPy在数值解法中的应用,包括常微分方程、偏微分方程和线性方程组的数值解。最后,我们通过高级实践案例,如分子动力学模拟、量子力学模拟和有限元方法,展示了NumPy在解决复杂物理问题中的强大能力。
NumPy作为Python科学计算的核心库,提供了高效的数组操作和丰富的数学函数,使得科学家和工程师能够快速实现复杂的物理模型和数值算法。通过向量化操作、内存布局优化、使用内置函数和结合Numba等工具,我们可以进一步提高NumPy代码的性能,处理更大规模的科学计算问题。
随着科学计算和工程应用的不断发展,NumPy在物理模拟和数值解法中的应用将更加广泛。未来,我们可以期待NumPy在以下方面的进一步发展:
1. 更高效的并行计算支持:随着多核处理器和GPU的普及,NumPy将提供更好的并行计算支持,进一步提高计算效率。
2. 更丰富的物理模拟库:基于NumPy的物理模拟库将不断丰富,覆盖更多的物理领域和更复杂的物理现象。
3. 更好的机器学习集成:NumPy与机器学习框架的集成将更加紧密,为物理模拟和数据分析提供更强大的工具。
4. 更友好的可视化工具:基于NumPy的可视化工具将更加友好和强大,使得物理模拟结果更易于理解和展示。
更高效的并行计算支持:随着多核处理器和GPU的普及,NumPy将提供更好的并行计算支持,进一步提高计算效率。
更丰富的物理模拟库:基于NumPy的物理模拟库将不断丰富,覆盖更多的物理领域和更复杂的物理现象。
更好的机器学习集成:NumPy与机器学习框架的集成将更加紧密,为物理模拟和数据分析提供更强大的工具。
更友好的可视化工具:基于NumPy的可视化工具将更加友好和强大,使得物理模拟结果更易于理解和展示。
总之,NumPy在物理模拟与数值解法中的应用前景广阔,它将继续为科学研究和工程应用提供强大的支持,帮助我们更好地理解和解决复杂的物理问题。通过掌握NumPy的基础知识和高级应用,我们可以提升科学计算能力,为解决复杂物理问题提供有力的工具。 |
|