Python实现6-DOF刚体仿真器(上)——状态管理与时间推进
2026/7/17 9:50:27 网站建设 项目流程

1. 摘要 (Abstract)

一个健壮的6-DOF仿真器不仅仅是公式的堆砌,更是软件架构的艺术。本文将基于Python构建一个面向对象的SixDOFSimulator类。我们将定义严格的状态向量(State Vector)结构,采用dataclasses提升代码可读性,并利用scipy.integrate.solve_ivp替换手动编写的RK4循环,以获得更好的数值稳定性和事件处理能力。本文最终实现了一个“无动力滑翔”的基准Demo,用于验证系统集成是否正确。


2. 架构设计:从脚本到系统

在编写代码前,我们需要明确仿真器的核心组件及其职责。

2.1 核心组件划分

仿真器的UML类图。仿真器持有状态,并调用飞机模型和大气模型来计算导数。

2.2 数据流:标准控制循环

我们将采用标准的连续-离散混合架构:

  1. 连续域:物理世界(微分方程)。

  2. 离散域:计算机仿真(数值积分)。

  3. 接口:在每个时间步,积分器询问“在当前状态下,导数(x˙)是多少?”,仿真器调用物理模型计算并返回。


3. 代码实现:构建仿真内核

3.1 状态向量封装 (State Class)

状态是仿真器的“灵魂”。我们将12个核心变量封装在一个类中,并提供与NumPy数组互转的方法(这是为了适配solve_ivp的输入输出格式)。

# sixdof/state.py import numpy as np from dataclasses import dataclass @dataclass class State: """ 6-DOF 状态向量封装 位置/速度在NED惯性系,角速度在体轴系 """ # Position (NED) [m] pn: float = 0.0 pe: float = 0.0 pd: float = 0.0 # Velocity (Body) [m/s] u: float = 0.0 v: float = 0.0 w: float = 0.0 # Attitude (Quaternion) [unitless] q0: float = 1.0 q1: float = 0.0 q2: float = 0.0 q3: float = 0.0 # Angular Rates (Body) [rad/s] p: float = 0.0 q: float = 0.0 r: float = 0.0 # Acceleration (Body) [m/s^2] - Derived, for logging ax: float = 0.0 ay: float = 0.0 az: float = 0.0 def to_array(self) -> np.ndarray: """将状态转换为NumPy数组 (用于积分器)""" return np.array([ self.pn, self.pe, self.pd, self.u, self.v, self.w, self.q0, self.q1, self.q2, self.q3, self.p, self.q, self.r ]) @staticmethod def from_array(arr: np.ndarray) -> 'State': """从NumPy数组恢复状态""" return State(*arr[:13]) def normalize_quaternion(self): """四元数归一化""" norm = np.sqrt(self.q0**2 + self.q1**2 + self.q2**2 + self.q3**2) if norm < 1e-16: self.q0 = 1.0 self.q1 = self.q2 = self.q3 = 0.0 else: self.q0 /= norm self.q1 /= norm self.q2 /= norm self.q3 /= norm def rotation_matrix(self) -> np.ndarray: """返回 C_b^n (Body to NED)""" q0, q1, q2, q3 = self.q0, self.q1, self.q2, self.q3 return np.array([ [q0**2+q1**2-q2**2-q3**2, 2*(q1*q2 + q0*q3), 2*(q1*q3 - q0*q2)], [2*(q1*q2 - q0*q3), q0**2-q1**2+q2**2-q3**2, 2*(q2*q3 + q0*q1)], [2*(q1*q3 + q0*q2), 2*(q2*q3 - q0*q1), q0**2-q1**2-q2**2+q3**2] ])

3.2 飞机模型封装 (Aircraft Class)

飞机模型负责所有物理参数的存储和力/力矩的计算。

