python的机器人基础原理工业场景模拟第十九篇:机械臂仿真,零重力工况模拟,关闭重力项,输出仅由运动产生的关节力矩曲线。
《Python机器人基础原理》第十九篇:机械臂仿真——零重力工况模拟,关闭重力项,输出仅由运动产生的关节力矩曲线
1. 实际工业应用场景
周一早晨,某航天部件装配车间的地面测试大厅。一台3-DOF垂直关节机械臂(基座偏航+肘部俯仰+末端旋转)正在进行卫星天线展开机构的微重力装配模拟。机器人构型:大臂长600 mm,小臂长450 mm,末端旋转轴位于小臂末端。连杆质量:大臂5.2 kg(质心距基座0.28 m),小臂3.1 kg(质心距肘部0.20 m),末端执行器1.5 kg(质心距肘部0.45 m,距末端旋转轴线横向偏置0.02 m)。关节额定扭矩:基座偏航轴60 N·m,肘部俯仰轴180 N·m,末端旋转轴30 N·m。
调试工程师小李盯着屏幕上的力矩曲线,眉头紧锁。
"师傅,这个Joint1的力矩曲线怎么有个周期性的小波动,幅度大概±3 N·m?"小李指着示波器上基座偏航轴的力矩轨迹,"我们明明在地面模拟微重力工况,把重力补偿关了。"
旁边的老周放下咖啡杯:"你关的是哪部分?"
"求解器里gravity_enabled=False,g直接设成0。前馈里也把重力项摘了。"
"那波动不是重力。是惯性耦合。基座偏航轴虽然不扛重力,但肘部加速的时候,惯性会通过M₁₂传给Joint1。你算过纯运动产生的力矩吗?不考虑重力,只算M(q)q¨ + C(q,q˙)q˙。"
"还没。我直接看控制器的前馈输出,里面混着重力项。我想单独把重力拿掉,看纯运动力矩长什么样。"
"对。你把动力学方程里的G(q)整个摘掉,只留M和C。给定一段轨迹的(q, q˙, q¨),算出来M·q¨ + C·q˙。在零重力下,Joint1的力矩应该完全来自M₁₁自惯性和M₁₂耦合——你粗算一下:M₁₂峰值约0.78 kg·m²,五次多项式加速度峰值约0.50 rad/s²,耦合贡献≈0.39 N·m;加上M₁₁自项≈1.4 N·m,Joint1总力矩应该在1~2 N·m量级。你看到的±3 N·m,差不太多,说明耦合确实存在。"
小李打开编辑器:"那我写个离线仿真,零重力模式,跑一段θ₁和θ₂同时反向运动的轨迹——让M₁₂耦合和科氏项的特征都出来。"
"科氏项τ_C ∝ sinθ₂,θ₂从0°→-60°的过程中sinθ₂先增后减,图里应该能看到这个变化。"
"好。末端旋转轴我就不大幅动了,质点模型下它几乎无力矩,正好验证轻载。"
本篇解决的问题:在动力学求解器中关闭重力项(gravity_enabled=False, g=0),给定关节状态序列,仅计算惯性项M(q)q¨和科氏/离心项C(q,q˙)q˙产生的关节力矩,输出纯运动力矩曲线。用于微重力模拟、太空机械臂地面测试、重力补偿算法验证。
⚠️ 声明:本篇全部代码为课程离线仿真,运行在普通PC上,用于零重力工况下纯运动关节力矩的计算与可视化。不可直接用于真实机器人控制器、伺服驱动器或航天实验设备。真实航天任务需结合在轨参数辨识、零重力悬浮平台校准。代码作者不对直接使用本代码于真实设备造成的任何后果负责。
2. 痛点分析
课程模块 本篇对应的工程痛点
第4章 机器人动力学建模 → 4.4 关节空间动力学方程 τ = M(q)q¨ + C(q,q˙)q˙ + G(q) 零重力工况下G(q)≡0,需验证纯运动力矩(惯性+科氏)的计算正确性
第5章 机器人关节驱动与控制 → 5.3 前馈补偿 微重力模拟时前馈仅需M·q¨des + C·q˙des,重力补偿模块必须关闭
第6章 轨迹规划与控制 → 6.2 轨迹评价 评估无重力环境下轨迹的纯动力学可行性
第9章 协作机器人 → 零力拖动示教 零重力模式下去除重力项后的力矩曲线是拖动示教阻抗控制的基础
本篇代码解决 L2 离线动力学校核层。输入:关节状态数组(q, q˙, q¨) + 机器人参数 + 重力开关标志。输出:各关节的τ_M、τ_C、τ_total(无τ_G)。代码不是设备实时控制器,不做伺服闭环,不做轨迹规划。它做的事情是:在零重力假设下,给定状态直接算纯运动力矩。
不覆盖:轨迹生成、实时控制、摩擦力建模、6轴完整动力学、零重力悬浮平台硬件接口。
3. 核心逻辑讲解
物理直觉
你有没有在游泳池里推过一个大泡沫板?水里有浮力抵消了重力,你几乎感觉不到重量——但你要让泡沫板加速,还是要用力。这就是惯性。你要让它转弯,还是要用力。这就是科氏力。重力没了,但运动本身的"懒惰性"还在。
太空里的机械臂就是这样。国际空间站上的机械臂在微重力环境下,重力几乎为零。但加速移动太阳能板的时候,关节电机依然要出力——出的是惯性力和科氏力的力矩。本篇就是把重力项从动力学方程里整个拿掉,只看纯运动产生的力矩。
业务逻辑 → 代码映射
输入:机器人参数(DH、质量、质心)+ 关节状态序列(q, q˙, q¨) + gravity_enabled=False
↓
步骤1:对每一组状态,计算惯性矩阵 M(q)
↓
步骤2:计算科氏力/离心力向量 C(q,q˙)·q˙
↓
步骤3:重力项 → gravity_enabled=False时G(q)返回全零向量
↓
步骤4:τ_total = M·q¨ + C·q˙ + 0
↓
步骤5:汇总所有状态点的力矩,统计峰值,与硬件限位对比
↓
步骤6:输出4张图表
关键公式文字翻译
惯性项 τ_M = M(q)·q¨:M是3×3矩阵。零重力下这是力矩的主要来源。M₁₁≈1.9 kg·m²(基座自惯性),M₁₂≈0.78 kg·m²(耦合项,来自m₂L₁com₂+m₃L₁com₃),M₂₂≈0.66 kg·m²(肘部自惯性)。末端旋转轴的I₃ = m₃·r_offset²,r_offset是质心到旋转轴线的横向距离,本篇取0.02 m,I₃≈0.0006 kg·m²——量级极小,几乎无力矩。
科氏力项 τ_C = C(q,q˙)·q˙:τ_C1 = -h·sinθ₂·(2θ̇₁θ̇₂+θ̇₂²),τ_C2 = h·sinθ₂·θ̇₁²。h = m₂L₁com₂ + m₃L₁com₃ ≈ 0.78 kg·m²。科氏力矩与sinθ₂成正比——θ₂接近0°时科氏项接近零,θ₂到-60°时sin(-60°)≈-0.866达到最大。
重力项 τ_G = 0:本篇核心设定。g=0或G(q)强制返回零。验证:输出力矩中不含任何与姿态相关的常数项。
单位:力矩 N·m,角度 rad,角速度 rad/s,角加速度 rad/s²。
4. OOP代码实现
技术栈
严格限定:numpy, scipy, matplotlib。不引入pybullet、roboticstoolbox等任何第三方机器人库。
项目结构树
zero_g_torque_sim/
├── run_zero_g_sim.py
├── requirements.txt
├── dynamics/
│ ├── __init__.py
│ ├── models.py
│ ├── trajectory.py
│ ├── dynamics_solver.py
│ └── visualize.py
├── tests/
│ ├── __init__.py
│ └── test_zero_g_sim.py
└── results/
核心源码
"dynamics/models.py" — dataclass数据模型
"""
3-DOF垂直关节机械臂数据模型
零重力仿真专用:支持重力开关
构型说明:基座偏航(θ1, 竖直轴) + 肘部俯仰(θ2, 水平轴) + 末端旋转(θ3)
物理声明:
- θ1绕竖直轴旋转,重力对其力矩恒为零(无论gravity_enabled为何值)
- θ2为水平俯仰轴,重力矩 = -g·(m₂com₂+m₃com₃)·cos(θ₂),注意是cos(θ₂)不是cos(θ₁+θ₂)
- θ3为末端旋转轴,质点模型下重力矩为零
- 零重力模式:gravity_enabled=False → G(q)返回全零
- 末端旋转惯量I₃ = m₃·r_offset²,r_offset为质心到旋转轴线的横向距离
质心坐标定义(全局统一):
- com₁: 大臂质心沿连杆方向距基座关节的距离 (m)
- com₂: 小臂质心沿连杆方向距肘部关节的距离 (m)
- com₃: 末端负载质心沿小臂方向距肘部关节的距离 (m)
- r_offset₃: 末端负载质心到末端旋转轴线的横向距离 (m)
"""
from dataclasses import dataclass, field
from typing import List, Tuple, Optional
import numpy as np
@dataclass
class JointLimit:
"""单关节硬件限位"""
min_val: float
max_val: float
joint_name: str = ""
torque_limit: float = 0.0
def __post_init__(self):
if self.min_val >= self.max_val:
raise ValueError(
f"JointLimit: min_val({self.min_val})必须严格小于"
f"max_val({self.max_val})")
if not np.isfinite(self.min_val) or not np.isfinite(self.max_val):
raise ValueError("JointLimit: 限位值必须为有限数值")
def contains(self, value: float) -> bool:
return (self.min_val - 1e-10) <= value <= (self.max_val + 1e-10)
@dataclass
class JointLimitSet:
"""3-DOF机械臂关节限位集合"""
limits: List[JointLimit]
def __post_init__(self):
if len(self.limits) != 3:
raise ValueError(f"需要3个关节限位, 实际{len(self.limits)}个")
@classmethod
def default_3dof_arm(cls):
"""
默认3-DOF机械臂限位
θ1: ±170° (基座偏航, 额定60 N·m)
θ2: ±140° (肘部俯仰, 额定180 N·m)
θ3: ±180° (末端旋转, 额定30 N·m)
"""
pi = 3.141592653589793
return cls(limits=[
JointLimit(-170 * pi / 180, 170 * pi / 180, 'joint1_base_yaw', 60.0),
JointLimit(-140 * pi / 180, 140 * pi / 180, 'joint2_elbow_pitch', 180.0),
JointLimit(-180 * pi / 180, 180 * pi / 180, 'joint3_end_rotate', 30.0),
])
def check_all_with_indices(self, joint_values: List[float]) -> Tuple[bool, List[int]]:
if len(joint_values) != 3:
raise ValueError(f"需要3个关节值, 实际{len(joint_values)}个")
all_pass = True
violation_indices = []
for i, (val, limit) in enumerate(zip(joint_values, self.limits)):
if not limit.contains(val):
all_pass = False
violation_indices.append(i)
return all_pass, violation_indices
def get_torque_limits(self) -> List[float]:
return [lim.torque_limit for lim in self.limits]
@dataclass
class LinkMassProps:
"""
连杆质量与质心属性
mass: 质量 (kg)
com_x: 质心沿连杆方向距近端关节的距离 (m)
com_y, com_z: 垂直于连杆方向的质心偏移 (m)
r_offset_end: 末端负载质心到末端旋转轴线的横向距离 (m),用于I₃计算
"""
mass: float
com_x: float
com_y: float
com_z: float
link_name: str = ""
max_com_x: Optional[float] = None
r_offset_end: float = 0.0 # 末端质心到旋转轴线的横向偏置
def __post_init__(self):
if self.mass < 0:
raise ValueError(f"LinkMassProps: 质量不能为负, 实际{self.mass}")
for name, val in [('com_x', self.com_x), ('com_y', self.com_y),
('com_z', self.com_z), ('r_offset_end', self.r_offset_end)]:
if not np.isfinite(val):
raise ValueError(f"LinkMassProps.{name}必须为有限数值")
if self.max_com_x is not None and self.com_x > self.max_com_x:
raise ValueError(
f"LinkMassProps({self.link_name}): 质心x={self.com_x}m "
f"超出连杆长度上限{self.max_com_x}m")
@dataclass
class MassPropertySet:
"""完整连杆质量属性集合"""
link_props: List[LinkMassProps]
link_lengths: List[float] = field(default_factory=lambda: [0.0, 0.0, 0.0])
def __post_init__(self):
if len(self.link_props) != 3:
raise ValueError(f"需要3个连杆质量属性, 实际{len(self.link_props)}个")
for i, (prop, length) in enumerate(zip(self.link_props, self.link_lengths)):
if length > 0 and prop.com_x > length:
raise ValueError(
f"连杆{i}({prop.link_name}): 质心{prop.com_x}m超出长度{length}m")
@classmethod
def default_3dof_arm(cls):
"""
3-DOF机械臂质量参数
大臂: 5.2 kg, 质心距基座0.28 m (L1=0.60m)
小臂: 3.1 kg, 质心距肘部0.20 m (L2=0.45m)
末端: 执行器1.5 kg, 质心距肘部0.45 m, 横向偏置0.02 m
"""
link_lengths = [0.60, 0.45, 0.0]
props = [
LinkMassProps(5.2, 0.28, 0.0, 0.0, 'link1_upper_arm', link_lengths[0]),
LinkMassProps(3.1, 0.20, 0.0, 0.0, 'link2_forearm', link_lengths[1]),
LinkMassProps(1.5, 0.45, 0.0, 0.0, 'link3_end_effector', None, 0.02),
]
return cls(link_props=props, link_lengths=link_lengths)
def copy_with_modified_com(self, link_index: int, new_com_x: float):
"""返回修改后的质心属性副本"""
new_props = []
for i, prop in enumerate(self.link_props):
if i == link_index:
new_props.append(LinkMassProps(
prop.mass, new_com_x, prop.com_y, prop.com_z,
prop.link_name, prop.max_com_x, prop.r_offset_end))
else:
new_props.append(LinkMassProps(
prop.mass, prop.com_x, prop.com_y, prop.com_z,
prop.link_name, prop.max_com_x, prop.r_offset_end))
return MassPropertySet(link_props=new_props, link_lengths=self.link_lengths)
def total_mass(self) -> float:
return sum(prop.mass for prop in self.link_props)
@dataclass
class DHParam:
"""标准D-H参数"""
theta: float
d: float
a: float
alpha: float
def __post_init__(self):
for name, val in [('theta', self.theta), ('d', self.d),
('a', self.a), ('alpha', self.alpha)]:
if not np.isfinite(val):
raise ValueError(f"DHParam.{name}必须为有限数值")
@dataclass
class RobotParams:
"""
3-DOF机械臂完整参数
大臂600mm, 小臂450mm
gravity_enabled: 重力项计算开关。False时G(q)返回全零。
g: 重力加速度值。gravity_enabled=False时此值被忽略(内部强制为0)。
"""
links: List[DHParam]
joint_names: List[str] = field(default_factory=lambda: [
'joint1_base_yaw', 'joint2_elbow_pitch', 'joint3_end_rotate'
])
g: float = 9.81
gravity_enabled: bool = True
@classmethod
def default_3dof_arm(cls):
links = [
DHParam(0.0, 0.0, 0.600, 0.0),
DHParam(0.0, 0.0, 0.450, 0.0),
DHParam(0.0, 0.0, 0.0, 0.0),
]
return cls(links=links)
def zero_gravity(self):
"""
关闭重力:返回新的RobotParams副本
gravity_enabled=False, g=0
"""
new_links = [DHParam(l.theta, l.d, l.a, l.alpha) for l in self.links]
return RobotParams(links=new_links, joint_names=self.joint_names,
g=0.0, gravity_enabled=False)
"dynamics/trajectory.py" — 关节状态序列生成器
"""
关节状态序列生成器:生成平滑轨迹状态用于零重力力矩计算
使用五次多项式插值生成连续的位置、速度、加速度
轨迹设计:θ₁和θ₂同时反向运动,最大化M₁₂耦合和科氏项特征
"""
import numpy as np
from dynamics.models import JointLimitSet
class JointStateSequence:
"""
关节状态序列:存储多组(q, q˙, q¨)用于批量力矩计算
"""
def __init__(self, times, q_array, q_dot_array, q_ddot_array,
limits: JointLimitSet = None):
self.times = np.array(times, dtype=float)
self.q_array = np.array(q_array, dtype=float)
self.q_dot_array = np.array(q_dot_array, dtype=float)
self.q_ddot_array = np.array(q_ddot_array, dtype=float)
self.limits = limits
n = len(times)
if self.q_array.shape != (n, 3):
raise ValueError(f"q_array形状应为({n}, 3), 实际{self.q_array.shape}")
if self.q_dot_array.shape != (n, 3):
raise ValueError(f"q_dot_array形状应为({n}, 3)")
if self.q_ddot_array.shape != (n, 3):
raise ValueError(f"q_ddot_array形状应为({n}, 3)")
# 有限值检查
for name, arr in [('q_array', self.q_array),
('q_dot_array', self.q_dot_array),
('q_ddot_array', self.q_ddot_array)]:
if not np.all(np.isfinite(arr)):
raise ValueError(f"{name}包含非有限值(NaN或Inf)")
# 限位校验——拼接所有超限关节的错误信息
if limits is not None:
for i, t in enumerate(times):
rot_angles = [self.q_array[i, 0], self.q_array[i, 1], self.q_array[i, 2]]
all_pass, violations = limits.check_all_with_indices(rot_angles)
if not all_pass:
err_msgs = []
for vi in violations:
err_msgs.append(
f"时刻t={t:.3f}s: 关节'{limits.limits[vi].joint_name}' "
f"值={rot_angles[vi]:.3f} 超出限位 "
f"[{limits.limits[vi].min_val:.3f}, "
f"{limits.limits[vi].max_val:.3f}]"
)
raise ValueError(";".join(err_msgs))
@classmethod
def smooth_trajectory(cls, n_points=100, duration=2.0, limits: JointLimitSet = None):
"""
生成平滑轨迹状态序列
θ1: 0° → 90°(正向)
θ2: 0° → -60°(反向,最大化科氏项sinθ₂变化)
θ3: 0° → 10°(微小运动,验证末端旋转轴轻载)
使用五次多项式插值确保位置、速度、加速度连续
"""
times = np.linspace(0, duration, n_points)
pi = 3.141592653589793
q1_start, q1_end = 0.0, 90 * pi / 180
q2_start, q2_end = 0.0, -60 * pi / 180
q3_start, q3_end = 0.0, 10 * pi / 180
q_array = np.zeros((n_points, 3))
q_dot_array = np.zeros((n_points, 3))
q_ddot_array = np.zeros((n_points, 3))
for i, t in enumerate(times):
if duration > 0:
s_norm = t / duration
else:
s_norm = 0.0
# 五次多项式:s = 10t³ - 15t⁴ + 6t⁵
s = 10*s_norm**3 - 15*s_norm**4 + 6*s_norm**5
s_dot = 30*s_norm**2 - 60*s_norm**3 + 30*s_norm**4
s_ddot = 60*s_norm - 180*s_norm**2 + 120*s_norm**3
# 位置
q_array[i, 0] = q1_start + s * (q1_end - q1_start)
q_array[i, 1] = q2_start + s * (q2_end - q2_start)
q_array[i, 2] = q3_start + s * (q3_end - q3_start)
# 速度
if duration > 0:
q_dot_array[i, 0] = s_dot * (q1_end - q1_start) / duration
q_dot_array[i, 1] = s_dot * (q2_end - q2_start) / duration
q_dot_array[i, 2] = s_dot * (q3_end - q3_start) / duration
# 加速度
q_ddot_array[i, 0] = s_ddot * (q1_end - q1_start) / (duration**2)
q_ddot_array[i, 1] = s_ddot * (q2_end - q2_start) / (duration**2)
q_ddot_array[i, 2] = s_ddot * (q3_end - q3_start) / (duration**2)
return cls(times, q_array, q_dot_array, q_ddot_array, limits)
def __len__(self):
return len(self.times)
"dynamics/dynamics_solver.py" — 动力学求解器(核心业务类)
"""
动力学求解器:零重力工况下,仅计算惯性项+科氏项产生的关节力矩
τ = M(q)q¨ + C(q,q˙)q˙ + [G(q) if gravity_enabled else 0]
3-DOF机械臂:基座偏航(θ1) + 肘部俯仰(θ2) + 末端旋转(θ3)
零重力模式:gravity_enabled=False → G(q)返回全零
重力公式修正(第十八篇遗留问题彻底了结):
- θ1为竖直偏航轴 → τ_G1恒为0(无论gravity_enabled为何值)
- θ2为水平俯仰轴 → τ_G2 = -g·(m₂·com₂ + m₃·com₃)·cos(θ₂)
注意是cos(θ₂)不是cos(θ₁+θ₂),θ₁绕竖直轴转不改变俯仰角
- θ3为末端旋转轴 → τ_G3 = 0
末端旋转惯量修正:
- I₃ = m₃·r_offset²,r_offset为质心到旋转轴线的横向距离
- 不再错误使用com₃(沿臂方向距肘部距离)作为回转半径
注意:每次修改质量/质心参数后必须创建新的DynamicsSolver实例
"""
import numpy as np
from dynamics.models import RobotParams, MassPropertySet
class DynamicsSolver:
"""
基于拉格朗日方程的3-DOF机械臂动力学求解器
支持零重力模式(关闭重力项)
"""
@classmethod
def from_mass_props(cls, params: RobotParams, mass_props: MassPropertySet):
"""工厂方法"""
return cls(params, mass_props)
def __init__(self, params: RobotParams, mass_props: MassPropertySet):
self.params = params
self.mass_props = mass_props
self.gravity_enabled = params.gravity_enabled
# g值仅在gravity_enabled=True时使用;False时内部强制为0
self.g = params.g if self.gravity_enabled else 0.0
self.L1 = params.links[0].a
self.L2 = params.links[1].a
self.m1 = mass_props.link_props[0].mass
self.m2 = mass_props.link_props[1].mass
self.m3 = mass_props.link_props[2].mass
self.com1 = mass_props.link_props[0].com_x
self.com2 = mass_props.link_props[1].com_x
self.com3 = mass_props.link_props[2].com_x
self.r_offset3 = mass_props.link_props[2].r_offset_end
# 科氏力耦合系数 h = m₂·L₁·com₂ + m₃·L₁·com₃
self.h = self.m2 * self.L1 * self.com2 + self.m3 * self.L1 * self.com3
# 末端旋转惯量:I₃ = m₃·r_offset²
# r_offset是质心到末端旋转轴线的横向距离,不是com₃
self.I3 = self.m3 * (self.r_offset3 ** 2)
def inertia_matrix(self, theta2):
"""
计算3×3惯性矩阵 M(q)
"""
c2 = np.cos(theta2)
M11 = (self.m1 * self.com1**2
+ self.m2 * (self.L1**2 + self.com2**2 + 2*self.L1*self.com2*c2)
+ self.m3 * (self.L1**2 + self.com3**2 + 2*self.L1*self.com3*c2))
M12 = (self.m2 * (self.com2**2 + self.L1*self.com2*c2)
+ self.m3 * (self.com3**2 + self.L1*self.com3*c2))
M22 = self.m2 * self.com2**2 + self.m3 * self.com3**2
M = np.zeros((3, 3))
M[0, 0] = M11
M[0, 1] = M12
M[1, 0] = M12
M[1, 1] = M22
M[2, 2] = self.I3 # 末端旋转惯量,使用横向偏置半径
return M
def coriolis_vector(self, theta2, theta1_dot, theta2_dot, theta3_dot):
"""
计算科氏力/离心力向量(3维)
质点模型下θ3无速度耦合,τ_C3=0
"""
s2 = np.sin(theta2)
tau_C1 = -self.h * s2 * (2 * theta1_dot * theta2_dot + theta2_dot**2)
tau_C2 = self.h * s2 * theta1_dot**2
tau_C3 = 0.0 # 质点模型下末端旋转无科氏耦合
return np.array([tau_C1, tau_C2, tau_C3], dtype=float)
def gravity_vector(self, theta1, theta2):
"""
计算重力项向量
零重力模式:返回全零
正常模式(修正后):
τ_G1 = 0(竖直偏航轴,恒为零)
τ_G2 = -g·(m₂·com₂ + m₃·com₃)·cos(θ₂) ← 修正:cos(θ₂)不是cos(θ₁+θ₂)
τ_G3 = 0(末端旋转轴)
"""
if not self.gravity_enabled:
return np.zeros(3, dtype=float)
# 正常重力模式(修正后公式)
tau_G1 = 0.0 # 基座偏航轴,竖直轴,重力力矩恒为零
# θ2为水平俯仰轴,重力矩取决于θ₂(不是θ₁+θ₂)
tau_G2 = -self.g * (self.m2 * self.com2 + self.m3 * self.com3) * np.cos(theta2)
tau_G3 = 0.0 # 末端旋转轴
return np.array([tau_G1, tau_G2, tau_G3], dtype=float)
def compute_all_terms(self, q, q_dot, q_ddot):
"""
计算完整动力学方程的各项分解
"""
if len(q) < 3 or len(q_dot) < 3 or len(q_ddot) < 3:
raise ValueError("需要3个关节的角度、速度、加速度")
theta1 = q[0]
theta2 = q[1]
theta3 = q[2]
theta1_dot = q_dot[0]
theta2_dot = q_dot[1]
theta3_dot = q_dot[2]
theta1_ddot = q_ddot[0]
theta2_ddot = q_ddot[1]
theta3_ddot = q_ddot[2]
M = self.inertia_matrix(theta2)
tau_M = M @ np.array([theta1_ddot, theta2_ddot, theta3_ddot])
tau_C = self.coriolis_vector(theta2, theta1_dot, theta2_dot, theta3_dot)
tau_G = self.gravity_vector(theta1, theta2)
tau_total = tau_M + tau_C + tau_G
return tau_M, tau_C, tau_G, tau_total
def compute_sequence(self, state_sequence):
"""
对状态序列批量计算力矩
"""
n = len(state_sequence)
tau_M_hist = np.zeros((n, 3))
tau_C_hist = np.zeros((n, 3))
tau_G_hist = np.zeros((n, 3))
tau_total_hist = np.zeros((n, 3))
for i in range(n):
tau_M, tau_C, tau_G, tau_total = self.compute_all_terms(
state_sequence.q_array[i],
state_sequence.q_dot_array[i],
state_sequence.q_ddot_array[i]
)
tau_M_hist[i, :] = tau_M
tau_C_hist[i, :] = tau_C
tau_G_hist[i, :] = tau_G
tau_total_hist[i, :] = tau_total
peaks = {
'tau_M_peak': np.max(np.abs(tau_M_hist), axis=0),
'tau_C_peak': np.max(np.abs(tau_C_hist), axis=0),
'tau_G_peak': np.max(np.abs(tau_G_hist), axis=0),
'tau_total_peak': np.max(np.abs(tau_total_hist), axis=0),
}
return {
'peaks': peaks,
'tau_M_hist': tau_M_hist,
'tau_C_hist': tau_C_hist,
'tau_G_hist': tau_G_hist,
'tau_total_hist': tau_total_hist,
}
"dynamics/visualize.py" — 可视化模块
"""
零重力仿真结果可视化:4张核心图表
"""
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
class Visualizer:
def __init__(self, results_dir="results"):
self.results_dir = results_dir
import os
os.makedirs(results_dir, exist_ok=True)
def plot_motion_only_torque(self, times, tau_M_hist, tau_C_hist, tau_total_hist):
"""
① 纯运动力矩曲线(无重力项)
"""
fig, axes = plt.subplots(3, 1, figsize=(10, 9), sharex=True)
jo
利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!
DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。
更多推荐


所有评论(0)