# sixdof/aircraft.py import numpy as np class Aircraft: def __init__(self, mass, inertia, S_ref): self.mass = mass self.inertia = np.array(inertia) # [Ixx, Iyy, Izz] self.S_ref = S_ref # 气动导数 (简化模型) self.CLA = 5.5 # Lift slope self.CD0 = 0.02 # Zero-lift drag self.CDA = 0.3 # Induced drag factor self.Cm0 = 0.01 # Zero-lift pitching moment self.Cma = -1.2 # Static stability self.Cmq = -25.0 # Pitch damping self.Cm_de = -1.0 # Elevator effectiveness def get_forces_and_moments(self, state: 'State', control: np.ndarray, rho: float) -> tuple: """ 计算气动力和力矩 control: [de, da, dr, throttle] """ # --- Kinematics --- V = np.sqrt(state.u**2 + state.v**2 + state.w**2) if V < 0.1: return np.zeros(3), np.zeros(3) alpha = np.arctan2(state.w, state.u) beta = np.arcsin(state.v / V) q_bar = 0.5 * rho * V**2 de = control[0] # Elevator deflection # --- Aerodynamics --- # Lift & Drag CL = self.CLA * alpha CD = self.CD0 + self.CDA * CL**2 X_aero = -CD * q_bar * self.S_ref Z_aero = -CL * q_bar * self.S_ref # Pitching Moment mac = 1.5 # Mean Aerodynamic Chord Cm = (self.Cm0 + self.Cma * alpha + self.Cmq * state.q * mac / (2 * V) + self.Cm_de * de) M = Cm * q_bar * self.S_ref * mac forces_b = np.array([X_aero, 0.0, Z_aero]) moments_b = np.array([0.0, M, 0.0]) return forces_b, moments_b

3.3 仿真器核心 (SixDOFSimulator Class)

这是最重要的部分。我们将使用solve_ivp来管理时间推进,并将导数计算逻辑集中在此。

# sixdof/simulator.py import numpy as np from scipy.integrate import solve_ivp from typing import Callable, List from .state import State from .aircraft import Aircraft class SixDOFSimulator: def __init__(self, aircraft: Aircraft, initial_state: State): self.aircraft = aircraft self.state = initial_state self.time = 0.0 # History for logging self.history: List[State] = [initial_state] self.time_history: List[float] = [0.0] # Control input function (can be set externally) self.control_func: Callable[[float], np.ndarray] = lambda t: np.zeros(4) # Atmosphere model (simplified) self.rho0 = 1.225 self.H = 8500.0 def density(self, altitude): """Exponential atmosphere model""" return self.rho0 * np.exp(-altitude / self.H) def _derivatives(self, t: float, y: np.ndarray) -> np.ndarray: """ 计算状态导数 dy/dt。这是solve_ivp调用的核心函数。 """ # 1. Update internal state from integrator's array current_state = State.from_array(y) current_state.normalize_quaternion() # 2. Get control inputs control = self.control_func(t) # 3. Get environment properties rho = self.density(-current_state.pd) # pd is negative altitude # 4. Calculate Forces and Moments forces_b, moments_b = self.aircraft.get_forces_and_moments( current_state, control, rho ) # Add Gravity (in body frame) R_bn = current_state.rotation_matrix().T # C_n^b gravity_force_n = np.array([0, 0, self.aircraft.mass * 9.81]) gravity_force_b = R_bn @ gravity_force_n forces_b += gravity_force_b # 5. Kinematics (Position and Attitude) # Velocity in NED vel_n = current_state.rotation_matrix() @ np.array([current_state.u, current_state.v, current_state.w]) pos_dot = vel_n # Quaternion derivative p, q, r = current_state.p, current_state.q, current_state.r Omega = np.array([ [0, -p, -q, -r], [p, 0, r, -q], [q, -r, 0, p], [r, q, -p, 0] ]) quat_dot = 0.5 * Omega @ np.array([current_state.q0, current_state.q1, current_state.q2, current_state.q3]) # 6. Dynamics (Velocity and Angular Rates) # Translational dynamics (Body frame) vel_dot_b = forces_b / self.aircraft.mass - np.cross(np.array([p, q, r]), np.array([current_state.u, current_state.v, current_state.w])) # Rotational dynamics (Euler's equation) I = np.diag(self.aircraft.inertia) inv_I = np.linalg.inv(I) gyro_torque = np.cross(np.array([p, q, r]), I @ np.array([p, q, r])) omega_dot_b = inv_I @ (moments_b - gyro_torque) # 7. Assemble derivative vector return np.concatenate([pos_dot, vel_dot_b, quat_dot, omega_dot_b]) def step(self, dt: float): """ 执行一个仿真步长 (Wrapper for solve_ivp) """ t_span = (self.time, self.time + dt) y0 = self.state.to_array() # Use RK45 (DOP853 is also excellent) sol = solve_ivp(self._derivatives, t_span, y0, method='RK45', rtol=1e-9, atol=1e-12) # Update state self.state = State.from_array(sol.y[:, -1]) self.state.normalize_quaternion() self.time = sol.t[-1] # Log self.history.append(self.state) self.time_history.append(self.time) def run(self, t_final: float, dt: float = 0.01): """运行仿真直到结束""" while self.time < t_final: step_dt = min(dt, t_final - self.time) self.step(step_dt) def get_history_arrays(self) -> dict: """提取历史数据为字典,方便绘图""" data = { 'time': np.array(self.time_history), 'pn': [], 'pe': [], 'pd': [], 'u': [], 'v': [], 'w': [], 'alpha': [], 'beta': [], 'quat': [], 'omega': [] } for s in self.history: data['pn'].append(s.pn); data['pe'].append(s.pe); data['pd'].append(s.pd) data['u'].append(s.u); data['v'].append(s.v); data['w'].append(s.w) data['quat'].append([s.q0, s.q1, s.q2, s.q3]) data['omega'].append([s.p, s.q, s.r]) V = np.sqrt(s.u**2 + s.v**2 + s.w**2) data['alpha'].append(np.rad2deg(np.arctan2(s.w, s.u))) if V > 0.1 else data['alpha'].append(0) data['beta'].append(np.rad2deg(np.arcsin(s.v / V))) if V > 0.1 else data['beta'].append(0) return {k: np.array(v) for k, v in data.items()}

4. 仿真Demo:无动力滑翔

让我们测试这个仿真器。设定一个简单的场景:飞机具有一定的初速度,无动力,无操纵面偏转,观察其滑翔轨迹。

# examples/steady_glide_test.py import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D from sixdof.simulator import SixDOFSimulator from sixdof.aircraft import Aircraft from sixdof.state import State def main(): # 1. Initialize Aircraft aircraft = Aircraft( mass=10.0, inertia=[0.2, 1.0, 1.0], # Ixx, Iyy, Izz S_ref=0.5 ) # 2. Initial State init_state = State( pn=0.0, pe=0.0, pd=0.0, # Start at origin u=30.0, v=0.0, w=-2.0, # Forward speed 30m/s, slight downward q0=1.0, # Level attitude p=0.0, q=0.0, r=0.0 ) # 3. Initialize Simulator sim = SixDOFSimulator(aircraft, init_state) # 4. Define Control (Zero control) sim.control_func = lambda t: np.array([0.0, 0.0, 0.0, 0.0]) # [de, da, dr, throttle] # 5. Run Simulation print("Starting simulation...") sim.run(t_final=20.0, dt=0.02) # Run for 20 seconds print("Simulation finished.") # 6. Extract Data data = sim.get_history_arrays() # 7. Visualization fig = plt.figure(figsize=(14, 6)) # Subplot 1: 3D Trajectory ax1 = fig.add_subplot(121, projection='3d') ax1.plot(data['pn'], data['pe'], -data['pd'], label='Glide Path') ax1.set_xlabel('North (m)') ax1.set_ylabel('East (m)') ax1.set_zlabel('Altitude (m)') ax1.set_title('3D Glide Trajectory') ax1.legend() # Subplot 2: Flight Parameters ax2 = fig.add_subplot(122) ax2.plot(data['time'], data['alpha'], label='AoA (deg)') ax2.plot(data['time'], data['pd'], label='Altitude (m)') ax2.axhline(0, color='k', linestyle='--', alpha=0.3) ax2.set_xlabel('Time (s)') ax2.set_ylabel('Value') ax2.set_title('Flight Parameters Over Time') ax2.legend() ax2.grid(True) plt.tight_layout() plt.show() if __name__ == "__main__": main()

4.1 结果分析

运行上述代码,你应该看到:

  1. 3D轨迹:一条平滑向下的滑翔曲线。由于没有侧滑和转弯,飞机沿直线前进。

  2. 参数曲线

    • 高度:持续下降,符合重力作用。

    • 迎角(AoA):在初始阶段略有波动(由于初始w的存在),随后迅速收敛到一个稳定的正值。这证明我们的静稳定导数(Cmα​<0)生效了——飞机自动找到了升力平衡重力的迎角。


5. 总结与展望 (Conclusion)

本篇标志着我们的6-DOF仿真从“理论推导”正式迈入“工程实践”:

  1. 完成了架构搭建:设计了StateAircraftSixDOFSimulator三个核心类,实现了高内聚低耦合。

  2. 标准化了积分接口:利用solve_ivp接管时间推进,大幅提升了数值鲁棒性,并简化了主循环逻辑。

  3. 验证了系统集成:通过无动力滑翔Demo,验证了动力学、运动学与气动模型的联合运作正常,特别是静稳定性的体现。

当前局限

目前的仿真器虽然能飞,但环境模型过于简单(无风),控制输入也是开环的。飞机无法按照预定航线飞行,因为它还没有“大脑”(控制器)。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询